diff options
author | 2022-11-02 01:16:13 -0700 | |
---|---|---|
committer | 2022-11-02 01:16:13 -0700 | |
commit | 81033c52fb8c4c6b9370b960451b21ac9f876744 (patch) | |
tree | 1b45a75645e94eeaefd7eebafb64f11df8ea601a | |
parent | 86639fe62a8b94fe68c64846d18de4dcf16c09e6 (diff) | |
download | bun-81033c52fb8c4c6b9370b960451b21ac9f876744.tar.gz bun-81033c52fb8c4c6b9370b960451b21ac9f876744.tar.zst bun-81033c52fb8c4c6b9370b960451b21ac9f876744.zip |
[bun:ffi] Implement `JSCallback` so C can call into JS
-rw-r--r-- | README.md | 108 | ||||
-rw-r--r-- | src/bun.js/api/FFI.h | 31 | ||||
-rw-r--r-- | src/bun.js/api/bun.zig | 3 | ||||
-rw-r--r-- | src/bun.js/api/ffi.zig | 211 | ||||
-rw-r--r-- | src/bun.js/bindings/JSFFIFunction.cpp | 239 | ||||
-rw-r--r-- | src/bun.js/ffi.exports.js | 62 | ||||
-rw-r--r-- | test/bun.js/ffi.test.fixture.callback.c | 39 | ||||
-rw-r--r-- | test/bun.js/ffi.test.fixture.receiver.c | 33 | ||||
-rw-r--r-- | test/bun.js/ffi.test.js | 164 |
9 files changed, 659 insertions, 231 deletions
@@ -3682,24 +3682,25 @@ rustc --crate-type cdylib add.rs #### Supported FFI types (`FFIType`) -| `FFIType` | C Type | Aliases | -| --------- | ---------- | --------------------------- | -| cstring | `char*` | | -| ptr | `void*` | `pointer`, `void*`, `char*` | -| i8 | `int8_t` | `int8_t` | -| i16 | `int16_t` | `int16_t` | -| i32 | `int32_t` | `int32_t`, `int` | -| i64 | `int64_t` | `int64_t` | -| i64_fast | `int64_t` | | -| u8 | `uint8_t` | `uint8_t` | -| u16 | `uint16_t` | `uint16_t` | -| u32 | `uint32_t` | `uint32_t` | -| u64 | `uint64_t` | `uint64_t` | -| u64_fast | `uint64_t` | | -| f32 | `float` | `float` | -| f64 | `double` | `double` | -| bool | `bool` | | -| char | `char` | | +| `FFIType` | C Type | Aliases | +| --------- | -------------- | --------------------------- | +| cstring | `char*` | | +| function | `(void*)(*)()` | `fn`, `callback` | +| ptr | `void*` | `pointer`, `void*`, `char*` | +| i8 | `int8_t` | `int8_t` | +| i16 | `int16_t` | `int16_t` | +| i32 | `int32_t` | `int32_t`, `int` | +| i64 | `int64_t` | `int64_t` | +| i64_fast | `int64_t` | | +| u8 | `uint8_t` | `uint8_t` | +| u16 | `uint16_t` | `uint16_t` | +| u32 | `uint32_t` | `uint32_t` | +| u64 | `uint64_t` | `uint64_t` | +| u64_fast | `uint64_t` | | +| f32 | `float` | `float` | +| f64 | `double` | `double` | +| bool | `bool` | | +| char | `char` | | #### Strings (`CString`) @@ -3826,6 +3827,77 @@ const [major, minor, patch] = [ ]; ``` +#### Callbacks (`JSCallback`) + +Bun v0.2.3 added `JSCallback` which lets you create JavaScript callback functions that you can pass to C/FFI functions. The C/FFI function can call into the JavaScript/TypeScript code. This is useful for asynchronous code or otherwise when you want to call into JavaScript code from C. + +```ts +import { dlopen, JSCallback } from "bun:ffi"; + +const { + symbols: { setOnResolve, setOnReject }, +} = dlopen("libmylib", { + setOnResolve: { + returns: "bool", + args: ["function"], + }, + setOnReject: { + returns: "bool", + args: ["function"], + }, +}); + +const onResolve = new JSCallback( + { + returns: "bool", + args: ["i32"], + }, + (arg) => arg === 42 +); + +const onReject = new JSCallback( + { + returns: "bool", + args: ["i32"], + }, + (arg) => arg > 42 +); + +setOnResolve(onResolve); +setOnReject(onReject); + +// Sometime later: +setTimeout(() => { + onResolve.close(); + onReject.close(); +}, 5000); +``` + +When you're done with a JSCallback, you should call `close()` to free the memory. + +For a slight performance boost, directly pass `JSCallback.prototype.ptr` instead of the `JSCallback` object: + +```ts +const onResolve = new JSCallback( + { + returns: "bool", + args: ["i32"], + }, + (arg) => arg === 42 +); +const setOnResolve = new CFunction({ + returns: "bool", + args: ["function"], + ptr: myNativeLibrarySetOnResolve, +}); + +// This code runs slightly faster: +setOnResolve(onResolve.ptr); + +// Compared to this: +setOnResolve(onResolve); +``` + #### Pointers Bun represents [pointers](<https://en.wikipedia.org/wiki/Pointer_(computer_programming)>) as a `number` in JavaScript. diff --git a/src/bun.js/api/FFI.h b/src/bun.js/api/FFI.h index 1233e3e2e..4763c90a3 100644 --- a/src/bun.js/api/FFI.h +++ b/src/bun.js/api/FFI.h @@ -101,9 +101,16 @@ typedef void* JSContext; #ifdef IS_CALLBACK -extern int64_t bun_call(JSContext, void* func, void* thisValue, size_t len, const EncodedJSValue args[], void* exception); -JSContext cachedJSContext; -void* cachedCallbackFunction; +ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args); +// We wrap +static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) __attribute__((__always_inline__)); +static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) { + EncodedJSValue return_value; + return_value.asZigRepr = FFI_Callback_call(ctx, argCount, args); + return return_value; +} +static ZIG_REPR_TYPE arguments[100]; + #endif static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__)); @@ -115,10 +122,10 @@ static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_i uint64_t JSVALUE_TO_UINT64_SLOW(EncodedJSValue value); int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value); -EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* globalObject, uint64_t val); -EncodedJSValue INT64_TO_JSVALUE_SLOW(void* globalObject, int64_t val); -static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) __attribute__((__always_inline__)); -static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) __attribute__((__always_inline__)); +EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* jsGlobalObject, uint64_t val); +EncodedJSValue INT64_TO_JSVALUE_SLOW(void* jsGlobalObject, int64_t val); +static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) __attribute__((__always_inline__)); +static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) __attribute__((__always_inline__)); static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__)); @@ -240,7 +247,7 @@ static int64_t JSVALUE_TO_INT64(EncodedJSValue value) { return JSVALUE_TO_INT64_SLOW(value); } -static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) { +static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) { if (val < MAX_INT32) { return INT32_TO_JSVALUE((int32_t)val); } @@ -249,10 +256,10 @@ static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) { return DOUBLE_TO_JSVALUE((double)val); } - return UINT64_TO_JSVALUE_SLOW(globalObject, val); + return UINT64_TO_JSVALUE_SLOW(jsGlobalObject, val); } -static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) { +static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) { if (val >= -MAX_INT32 && val <= MAX_INT32) { return INT32_TO_JSVALUE((int32_t)val); } @@ -261,11 +268,11 @@ static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) { return DOUBLE_TO_JSVALUE((double)val); } - return INT64_TO_JSVALUE_SLOW(globalObject, val); + return INT64_TO_JSVALUE_SLOW(jsGlobalObject, val); } #ifndef IS_CALLBACK -ZIG_REPR_TYPE JSFunctionCall(void* globalObject, void* callFrame); +ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame); #endif diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 5189da1a7..324a6f9db 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -2603,6 +2603,9 @@ pub const FFI = struct { .toArrayBuffer = .{ .rfn = JSC.wrapWithHasContainer(@This(), "toArrayBuffer", false, false, true), }, + .closeCallback = .{ + .rfn = JSC.wrapWithHasContainer(JSC.FFI, "closeCallback", false, false, false), + }, }, .{ .read = .{ diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig index b9053de47..e9ee4d1ab 100644 --- a/src/bun.js/api/ffi.zig +++ b/src/bun.js/api/ffi.zig @@ -91,6 +91,12 @@ pub const FFI = struct { .{}, ); + pub fn closeCallback(globalThis: *JSGlobalObject, ctx: JSValue) JSValue { + var function = ctx.asPtr(Function); + function.deinit(globalThis, bun.default_allocator); + return JSValue.jsUndefined(); + } + pub fn callback(globalThis: *JSGlobalObject, interface: JSC.JSValue, js_callback: JSC.JSValue) JSValue { JSC.markBinding(@src()); if (!interface.isObject()) { @@ -110,13 +116,12 @@ pub const FFI = struct { } // TODO: WeakRefHandle that automatically frees it? - JSC.C.JSValueProtect(globalThis, js_callback.asObjectRef()); func.base_name = ""; + js_callback.ensureStillAlive(); - func.compileCallback(allocator, globalThis, js_callback.asObjectRef().?) catch return ZigString.init("Out of memory").toErrorInstance(globalThis); + func.compileCallback(allocator, globalThis, js_callback) catch return ZigString.init("Out of memory").toErrorInstance(globalThis); switch (func.step) { .failed => |err| { - JSC.C.JSValueUnprotect(globalThis, js_callback.asObjectRef()); const message = ZigString.init(err.msg).toErrorInstance(globalThis); func.deinit(globalThis, allocator); @@ -124,14 +129,19 @@ pub const FFI = struct { return message; }, .pending => { - JSC.C.JSValueUnprotect(globalThis, js_callback.asObjectRef()); func.deinit(globalThis, allocator); return ZigString.init("Failed to compile, but not sure why. Please report this bug").toErrorInstance(globalThis); }, .compiled => { var function_ = bun.default_allocator.create(Function) catch unreachable; function_.* = func.*; - return JSC.JSValue.fromPtrAddress(@ptrToInt(function_.step.compiled.ptr)); + return JSValue.createObject2( + globalThis, + ZigString.static("ptr"), + ZigString.static("ctx"), + JSC.JSValue.fromPtrAddress(@ptrToInt(function_.step.compiled.ptr)), + JSC.JSValue.fromPtrAddress(@ptrToInt(function_)), + ); }, } } @@ -179,7 +189,7 @@ pub const FFI = struct { function.base_name = "my_callback_function"; - function.printCallbackSourceCode(&writer) catch { + function.printCallbackSourceCode(null, null, &writer) catch { return ZigString.init("Error while printing code").toErrorInstance(global); }; return ZigString.init(arraylist.items).toValueGC(global); @@ -628,6 +638,8 @@ pub const FFI = struct { pub var lib_dirZ: [*:0]const u8 = ""; + extern "C" fn FFICallbackFunctionWrapper_destroy(*anyopaque) void; + pub fn deinit(val: *Function, globalThis: *JSC.JSGlobalObject, allocator: std.mem.Allocator) void { if (val.base_name) |base_name| { if (std.mem.span(base_name).len > 0) { @@ -649,6 +661,11 @@ pub const FFI = struct { // _ = JSC.untrackFunction(globalThis, val.step.compiled.js_function); val.step.compiled.js_function = .zero; } + + if (val.step.compiled.ffi_callback_function_wrapper) |wrapper| { + FFICallbackFunctionWrapper_destroy(wrapper); + val.step.compiled.ffi_callback_function_wrapper = null; + } } if (val.step == .failed and val.step.failed.allocated) { @@ -660,10 +677,10 @@ pub const FFI = struct { pending: void, compiled: struct { ptr: *anyopaque, - fast_path_ptr: ?*anyopaque = null, buf: []u8, js_function: JSValue = JSValue.zero, js_context: ?*anyopaque = null, + ffi_callback_function_wrapper: ?*anyopaque = null, }, failed: struct { msg: []const u8, @@ -796,7 +813,8 @@ pub const FFI = struct { } if (relocation_size < 0) { - this.step = .{ .failed = .{ .msg = "tcc_relocate returned a negative value" } }; + if (this.step != .failed) + this.step = .{ .failed = .{ .msg = "tcc_relocate returned a negative value" } }; return; } @@ -891,14 +909,13 @@ pub const FFI = struct { pub fn compileCallback( this: *Function, allocator: std.mem.Allocator, - js_context: *anyopaque, - js_function: *anyopaque, + js_context: *JSC.JSGlobalObject, + js_function: JSValue, ) !void { - Output.debug("welcome", .{}); var source_code = std.ArrayList(u8).init(allocator); var source_code_writer = source_code.writer(); - try this.printCallbackSourceCode(&source_code_writer); - Output.debug("helllooo", .{}); + var ffi_wrapper = Bun__createFFICallbackFunction(js_context, js_function); + try this.printCallbackSourceCode(js_context, ffi_wrapper, &source_code_writer); try source_code.append(0); // defer source_code.deinit(); var state = TCC.tcc_new() orelse return error.TCCMissing; @@ -920,7 +937,6 @@ pub const FFI = struct { state, source_code.items.ptr, ); - Output.debug("compile", .{}); // did tcc report an error? if (this.step == .failed) { return; @@ -934,13 +950,30 @@ pub const FFI = struct { } CompilerRT.inject(state); - Output.debug("here", .{}); - _ = TCC.tcc_add_symbol(state, "bun_call", workaround.bun_call.*); - _ = TCC.tcc_add_symbol(state, "cachedJSContext", js_context); - _ = TCC.tcc_add_symbol(state, "cachedCallbackFunction", js_function); - + _ = TCC.tcc_add_symbol( + state, + "FFI_Callback_call", + // TODO: stage2 - make these ptrs + switch (this.arg_types.items.len) { + 0 => FFI_Callback_call_0, + 1 => FFI_Callback_call_1, + 2 => FFI_Callback_call_2, + 3 => FFI_Callback_call_3, + 4 => FFI_Callback_call_4, + 5 => FFI_Callback_call_5, + 6 => FFI_Callback_call_6, + 7 => FFI_Callback_call_7, + else => FFI_Callback_call, + }, + ); var relocation_size = TCC.tcc_relocate(state, null); - if (relocation_size == 0) return; + + if (relocation_size < 0) { + if (this.step != .failed) + this.step = .{ .failed = .{ .msg = "tcc_relocate returned a negative value" } }; + return; + } + var bytes: []u8 = try allocator.rawAlloc(@intCast(usize, relocation_size), 16, 16, 0); defer { if (this.step == .failed) { @@ -966,8 +999,9 @@ pub const FFI = struct { .compiled = .{ .ptr = symbol, .buf = bytes, - .js_function = JSC.JSValue.fromPtr(js_function), + .js_function = js_function, .js_context = js_context, + .ffi_callback_function_wrapper = ffi_wrapper, }, }; } @@ -1021,7 +1055,7 @@ pub const FFI = struct { \\ \\ \\/* ---- Your Wrapper Function ---- */ - \\ZIG_REPR_TYPE JSFunctionCall(void* globalObject, void* callFrame) { + \\ZIG_REPR_TYPE JSFunctionCall(void* JS_GLOBAL_OBJECT, void* callFrame) { \\ ); @@ -1125,10 +1159,24 @@ pub const FFI = struct { try writer.writeAll(";\n}\n\n"); } + extern fn FFI_Callback_call(*anyopaque, usize, [*]JSValue) JSValue; + extern fn FFI_Callback_call_0(*anyopaque, usize, [*]JSValue) JSValue; + extern fn FFI_Callback_call_1(*anyopaque, usize, [*]JSValue) JSValue; + extern fn FFI_Callback_call_2(*anyopaque, usize, [*]JSValue) JSValue; + extern fn FFI_Callback_call_3(*anyopaque, usize, [*]JSValue) JSValue; + extern fn FFI_Callback_call_4(*anyopaque, usize, [*]JSValue) JSValue; + extern fn FFI_Callback_call_5(*anyopaque, usize, [*]JSValue) JSValue; + extern fn FFI_Callback_call_6(*anyopaque, usize, [*]JSValue) JSValue; + extern fn FFI_Callback_call_7(*anyopaque, usize, [*]JSValue) JSValue; + extern fn Bun__createFFICallbackFunction(*JSC.JSGlobalObject, JSValue) *anyopaque; + pub fn printCallbackSourceCode( this: *Function, + globalObject: ?*JSC.JSGlobalObject, + context_ptr: ?*anyopaque, writer: anytype, ) !void { + try writer.print("#define JS_GLOBAL_OBJECT (void*)0x{X}UL\n", .{@ptrToInt(globalObject)}); try writer.writeAll("#define IS_CALLBACK 1\n"); brk: { @@ -1193,44 +1241,40 @@ pub const FFI = struct { first = true; if (this.arg_types.items.len > 0) { - try writer.print(" EncodedJSValue arguments[{d}] = {{\n", .{this.arg_types.items.len}); - var arg_buf: [512]u8 = undefined; arg_buf[0.."arg".len].* = "arg".*; for (this.arg_types.items) |arg, i| { const printed = std.fmt.bufPrintIntToSlice(arg_buf["arg".len..], i, 10, .lower, .{}); const arg_name = arg_buf[0 .. "arg".len + printed.len]; - try writer.print(" {}", .{arg.toJS(arg_name)}); - if (i < this.arg_types.items.len - 1) { - try writer.writeAll(",\n"); - } + try writer.print("arguments[{d}] = {}.asZigRepr;\n", .{ i, arg.toJS(arg_name) }); } - try writer.writeAll("\n };\n"); - } else { - try writer.writeAll(" EncodedJSValue arguments[1] = {{0}};\n"); } try writer.writeAll(" "); - if (!(this.return_type == .void)) { - try writer.writeAll("EncodedJSValue return_value = {"); - } - // JSC.C.JSObjectCallAsFunction( - // ctx, - // object, - // thisObject, - // argumentCount, - // arguments, - // exception, - // ); - try writer.writeAll("bun_call(cachedJSContext, cachedCallbackFunction, (void*)0, "); + var inner_buf_: [372]u8 = undefined; + var inner_buf: []u8 = &.{}; if (this.arg_types.items.len > 0) { - try writer.print("{d}, &arguments[0], (void*)0)", .{this.arg_types.items.len}); + inner_buf = try std.fmt.bufPrint( + inner_buf_[1..], + "FFI_Callback_call((void*)0x{X}UL, {d}, arguments)", + .{ @ptrToInt(context_ptr), this.arg_types.items.len }, + ); } else { - try writer.writeAll("0, &arguments[0], (void*)0)"); + inner_buf = try std.fmt.bufPrint( + inner_buf_[1..], + "FFI_Callback_call((void*)0x{X}UL, 0, (ZIG_REPR_TYPE*)0)", + .{ + @ptrToInt(context_ptr), + }, + ); } - - if (this.return_type != .void) { - try writer.print("}};\n return {}", .{this.return_type.toC("return_value")}); + if (this.return_type == .void) { + try writer.writeAll(inner_buf); + } else { + const len = inner_buf.len + 1; + inner_buf = inner_buf_[0..len]; + inner_buf[0] = '_'; + try writer.print("return {s}", .{this.return_type.toCExact(inner_buf)}); } try writer.writeAll(";\n}\n\n"); @@ -1267,6 +1311,8 @@ pub const FFI = struct { i64_fast = 15, u64_fast = 16, + function = 17, + /// Types that we can directly pass through as an `int64_t` pub fn needsACastInC(this: ABIType) bool { return switch (this) { @@ -1311,6 +1357,9 @@ pub const FFI = struct { .{ "cstring", ABIType.@"cstring" }, .{ "i64_fast", ABIType.i64_fast }, .{ "u64_fast", ABIType.u64_fast }, + .{ "function", ABIType.function }, + .{ "callback", ABIType.callback }, + .{ "fn", ABIType.function }, }; pub const label = ComptimeStringMap(ABIType, map); const EnumMapFormatter = struct { @@ -1362,32 +1411,58 @@ pub const FFI = struct { const ToCFormatter = struct { symbol: string, tag: ABIType, + exact: bool = false, pub fn format(self: ToCFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { switch (self.tag) { - .void => {}, + .void => { + return; + }, .bool => { - try writer.print("JSVALUE_TO_BOOL({s})", .{self.symbol}); + if (self.exact) + try writer.writeAll("(bool)"); + try writer.writeAll("JSVALUE_TO_BOOL("); }, .char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => { - try writer.print("JSVALUE_TO_INT32({s})", .{self.symbol}); + if (self.exact) + try writer.print("({s})", .{std.mem.span(@tagName(self.tag))}); + + try writer.writeAll("JSVALUE_TO_INT32("); }, .i64_fast, .int64_t => { - try writer.print("JSVALUE_TO_INT64({s})", .{self.symbol}); + if (self.exact) + try writer.writeAll("(int64_t)"); + try writer.writeAll("JSVALUE_TO_INT64("); }, .u64_fast, .uint64_t => { - try writer.print("JSVALUE_TO_UINT64({s})", .{self.symbol}); + if (self.exact) + try writer.writeAll("(uint64_t)"); + try writer.writeAll("JSVALUE_TO_UINT64("); }, - .cstring, .ptr => { - try writer.print("JSVALUE_TO_PTR({s})", .{self.symbol}); + .function, .cstring, .ptr => { + if (self.exact) + try writer.writeAll("(void*)"); + try writer.writeAll("JSVALUE_TO_PTR("); }, .double => { - try writer.print("JSVALUE_TO_DOUBLE({s})", .{self.symbol}); + if (self.exact) + try writer.writeAll("(double)"); + try writer.writeAll("JSVALUE_TO_DOUBLE("); }, .float => { - try writer.print("JSVALUE_TO_FLOAT({s})", .{self.symbol}); + if (self.exact) + try writer.writeAll("(float)"); + try writer.writeAll("JSVALUE_TO_FLOAT("); }, } + // if (self.fromi64) { + // try writer.writeAll("EncodedJSValue{ "); + // } + try writer.writeAll(self.symbol); + // if (self.fromi64) { + // try writer.writeAll(", }"); + // } + try writer.writeAll(")"); } }; @@ -1401,22 +1476,22 @@ pub const FFI = struct { .bool => { try writer.print("BOOLEAN_TO_JSVALUE({s})", .{self.symbol}); }, - .char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => { - try writer.print("INT32_TO_JSVALUE({s})", .{self.symbol}); + .char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t => { + try writer.print("INT32_TO_JSVALUE((int32_t){s})", .{self.symbol}); }, - .i64_fast => { - try writer.print("INT64_TO_JSVALUE(globalObject, {s})", .{self.symbol}); + .uint32_t, .i64_fast => { + try writer.print("INT64_TO_JSVALUE(JS_GLOBAL_OBJECT, (int64_t){s})", .{self.symbol}); }, .int64_t => { - try writer.print("INT64_TO_JSVALUE_SLOW(globalObject, {s})", .{self.symbol}); + try writer.print("INT64_TO_JSVALUE_SLOW(JS_GLOBAL_OBJECT, {s})", .{self.symbol}); }, .u64_fast => { - try writer.print("UINT64_TO_JSVALUE(globalObject, {s})", .{self.symbol}); + try writer.print("UINT64_TO_JSVALUE(JS_GLOBAL_OBJECT, {s})", .{self.symbol}); }, .uint64_t => { - try writer.print("UINT64_TO_JSVALUE_SLOW(globalObject, {s})", .{self.symbol}); + try writer.print("UINT64_TO_JSVALUE_SLOW(JS_GLOBAL_OBJECT, {s})", .{self.symbol}); }, - .cstring, .ptr => { + .function, .cstring, .ptr => { try writer.print("PTR_TO_JSVALUE({s})", .{self.symbol}); }, .double => { @@ -1433,6 +1508,10 @@ pub const FFI = struct { return ToCFormatter{ .tag = this, .symbol = symbol }; } + pub fn toCExact(this: ABIType, symbol: string) ToCFormatter { + return ToCFormatter{ .tag = this, .symbol = symbol, .exact = true }; + } + pub fn toJS( this: ABIType, symbol: string, @@ -1449,7 +1528,7 @@ pub const FFI = struct { pub fn typenameLabel(this: ABIType) []const u8 { return switch (this) { - .cstring, .ptr => "void*", + .function, .cstring, .ptr => "void*", .bool => "bool", .int8_t => "int8_t", .uint8_t => "uint8_t", diff --git a/src/bun.js/bindings/JSFFIFunction.cpp b/src/bun.js/bindings/JSFFIFunction.cpp index 07fefac1f..66fdc701d 100644 --- a/src/bun.js/bindings/JSFFIFunction.cpp +++ b/src/bun.js/bindings/JSFFIFunction.cpp @@ -36,6 +36,40 @@ #include "DOMJITIDLTypeFilter.h" #include "DOMJITHelpers.h" +class FFICallbackFunctionWrapper { + + WTF_MAKE_FAST_ALLOCATED; + +public: + JSC::Strong<JSC::JSFunction> m_function; + JSC::Strong<Zig::GlobalObject> globalObject; + ~FFICallbackFunctionWrapper() = default; + + FFICallbackFunctionWrapper(JSC::JSFunction* function, Zig::GlobalObject* globalObject) + : m_function(globalObject->vm(), function) + , globalObject(globalObject->vm(), globalObject) + { + } +}; +extern "C" void FFICallbackFunctionWrapper_destroy(FFICallbackFunctionWrapper* wrapper) +{ + delete wrapper; +} + +extern "C" FFICallbackFunctionWrapper* Bun__createFFICallbackFunction( + Zig::GlobalObject* globalObject, + JSC::EncodedJSValue callbackFn) +{ + auto* vm = &globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(*vm); + + auto* callbackFunction = jsCast<JSC::JSFunction*>(JSC::JSValue::decode(callbackFn)); + + auto* wrapper = new FFICallbackFunctionWrapper(callbackFunction, globalObject); + + return wrapper; +} + extern "C" Zig::JSFFIFunction* Bun__CreateFFIFunction(Zig::GlobalObject* globalObject, const ZigString* symbolName, unsigned argCount, Zig::FFIFunction functionPointer, bool strong) { JSC::VM& vm = globalObject->vm(); @@ -96,3 +130,208 @@ JSFFIFunction* JSFFIFunction::create(VM& vm, Zig::GlobalObject* globalObject, un } } // namespace JSC + +extern "C" JSC::EncodedJSValue +FFI_Callback_call(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::EncodedJSValue* args) +{ + auto* function = wrapper.m_function.get(); + auto* globalObject = wrapper.globalObject.get(); + auto& vm = globalObject->vm(); + JSC::MarkedArgumentBuffer arguments; + for (size_t i = 0; i < argCount; ++i) + arguments.appendWithCrashOnOverflow(JSC::JSValue::decode(args[i])); + WTF::NakedPtr<JSC::Exception> exception; + auto result = JSC::call(globalObject, function, JSC::getCallData(function), JSC::jsUndefined(), arguments, exception); + if (UNLIKELY(exception)) { + auto scope = DECLARE_THROW_SCOPE(vm); + scope.throwException(globalObject, exception); + return JSC::JSValue::encode(JSC::jsNull()); + } + + return JSC::JSValue::encode(result); +} + +extern "C" JSC::EncodedJSValue +FFI_Callback_call_0(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::EncodedJSValue* args) +{ + auto* function = wrapper.m_function.get(); + auto* globalObject = wrapper.globalObject.get(); + auto& vm = globalObject->vm(); + + JSC::MarkedArgumentBuffer arguments; + + WTF::NakedPtr<JSC::Exception> exception; + auto result = JSC::call(globalObject, function, JSC::getCallData(function), JSC::jsUndefined(), arguments, exception); + if (UNLIKELY(exception)) { + auto scope = DECLARE_THROW_SCOPE(vm); + scope.throwException(globalObject, exception); + return JSC::JSValue::encode(JSC::jsNull()); + } + + return JSC::JSValue::encode(result); +} + +extern "C" JSC::EncodedJSValue +FFI_Callback_call_1(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::EncodedJSValue* args) +{ + auto* function = wrapper.m_function.get(); + auto* globalObject = wrapper.globalObject.get(); + auto& vm = globalObject->vm(); + + JSC::MarkedArgumentBuffer arguments; + arguments.append(JSC::JSValue::decode(args[0])); + + WTF::NakedPtr<JSC::Exception> exception; + auto result = JSC::call(globalObject, function, JSC::getCallData(function), JSC::jsUndefined(), arguments, exception); + if (UNLIKELY(exception)) { + auto scope = DECLARE_THROW_SCOPE(vm); + scope.throwException(globalObject, exception); + return JSC::JSValue::encode(JSC::jsNull()); + } + + return JSC::JSValue::encode(result); +} + +extern "C" JSC::EncodedJSValue +FFI_Callback_call_2(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::EncodedJSValue* args) +{ + auto* function = wrapper.m_function.get(); + auto* globalObject = wrapper.globalObject.get(); + auto& vm = globalObject->vm(); + + JSC::MarkedArgumentBuffer arguments; + arguments.append(JSC::JSValue::decode(args[0])); + arguments.append(JSC::JSValue::decode(args[1])); + + WTF::NakedPtr<JSC::Exception> exception; + auto result = JSC::call(globalObject, function, JSC::getCallData(function), JSC::jsUndefined(), arguments, exception); + if (UNLIKELY(exception)) { + auto scope = DECLARE_THROW_SCOPE(vm); + scope.throwException(globalObject, exception); + return JSC::JSValue::encode(JSC::jsNull()); + } + + return JSC::JSValue::encode(result); +} + +extern "C" JSC::EncodedJSValue FFI_Callback_call_3(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::EncodedJSValue* args) +{ + auto* function = wrapper.m_function.get(); + auto* globalObject = wrapper.globalObject.get(); + auto& vm = globalObject->vm(); + + JSC::MarkedArgumentBuffer arguments; + arguments.append(JSC::JSValue::decode(args[0])); + arguments.append(JSC::JSValue::decode(args[1])); + arguments.append(JSC::JSValue::decode(args[2])); + + WTF::NakedPtr<JSC::Exception> exception; + auto result = JSC::call(globalObject, function, JSC::getCallData(function), JSC::jsUndefined(), arguments, exception); + if (UNLIKELY(exception)) { + auto scope = DECLARE_THROW_SCOPE(vm); + scope.throwException(globalObject, exception); + return JSC::JSValue::encode(JSC::jsNull()); + } + + return JSC::JSValue::encode(result); +} + +extern "C" JSC::EncodedJSValue FFI_Callback_call_4(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::EncodedJSValue* args) +{ + auto* function = wrapper.m_function.get(); + auto* globalObject = wrapper.globalObject.get(); + auto& vm = globalObject->vm(); + + JSC::MarkedArgumentBuffer arguments; + arguments.append(JSC::JSValue::decode(args[0])); + arguments.append(JSC::JSValue::decode(args[1])); + arguments.append(JSC::JSValue::decode(args[2])); + arguments.append(JSC::JSValue::decode(args[3])); + + WTF::NakedPtr<JSC::Exception> exception; + auto result = JSC::call(globalObject, function, JSC::getCallData(function), JSC::jsUndefined(), arguments, exception); + if (UNLIKELY(exception)) { + auto scope = DECLARE_THROW_SCOPE(vm); + scope.throwException(globalObject, exception); + return JSC::JSValue::encode(JSC::jsNull()); + } + + return JSC::JSValue::encode(result); +} + +extern "C" JSC::EncodedJSValue FFI_Callback_call_5(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::EncodedJSValue* args) +{ + auto* function = wrapper.m_function.get(); + auto* globalObject = wrapper.globalObject.get(); + auto& vm = globalObject->vm(); + + JSC::MarkedArgumentBuffer arguments; + arguments.append(JSC::JSValue::decode(args[0])); + arguments.append(JSC::JSValue::decode(args[1])); + arguments.append(JSC::JSValue::decode(args[2])); + arguments.append(JSC::JSValue::decode(args[3])); + arguments.append(JSC::JSValue::decode(args[4])); + + WTF::NakedPtr<JSC::Exception> exception; + auto result = JSC::call(globalObject, function, JSC::getCallData(function), JSC::jsUndefined(), arguments, exception); + if (UNLIKELY(exception)) { + auto scope = DECLARE_THROW_SCOPE(vm); + scope.throwException(globalObject, exception); + return JSC::JSValue::encode(JSC::jsNull()); + } + + return JSC::JSValue::encode(result); +} + +extern "C" JSC::EncodedJSValue +FFI_Callback_call_6(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::EncodedJSValue* args) +{ + auto* function = wrapper.m_function.get(); + auto* globalObject = wrapper.globalObject.get(); + auto& vm = globalObject->vm(); + + JSC::MarkedArgumentBuffer arguments; + arguments.append(JSC::JSValue::decode(args[0])); + arguments.append(JSC::JSValue::decode(args[1])); + arguments.append(JSC::JSValue::decode(args[2])); + arguments.append(JSC::JSValue::decode(args[3])); + arguments.append(JSC::JSValue::decode(args[4])); + arguments.append(JSC::JSValue::decode(args[5])); + + WTF::NakedPtr<JSC::Exception> exception; + auto result = JSC::call(globalObject, function, JSC::getCallData(function), JSC::jsUndefined(), arguments, exception); + if (UNLIKELY(exception)) { + auto scope = DECLARE_THROW_SCOPE(vm); + scope.throwException(globalObject, exception); + return JSC::JSValue::encode(JSC::jsNull()); + } + + return JSC::JSValue::encode(result); +} + +extern "C" JSC::EncodedJSValue +FFI_Callback_call_7(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::EncodedJSValue* args) +{ + auto* function = wrapper.m_function.get(); + auto* globalObject = wrapper.globalObject.get(); + auto& vm = globalObject->vm(); + + JSC::MarkedArgumentBuffer arguments; + arguments.append(JSC::JSValue::decode(args[0])); + arguments.append(JSC::JSValue::decode(args[1])); + arguments.append(JSC::JSValue::decode(args[2])); + arguments.append(JSC::JSValue::decode(args[3])); + arguments.append(JSC::JSValue::decode(args[4])); + arguments.append(JSC::JSValue::decode(args[5])); + arguments.append(JSC::JSValue::decode(args[6])); + + WTF::NakedPtr<JSC::Exception> exception; + auto result = JSC::call(globalObject, function, JSC::getCallData(function), JSC::jsUndefined(), arguments, exception); + if (UNLIKELY(exception)) { + auto scope = DECLARE_THROW_SCOPE(vm); + scope.throwException(globalObject, exception); + return JSC::JSValue::encode(JSC::jsNull()); + } + + return JSC::JSValue::encode(result); +} diff --git a/src/bun.js/ffi.exports.js b/src/bun.js/ffi.exports.js index 737b7204b..faf74d1b8 100644 --- a/src/bun.js/ffi.exports.js +++ b/src/bun.js/ffi.exports.js @@ -9,6 +9,36 @@ export const viewSource = ffi.viewSource; const BunCString = ffi.CString; const nativeLinkSymbols = ffi.linkSymbols; +const nativeDLOpen = ffi.dlopen; +const nativeCallback = ffi.callback; +const closeCallback = ffi.closeCallback; +delete ffi.callback; +delete ffi.closeCallback; + +export class JSCallback { + constructor(options, cb) { + const { ctx, ptr } = nativeCallback(options, cb); + this.#ctx = ctx; + this.ptr = ptr; + } + + ptr; + #ctx; + + [Symbol.toPrimitive]() { + return this.ptr; + } + + close() { + const ctx = this.#ctx; + this.ptr = null; + this.#ctx = null; + + if (ctx) { + closeCallback(ctx); + } + } +} export class CString extends String { constructor(ptr, byteOffset, byteLength) { @@ -55,7 +85,8 @@ Object.defineProperty(globalThis, "__GlobalBunCString", { configurable: false, }); -const ffiWrappers = new Array(16); +const ffiWrappers = new Array(18); + var char = (val) => val | 0; ffiWrappers.fill(char); ffiWrappers[FFIType.uint8_t] = function uint8(val) { @@ -195,6 +226,25 @@ function cstringReturnType(val) { return new __GlobalBunCString(val); } +ffiWrappers[FFIType.function] = function functionType(val) { + if (typeof val === "number") { + return val; + } + + if (typeof val === "bigint") { + return Number(val); + } + + // we overwrote Symbol.toPrimitive + var ptr = +val; + + if (!Number.isFinite(ptr) || ptr === 0) { + throw new Error("Expected function to be a JSCallback or a number"); + } + + return ptr; +}; + function FFIBuilder(params, returnType, functionToCall, name) { const hasReturnType = typeof FFIType[returnType] === "number" && @@ -293,11 +343,11 @@ function FFIBuilder(params, returnType, functionToCall, name) { return wrap; } -const nativeDLOpen = ffi.dlopen; -const nativeCallback = ffi.callback; export const native = { dlopen: nativeDLOpen, - callback: nativeCallback, + callback: () => { + throw new Error("Deprecated. Use new JSCallback(options, fn) instead"); + }, }; export function dlopen(path, options) { @@ -383,8 +433,4 @@ export function CFunction(options) { return result.symbols[identifier]; } -export function callback(options, cb) { - return nativeCallback(options, cb); -} - export const read = ffi.read; diff --git a/test/bun.js/ffi.test.fixture.callback.c b/test/bun.js/ffi.test.fixture.callback.c index cb38eebe8..58e6c482e 100644 --- a/test/bun.js/ffi.test.fixture.callback.c +++ b/test/bun.js/ffi.test.fixture.callback.c @@ -1,3 +1,4 @@ +#define JS_GLOBAL_OBJECT (void*)0x0UL #define IS_CALLBACK 1 // This file is part of Bun! // You can find the original source: @@ -102,9 +103,16 @@ typedef void* JSContext; #ifdef IS_CALLBACK -extern int64_t bun_call(JSContext, void* func, void* thisValue, size_t len, const EncodedJSValue args[], void* exception); -JSContext cachedJSContext; -void* cachedCallbackFunction; +ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args); +// We wrap +static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) __attribute__((__always_inline__)); +static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) { + EncodedJSValue return_value; + return_value.asZigRepr = FFI_Callback_call(ctx, argCount, args); + return return_value; +} +static ZIG_REPR_TYPE arguments[100]; + #endif static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__)); @@ -116,10 +124,10 @@ static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_i uint64_t JSVALUE_TO_UINT64_SLOW(EncodedJSValue value); int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value); -EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* globalObject, uint64_t val); -EncodedJSValue INT64_TO_JSVALUE_SLOW(void* globalObject, int64_t val); -static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) __attribute__((__always_inline__)); -static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) __attribute__((__always_inline__)); +EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* jsGlobalObject, uint64_t val); +EncodedJSValue INT64_TO_JSVALUE_SLOW(void* jsGlobalObject, int64_t val); +static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) __attribute__((__always_inline__)); +static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) __attribute__((__always_inline__)); static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__)); @@ -241,7 +249,7 @@ static int64_t JSVALUE_TO_INT64(EncodedJSValue value) { return JSVALUE_TO_INT64_SLOW(value); } -static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) { +static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) { if (val < MAX_INT32) { return INT32_TO_JSVALUE((int32_t)val); } @@ -250,10 +258,10 @@ static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) { return DOUBLE_TO_JSVALUE((double)val); } - return UINT64_TO_JSVALUE_SLOW(globalObject, val); + return UINT64_TO_JSVALUE_SLOW(jsGlobalObject, val); } -static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) { +static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) { if (val >= -MAX_INT32 && val <= MAX_INT32) { return INT32_TO_JSVALUE((int32_t)val); } @@ -262,11 +270,11 @@ static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) { return DOUBLE_TO_JSVALUE((double)val); } - return INT64_TO_JSVALUE_SLOW(globalObject, val); + return INT64_TO_JSVALUE_SLOW(jsGlobalObject, val); } #ifndef IS_CALLBACK -ZIG_REPR_TYPE JSFunctionCall(void* globalObject, void* callFrame); +ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame); #endif @@ -282,10 +290,7 @@ bool my_callback_function(void* arg0) { #ifdef INJECT_BEFORE INJECT_BEFORE; #endif - EncodedJSValue arguments[1] = { - PTR_TO_JSVALUE(arg0) - }; - EncodedJSValue return_value = {bun_call(cachedJSContext, cachedCallbackFunction, (void*)0, 1, &arguments[0], (void*)0)}; - return JSVALUE_TO_BOOL(return_value); +arguments[0] = PTR_TO_JSVALUE(arg0).asZigRepr; + return (bool)JSVALUE_TO_BOOL(_FFI_Callback_call((void*)0x0UL, 1, arguments)); } diff --git a/test/bun.js/ffi.test.fixture.receiver.c b/test/bun.js/ffi.test.fixture.receiver.c index f8417805d..c972c2df1 100644 --- a/test/bun.js/ffi.test.fixture.receiver.c +++ b/test/bun.js/ffi.test.fixture.receiver.c @@ -103,9 +103,16 @@ typedef void* JSContext; #ifdef IS_CALLBACK -extern int64_t bun_call(JSContext, void* func, void* thisValue, size_t len, const EncodedJSValue args[], void* exception); -JSContext cachedJSContext; -void* cachedCallbackFunction; +ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args); +// We wrap +static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) __attribute__((__always_inline__)); +static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) { + EncodedJSValue return_value; + return_value.asZigRepr = FFI_Callback_call(ctx, argCount, args); + return return_value; +} +static ZIG_REPR_TYPE arguments[100]; + #endif static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__)); @@ -117,10 +124,10 @@ static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_i uint64_t JSVALUE_TO_UINT64_SLOW(EncodedJSValue value); int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value); -EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* globalObject, uint64_t val); -EncodedJSValue INT64_TO_JSVALUE_SLOW(void* globalObject, int64_t val); -static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) __attribute__((__always_inline__)); -static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) __attribute__((__always_inline__)); +EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* jsGlobalObject, uint64_t val); +EncodedJSValue INT64_TO_JSVALUE_SLOW(void* jsGlobalObject, int64_t val); +static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) __attribute__((__always_inline__)); +static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) __attribute__((__always_inline__)); static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__)); @@ -242,7 +249,7 @@ static int64_t JSVALUE_TO_INT64(EncodedJSValue value) { return JSVALUE_TO_INT64_SLOW(value); } -static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) { +static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) { if (val < MAX_INT32) { return INT32_TO_JSVALUE((int32_t)val); } @@ -251,10 +258,10 @@ static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) { return DOUBLE_TO_JSVALUE((double)val); } - return UINT64_TO_JSVALUE_SLOW(globalObject, val); + return UINT64_TO_JSVALUE_SLOW(jsGlobalObject, val); } -static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) { +static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) { if (val >= -MAX_INT32 && val <= MAX_INT32) { return INT32_TO_JSVALUE((int32_t)val); } @@ -263,11 +270,11 @@ static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) { return DOUBLE_TO_JSVALUE((double)val); } - return INT64_TO_JSVALUE_SLOW(globalObject, val); + return INT64_TO_JSVALUE_SLOW(jsGlobalObject, val); } #ifndef IS_CALLBACK -ZIG_REPR_TYPE JSFunctionCall(void* globalObject, void* callFrame); +ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame); #endif @@ -278,7 +285,7 @@ float not_a_callback(float arg0); /* ---- Your Wrapper Function ---- */ -ZIG_REPR_TYPE JSFunctionCall(void* globalObject, void* callFrame) { +ZIG_REPR_TYPE JSFunctionCall(void* JS_GLOBAL_OBJECT, void* callFrame) { LOAD_ARGUMENTS_FROM_CALL_FRAME; EncodedJSValue arg0; arg0.asInt64 = *argsPtr; diff --git a/test/bun.js/ffi.test.js b/test/bun.js/ffi.test.js index 536e6d7aa..6433f0161 100644 --- a/test/bun.js/ffi.test.js +++ b/test/bun.js/ffi.test.js @@ -4,6 +4,7 @@ import { CFunction, CString, dlopen as _dlopen, + JSCallback, ptr, read, toArrayBuffer, @@ -372,6 +373,7 @@ function ffiRunner(fast) { }, close, } = dlopen("/tmp/bun-ffi-test.dylib", types); + Bun.gc(true); expect(returns_true()).toBe(true); Bun.gc(true); @@ -407,6 +409,7 @@ function ffiRunner(fast) { expect(identity_int8_t(10)).toBe(10); expect(identity_int16_t(10)).toBe(10); + if (fast) expect(identity_int64_t(10)).toBe(10); else expect(identity_int64_t(10)).toBe(10n); expect(identity_uint8_t(10)).toBe(10); @@ -486,6 +489,70 @@ function ffiRunner(fast) { }); expect(myCFunction()).toBe(true); + { + const typeMap = { + int8_t: -8, + int16_t: -16, + int32_t: -32, + int64_t: -64n, + uint8_t: 8, + uint16_t: 16, + uint32_t: 32, + uint64_t: 64n, + float: 32.5, + double: 64.5, + ptr: 0xdeadbeef, + "void*": null, + }; + + // Return types, 1 argument + for (let [returnName, returnValue] of Object.entries(typeMap)) { + var roundtripFunction = new CFunction({ + ptr: new JSCallback( + { + returns: returnName, + args: [returnName], + }, + (input) => { + return input; + } + ).ptr, + returns: returnName, + args: [returnName], + }); + expect(roundtripFunction(returnValue)).toBe(returnValue); + } + + { + var toClose = new JSCallback( + { + returns: "bool", + args: ["bool"], + }, + (input) => { + return input; + } + ); + expect(toClose.ptr > 0).toBe(true); + toClose.close(); + expect(toClose.ptr === null).toBe(true); + } + + // Return types, no args + for (let [name, value] of Object.entries(typeMap)) { + var roundtripFunction = new CFunction({ + ptr: new JSCallback( + { + returns: name, + }, + () => value + ).ptr, + returns: name, + }); + expect(roundtripFunction()).toBe(value); + } + } + // check deallocator is called // for (let constructor of [toArrayBuffer, toBuffer]) { @@ -506,103 +573,6 @@ function ffiRunner(fast) { // Bun.gc(true); // } close(); - /* - --- - This style of callback is not implemented yet - */ - // function identityBool() { - // return true; - // } - // globalThis.identityBool = identityBool; - - // const first = native.callback( - // { - // returns: "bool", - // }, - // identityBool - // ); - // expect( - // cb_identity_true() - // ).toBe(true); - - // expect(cb_identity_true(first)).toBe(true); - - // expect( - // cb_identity_false( - // callback( - // { - // returns: "bool", - // }, - // () => false - // ) - // ) - // ).toBe(false); - - // expect( - // cb_identity_42_char( - // callback( - // { - // returns: "char", - // }, - // () => 42 - // ) - // ) - // ).toBe(42); - // expect( - // cb_identity_42_uint8_t( - // callback( - // { - // returns: "uint8_t", - // }, - // () => 42 - // ) - // ) - // ).toBe(42); - - // cb_identity_neg_42_int8_t( - // callback( - // { - // returns: "int8_t", - // }, - // () => -42 - // ) - // ).toBe(-42); - - // cb_identity_42_uint16_t( - // callback( - // { - // returns: "uint16_t", - // }, - // () => 42 - // ) - // ).toBe(42); - - // cb_identity_42_uint32_t( - // callback( - // { - // returns: "uint32_t", - // }, - // () => 42 - // ) - // ).toBe(42); - - // cb_identity_neg_42_int16_t( - // callback( - // { - // returns: "int16_t", - // }, - // () => -42 - // ) - // ).toBe(-42); - - // cb_identity_neg_42_int32_t( - // callback( - // { - // returns: "int32_t", - // }, - // () => -42 - // ) - // ).toBe(-42); } // TODO: There is a crash when dlopen() two times the same library in quick succession |