| /* ----------------------------------------------------------------------- | |
| ffi.c - Copyright (c) 2018-2023 Hood Chatham, Brion Vibber, Kleis Auke Wolthuizen, and others. | |
| wasm32/emscripten 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 <emscripten/emscripten.h> | |
| #ifdef DEBUG_F | |
| #define LOG_DEBUG(args...) \ | |
| console.warn(`====LIBFFI(line __LINE__)`, args) | |
| #else | |
| #define LOG_DEBUG(args...) 0 | |
| #endif | |
| #define EM_JS_MACROS(ret, name, args, body...) EM_JS(ret, name, args, body) | |
| EM_JS_DEPS(libffi, "$getWasmTableEntry,$setWasmTableEntry,$getEmptyTableSlot,$convertJsFunctionToWasm"); | |
| #define DEREF_U8(addr, offset) HEAPU8[addr + offset] | |
| #define DEREF_S8(addr, offset) HEAP8[addr + offset] | |
| #define DEREF_U16(addr, offset) HEAPU16[(addr >> 1) + offset] | |
| #define DEREF_S16(addr, offset) HEAP16[(addr >> 1) + offset] | |
| #define DEREF_U32(addr, offset) HEAPU32[(addr >> 2) + offset] | |
| #define DEREF_S32(addr, offset) HEAP32[(addr >> 2) + offset] | |
| #define DEREF_F32(addr, offset) HEAPF32[(addr >> 2) + offset] | |
| #define DEREF_F64(addr, offset) HEAPF64[(addr >> 3) + offset] | |
| #define DEREF_U64(addr, offset) HEAPU64[(addr >> 3) + offset] | |
| #define CHECK_FIELD_OFFSET(struct, field, offset) \ | |
| _Static_assert( \ | |
| offsetof(struct, field) == offset, \ | |
| "Memory layout of '" #struct "' has changed: '" #field "' is in an unexpected location"); | |
| #if __SIZEOF_POINTER__ == 4 | |
| #define FFI_EMSCRIPTEN_ABI FFI_WASM32_EMSCRIPTEN | |
| #define PTR_SIG 'i' | |
| #define DEC_PTR(p) p | |
| #define ENC_PTR(p) p | |
| #define DEREF_PTR(addr, offset) DEREF_U32(addr, offset) | |
| #define DEREF_PTR_NUMBER(addr, offset) DEREF_PTR(addr, offset) | |
| CHECK_FIELD_OFFSET(ffi_cif, abi, 4*0); | |
| CHECK_FIELD_OFFSET(ffi_cif, nargs, 4*1); | |
| CHECK_FIELD_OFFSET(ffi_cif, arg_types, 4*2); | |
| CHECK_FIELD_OFFSET(ffi_cif, rtype, 4*3); | |
| CHECK_FIELD_OFFSET(ffi_cif, flags, 4*5); | |
| CHECK_FIELD_OFFSET(ffi_cif, nfixedargs, 4*6); | |
| #define CIF__ABI(addr) DEREF_U32(addr, 0) | |
| #define CIF__NARGS(addr) DEREF_U32(addr, 1) | |
| #define CIF__ARGTYPES(addr) DEREF_U32(addr, 2) | |
| #define CIF__RTYPE(addr) DEREF_U32(addr, 3) | |
| #define CIF__FLAGS(addr) DEREF_U32(addr, 5) | |
| #define CIF__NFIXEDARGS(addr) DEREF_U32(addr, 6) | |
| CHECK_FIELD_OFFSET(ffi_type, size, 0); | |
| CHECK_FIELD_OFFSET(ffi_type, alignment, 4); | |
| CHECK_FIELD_OFFSET(ffi_type, type, 6); | |
| CHECK_FIELD_OFFSET(ffi_type, elements, 8); | |
| #define FFI_TYPE__SIZE(addr) DEREF_U32(addr, 0) | |
| #define FFI_TYPE__ALIGN(addr) DEREF_U16(addr + 4, 0) | |
| #define FFI_TYPE__TYPEID(addr) DEREF_U16(addr + 6, 0) | |
| #define FFI_TYPE__ELEMENTS(addr) DEREF_U32(addr + 8, 0) | |
| #elif __SIZEOF_POINTER__ == 8 | |
| #define FFI_EMSCRIPTEN_ABI FFI_WASM64_EMSCRIPTEN | |
| #define PTR_SIG 'j' | |
| // DEC_PTR casts a pointer value (comming from Wasm) represented as BigInt (i64) to Number (i53). | |
| // This should be used for a pointer that is expected to be within the i53 range. If the pointer | |
| // value is outside the Number's range, the value will become NaN. | |
| #define DEC_PTR(p) bigintToI53Checked(p) | |
| // ENC_PTR casts a pointer value represented as Number to BigInt (i64) | |
| #define ENC_PTR(p) BigInt(p) | |
| #define DEREF_PTR(addr, offset) DEREF_U64(addr, offset) | |
| #define DEREF_PTR_NUMBER(addr, offset) DEC_PTR(DEREF_PTR(addr, offset)) | |
| CHECK_FIELD_OFFSET(ffi_cif, abi, 0); | |
| CHECK_FIELD_OFFSET(ffi_cif, nargs, 4); | |
| CHECK_FIELD_OFFSET(ffi_cif, arg_types, 8); | |
| CHECK_FIELD_OFFSET(ffi_cif, rtype, 16); | |
| CHECK_FIELD_OFFSET(ffi_cif, flags, 28); | |
| CHECK_FIELD_OFFSET(ffi_cif, nfixedargs, 32); | |
| #define CIF__ABI(addr) DEREF_U32(addr, 0) | |
| #define CIF__NARGS(addr) DEREF_U32(addr + 4, 0) | |
| #define CIF__ARGTYPES(addr) DEREF_U64(addr + 8, 0) | |
| #define CIF__RTYPE(addr) DEREF_U64(addr + 16, 0) | |
| #define CIF__FLAGS(addr) DEREF_U32(addr + 28, 0) | |
| #define CIF__NFIXEDARGS(addr) DEREF_U32(addr + 32, 0) | |
| CHECK_FIELD_OFFSET(ffi_type, size, 0); | |
| CHECK_FIELD_OFFSET(ffi_type, alignment, 8); | |
| CHECK_FIELD_OFFSET(ffi_type, type, 10); | |
| CHECK_FIELD_OFFSET(ffi_type, elements, 16); | |
| #define FFI_TYPE__SIZE(addr) DEREF_U64(addr, 0) | |
| #define FFI_TYPE__ALIGN(addr) DEREF_U16(addr + 8, 0) | |
| #define FFI_TYPE__TYPEID(addr) DEREF_U16(addr + 10, 0) | |
| #define FFI_TYPE__ELEMENTS(addr) DEREF_U64(addr + 16, 0) | |
| #else | |
| #error "Unknown pointer size" | |
| #endif | |
| #define ALIGN_ADDRESS(addr, align) (addr &= (~((align) - 1))) | |
| #define STACK_ALLOC(stack, size, align) ((stack -= (size)), ALIGN_ADDRESS(stack, align)) | |
| // Most wasm runtimes support at most 1000 Js trampoline args. | |
| #define MAX_ARGS 1000 | |
| #include <stddef.h> | |
| #define VARARGS_FLAG 1 | |
| #define FFI_OK_MACRO 0 | |
| _Static_assert(FFI_OK_MACRO == FFI_OK, "FFI_OK must be 0"); | |
| #define FFI_BAD_TYPEDEF_MACRO 1 | |
| _Static_assert(FFI_BAD_TYPEDEF_MACRO == FFI_BAD_TYPEDEF, "FFI_BAD_TYPEDEF must be 1"); | |
| ffi_status FFI_HIDDEN | |
| ffi_prep_cif_machdep(ffi_cif *cif) | |
| { | |
| if (cif->abi != FFI_EMSCRIPTEN_ABI) | |
| return FFI_BAD_ABI; | |
| // This is called after ffi_prep_cif_machdep_var so we need to avoid | |
| // overwriting cif->nfixedargs. | |
| if (!(cif->flags & VARARGS_FLAG)) | |
| cif->nfixedargs = cif->nargs; | |
| if (cif->nargs > MAX_ARGS) | |
| return FFI_BAD_TYPEDEF; | |
| if (cif->rtype->type == FFI_TYPE_COMPLEX) | |
| return FFI_BAD_TYPEDEF; | |
| // If they put the COMPLEX type into a struct we won't notice, but whatever. | |
| for (int i = 0; i < cif->nargs; i++) | |
| if (cif->arg_types[i]->type == FFI_TYPE_COMPLEX) | |
| return FFI_BAD_TYPEDEF; | |
| return FFI_OK; | |
| } | |
| ffi_status FFI_HIDDEN | |
| ffi_prep_cif_machdep_var(ffi_cif *cif, unsigned nfixedargs, unsigned ntotalargs) | |
| { | |
| cif->flags |= VARARGS_FLAG; | |
| cif->nfixedargs = nfixedargs; | |
| // The varargs takes up one extra argument | |
| if (cif->nfixedargs + 1 > MAX_ARGS) | |
| return FFI_BAD_TYPEDEF; | |
| return FFI_OK; | |
| } | |
| /** | |
| * A Javascript helper function. This takes an argument typ which is a wasm | |
| * pointer to an ffi_type object. It returns a pair a type and a type id. | |
| * | |
| * - If it is not a struct, return its type and its typeid field. | |
| * - If it is a struct of size >= 2, return the type and its typeid (which | |
| * will be FFI_TYPE_STRUCT) | |
| * - If it is a struct of size 0, return FFI_TYPE_VOID (????? this is broken) | |
| * - If it is a struct of size 1, replace it with the single field and apply | |
| * the same logic again to that. | |
| * | |
| * By always unboxing structs up front, we can avoid messy casework later. | |
| */ | |
| EM_JS_MACROS( | |
| void, | |
| unbox_small_structs, (ffi_type type_ptr), { | |
| type_ptr = DEC_PTR(type_ptr); | |
| var type_id = FFI_TYPE__TYPEID(type_ptr); | |
| while (type_id === FFI_TYPE_STRUCT) { | |
| // Don't unbox single element structs if they are bigger than 16 bytes. This | |
| // is a work around for the fact that Python will give incorrect values for | |
| // the size of the field in these cases: it says that the struct has pointer | |
| // size and alignment and are of type pointer, even though it is more | |
| // accurately a struct and has a larger size. Keeping it as a struct here | |
| // will let us get the ABI right (which is in fact that the true argument is | |
| // a pointer to the stack... so maybe Python issn't so wrong??) | |
| // | |
| // See the Python comment here: | |
| // https://github.com/python/cpython/blob/a16a9f978f42b8a09297c1efbf33877f6388c403/Modules/_ctypes/stgdict.c#L718-L779 | |
| if (DEC_PTR(FFI_TYPE__SIZE(type_ptr)) > 16) { | |
| break; | |
| } | |
| var elements = DEC_PTR(FFI_TYPE__ELEMENTS(type_ptr)); | |
| var first_element = DEREF_PTR_NUMBER(elements, 0); | |
| if (first_element === 0) { | |
| type_id = FFI_TYPE_VOID; | |
| break; | |
| } else if (DEREF_PTR_NUMBER(elements, 1) === 0) { | |
| type_ptr = first_element; | |
| type_id = FFI_TYPE__TYPEID(first_element); | |
| } else { | |
| break; | |
| } | |
| } | |
| return [type_ptr, type_id]; | |
| }) | |
| EM_JS_MACROS( | |
| void, | |
| ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), | |
| { | |
| cif = DEC_PTR(cif); | |
| fn = DEC_PTR(fn); | |
| rvalue = DEC_PTR(rvalue); | |
| avalue = DEC_PTR(avalue); | |
| var abi = CIF__ABI(cif); | |
| var nargs = CIF__NARGS(cif); | |
| var nfixedargs = CIF__NFIXEDARGS(cif); | |
| var arg_types_ptr = DEC_PTR(CIF__ARGTYPES(cif)); | |
| var flags = CIF__FLAGS(cif); | |
| var rtype_unboxed = unbox_small_structs(CIF__RTYPE(cif)); | |
| var rtype_ptr = rtype_unboxed[0]; | |
| var rtype_id = rtype_unboxed[1]; | |
| var orig_stack_ptr = stackSave(); | |
| var cur_stack_ptr = orig_stack_ptr; | |
| var args = []; | |
| // Does our onwards call return by argument or normally? We return by argument | |
| // no matter what. | |
| var ret_by_arg = false; | |
| if (rtype_id === FFI_TYPE_COMPLEX) { | |
| throw new Error('complex ret marshalling nyi'); | |
| } | |
| if (rtype_id < 0 || rtype_id > FFI_TYPE_LAST) { | |
| throw new Error('Unexpected rtype ' + rtype_id); | |
| } | |
| // If the return type is a struct with multiple entries or a long double, the | |
| // function takes an extra first argument which is a pointer to return value. | |
| // Conveniently, we've already received a pointer to return value, so we can | |
| // just use this. We also mark a flag that we don't need to convert the return | |
| // value of the dynamic call back to C. | |
| if (rtype_id === FFI_TYPE_LONGDOUBLE || rtype_id === FFI_TYPE_STRUCT) { | |
| args.push(ENC_PTR(rvalue)); | |
| ret_by_arg = true; | |
| } | |
| // Accumulate a Javascript list of arguments for the Javascript wrapper for | |
| // the wasm function. The Javascript wrapper does a type conversion from | |
| // Javascript to C automatically, here we manually do the inverse conversion | |
| // from C to Javascript. | |
| for (var i = 0; i < nfixedargs; i++) { | |
| var arg_ptr = DEREF_PTR_NUMBER(avalue, i); | |
| var arg_unboxed = unbox_small_structs(DEREF_PTR(arg_types_ptr, i)); | |
| var arg_type_ptr = arg_unboxed[0]; | |
| var arg_type_id = arg_unboxed[1]; | |
| // It's okay here to always use unsigned integers as long as the size is 32 | |
| // or 64 bits. Smaller sizes get extended to 32 bits differently according | |
| // to whether they are signed or unsigned. | |
| switch (arg_type_id) { | |
| case FFI_TYPE_INT: | |
| case FFI_TYPE_SINT32: | |
| case FFI_TYPE_UINT32: | |
| args.push(DEREF_U32(arg_ptr, 0)); | |
| break; | |
| case FFI_TYPE_FLOAT: | |
| args.push(DEREF_F32(arg_ptr, 0)); | |
| break; | |
| case FFI_TYPE_DOUBLE: | |
| args.push(DEREF_F64(arg_ptr, 0)); | |
| break; | |
| case FFI_TYPE_UINT8: | |
| args.push(DEREF_U8(arg_ptr, 0)); | |
| break; | |
| case FFI_TYPE_SINT8: | |
| args.push(DEREF_S8(arg_ptr, 0)); | |
| break; | |
| case FFI_TYPE_UINT16: | |
| args.push(DEREF_U16(arg_ptr, 0)); | |
| break; | |
| case FFI_TYPE_SINT16: | |
| args.push(DEREF_S16(arg_ptr, 0)); | |
| break; | |
| case FFI_TYPE_UINT64: | |
| case FFI_TYPE_SINT64: | |
| args.push(DEREF_U64(arg_ptr, 0)); | |
| break; | |
| case FFI_TYPE_LONGDOUBLE: | |
| // long double is passed as a pair of BigInts. | |
| args.push(DEREF_U64(arg_ptr, 0)); | |
| args.push(DEREF_U64(arg_ptr, 1)); | |
| break; | |
| case FFI_TYPE_STRUCT: | |
| // Nontrivial structs are passed by pointer. | |
| // Have to copy the struct onto the stack though because C ABI says it's | |
| // call by value. | |
| var size = DEC_PTR(FFI_TYPE__SIZE(arg_type_ptr)); | |
| var align = FFI_TYPE__ALIGN(arg_type_ptr); | |
| STACK_ALLOC(cur_stack_ptr, size, align); | |
| HEAP8.subarray(cur_stack_ptr, cur_stack_ptr+size).set(HEAP8.subarray(arg_ptr, arg_ptr + size)); | |
| args.push(ENC_PTR(cur_stack_ptr)); | |
| break; | |
| case FFI_TYPE_POINTER: | |
| args.push(DEREF_PTR(arg_ptr, 0)); | |
| break; | |
| case FFI_TYPE_COMPLEX: | |
| throw new Error('complex marshalling nyi'); | |
| default: | |
| throw new Error('Unexpected type ' + arg_type_id); | |
| } | |
| } | |
| // Wasm functions can't directly manipulate the callstack, so varargs | |
| // arguments have to go on a separate stack. A varags function takes one extra | |
| // argument which is a pointer to where on the separate stack the args are | |
| // located. Because stacks are allocated backwards, we have to loop over the | |
| // varargs backwards. | |
| // | |
| // We don't have any way of knowing how many args were actually passed, so we | |
| // just always copy extra nonsense past the end. The ownwards call will know | |
| // not to look at it. | |
| if (flags & VARARGS_FLAG) { | |
| var struct_arg_info = []; | |
| for (var i = nargs - 1; i >= nfixedargs; i--) { | |
| var arg_ptr = DEREF_PTR_NUMBER(avalue, i); | |
| var arg_unboxed = unbox_small_structs(DEREF_PTR(arg_types_ptr, i)); | |
| var arg_type_ptr = arg_unboxed[0]; | |
| var arg_type_id = arg_unboxed[1]; | |
| switch (arg_type_id) { | |
| case FFI_TYPE_UINT8: | |
| case FFI_TYPE_SINT8: | |
| STACK_ALLOC(cur_stack_ptr, 1, 1); | |
| DEREF_U8(cur_stack_ptr, 0) = DEREF_U8(arg_ptr, 0); | |
| break; | |
| case FFI_TYPE_UINT16: | |
| case FFI_TYPE_SINT16: | |
| STACK_ALLOC(cur_stack_ptr, 2, 2); | |
| DEREF_U16(cur_stack_ptr, 0) = DEREF_U16(arg_ptr, 0); | |
| break; | |
| case FFI_TYPE_INT: | |
| case FFI_TYPE_UINT32: | |
| case FFI_TYPE_SINT32: | |
| case FFI_TYPE_FLOAT: | |
| STACK_ALLOC(cur_stack_ptr, 4, 4); | |
| DEREF_U32(cur_stack_ptr, 0) = DEREF_U32(arg_ptr, 0); | |
| break; | |
| case FFI_TYPE_DOUBLE: | |
| case FFI_TYPE_UINT64: | |
| case FFI_TYPE_SINT64: | |
| STACK_ALLOC(cur_stack_ptr, 8, 8); | |
| DEREF_U32(cur_stack_ptr, 0) = DEREF_U32(arg_ptr, 0); | |
| DEREF_U32(cur_stack_ptr, 1) = DEREF_U32(arg_ptr, 1); | |
| break; | |
| case FFI_TYPE_LONGDOUBLE: | |
| STACK_ALLOC(cur_stack_ptr, 16, 8); | |
| DEREF_U32(cur_stack_ptr, 0) = DEREF_U32(arg_ptr, 0); | |
| DEREF_U32(cur_stack_ptr, 1) = DEREF_U32(arg_ptr, 1); | |
| DEREF_U32(cur_stack_ptr, 2) = DEREF_U32(arg_ptr, 2); | |
| DEREF_U32(cur_stack_ptr, 3) = DEREF_U32(arg_ptr, 3); | |
| break; | |
| case FFI_TYPE_STRUCT: | |
| // Again, struct must be passed by pointer. | |
| // But ABI is by value, so have to copy struct onto stack. | |
| // Currently arguments are going onto stack so we can't put it there now. Come back for this. | |
| STACK_ALLOC(cur_stack_ptr, __SIZEOF_POINTER__, __SIZEOF_POINTER__); | |
| struct_arg_info.push([cur_stack_ptr, arg_ptr, DEC_PTR(FFI_TYPE__SIZE(arg_type_ptr)), FFI_TYPE__ALIGN(arg_type_ptr)]); | |
| break; | |
| case FFI_TYPE_POINTER: | |
| STACK_ALLOC(cur_stack_ptr, __SIZEOF_POINTER__, __SIZEOF_POINTER__); | |
| DEREF_PTR(cur_stack_ptr, 0) = DEREF_PTR(arg_ptr, 0); | |
| break; | |
| case FFI_TYPE_COMPLEX: | |
| throw new Error('complex arg marshalling nyi'); | |
| default: | |
| throw new Error('Unexpected argtype ' + arg_type_id); | |
| } | |
| } | |
| // extra normal argument which is the pointer to the varargs. | |
| args.push(ENC_PTR(cur_stack_ptr)); | |
| // Now allocate variable struct args on stack too. | |
| for (var i = 0; i < struct_arg_info.length; i++) { | |
| var struct_info = struct_arg_info[i]; | |
| var arg_target = struct_info[0]; | |
| var arg_ptr = struct_info[1]; | |
| var size = struct_info[2]; | |
| var align = struct_info[3]; | |
| STACK_ALLOC(cur_stack_ptr, size, align); | |
| HEAP8.subarray(cur_stack_ptr, cur_stack_ptr+size).set(HEAP8.subarray(arg_ptr, arg_ptr + size)); | |
| DEREF_PTR(arg_target, 0) = ENC_PTR(cur_stack_ptr); | |
| } | |
| } | |
| stackRestore(cur_stack_ptr); | |
| stackAlloc(0); // stackAlloc enforces alignment invariants on the stack pointer | |
| LOG_DEBUG("CALL_FUNC_PTR", "fn:", fn, "args:", args); | |
| var result = getWasmTableEntry(fn).apply(null, args); | |
| // Put the stack pointer back (we moved it if there were any struct args or we | |
| // made a varargs call) | |
| stackRestore(orig_stack_ptr); | |
| // We need to return by argument. If return value was a nontrivial struct or | |
| // long double, the onwards call already put the return value in rvalue | |
| if (ret_by_arg) { | |
| return; | |
| } | |
| // Otherwise the result was automatically converted from C into Javascript and | |
| // we need to manually convert it back to C. | |
| switch (rtype_id) { | |
| case FFI_TYPE_VOID: | |
| break; | |
| case FFI_TYPE_INT: | |
| case FFI_TYPE_UINT32: | |
| case FFI_TYPE_SINT32: | |
| DEREF_U32(rvalue, 0) = result; | |
| break; | |
| case FFI_TYPE_FLOAT: | |
| DEREF_F32(rvalue, 0) = result; | |
| break; | |
| case FFI_TYPE_DOUBLE: | |
| DEREF_F64(rvalue, 0) = result; | |
| break; | |
| case FFI_TYPE_UINT8: | |
| case FFI_TYPE_SINT8: | |
| DEREF_U8(rvalue, 0) = result; | |
| break; | |
| case FFI_TYPE_UINT16: | |
| case FFI_TYPE_SINT16: | |
| DEREF_U16(rvalue, 0) = result; | |
| break; | |
| case FFI_TYPE_UINT64: | |
| case FFI_TYPE_SINT64: | |
| DEREF_U64(rvalue, 0) = result; | |
| break; | |
| case FFI_TYPE_POINTER: | |
| DEREF_PTR(rvalue, 0) = result; | |
| break; | |
| case FFI_TYPE_COMPLEX: | |
| throw new Error('complex ret marshalling nyi'); | |
| default: | |
| throw new Error('Unexpected rtype ' + rtype_id); | |
| } | |
| }); | |
| void ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue) { | |
| ffi_call_js(cif, fn, rvalue, avalue); | |
| } | |
| #if __SIZEOF_POINTER__ == 4 | |
| CHECK_FIELD_OFFSET(ffi_closure, ftramp, 4*0); | |
| CHECK_FIELD_OFFSET(ffi_closure, cif, 4*1); | |
| CHECK_FIELD_OFFSET(ffi_closure, fun, 4*2); | |
| CHECK_FIELD_OFFSET(ffi_closure, user_data, 4*3); | |
| #define CLOSURE__wrapper(addr) DEREF_U32(addr, 0) | |
| #define CLOSURE__cif(addr) DEREF_U32(addr, 1) | |
| #define CLOSURE__fun(addr) DEREF_U32(addr, 2) | |
| #define CLOSURE__user_data(addr) DEREF_U32(addr, 3) | |
| #elif __SIZEOF_POINTER__ == 8 | |
| CHECK_FIELD_OFFSET(ffi_closure, ftramp, 0); | |
| CHECK_FIELD_OFFSET(ffi_closure, cif, 8); | |
| CHECK_FIELD_OFFSET(ffi_closure, fun, 16); | |
| CHECK_FIELD_OFFSET(ffi_closure, user_data, 24); | |
| #define CLOSURE__wrapper(addr) DEREF_U64(addr, 0) | |
| #define CLOSURE__cif(addr) DEREF_U64(addr, 1) | |
| #define CLOSURE__fun(addr) DEREF_U64(addr, 2) | |
| #define CLOSURE__user_data(addr) DEREF_U64(addr, 3) | |
| #else | |
| #error "Unknown pointer size" | |
| #endif | |
| EM_JS_MACROS(void *, ffi_closure_alloc_js, (size_t size, void **code), { | |
| size = DEC_PTR(size); | |
| code = DEC_PTR(code); | |
| var closure = _malloc(size); | |
| var index = getEmptyTableSlot(); | |
| DEREF_PTR(code, 0) = ENC_PTR(index); | |
| CLOSURE__wrapper(closure) = ENC_PTR(index); | |
| return ENC_PTR(closure); | |
| }) | |
| void * __attribute__ ((visibility ("default"))) | |
| ffi_closure_alloc(size_t size, void **code) { | |
| return ffi_closure_alloc_js(size, code); | |
| } | |
| EM_JS_MACROS(void, ffi_closure_free_js, (void *closure), { | |
| closure = DEC_PTR(closure); | |
| var index = DEC_PTR(CLOSURE__wrapper(closure)); | |
| freeTableIndexes.push(index); | |
| _free(closure); | |
| }) | |
| void __attribute__ ((visibility ("default"))) | |
| ffi_closure_free(void *closure) { | |
| return ffi_closure_free_js(closure); | |
| } | |
| EM_JS_MACROS( | |
| ffi_status, | |
| ffi_prep_closure_loc_js, | |
| (ffi_closure *closure, ffi_cif *cif, void *fun, void *user_data, void *codeloc), | |
| { | |
| closure = DEC_PTR(closure); | |
| cif = DEC_PTR(cif); | |
| fun = DEC_PTR(fun); | |
| user_data = DEC_PTR(user_data); | |
| codeloc = DEC_PTR(codeloc); | |
| var abi = CIF__ABI(cif); | |
| var nargs = CIF__NARGS(cif); | |
| var nfixedargs = CIF__NFIXEDARGS(cif); | |
| var arg_types_ptr = DEC_PTR(CIF__ARGTYPES(cif)); | |
| var rtype_unboxed = unbox_small_structs(CIF__RTYPE(cif)); | |
| var rtype_ptr = rtype_unboxed[0]; | |
| var rtype_id = rtype_unboxed[1]; | |
| // First construct the signature of the javascript trampoline we are going to create. | |
| // Important: this is the signature for calling us, the onward call always has sig viiii. | |
| var sig; | |
| var ret_by_arg = false; | |
| switch (rtype_id) { | |
| case FFI_TYPE_VOID: | |
| sig = 'v'; | |
| break; | |
| case FFI_TYPE_STRUCT: | |
| case FFI_TYPE_LONGDOUBLE: | |
| // Return via a first pointer argument. | |
| sig = 'v' + PTR_SIG; | |
| ret_by_arg = true; | |
| break; | |
| case FFI_TYPE_INT: | |
| case FFI_TYPE_UINT8: | |
| case FFI_TYPE_SINT8: | |
| case FFI_TYPE_UINT16: | |
| case FFI_TYPE_SINT16: | |
| case FFI_TYPE_UINT32: | |
| case FFI_TYPE_SINT32: | |
| sig = 'i'; | |
| break; | |
| case FFI_TYPE_FLOAT: | |
| sig = 'f'; | |
| break; | |
| case FFI_TYPE_DOUBLE: | |
| sig = 'd'; | |
| break; | |
| case FFI_TYPE_UINT64: | |
| case FFI_TYPE_SINT64: | |
| sig = 'j'; | |
| break; | |
| case FFI_TYPE_POINTER: | |
| sig = PTR_SIG; | |
| break; | |
| case FFI_TYPE_COMPLEX: | |
| throw new Error('complex ret marshalling nyi'); | |
| default: | |
| throw new Error('Unexpected rtype ' + rtype_id); | |
| } | |
| var unboxed_arg_type_id_list = []; | |
| var unboxed_arg_type_info_list = []; | |
| for (var i = 0; i < nargs; i++) { | |
| var arg_unboxed = unbox_small_structs(DEREF_PTR(arg_types_ptr, i)); | |
| var arg_type_ptr = arg_unboxed[0]; | |
| var arg_type_id = arg_unboxed[1]; | |
| unboxed_arg_type_id_list.push(arg_type_id); | |
| unboxed_arg_type_info_list.push([DEC_PTR(FFI_TYPE__SIZE(arg_type_ptr)), FFI_TYPE__ALIGN(arg_type_ptr)]); | |
| } | |
| for (var i = 0; i < nfixedargs; i++) { | |
| switch (unboxed_arg_type_id_list[i]) { | |
| case FFI_TYPE_INT: | |
| case FFI_TYPE_UINT8: | |
| case FFI_TYPE_SINT8: | |
| case FFI_TYPE_UINT16: | |
| case FFI_TYPE_SINT16: | |
| case FFI_TYPE_UINT32: | |
| case FFI_TYPE_SINT32: | |
| sig += 'i'; | |
| break; | |
| case FFI_TYPE_FLOAT: | |
| sig += 'f'; | |
| break; | |
| case FFI_TYPE_DOUBLE: | |
| sig += 'd'; | |
| break; | |
| case FFI_TYPE_LONGDOUBLE: | |
| sig += 'jj'; | |
| break; | |
| case FFI_TYPE_UINT64: | |
| case FFI_TYPE_SINT64: | |
| sig += 'j'; | |
| break; | |
| case FFI_TYPE_STRUCT: | |
| case FFI_TYPE_POINTER: | |
| sig += PTR_SIG; | |
| break; | |
| case FFI_TYPE_COMPLEX: | |
| throw new Error('complex marshalling nyi'); | |
| default: | |
| throw new Error('Unexpected argtype ' + arg_type_id); | |
| } | |
| } | |
| if (nfixedargs < nargs) { | |
| // extra pointer to varargs stack | |
| sig += PTR_SIG; | |
| } | |
| LOG_DEBUG("CREATE_CLOSURE", "sig:", sig); | |
| function trampoline() { | |
| var args = Array.prototype.slice.call(arguments); | |
| var size = 0; | |
| var orig_stack_ptr = stackSave(); | |
| var cur_ptr = orig_stack_ptr; | |
| var ret_ptr; | |
| var jsarg_idx = 0; | |
| // Should we return by argument or not? The onwards call returns by argument | |
| // no matter what. (Warning: ret_by_arg means the opposite in ffi_call) | |
| if (ret_by_arg) { | |
| ret_ptr = args[jsarg_idx++]; | |
| } else { | |
| // We might return 4 bytes or 8 bytes, allocate 8 just in case. | |
| STACK_ALLOC(cur_ptr, 8, 8); | |
| ret_ptr = cur_ptr; | |
| } | |
| cur_ptr -= __SIZEOF_POINTER__ * nargs; | |
| var args_ptr = cur_ptr; | |
| var carg_idx = 0; | |
| // Here we either have the actual argument, or a pair of BigInts for long | |
| // double, or a pointer to struct. We have to store into args_ptr[i] a | |
| // pointer to the ith argument. If the argument is a struct, just store the | |
| // pointer. Otherwise allocate stack space and copy the js argument onto the | |
| // stack. | |
| for (; carg_idx < nfixedargs; carg_idx++) { | |
| // jsarg_idx might start out as 0 or 1 depending on ret_by_arg | |
| // it advances an extra time for long double | |
| var cur_arg = args[jsarg_idx++]; | |
| var arg_type_info = unboxed_arg_type_info_list[carg_idx]; | |
| var arg_size = arg_type_info[0]; | |
| var arg_align = arg_type_info[1]; | |
| var arg_type_id = unboxed_arg_type_id_list[carg_idx]; | |
| switch (arg_type_id) { | |
| case FFI_TYPE_UINT8: | |
| case FFI_TYPE_SINT8: | |
| // Bad things happen if we don't align to 4 here | |
| STACK_ALLOC(cur_ptr, 1, 4); | |
| DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(cur_ptr); | |
| DEREF_U8(cur_ptr, 0) = cur_arg; | |
| break; | |
| case FFI_TYPE_UINT16: | |
| case FFI_TYPE_SINT16: | |
| // Bad things happen if we don't align to 4 here | |
| STACK_ALLOC(cur_ptr, 2, 4); | |
| DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(cur_ptr); | |
| DEREF_U16(cur_ptr, 0) = cur_arg; | |
| break; | |
| case FFI_TYPE_INT: | |
| case FFI_TYPE_UINT32: | |
| case FFI_TYPE_SINT32: | |
| STACK_ALLOC(cur_ptr, 4, 4); | |
| DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(cur_ptr); | |
| DEREF_U32(cur_ptr, 0) = cur_arg; | |
| break; | |
| case FFI_TYPE_STRUCT: | |
| // cur_arg is already a pointer to struct | |
| // copy it onto stack to pass by value | |
| STACK_ALLOC(cur_ptr, arg_size, arg_align); | |
| HEAP8.subarray(cur_ptr, cur_ptr + arg_size).set(HEAP8.subarray(DEC_PTR(cur_arg), DEC_PTR(cur_arg) + arg_size)); | |
| DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(cur_ptr); | |
| break; | |
| case FFI_TYPE_FLOAT: | |
| STACK_ALLOC(cur_ptr, 4, 4); | |
| DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(cur_ptr); | |
| DEREF_F32(cur_ptr, 0) = cur_arg; | |
| break; | |
| case FFI_TYPE_DOUBLE: | |
| STACK_ALLOC(cur_ptr, 8, 8); | |
| DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(cur_ptr); | |
| DEREF_F64(cur_ptr, 0) = cur_arg; | |
| break; | |
| case FFI_TYPE_UINT64: | |
| case FFI_TYPE_SINT64: | |
| STACK_ALLOC(cur_ptr, 8, 8); | |
| DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(cur_ptr); | |
| DEREF_U64(cur_ptr, 0) = cur_arg; | |
| break; | |
| case FFI_TYPE_LONGDOUBLE: | |
| STACK_ALLOC(cur_ptr, 16, 8); | |
| DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(cur_ptr); | |
| DEREF_U64(cur_ptr, 0) = cur_arg; | |
| cur_arg = args[jsarg_idx++]; | |
| DEREF_U64(cur_ptr, 1) = cur_arg; | |
| break; | |
| case FFI_TYPE_POINTER: | |
| STACK_ALLOC(cur_ptr, __SIZEOF_POINTER__, __SIZEOF_POINTER__); | |
| DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(cur_ptr); | |
| DEREF_PTR(cur_ptr, 0) = cur_arg; | |
| break; | |
| } | |
| } | |
| // If its a varargs call, last js argument is a pointer to the varargs. | |
| var varargs = DEC_PTR(args[args.length - 1]); | |
| // We have no way of knowing how many varargs were actually provided, this | |
| // fills the rest of the stack space allocated with nonsense. The onward | |
| // call will know to ignore the nonsense. | |
| // We either have a pointer to the argument if the argument is not a struct | |
| // or a pointer to pointer to struct. We need to store a pointer to the | |
| // argument into args_ptr[i] | |
| for (; carg_idx < nargs; carg_idx++) { | |
| var arg_type_id = unboxed_arg_type_id_list[carg_idx]; | |
| var arg_type_info = unboxed_arg_type_info_list[carg_idx]; | |
| var arg_size = arg_type_info[0]; | |
| var arg_align = arg_type_info[1]; | |
| if (arg_type_id === FFI_TYPE_STRUCT) { | |
| // In this case varargs is a pointer to pointer to struct so we need to | |
| // deref once | |
| var struct_ptr = DEREF_PTR_NUMBER(varargs, 0); | |
| STACK_ALLOC(cur_ptr, arg_size, arg_align); | |
| HEAP8.subarray(cur_ptr, cur_ptr + arg_size).set(HEAP8.subarray(struct_ptr, struct_ptr + arg_size)); | |
| DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(cur_ptr); | |
| } else { | |
| DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(varargs); | |
| } | |
| varargs += __SIZEOF_POINTER__; | |
| } | |
| stackRestore(cur_ptr); | |
| stackAlloc(0); // stackAlloc enforces alignment invariants on the stack pointer | |
| LOG_DEBUG("CALL_CLOSURE", "closure:", closure, "fptr", CLOSURE__fun(closure), "cif", CLOSURE__cif(closure)); | |
| getWasmTableEntry(CLOSURE__fun(closure))( | |
| CLOSURE__cif(closure), ENC_PTR(ret_ptr), ENC_PTR(args_ptr), | |
| CLOSURE__user_data(closure) | |
| ); | |
| stackRestore(orig_stack_ptr); | |
| // If we aren't supposed to return by argument, figure out what to return. | |
| if (!ret_by_arg) { | |
| switch (sig[0]) { | |
| case 'i': | |
| return DEREF_U32(ret_ptr, 0); | |
| case 'j': | |
| return DEREF_U64(ret_ptr, 0); | |
| case 'd': | |
| return DEREF_F64(ret_ptr, 0); | |
| case 'f': | |
| return DEREF_F32(ret_ptr, 0); | |
| } | |
| } | |
| } | |
| try { | |
| var wasm_trampoline = convertJsFunctionToWasm(trampoline, sig); | |
| } catch(e) { | |
| return FFI_BAD_TYPEDEF_MACRO; | |
| } | |
| setWasmTableEntry(codeloc, wasm_trampoline); | |
| CLOSURE__cif(closure) = ENC_PTR(cif); | |
| CLOSURE__fun(closure) = ENC_PTR(fun); | |
| CLOSURE__user_data(closure) = ENC_PTR(user_data); | |
| return FFI_OK_MACRO; | |
| }) | |
| // EM_JS does not correctly handle function pointer arguments, so we need a | |
| // helper | |
| ffi_status ffi_prep_closure_loc(ffi_closure *closure, ffi_cif *cif, | |
| void (*fun)(ffi_cif *, void *, void **, void *), | |
| void *user_data, void *codeloc) { | |
| if (cif->abi != FFI_EMSCRIPTEN_ABI) | |
| return FFI_BAD_ABI; | |
| return ffi_prep_closure_loc_js(closure, cif, (void *)fun, user_data, | |
| codeloc); | |
| } |