aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-11-02 01:16:13 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-11-02 01:16:13 -0700
commit81033c52fb8c4c6b9370b960451b21ac9f876744 (patch)
tree1b45a75645e94eeaefd7eebafb64f11df8ea601a
parent86639fe62a8b94fe68c64846d18de4dcf16c09e6 (diff)
downloadbun-81033c52fb8c4c6b9370b960451b21ac9f876744.tar.gz
bun-81033c52fb8c4c6b9370b960451b21ac9f876744.tar.zst
bun-81033c52fb8c4c6b9370b960451b21ac9f876744.zip
[bun:ffi] Implement `JSCallback` so C can call into JS
-rw-r--r--README.md108
-rw-r--r--src/bun.js/api/FFI.h31
-rw-r--r--src/bun.js/api/bun.zig3
-rw-r--r--src/bun.js/api/ffi.zig211
-rw-r--r--src/bun.js/bindings/JSFFIFunction.cpp239
-rw-r--r--src/bun.js/ffi.exports.js62
-rw-r--r--test/bun.js/ffi.test.fixture.callback.c39
-rw-r--r--test/bun.js/ffi.test.fixture.receiver.c33
-rw-r--r--test/bun.js/ffi.test.js164
9 files changed, 659 insertions, 231 deletions
diff --git a/README.md b/README.md
index 20d11bade..bec00eed3 100644
--- a/README.md
+++ b/README.md
@@ -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