| /* ----------------------------------------------------------------------- |
| ffi.c - Copyright (c) 2012 Tilera Corp. |
| |
| TILE Foreign Function Interface |
| |
| Permission is hereby granted, free of charge, to any person obtaining |
| a copy of this software and associated documentation files (the |
| ``Software''), to deal in the Software without restriction, including |
| without limitation the rights to use, copy, modify, merge, publish, |
| distribute, sublicense, and/or sell copies of the Software, and to |
| permit persons to whom the Software is furnished to do so, subject to |
| the following conditions: |
| |
| The above copyright notice and this permission notice shall be included |
| in all copies or substantial portions of the Software. |
| |
| THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, |
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| DEALINGS IN THE SOFTWARE. |
| ----------------------------------------------------------------------- */ |
| |
| #include <ffi.h> |
| #include <ffi_common.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <arch/abi.h> |
| #include <arch/icache.h> |
| #include <arch/opcode.h> |
| |
| |
| /* The first 10 registers are used to pass arguments and return values. */ |
| #define NUM_ARG_REGS 10 |
| |
| /* Performs a raw function call with the given NUM_ARG_REGS register arguments |
| and the specified additional stack arguments (if any). */ |
| extern void ffi_call_tile(ffi_sarg reg_args[NUM_ARG_REGS], |
| const ffi_sarg *stack_args, |
| size_t stack_args_bytes, |
| void (*fnaddr)(void)) |
| FFI_HIDDEN; |
| |
| /* This handles the raw call from the closure stub, cleaning up the |
| parameters and delegating to ffi_closure_tile_inner. */ |
| extern void ffi_closure_tile(void) FFI_HIDDEN; |
| |
| |
| ffi_status |
| ffi_prep_cif_machdep(ffi_cif *cif) |
| { |
| /* We always allocate room for all registers. Even if we don't |
| use them as parameters, they get returned in the same array |
| as struct return values so we need to make room. */ |
| if (cif->bytes < NUM_ARG_REGS * FFI_SIZEOF_ARG) |
| cif->bytes = NUM_ARG_REGS * FFI_SIZEOF_ARG; |
| |
| if (cif->rtype->size > NUM_ARG_REGS * FFI_SIZEOF_ARG) |
| cif->flags = FFI_TYPE_STRUCT; |
| else |
| cif->flags = FFI_TYPE_INT; |
| |
| /* Nothing to do. */ |
| return FFI_OK; |
| } |
| |
| |
| static long |
| assign_to_ffi_arg(ffi_sarg *out, void *in, const ffi_type *type, |
| int write_to_reg) |
| { |
| switch (type->type) |
| { |
| case FFI_TYPE_SINT8: |
| *out = *(SINT8 *)in; |
| return 1; |
| |
| case FFI_TYPE_UINT8: |
| *out = *(UINT8 *)in; |
| return 1; |
| |
| case FFI_TYPE_SINT16: |
| *out = *(SINT16 *)in; |
| return 1; |
| |
| case FFI_TYPE_UINT16: |
| *out = *(UINT16 *)in; |
| return 1; |
| |
| case FFI_TYPE_SINT32: |
| case FFI_TYPE_UINT32: |
| #ifndef __LP64__ |
| case FFI_TYPE_POINTER: |
| #endif |
| /* Note that even unsigned 32-bit quantities are sign extended |
| on tilegx when stored in a register. */ |
| *out = *(SINT32 *)in; |
| return 1; |
| |
| case FFI_TYPE_FLOAT: |
| #ifdef __tilegx__ |
| if (write_to_reg) |
| { |
| /* Properly sign extend the value. */ |
| union { float f; SINT32 s32; } val; |
| val.f = *(float *)in; |
| *out = val.s32; |
| } |
| else |
| #endif |
| { |
| *(float *)out = *(float *)in; |
| } |
| return 1; |
| |
| case FFI_TYPE_SINT64: |
| case FFI_TYPE_UINT64: |
| case FFI_TYPE_DOUBLE: |
| #ifdef __LP64__ |
| case FFI_TYPE_POINTER: |
| #endif |
| *(UINT64 *)out = *(UINT64 *)in; |
| return sizeof(UINT64) / FFI_SIZEOF_ARG; |
| |
| case FFI_TYPE_STRUCT: |
| memcpy(out, in, type->size); |
| return (type->size + FFI_SIZEOF_ARG - 1) / FFI_SIZEOF_ARG; |
| |
| case FFI_TYPE_VOID: |
| /* Must be a return type. Nothing to do. */ |
| return 0; |
| |
| default: |
| FFI_ASSERT(0); |
| return -1; |
| } |
| } |
| |
| |
| void |
| ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue) |
| { |
| ffi_sarg * const arg_mem = alloca(cif->bytes); |
| ffi_sarg * const reg_args = arg_mem; |
| ffi_sarg * const stack_args = ®_args[NUM_ARG_REGS]; |
| ffi_sarg *argp = arg_mem; |
| ffi_type ** const arg_types = cif->arg_types; |
| const long num_args = cif->nargs; |
| long i; |
| |
| if (cif->flags == FFI_TYPE_STRUCT) |
| { |
| /* Pass a hidden pointer to the return value. We make sure there |
| is scratch space for the callee to store the return value even if |
| our caller doesn't care about it. */ |
| *argp++ = (intptr_t)(rvalue ? rvalue : alloca(cif->rtype->size)); |
| |
| /* No more work needed to return anything. */ |
| rvalue = NULL; |
| } |
| |
| for (i = 0; i < num_args; i++) |
| { |
| ffi_type *type = arg_types[i]; |
| void * const arg_in = avalue[i]; |
| ptrdiff_t arg_word = argp - arg_mem; |
| |
| #ifndef __tilegx__ |
| /* Doubleword-aligned values are always in an even-number register |
| pair, or doubleword-aligned stack slot if out of registers. */ |
| long align = arg_word & (type->alignment > FFI_SIZEOF_ARG); |
| argp += align; |
| arg_word += align; |
| #endif |
| |
| if (type->type == FFI_TYPE_STRUCT) |
| { |
| const size_t arg_size_in_words = |
| (type->size + FFI_SIZEOF_ARG - 1) / FFI_SIZEOF_ARG; |
| |
| if (arg_word < NUM_ARG_REGS && |
| arg_word + arg_size_in_words > NUM_ARG_REGS) |
| { |
| /* Args are not allowed to span registers and the stack. */ |
| argp = stack_args; |
| } |
| |
| memcpy(argp, arg_in, type->size); |
| argp += arg_size_in_words; |
| } |
| else |
| { |
| argp += assign_to_ffi_arg(argp, arg_in, arg_types[i], 1); |
| } |
| } |
| |
| /* Actually do the call. */ |
| ffi_call_tile(reg_args, stack_args, |
| cif->bytes - (NUM_ARG_REGS * FFI_SIZEOF_ARG), fn); |
| |
| if (rvalue != NULL) |
| assign_to_ffi_arg(rvalue, reg_args, cif->rtype, 0); |
| } |
| |
| |
| /* Template code for closure. */ |
| extern const UINT64 ffi_template_tramp_tile[] FFI_HIDDEN; |
| |
| |
| ffi_status |
| ffi_prep_closure_loc (ffi_closure *closure, |
| ffi_cif *cif, |
| void (*fun)(ffi_cif*, void*, void**, void*), |
| void *user_data, |
| void *codeloc) |
| { |
| #ifdef __tilegx__ |
| /* TILE-Gx */ |
| SINT64 c; |
| SINT64 h; |
| int s; |
| UINT64 *out; |
| |
| if (cif->abi != FFI_UNIX) |
| return FFI_BAD_ABI; |
| |
| out = (UINT64 *)closure->tramp; |
| |
| c = (intptr_t)closure; |
| h = (intptr_t)ffi_closure_tile; |
| s = 0; |
| |
| /* Find the smallest shift count that doesn't lose information |
| (i.e. no need to explicitly insert high bits of the address that |
| are just the sign extension of the low bits). */ |
| while ((c >> s) != (SINT16)(c >> s) || (h >> s) != (SINT16)(h >> s)) |
| s += 16; |
| |
| #define OPS(a, b, shift) \ |
| (create_Imm16_X0((a) >> (shift)) | create_Imm16_X1((b) >> (shift))) |
| |
| /* Emit the moveli. */ |
| *out++ = ffi_template_tramp_tile[0] | OPS(c, h, s); |
| for (s -= 16; s >= 0; s -= 16) |
| *out++ = ffi_template_tramp_tile[1] | OPS(c, h, s); |
| |
| #undef OPS |
| |
| *out++ = ffi_template_tramp_tile[2]; |
| |
| #else |
| /* TILEPro */ |
| UINT64 *out; |
| intptr_t delta; |
| |
| if (cif->abi != FFI_UNIX) |
| return FFI_BAD_ABI; |
| |
| out = (UINT64 *)closure->tramp; |
| delta = (intptr_t)ffi_closure_tile - (intptr_t)codeloc; |
| |
| *out++ = ffi_template_tramp_tile[0] | create_JOffLong_X1(delta >> 3); |
| #endif |
| |
| closure->cif = cif; |
| closure->fun = fun; |
| closure->user_data = user_data; |
| |
| invalidate_icache(closure->tramp, (char *)out - closure->tramp, |
| getpagesize()); |
| |
| return FFI_OK; |
| } |
| |
| |
| /* This is called by the assembly wrapper for closures. This does |
| all of the work. On entry reg_args[0] holds the values the registers |
| had when the closure was invoked. On return reg_args[1] holds the register |
| values to be returned to the caller (many of which may be garbage). */ |
| void FFI_HIDDEN |
| ffi_closure_tile_inner(ffi_closure *closure, |
| ffi_sarg reg_args[2][NUM_ARG_REGS], |
| ffi_sarg *stack_args) |
| { |
| ffi_cif * const cif = closure->cif; |
| void ** const avalue = alloca(cif->nargs * sizeof(void *)); |
| void *rvalue; |
| ffi_type ** const arg_types = cif->arg_types; |
| ffi_sarg * const reg_args_in = reg_args[0]; |
| ffi_sarg * const reg_args_out = reg_args[1]; |
| ffi_sarg * argp; |
| long i, arg_word, nargs = cif->nargs; |
| /* Use a union to guarantee proper alignment for double. */ |
| union { ffi_sarg arg[NUM_ARG_REGS]; double d; UINT64 u64; } closure_ret; |
| |
| /* Start out reading register arguments. */ |
| argp = reg_args_in; |
| |
| /* Copy the caller's structure return address to that the closure |
| returns the data directly to the caller. */ |
| if (cif->flags == FFI_TYPE_STRUCT) |
| { |
| /* Return by reference via hidden pointer. */ |
| rvalue = (void *)(intptr_t)*argp++; |
| arg_word = 1; |
| } |
| else |
| { |
| /* Return the value in registers. */ |
| rvalue = &closure_ret; |
| arg_word = 0; |
| } |
| |
| /* Grab the addresses of the arguments. */ |
| for (i = 0; i < nargs; i++) |
| { |
| ffi_type * const type = arg_types[i]; |
| const size_t arg_size_in_words = |
| (type->size + FFI_SIZEOF_ARG - 1) / FFI_SIZEOF_ARG; |
| |
| #ifndef __tilegx__ |
| /* Doubleword-aligned values are always in an even-number register |
| pair, or doubleword-aligned stack slot if out of registers. */ |
| long align = arg_word & (type->alignment > FFI_SIZEOF_ARG); |
| argp += align; |
| arg_word += align; |
| #endif |
| |
| if (arg_word == NUM_ARG_REGS || |
| (arg_word < NUM_ARG_REGS && |
| arg_word + arg_size_in_words > NUM_ARG_REGS)) |
| { |
| /* Switch to reading arguments from the stack. */ |
| argp = stack_args; |
| arg_word = NUM_ARG_REGS; |
| } |
| |
| avalue[i] = argp; |
| argp += arg_size_in_words; |
| arg_word += arg_size_in_words; |
| } |
| |
| /* Invoke the closure. */ |
| closure->fun(cif, rvalue, avalue, closure->user_data); |
| |
| if (cif->flags != FFI_TYPE_STRUCT) |
| { |
| /* Canonicalize for register representation. */ |
| assign_to_ffi_arg(reg_args_out, &closure_ret, cif->rtype, 1); |
| } |
| } |