From 68f026b3cdc7bab14330e37e5f10cb192e682d40 Mon Sep 17 00:00:00 2001 From: Kai Tamkun <13513421+heimskr@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:54:24 -0800 Subject: [PATCH] FFI: provide napi_env explicitly (#15431) Co-authored-by: Jarred Sumner --- src/bun.js/api/FFI.h | 5 +- src/bun.js/api/ffi.zig | 38 +++++++--- test/js/bun/ffi/cc.test.ts | 4 +- test/js/bun/ffi/ffi.test.fixture.callback.c | 78 +++++++++++++++++++-- test/js/bun/ffi/ffi.test.fixture.receiver.c | 75 ++++++++++++++++++-- 5 files changed, 180 insertions(+), 20 deletions(-) diff --git a/src/bun.js/api/FFI.h b/src/bun.js/api/FFI.h index a6d6e88c86..50a7d0bcd3 100644 --- a/src/bun.js/api/FFI.h +++ b/src/bun.js/api/FFI.h @@ -60,8 +60,9 @@ typedef enum { napi_detachable_arraybuffer_expected, napi_would_deadlock // unused } napi_status; -void* NapiHandleScope__open(void* jsGlobalObject, bool detached); -void NapiHandleScope__close(void* jsGlobalObject, void* handleScope); +void* NapiHandleScope__open(void* napi_env, bool detached); +void NapiHandleScope__close(void* napi_env, void* handleScope); +extern struct napi_env__ Bun__thisFFIModuleNapiEnv; #endif diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig index d37c9123c1..262ff8357e 100644 --- a/src/bun.js/api/ffi.zig +++ b/src/bun.js/api/ffi.zig @@ -444,6 +444,13 @@ pub const FFI = struct { return error.DeferredErrors; } + for (this.symbols.map.values()) |*symbol| { + if (symbol.needsNapiEnv()) { + _ = TCC.tcc_add_symbol(state, "Bun__thisFFIModuleNapiEnv", globalThis); + break; + } + } + for (this.define.items) |define| { TCC.tcc_define_symbol(state, define[0], define[1]); @@ -801,7 +808,7 @@ pub const FFI = struct { const function_name = function.base_name.?; const allocator = bun.default_allocator; - function.compile(allocator) catch |err| { + function.compile(allocator, globalThis) catch |err| { if (!globalThis.hasException()) { const ret = JSC.toInvalidArguments("{s} when translating symbol \"{s}\"", .{ @errorName(err), @@ -1141,7 +1148,7 @@ pub const FFI = struct { function.symbol_from_dynamic_library = resolved_symbol; } - function.compile(allocator) catch |err| { + function.compile(allocator, global) catch |err| { const ret = JSC.toInvalidArguments("{s} when compiling symbol \"{s}\" in \"{s}\"", .{ bun.asByteSlice(@errorName(err)), bun.asByteSlice(function_name), @@ -1246,7 +1253,7 @@ pub const FFI = struct { return ret; } - function.compile(allocator) catch |err| { + function.compile(allocator, global) catch |err| { const ret = JSC.toInvalidArguments("{s} when compiling symbol \"{s}\"", .{ bun.asByteSlice(@errorName(err)), bun.asByteSlice(function_name), @@ -1555,6 +1562,7 @@ pub const FFI = struct { pub fn compile( this: *Function, allocator: std.mem.Allocator, + globalObject: *JSC.JSGlobalObject, ) !void { var source_code = std.ArrayList(u8).init(allocator); var source_code_writer = source_code.writer(); @@ -1577,6 +1585,8 @@ pub const FFI = struct { _ = TCC.tcc_set_output_type(state, TCC.TCC_OUTPUT_MEMORY); + _ = TCC.tcc_add_symbol(state, "Bun__thisFFIModuleNapiEnv", globalObject); + CompilerRT.define(state); // TCC.tcc_define_symbol( @@ -1684,6 +1694,8 @@ pub const FFI = struct { _ = TCC.tcc_set_output_type(state, TCC.TCC_OUTPUT_MEMORY); + _ = TCC.tcc_add_symbol(state, "Bun__thisFFIModuleNapiEnv", js_context); + CompilerRT.define(state); const compilation_result = TCC.tcc_compile_string( @@ -1811,7 +1823,7 @@ pub const FFI = struct { if (this.needsHandleScope()) { try writer.writeAll( - \\ void* handleScope = NapiHandleScope__open(JS_GLOBAL_OBJECT, false); + \\ void* handleScope = NapiHandleScope__open(&Bun__thisFFIModuleNapiEnv, false); \\ ); } @@ -1824,7 +1836,7 @@ pub const FFI = struct { for (this.arg_types.items, 0..) |arg, i| { if (arg == .napi_env) { try writer.print( - \\ napi_env arg{d} = (napi_env)JS_GLOBAL_OBJECT; + \\ napi_env arg{d} = (napi_env)&Bun__thisFFIModuleNapiEnv; \\ argsPtr++; \\ , @@ -1924,7 +1936,7 @@ pub const FFI = struct { if (this.needsHandleScope()) { try writer.writeAll( - \\ NapiHandleScope__close(JS_GLOBAL_OBJECT, handleScope); + \\ NapiHandleScope__close(&Bun__thisFFIModuleNapiEnv, handleScope); \\ ); } @@ -2056,6 +2068,16 @@ pub const FFI = struct { try writer.writeAll(";\n}\n\n"); } + + fn needsNapiEnv(this: *const FFI.Function) bool { + for (this.arg_types.items) |arg| { + if (arg == .napi_env or arg == .napi_value) { + return true; + } + } + + return false; + } }; // Must be kept in sync with JSFFIFunction.h version @@ -2240,7 +2262,7 @@ pub const FFI = struct { try writer.writeAll("JSVALUE_TO_FLOAT("); }, .napi_env => { - try writer.writeAll("(napi_env)JS_GLOBAL_OBJECT"); + try writer.writeAll("((napi_env)&Bun__thisFFIModuleNapiEnv)"); return; }, .napi_value => { @@ -2301,7 +2323,7 @@ pub const FFI = struct { try writer.print("FLOAT_TO_JSVALUE({s})", .{self.symbol}); }, .napi_env => { - try writer.writeAll("JS_GLOBAL_OBJECT"); + try writer.writeAll("((napi_env)&Bun__thisFFIModuleNapiEnv)"); }, .napi_value => { try writer.print("((EncodedJSValue) {{.asNapiValue = {s} }} )", .{self.symbol}); diff --git a/test/js/bun/ffi/cc.test.ts b/test/js/bun/ffi/cc.test.ts index 8e90b8ec52..93c5429f3e 100644 --- a/test/js/bun/ffi/cc.test.ts +++ b/test/js/bun/ffi/cc.test.ts @@ -2,8 +2,8 @@ import { expect, it } from "bun:test"; import { bunEnv, bunExe, isWindows } from "harness"; import path from "path"; -// TODO: we need to install build-essential and apple SDK in CI. -// it can't find includes. It can on machiens with that enabled. +// TODO: we need to install build-essential and Apple SDK in CI. +// It can't find includes. It can on machines with that enabled. it.todoIf(isWindows)("can run a .c file", () => { const result = Bun.spawnSync({ cmd: [bunExe(), path.join(__dirname, "cc-fixture.js")], diff --git a/test/js/bun/ffi/ffi.test.fixture.callback.c b/test/js/bun/ffi/ffi.test.fixture.callback.c index 3807160af4..6b3c626c09 100644 --- a/test/js/bun/ffi/ffi.test.fixture.callback.c +++ b/test/js/bun/ffi/ffi.test.fixture.callback.c @@ -35,6 +35,38 @@ typedef _Bool bool; #define true 1 #define false 0 +#ifndef SRC_JS_NATIVE_API_TYPES_H_ +typedef struct napi_env__ *napi_env; +typedef int64_t napi_value; +typedef enum { + napi_ok, + napi_invalid_arg, + napi_object_expected, + napi_string_expected, + napi_name_expected, + napi_function_expected, + napi_number_expected, + napi_boolean_expected, + napi_array_expected, + napi_generic_failure, + napi_pending_exception, + napi_cancelled, + napi_escape_called_twice, + napi_handle_scope_mismatch, + napi_callback_scope_mismatch, + napi_queue_full, + napi_closing, + napi_bigint_expected, + napi_date_expected, + napi_arraybuffer_expected, + napi_detachable_arraybuffer_expected, + napi_would_deadlock // unused +} napi_status; +void* NapiHandleScope__open(void* napi_env, bool detached); +void NapiHandleScope__close(void* napi_env, void* handleScope); +extern struct napi_env__ Bun__thisFFIModuleNapiEnv; +#endif + #ifdef INJECT_BEFORE // #include @@ -45,14 +77,14 @@ typedef _Bool bool; // begin with a 15-bit pattern within the range 0x0002..0xFFFC. #define DoubleEncodeOffsetBit 49 #define DoubleEncodeOffset (1ll << DoubleEncodeOffsetBit) -#define OtherTag 0x2 -#define BoolTag 0x4 -#define UndefinedTag 0x8 +#define OtherTag 0x2ll +#define BoolTag 0x4ll +#define UndefinedTag 0x8ll #define TagValueFalse (OtherTag | BoolTag | false) #define TagValueTrue (OtherTag | BoolTag | true) #define TagValueUndefined (OtherTag | UndefinedTag) #define TagValueNull (OtherTag) -#define NotCellMask NumberTag | OtherTag +#define NotCellMask (int64_t)(NumberTag | OtherTag) #define MAX_INT32 2147483648 #define MAX_INT52 9007199254740991 @@ -70,6 +102,8 @@ typedef union EncodedJSValue { JSCell *ptr; #endif +napi_value asNapiValue; + #if IS_BIG_ENDIAN struct { int32_t tag; @@ -140,6 +174,11 @@ static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inli static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__)); static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__)); static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__)); +static uint8_t GET_JSTYPE(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSTYPE_IS_TYPED_ARRAY(uint8_t type) __attribute__((__always_inline__)); +static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) __attribute__((__always_inline__)); +static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) __attribute__((__always_inline__)); +static uint64_t JSVALUE_TO_TYPED_ARRAY_LENGTH(EncodedJSValue val) __attribute__((__always_inline__)); static bool JSVALUE_IS_CELL(EncodedJSValue val) { return !(val.asInt64 & NotCellMask); @@ -153,6 +192,25 @@ static bool JSVALUE_IS_NUMBER(EncodedJSValue val) { return val.asInt64 & NumberTag; } +static uint8_t GET_JSTYPE(EncodedJSValue val) { + return *(uint8_t*)((uint8_t*)val.asPtr + JSCell__offsetOfType); +} + +static bool JSTYPE_IS_TYPED_ARRAY(uint8_t type) { + return type >= JSTypeArrayBufferViewMin && type <= JSTypeArrayBufferViewMax; +} + +static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) { + return JSVALUE_IS_CELL(val) && JSTYPE_IS_TYPED_ARRAY(GET_JSTYPE(val)); +} + +static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) { + return *(void**)((char*)val.asPtr + JSArrayBufferView__offsetOfVector); +} + +static uint64_t JSVALUE_TO_TYPED_ARRAY_LENGTH(EncodedJSValue val) { + return *(uint64_t*)((char*)val.asPtr + JSArrayBufferView__offsetOfLength); +} // JSValue numbers-as-pointers are represented as a 52-bit integer // Previously, the pointer was stored at the end of the 64-bit value @@ -162,6 +220,11 @@ static bool JSVALUE_IS_NUMBER(EncodedJSValue val) { static void* JSVALUE_TO_PTR(EncodedJSValue val) { if (val.asInt64 == TagValueNull) return 0; + + if (JSCELL_IS_TYPED_ARRAY(val)) { + return JSVALUE_TO_TYPED_ARRAY_VECTOR(val); + } + val.asInt64 -= DoubleEncodeOffset; size_t ptr = (size_t)val.asDouble; return (void*)ptr; @@ -244,6 +307,10 @@ static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) { return (uint64_t)JSVALUE_TO_DOUBLE(value); } + if (JSCELL_IS_TYPED_ARRAY(value)) { + return (uint64_t)JSVALUE_TO_TYPED_ARRAY_LENGTH(value); + } + return JSVALUE_TO_UINT64_SLOW(value); } static int64_t JSVALUE_TO_INT64(EncodedJSValue value) { @@ -293,6 +360,9 @@ ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame); /* --- The Callback Function */ bool my_callback_function(void* arg0) { +#ifdef INJECT_BEFORE +INJECT_BEFORE; +#endif ZIG_REPR_TYPE arguments[1]; arguments[0] = PTR_TO_JSVALUE(arg0).asZigRepr; return (bool)JSVALUE_TO_BOOL(_FFI_Callback_call((void*)0x0000000000000000ULL, 1, arguments)); diff --git a/test/js/bun/ffi/ffi.test.fixture.receiver.c b/test/js/bun/ffi/ffi.test.fixture.receiver.c index 6d448c806d..814c66491b 100644 --- a/test/js/bun/ffi/ffi.test.fixture.receiver.c +++ b/test/js/bun/ffi/ffi.test.fixture.receiver.c @@ -35,6 +35,38 @@ typedef _Bool bool; #define true 1 #define false 0 +#ifndef SRC_JS_NATIVE_API_TYPES_H_ +typedef struct napi_env__ *napi_env; +typedef int64_t napi_value; +typedef enum { + napi_ok, + napi_invalid_arg, + napi_object_expected, + napi_string_expected, + napi_name_expected, + napi_function_expected, + napi_number_expected, + napi_boolean_expected, + napi_array_expected, + napi_generic_failure, + napi_pending_exception, + napi_cancelled, + napi_escape_called_twice, + napi_handle_scope_mismatch, + napi_callback_scope_mismatch, + napi_queue_full, + napi_closing, + napi_bigint_expected, + napi_date_expected, + napi_arraybuffer_expected, + napi_detachable_arraybuffer_expected, + napi_would_deadlock // unused +} napi_status; +void* NapiHandleScope__open(void* napi_env, bool detached); +void NapiHandleScope__close(void* napi_env, void* handleScope); +extern struct napi_env__ Bun__thisFFIModuleNapiEnv; +#endif + #ifdef INJECT_BEFORE // #include @@ -45,14 +77,14 @@ typedef _Bool bool; // begin with a 15-bit pattern within the range 0x0002..0xFFFC. #define DoubleEncodeOffsetBit 49 #define DoubleEncodeOffset (1ll << DoubleEncodeOffsetBit) -#define OtherTag 0x2 -#define BoolTag 0x4 -#define UndefinedTag 0x8 +#define OtherTag 0x2ll +#define BoolTag 0x4ll +#define UndefinedTag 0x8ll #define TagValueFalse (OtherTag | BoolTag | false) #define TagValueTrue (OtherTag | BoolTag | true) #define TagValueUndefined (OtherTag | UndefinedTag) #define TagValueNull (OtherTag) -#define NotCellMask NumberTag | OtherTag +#define NotCellMask (int64_t)(NumberTag | OtherTag) #define MAX_INT32 2147483648 #define MAX_INT52 9007199254740991 @@ -70,6 +102,8 @@ typedef union EncodedJSValue { JSCell *ptr; #endif +napi_value asNapiValue; + #if IS_BIG_ENDIAN struct { int32_t tag; @@ -140,6 +174,11 @@ static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inli static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__)); static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__)); static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__)); +static uint8_t GET_JSTYPE(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSTYPE_IS_TYPED_ARRAY(uint8_t type) __attribute__((__always_inline__)); +static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) __attribute__((__always_inline__)); +static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) __attribute__((__always_inline__)); +static uint64_t JSVALUE_TO_TYPED_ARRAY_LENGTH(EncodedJSValue val) __attribute__((__always_inline__)); static bool JSVALUE_IS_CELL(EncodedJSValue val) { return !(val.asInt64 & NotCellMask); @@ -153,6 +192,25 @@ static bool JSVALUE_IS_NUMBER(EncodedJSValue val) { return val.asInt64 & NumberTag; } +static uint8_t GET_JSTYPE(EncodedJSValue val) { + return *(uint8_t*)((uint8_t*)val.asPtr + JSCell__offsetOfType); +} + +static bool JSTYPE_IS_TYPED_ARRAY(uint8_t type) { + return type >= JSTypeArrayBufferViewMin && type <= JSTypeArrayBufferViewMax; +} + +static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) { + return JSVALUE_IS_CELL(val) && JSTYPE_IS_TYPED_ARRAY(GET_JSTYPE(val)); +} + +static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) { + return *(void**)((char*)val.asPtr + JSArrayBufferView__offsetOfVector); +} + +static uint64_t JSVALUE_TO_TYPED_ARRAY_LENGTH(EncodedJSValue val) { + return *(uint64_t*)((char*)val.asPtr + JSArrayBufferView__offsetOfLength); +} // JSValue numbers-as-pointers are represented as a 52-bit integer // Previously, the pointer was stored at the end of the 64-bit value @@ -162,6 +220,11 @@ static bool JSVALUE_IS_NUMBER(EncodedJSValue val) { static void* JSVALUE_TO_PTR(EncodedJSValue val) { if (val.asInt64 == TagValueNull) return 0; + + if (JSCELL_IS_TYPED_ARRAY(val)) { + return JSVALUE_TO_TYPED_ARRAY_VECTOR(val); + } + val.asInt64 -= DoubleEncodeOffset; size_t ptr = (size_t)val.asDouble; return (void*)ptr; @@ -244,6 +307,10 @@ static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) { return (uint64_t)JSVALUE_TO_DOUBLE(value); } + if (JSCELL_IS_TYPED_ARRAY(value)) { + return (uint64_t)JSVALUE_TO_TYPED_ARRAY_LENGTH(value); + } + return JSVALUE_TO_UINT64_SLOW(value); } static int64_t JSVALUE_TO_INT64(EncodedJSValue value) {