From 8b8630c604f0e10b5a5e289bb7073c2d046a0f26 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Fri, 27 Jun 2025 17:02:17 +0900 Subject: [PATCH 1/5] src/wasm32: Allow building with Emscripten with 64bit support MEMORY64 enables 64bit pointers so this commit updates the accessors for the libffi data structures accordingly. Each JS functions in ffi.c receives pointers as BigInt (i64) values and with casts them to Numer (i53) using bigintToI53Checked. While memory64 supports 64bit addressing, the maximum memory size is currently limited to 16GiB [1]. Therefore, we can assume that the passed pointers are within the Number's range. [1] https://webassembly.github.io/memory64/js-api/#limits Signed-off-by: Kohei Tokunaga --- src/wasm32/ffi.c | 211 ++++++++++++++++++++++++++++++----------- src/wasm32/ffitarget.h | 17 ++++ 2 files changed, 174 insertions(+), 54 deletions(-) diff --git a/src/wasm32/ffi.c b/src/wasm32/ffi.c index 632848712..486ffa785 100644 --- a/src/wasm32/ffi.c +++ b/src/wasm32/ffi.c @@ -59,16 +59,29 @@ EM_JS_DEPS(libffi, "$getWasmTableEntry,$setWasmTableEntry,$getEmptyTableSlot,$co 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); @@ -81,6 +94,49 @@ CHECK_FIELD_OFFSET(ffi_type, elements, 8); #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)) @@ -100,7 +156,7 @@ _Static_assert(FFI_BAD_TYPEDEF_MACRO == FFI_BAD_TYPEDEF, "FFI_BAD_TYPEDEF must b ffi_status FFI_HIDDEN ffi_prep_cif_machdep(ffi_cif *cif) { - if (cif->abi != FFI_WASM32_EMSCRIPTEN) + 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. @@ -144,6 +200,7 @@ ffi_prep_cif_machdep_var(ffi_cif *cif, unsigned nfixedargs, unsigned ntotalargs) 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 @@ -156,15 +213,15 @@ unbox_small_structs, (ffi_type type_ptr), { // // See the Python comment here: // https://github.com/python/cpython/blob/a16a9f978f42b8a09297c1efbf33877f6388c403/Modules/_ctypes/stgdict.c#L718-L779 - if (FFI_TYPE__SIZE(type_ptr) > 16) { + if (DEC_PTR(FFI_TYPE__SIZE(type_ptr)) > 16) { break; } - var elements = FFI_TYPE__ELEMENTS(type_ptr); - var first_element = DEREF_U32(elements, 0); + 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_U32(elements, 1) === 0) { + } else if (DEREF_PTR_NUMBER(elements, 1) === 0) { type_ptr = first_element; type_id = FFI_TYPE__TYPEID(first_element); } else { @@ -178,10 +235,15 @@ 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 = CIF__ARGTYPES(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]; @@ -205,7 +267,7 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), // 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(rvalue); + args.push(ENC_PTR(rvalue)); ret_by_arg = true; } @@ -214,8 +276,8 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), // 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_U32(avalue, i); - var arg_unboxed = unbox_small_structs(DEREF_U32(arg_types_ptr, 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]; @@ -226,7 +288,6 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), case FFI_TYPE_INT: case FFI_TYPE_SINT32: case FFI_TYPE_UINT32: - case FFI_TYPE_POINTER: args.push(DEREF_U32(arg_ptr, 0)); break; case FFI_TYPE_FLOAT: @@ -260,11 +321,14 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), // 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 = FFI_TYPE__SIZE(arg_type_ptr); + 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(cur_stack_ptr); + 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'); @@ -282,11 +346,11 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), // 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 (nfixedargs != nargs) { + if (flags & VARARGS_FLAG) { var struct_arg_info = []; for (var i = nargs - 1; i >= nfixedargs; i--) { - var arg_ptr = DEREF_U32(avalue, i); - var arg_unboxed = unbox_small_structs(DEREF_U32(arg_types_ptr, 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) { @@ -303,7 +367,6 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), case FFI_TYPE_INT: case FFI_TYPE_UINT32: case FFI_TYPE_SINT32: - case FFI_TYPE_POINTER: case FFI_TYPE_FLOAT: STACK_ALLOC(cur_stack_ptr, 4, 4); DEREF_U32(cur_stack_ptr, 0) = DEREF_U32(arg_ptr, 0); @@ -326,8 +389,12 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), // 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, 4, 4); - struct_arg_info.push([cur_stack_ptr, arg_ptr, FFI_TYPE__SIZE(arg_type_ptr), FFI_TYPE__ALIGN(arg_type_ptr)]); + 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'); @@ -336,7 +403,7 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), } } // extra normal argument which is the pointer to the varargs. - args.push(cur_stack_ptr); + 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]; @@ -346,7 +413,7 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), 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_U32(arg_target, 0) = cur_stack_ptr; + DEREF_PTR(arg_target, 0) = ENC_PTR(cur_stack_ptr); } } stackRestore(cur_stack_ptr); @@ -371,7 +438,6 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), case FFI_TYPE_INT: case FFI_TYPE_UINT32: case FFI_TYPE_SINT32: - case FFI_TYPE_POINTER: DEREF_U32(rvalue, 0) = result; break; case FFI_TYPE_FLOAT: @@ -392,6 +458,9 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue), 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: @@ -403,6 +472,8 @@ 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); @@ -413,12 +484,30 @@ CHECK_FIELD_OFFSET(ffi_closure, user_data, 4*3); #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_U32(code, 0) = index; - CLOSURE__wrapper(closure) = index; - return closure; + DEREF_PTR(code, 0) = ENC_PTR(index); + CLOSURE__wrapper(closure) = ENC_PTR(index); + return ENC_PTR(closure); }) void * __attribute__ ((visibility ("default"))) @@ -427,7 +516,8 @@ ffi_closure_alloc(size_t size, void **code) { } EM_JS_MACROS(void, ffi_closure_free_js, (void *closure), { - var index = CLOSURE__wrapper(closure); + closure = DEC_PTR(closure); + var index = DEC_PTR(CLOSURE__wrapper(closure)); freeTableIndexes.push(index); _free(closure); }) @@ -442,10 +532,15 @@ 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 = CIF__ARGTYPES(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]; @@ -461,7 +556,7 @@ ffi_prep_closure_loc_js, case FFI_TYPE_STRUCT: case FFI_TYPE_LONGDOUBLE: // Return via a first pointer argument. - sig = 'vi'; + sig = 'v' + PTR_SIG; ret_by_arg = true; break; case FFI_TYPE_INT: @@ -471,7 +566,6 @@ ffi_prep_closure_loc_js, case FFI_TYPE_SINT16: case FFI_TYPE_UINT32: case FFI_TYPE_SINT32: - case FFI_TYPE_POINTER: sig = 'i'; break; case FFI_TYPE_FLOAT: @@ -484,6 +578,9 @@ ffi_prep_closure_loc_js, 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: @@ -492,11 +589,11 @@ ffi_prep_closure_loc_js, 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_U32(arg_types_ptr, 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([FFI_TYPE__SIZE(arg_type_ptr), FFI_TYPE__ALIGN(arg_type_ptr)]); + 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]) { @@ -507,8 +604,6 @@ ffi_prep_closure_loc_js, case FFI_TYPE_SINT16: case FFI_TYPE_UINT32: case FFI_TYPE_SINT32: - case FFI_TYPE_POINTER: - case FFI_TYPE_STRUCT: sig += 'i'; break; case FFI_TYPE_FLOAT: @@ -524,6 +619,10 @@ ffi_prep_closure_loc_js, 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: @@ -532,7 +631,7 @@ ffi_prep_closure_loc_js, } if (nfixedargs < nargs) { // extra pointer to varargs stack - sig += 'i'; + sig += PTR_SIG; } LOG_DEBUG("CREATE_CLOSURE", "sig:", sig); function trampoline() { @@ -551,7 +650,7 @@ ffi_prep_closure_loc_js, STACK_ALLOC(cur_ptr, 8, 8); ret_ptr = cur_ptr; } - cur_ptr -= 4 * nargs; + 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 @@ -572,58 +671,62 @@ ffi_prep_closure_loc_js, case FFI_TYPE_SINT8: // Bad things happen if we don't align to 4 here STACK_ALLOC(cur_ptr, 1, 4); - DEREF_U32(args_ptr, carg_idx) = cur_ptr; + 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_U32(args_ptr, carg_idx) = cur_ptr; + 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: - case FFI_TYPE_POINTER: STACK_ALLOC(cur_ptr, 4, 4); - DEREF_U32(args_ptr, carg_idx) = cur_ptr; + 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(cur_arg, cur_arg + arg_size)); - DEREF_U32(args_ptr, carg_idx) = cur_ptr; + 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_U32(args_ptr, carg_idx) = cur_ptr; + 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_U32(args_ptr, carg_idx) = cur_ptr; + 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_U32(args_ptr, carg_idx) = cur_ptr; + 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_U32(args_ptr, carg_idx) = cur_ptr; + 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 = args[args.length - 1]; + 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. @@ -639,20 +742,20 @@ ffi_prep_closure_loc_js, 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_U32(varargs, 0); + 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_U32(args_ptr, carg_idx) = cur_ptr; + DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(cur_ptr); } else { - DEREF_U32(args_ptr, carg_idx) = varargs; + DEREF_PTR(args_ptr, carg_idx) = ENC_PTR(varargs); } - varargs += 4; + 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), ret_ptr, args_ptr, + CLOSURE__cif(closure), ENC_PTR(ret_ptr), ENC_PTR(args_ptr), CLOSURE__user_data(closure) ); stackRestore(orig_stack_ptr); @@ -677,9 +780,9 @@ ffi_prep_closure_loc_js, return FFI_BAD_TYPEDEF_MACRO; } setWasmTableEntry(codeloc, wasm_trampoline); - CLOSURE__cif(closure) = cif; - CLOSURE__fun(closure) = fun; - CLOSURE__user_data(closure) = user_data; + CLOSURE__cif(closure) = ENC_PTR(cif); + CLOSURE__fun(closure) = ENC_PTR(fun); + CLOSURE__user_data(closure) = ENC_PTR(user_data); return FFI_OK_MACRO; }) @@ -688,7 +791,7 @@ ffi_prep_closure_loc_js, 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_WASM32_EMSCRIPTEN) + if (cif->abi != FFI_EMSCRIPTEN_ABI) return FFI_BAD_ABI; return ffi_prep_closure_loc_js(closure, cif, (void *)fun, user_data, codeloc); diff --git a/src/wasm32/ffitarget.h b/src/wasm32/ffitarget.h index ac78b7433..10041c00d 100644 --- a/src/wasm32/ffitarget.h +++ b/src/wasm32/ffitarget.h @@ -42,14 +42,31 @@ typedef void (*ffi_fp)(void); typedef enum ffi_abi { FFI_FIRST_ABI = 0, +#if __SIZEOF_POINTER__ == 4 FFI_WASM32, // "raw", no structures, varargs, or closures (not implemented!) FFI_WASM32_EMSCRIPTEN, // structures, varargs, and split 64-bit params +#elif __SIZEOF_POINTER__ == 8 + FFI_WASM64, + FFI_WASM64_EMSCRIPTEN, +#else +#error "Unknown pointer size" +#endif FFI_LAST_ABI, +#if __SIZEOF_POINTER__ == 4 #ifdef __EMSCRIPTEN__ FFI_DEFAULT_ABI = FFI_WASM32_EMSCRIPTEN #else FFI_DEFAULT_ABI = FFI_WASM32 #endif +#elif __SIZEOF_POINTER__ == 8 +#ifdef __EMSCRIPTEN__ + FFI_DEFAULT_ABI = FFI_WASM64_EMSCRIPTEN +#else + FFI_DEFAULT_ABI = FFI_WASM64 +#endif +#else +#error "Unknown pointer size" +#endif } ffi_abi; #define FFI_CLOSURES 1 From 626dbe7dded2aede43d547be97d0538212749461 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Fri, 27 Jun 2025 17:03:11 +0900 Subject: [PATCH 2/5] Add wasm64 target to the build scripts This commit adds support for the wasm64 target via the configure script. Emscripten supports two modes of the -sMEMORY64 flag[1] so the script allows users specifying the value through a configuration variable. Additionally, "src/wasm32" directory has been renamed to the more generic "src/wasm" because it's now shared between both 32bit and 64bit builds. [1] https://emscripten.org/docs/tools_reference/settings_reference.html#memory64 Signed-off-by: Kohei Tokunaga --- Makefile.am | 4 ++-- configure.ac | 8 ++++++++ configure.host | 7 ++++++- src/{wasm32 => wasm}/ffi.c | 0 src/{wasm32 => wasm}/ffitarget.h | 0 5 files changed, 16 insertions(+), 3 deletions(-) rename src/{wasm32 => wasm}/ffi.c (100%) rename src/{wasm32 => wasm}/ffitarget.h (100%) diff --git a/Makefile.am b/Makefile.am index cffcb9f6f..c3bcf00b3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -67,7 +67,7 @@ noinst_HEADERS = src/aarch64/ffitarget.h src/aarch64/internal.h \ src/s390/ffitarget.h src/s390/internal.h src/sh/ffitarget.h \ src/sh64/ffitarget.h src/sparc/ffitarget.h \ src/sparc/internal.h src/tile/ffitarget.h src/vax/ffitarget.h \ - src/wasm32/ffitarget.h \ + src/wasm/ffitarget.h \ src/x86/ffitarget.h src/x86/internal.h src/x86/internal64.h \ src/x86/asmnames.h src/xtensa/ffitarget.h src/dlmalloc.c \ src/kvx/ffitarget.h src/kvx/asm.h \ @@ -98,7 +98,7 @@ EXTRA_libffi_la_SOURCES = src/aarch64/ffi.c src/aarch64/sysv.S \ src/sh64/sysv.S src/sparc/ffi.c src/sparc/ffi64.c \ src/sparc/v8.S src/sparc/v9.S src/tile/ffi.c src/tile/tile.S \ src/vax/ffi.c src/vax/elfbsd.S src/x86/ffi.c src/x86/sysv.S \ - src/wasm32/ffi.c \ + src/wasm/ffi.c \ src/x86/ffiw64.c src/x86/win64.S src/x86/ffi64.c \ src/x86/unix64.S src/x86/sysv_intel.S src/x86/win64_intel.S \ src/xtensa/ffi.c src/xtensa/sysv.S src/kvx/ffi.c \ diff --git a/configure.ac b/configure.ac index 258107d70..72317382d 100644 --- a/configure.ac +++ b/configure.ac @@ -123,6 +123,8 @@ AC_C_BIGENDIAN GCC_AS_CFI_PSEUDO_OP +AC_ARG_VAR([WASM64_MEMORY64], [Used only for the wasm64 target. Set to 1 (default) or 2 for Emscripten's -sMEMORY64 mode]) + case "$TARGET" in SPARC) AC_CACHE_CHECK([assembler and linker support unaligned pc related relocs], @@ -182,6 +184,12 @@ case "$TARGET" in [Define if the compiler uses zarch features.]) fi ;; + wasm64) + if test -z "$WASM64_MEMORY64"; then + WASM64_MEMORY64=1 + fi + CFLAGS="$CFLAGS -sMEMORY64=$WASM64_MEMORY64" + ;; esac AC_CACHE_CHECK([whether compiler supports pointer authentication], diff --git a/configure.host b/configure.host index c69a49cef..4e10c3edb 100644 --- a/configure.host +++ b/configure.host @@ -261,7 +261,12 @@ case "${host}" in ;; wasm32-*-*) - TARGET=wasm32; TARGETDIR=wasm32 + TARGET=wasm32; TARGETDIR=wasm + SOURCES="ffi.c" + ;; + + wasm64-*-*) + TARGET=wasm64; TARGETDIR=wasm SOURCES="ffi.c" ;; diff --git a/src/wasm32/ffi.c b/src/wasm/ffi.c similarity index 100% rename from src/wasm32/ffi.c rename to src/wasm/ffi.c diff --git a/src/wasm32/ffitarget.h b/src/wasm/ffitarget.h similarity index 100% rename from src/wasm32/ffitarget.h rename to src/wasm/ffitarget.h From 653dda6b2984da1ce895e24e83e3c0709da1c1de Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Fri, 27 Jun 2025 17:08:01 +0900 Subject: [PATCH 3/5] GitHub Actions: Add wasm64 tests This commit adds a test matrix for wasm32, wasm64 and wasm64 with the -sMEMORY64=2 flag, using the latest version of Emscripten. -Wno-main is added to suppress the following warning in unwindtest.cc and unwindtest_ffi_call.cc. > FAIL: libffi.closures/unwindtest_ffi_call.cc -W -Wall -O2 (test for excess errors) > Excess errors: > ./libffi.closures/unwindtest_ffi_call.cc:20:5: warning: 'main' should not be 'extern "C"' [-Wmain] > 20 | int main (void) > | ^ > 1 warning generated. Signed-off-by: Kohei Tokunaga --- .github/workflows/emscripten.yml | 61 ++++++++++++++++++++++++++++-- testsuite/emscripten/build.sh | 4 +- testsuite/emscripten/node-tests.sh | 8 ++-- 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml index 2be8df404..a8c0a1001 100644 --- a/.github/workflows/emscripten.yml +++ b/.github/workflows/emscripten.yml @@ -19,7 +19,7 @@ env: # "info" field, or in Makefile.envs: # https://github.com/pyodide/pyodide/blob/main/Makefile.envs#L2 PYTHON_VERSION: 3.12.7 - EMSCRIPTEN_VERSION: 3.1.58 + EMSCRIPTEN_VERSION: 4.0.10 EM_CACHE_FOLDER: emsdk-cache jobs: @@ -39,6 +39,21 @@ jobs: actions-cache-folder: ${{ env.EM_CACHE_FOLDER }} test-dejagnu: + strategy: + matrix: + target: + - name: wasm32 + host: wasm32 + configureflags: + testflags: + - name: wasm64 + host: wasm64 + configureflags: + testflags: -sMEMORY64=1 + - name: wasm64-2 + host: wasm64 + configureflags: WASM64_MEMORY64=2 + testflags: -sMEMORY64=2 runs-on: ubuntu-24.04 needs: [setup-emsdk-cache] steps: @@ -62,11 +77,26 @@ jobs: version: ${{ env.EMSCRIPTEN_VERSION }} actions-cache-folder: ${{ env.EM_CACHE_FOLDER }} + - name: Setup node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + + # This step updates emsdk's configuration file ".emscripten" to point to + # nodejs installed in the previous step. + - name: Configure emsdk to use the installed node.js + run: sed -i -E 's|NODE_JS = .*|NODE_JS = '"'$(which node)'"'|g' ${EMSDK}/.emscripten + - name: Install dependencies run: sudo apt-get install dejagnu libltdl-dev - name: Run tests run: testsuite/emscripten/node-tests.sh + env: + TARGET_HOST: ${{ matrix.target.host }} + EXTRA_CONFIGURE_FLAGS: ${{ matrix.target.configureflags }} + EXTRA_CFLAGS: ${{ matrix.target.testflags }} + EXTRA_TEST_LDFLAGS: ${{ matrix.target.testflags }} - name: Install rlgl and run run: | @@ -77,6 +107,21 @@ jobs: exit $? build: + strategy: + matrix: + target: + - name: wasm32 + host: wasm32 + configureflags: + testflags: + - name: wasm64 + host: wasm64 + configureflags: + testflags: -sMEMORY64=1 + - name: wasm64-2 + host: wasm64 + configureflags: WASM64_MEMORY64=2 + testflags: -sMEMORY64=2 runs-on: ubuntu-24.04 needs: [setup-emsdk-cache] steps: @@ -100,6 +145,9 @@ jobs: - name: Build run: ./testsuite/emscripten/build.sh + env: + TARGET_HOST: ${{ matrix.target.host }} + EXTRA_CONFIGURE_FLAGS: ${{ matrix.target.configureflags }} - name: Build tests run: | @@ -107,16 +155,23 @@ jobs: cp -r testsuite/libffi.closures testsuite/libffi.closures.test ./testsuite/emscripten/build-tests.sh testsuite/libffi.call.test ./testsuite/emscripten/build-tests.sh testsuite/libffi.closures.test + env: + EXTRA_CFLAGS: ${{ matrix.target.testflags }} + EXTRA_LD_FLAGS: ${{ matrix.target.testflags }} - name: Store artifacts uses: actions/upload-artifact@v4 with: - name: built-tests + name: built-tests-${{ matrix.target.name }} path: ./testsuite/libffi.c*/ test: strategy: matrix: + target: + - name: wasm32 + - name: wasm64 + - name: wasm64-2 browser: ["chrome"] # FIXME: selenium can't find gecko driver for "firefox" runs-on: ubuntu-24.04 @@ -128,7 +183,7 @@ jobs: - name: Download build artifact uses: actions/download-artifact@v4 with: - name: built-tests + name: built-tests-${{ matrix.target.name }} path: ./testsuite/ - uses: conda-incubator/setup-miniconda@v3 diff --git a/testsuite/emscripten/build.sh b/testsuite/emscripten/build.sh index 83ece7bcf..a332c1d53 100755 --- a/testsuite/emscripten/build.sh +++ b/testsuite/emscripten/build.sh @@ -36,11 +36,11 @@ export PKG_CONFIG_PATH="$TARGET/lib/pkgconfig" export EM_PKG_CONFIG_PATH="$PKG_CONFIG_PATH" # Specific variables for cross-compilation -export CHOST="wasm32-unknown-linux" # wasm32-unknown-emscripten +export CHOST="${TARGET_HOST}-unknown-linux" # wasm32-unknown-emscripten autoreconf -fiv emconfigure ./configure --host=$CHOST --prefix="$TARGET" --enable-static --disable-shared --disable-dependency-tracking \ - --disable-builddir --disable-multi-os-directory --disable-raw-api --disable-docs + --disable-builddir --disable-multi-os-directory --disable-raw-api --disable-docs ${EXTRA_CONFIGURE_FLAGS} make install cp fficonfig.h target/include/ cp include/ffi_common.h target/include/ diff --git a/testsuite/emscripten/node-tests.sh b/testsuite/emscripten/node-tests.sh index 016e99c4b..2710947c0 100755 --- a/testsuite/emscripten/node-tests.sh +++ b/testsuite/emscripten/node-tests.sh @@ -8,16 +8,16 @@ fi # Common compiler flags export CFLAGS="-fPIC $EXTRA_CFLAGS" export CXXFLAGS="$CFLAGS -sNO_DISABLE_EXCEPTION_CATCHING $EXTRA_CXXFLAGS" -export LDFLAGS="-sEXPORTED_FUNCTIONS=_main,_malloc,_free -sALLOW_TABLE_GROWTH -sASSERTIONS -sNO_DISABLE_EXCEPTION_CATCHING -sWASM_BIGINT" +export LDFLAGS="-sEXPORTED_FUNCTIONS=_main,_malloc,_free -sALLOW_TABLE_GROWTH -sASSERTIONS -sNO_DISABLE_EXCEPTION_CATCHING -sWASM_BIGINT -Wno-main" # Specific variables for cross-compilation -export CHOST="wasm32-unknown-linux" # wasm32-unknown-emscripten +export CHOST="${TARGET_HOST}-unknown-linux" # wasm32-unknown-emscripten autoreconf -fiv emconfigure ./configure --prefix="$(pwd)/target" --host=$CHOST --enable-static --disable-shared \ - --disable-builddir --disable-multi-os-directory --disable-raw-api --disable-docs || + --disable-builddir --disable-multi-os-directory --disable-raw-api --disable-docs ${EXTRA_CONFIGURE_FLAGS} || (cat config.log && exit 1) make EMMAKEN_JUST_CONFIGURE=1 emmake make check \ - RUNTESTFLAGS="LDFLAGS_FOR_TARGET='$LDFLAGS'" || (cat testsuite/libffi.log && exit 1) + RUNTESTFLAGS="LDFLAGS_FOR_TARGET='$LDFLAGS $EXTRA_TEST_LDFLAGS'" || (cat testsuite/libffi.log && exit 1) From cc95b5432a0dafd0769f213b53d08c65c16f0cd6 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Fri, 27 Jun 2025 17:08:29 +0900 Subject: [PATCH 4/5] testsuite: Fix types of main function test_libffi.py calls each test's main function without arguments, but some tests define the main function with parameters. This signature mismatch causes a runtime error with the recent version of Emscripten. This commit resolves this issue by updating the function signatures to match the way they are called. Signed-off-by: Kohei Tokunaga --- testsuite/libffi.closures/cls_dbls_struct.c | 2 +- testsuite/libffi.closures/huge_struct.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/testsuite/libffi.closures/cls_dbls_struct.c b/testsuite/libffi.closures/cls_dbls_struct.c index 00e247e7e..d0c206c04 100644 --- a/testsuite/libffi.closures/cls_dbls_struct.c +++ b/testsuite/libffi.closures/cls_dbls_struct.c @@ -28,7 +28,7 @@ closure_test_gn(ffi_cif* cif __UNUSED__, void* resp __UNUSED__, closure_test_fn(*(Dbls*)args[0]); } -int main(int argc __UNUSED__, char** argv __UNUSED__) +int main(void) { ffi_cif cif; diff --git a/testsuite/libffi.closures/huge_struct.c b/testsuite/libffi.closures/huge_struct.c index 572a0c8fb..b3488304c 100644 --- a/testsuite/libffi.closures/huge_struct.c +++ b/testsuite/libffi.closures/huge_struct.c @@ -216,8 +216,7 @@ cls_large_fn(ffi_cif* cif __UNUSED__, void* resp, void** args, void* userdata __ ui8_5, si8_5); } -int -main(int argc __UNUSED__, const char** argv __UNUSED__) +int main (void) { void *code; ffi_closure *pcl = ffi_closure_alloc(sizeof(ffi_closure), &code); From 8df89f23932b1760e4305f30ecdde3b748c12d2e Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Fri, 27 Jun 2025 17:11:20 +0900 Subject: [PATCH 5/5] README: Add document about WASM64 Signed-off-by: Kohei Tokunaga --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 01ed28f28..f108ac984 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ tested: | TILE-Gx/TILEPro | Linux | GCC | | VAX | OpenBSD/vax | GCC | | WASM32 | Emscripten | EMCC | +| WASM64 | Emscripten | EMCC | | X86 | FreeBSD | GCC | | X86 | GNU HURD | GCC | | X86 | Interix | GCC |