aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--integration/bunjs-only-snippets/ffi-test.c33
-rw-r--r--integration/bunjs-only-snippets/ffi.test.js305
-rw-r--r--src/javascript/jsc/api/FFI.h59
-rw-r--r--src/javascript/jsc/api/bun.zig36
-rw-r--r--src/javascript/jsc/api/ffi.zig541
-rw-r--r--src/javascript/jsc/base.zig15
-rw-r--r--src/javascript/jsc/ffi.exports.js1
-rw-r--r--src/output.zig3
-rw-r--r--types/bun/ffi.d.ts162
9 files changed, 883 insertions, 272 deletions
diff --git a/integration/bunjs-only-snippets/ffi-test.c b/integration/bunjs-only-snippets/ffi-test.c
index 850ba3ef7..cc87d0528 100644
--- a/integration/bunjs-only-snippets/ffi-test.c
+++ b/integration/bunjs-only-snippets/ffi-test.c
@@ -113,22 +113,17 @@ void *return_a_function_ptr_to_function_that_returns_true() {
return (void *)&returns_true;
}
-bool cb_identity_true(bool (*cb)()) {
- printf("The memory address is: %p\n", cb);
- printf("The memory address should be %p\n", &returns_true);
- bool ran = cb();
- printf("The result is : %d\n", ran);
- return ran;
-}
-bool cb_identity_false(bool (^cb)()) { return cb(); }
-char cb_identity_42_char(char (^cb)()) { return cb(); }
-float cb_identity_42_float(float (^cb)()) { return cb(); }
-double cb_identity_42_double(double (^cb)()) { return cb(); }
-uint8_t cb_identity_42_uint8_t(uint8_t (^cb)()) { return cb(); }
-int8_t cb_identity_neg_42_int8_t(int8_t (^cb)()) { return cb(); }
-uint16_t cb_identity_42_uint16_t(uint16_t (^cb)()) { return cb(); }
-uint32_t cb_identity_42_uint32_t(uint32_t (^cb)()) { return cb(); }
-uint64_t cb_identity_42_uint64_t(uint64_t (^cb)()) { return cb(); }
-int16_t cb_identity_neg_42_int16_t(int16_t (^cb)()) { return cb(); }
-int32_t cb_identity_neg_42_int32_t(int32_t (^cb)()) { return cb(); }
-int64_t cb_identity_neg_42_int64_t(int64_t (^cb)()) { return cb(); } \ No newline at end of file
+bool cb_identity_true(bool (*cb)()) { return cb(); }
+
+bool cb_identity_false(bool (*cb)()) { return cb(); }
+char cb_identity_42_char(char (*cb)()) { return cb(); }
+float cb_identity_42_float(float (*cb)()) { return cb(); }
+double cb_identity_42_double(double (*cb)()) { return cb(); }
+uint8_t cb_identity_42_uint8_t(uint8_t (*cb)()) { return cb(); }
+int8_t cb_identity_neg_42_int8_t(int8_t (*cb)()) { return cb(); }
+uint16_t cb_identity_42_uint16_t(uint16_t (*cb)()) { return cb(); }
+uint32_t cb_identity_42_uint32_t(uint32_t (*cb)()) { return cb(); }
+uint64_t cb_identity_42_uint64_t(uint64_t (*cb)()) { return cb(); }
+int16_t cb_identity_neg_42_int16_t(int16_t (*cb)()) { return cb(); }
+int32_t cb_identity_neg_42_int32_t(int32_t (*cb)()) { return cb(); }
+int64_t cb_identity_neg_42_int64_t(int64_t (*cb)()) { return cb(); } \ No newline at end of file
diff --git a/integration/bunjs-only-snippets/ffi.test.js b/integration/bunjs-only-snippets/ffi.test.js
index 2337a44c8..fb297dae2 100644
--- a/integration/bunjs-only-snippets/ffi.test.js
+++ b/integration/bunjs-only-snippets/ffi.test.js
@@ -9,180 +9,280 @@ import {
toBuffer,
toArrayBuffer,
FFIType,
+ callback,
} from "bun:ffi";
-it("ffi print", () => {
- viewSource({
- add: {
- args: [FFIType.int],
- return_type: "int32_t",
- },
- })[0];
+it("ffi print", async () => {
+ await Bun.write(
+ "ffi.test.fixture.callback.c",
+ viewSource(
+ {
+ return_type: "bool",
+ args: [],
+ },
+ true
+ )
+ );
+ await Bun.write(
+ "ffi.test.fixture.receiver.c",
+ viewSource(
+ {
+ callback: {
+ return_type: 13,
+ args: ["ptr"],
+ },
+ },
+ false
+ )[0]
+ );
+ expect(
+ viewSource(
+ {
+ return_type: "int8_t",
+ args: [],
+ },
+ true
+ ).length > 0
+ ).toBe(true);
+ expect(
+ viewSource(
+ {
+ a: {
+ return_type: "int8_t",
+ args: [],
+ },
+ },
+ false
+ ).length > 0
+ ).toBe(true);
});
it("ffi run", () => {
const types = {
returns_true: {
return_type: "bool",
- params: [],
+ args: [],
},
returns_false: {
return_type: "bool",
- params: [],
+ args: [],
},
returns_42_char: {
return_type: "char",
- params: [],
+ args: [],
},
// returns_42_float: {
// return_type: "float",
- // params: [],
+ // args: [],
// },
// returns_42_double: {
// return_type: "double",
- // params: [],
+ // args: [],
// },
returns_42_uint8_t: {
return_type: "uint8_t",
- params: [],
+ args: [],
},
returns_neg_42_int8_t: {
return_type: "int8_t",
- params: [],
+ args: [],
},
returns_42_uint16_t: {
return_type: "uint16_t",
- params: [],
+ args: [],
},
returns_42_uint32_t: {
return_type: "uint32_t",
- params: [],
+ args: [],
},
// // returns_42_uint64_t: {
// // return_type: "uint64_t",
- // // params: [],
+ // // args: [],
// // },
returns_neg_42_int16_t: {
return_type: "int16_t",
- params: [],
+ args: [],
},
returns_neg_42_int32_t: {
return_type: "int32_t",
- params: [],
+ args: [],
},
// returns_neg_42_int64_t: {
// return_type: "int64_t",
- // params: [],
+ // args: [],
// },
identity_char: {
return_type: "char",
- params: ["char"],
+ args: ["char"],
},
// identity_float: {
// return_type: "float",
- // params: ["float"],
+ // args: ["float"],
// },
identity_bool: {
return_type: "bool",
- params: ["bool"],
+ args: ["bool"],
},
// identity_double: {
// return_type: "double",
- // params: ["double"],
+ // args: ["double"],
// },
identity_int8_t: {
return_type: "int8_t",
- params: ["int8_t"],
+ args: ["int8_t"],
},
identity_int16_t: {
return_type: "int16_t",
- params: ["int16_t"],
+ args: ["int16_t"],
},
identity_int32_t: {
return_type: "int32_t",
- params: ["int32_t"],
+ args: ["int32_t"],
},
// identity_int64_t: {
// return_type: "int64_t",
- // params: ["int64_t"],
+ // args: ["int64_t"],
// },
identity_uint8_t: {
return_type: "uint8_t",
- params: ["uint8_t"],
+ args: ["uint8_t"],
},
identity_uint16_t: {
return_type: "uint16_t",
- params: ["uint16_t"],
+ args: ["uint16_t"],
},
identity_uint32_t: {
return_type: "uint32_t",
- params: ["uint32_t"],
+ args: ["uint32_t"],
},
// identity_uint64_t: {
// return_type: "uint64_t",
- // params: ["uint64_t"],
+ // args: ["uint64_t"],
// },
add_char: {
return_type: "char",
- params: ["char", "char"],
+ args: ["char", "char"],
},
add_float: {
return_type: "float",
- params: ["float", "float"],
+ args: ["float", "float"],
},
add_double: {
return_type: "double",
- params: ["double", "double"],
+ args: ["double", "double"],
},
add_int8_t: {
return_type: "int8_t",
- params: ["int8_t", "int8_t"],
+ args: ["int8_t", "int8_t"],
},
add_int16_t: {
return_type: "int16_t",
- params: ["int16_t", "int16_t"],
+ args: ["int16_t", "int16_t"],
},
add_int32_t: {
return_type: "int32_t",
- params: ["int32_t", "int32_t"],
+ args: ["int32_t", "int32_t"],
},
// add_int64_t: {
// return_type: "int64_t",
- // params: ["int64_t", "int64_t"],
+ // args: ["int64_t", "int64_t"],
// },
add_uint8_t: {
return_type: "uint8_t",
- params: ["uint8_t", "uint8_t"],
+ args: ["uint8_t", "uint8_t"],
},
add_uint16_t: {
return_type: "uint16_t",
- params: ["uint16_t", "uint16_t"],
+ args: ["uint16_t", "uint16_t"],
},
add_uint32_t: {
return_type: "uint32_t",
- params: ["uint32_t", "uint32_t"],
+ args: ["uint32_t", "uint32_t"],
},
does_pointer_equal_42_as_int32_t: {
return_type: "bool",
- params: ["ptr"],
+ args: ["ptr"],
},
ptr_should_point_to_42_as_int32_t: {
return_type: "ptr",
- params: [],
+ args: [],
+ },
+ identity_ptr: {
+ return_type: "ptr",
+ args: ["ptr"],
},
// add_uint64_t: {
// return_type: "uint64_t",
- // params: ["uint64_t", "uint64_t"],
+ // args: ["uint64_t", "uint64_t"],
+ // },
+
+ cb_identity_true: {
+ return_type: "bool",
+ args: ["ptr"],
+ },
+ cb_identity_false: {
+ return_type: "bool",
+ args: ["ptr"],
+ },
+ cb_identity_42_char: {
+ return_type: "char",
+ args: ["ptr"],
+ },
+ // cb_identity_42_float: {
+ // return_type: "float",
+ // args: ["ptr"],
+ // },
+ // cb_identity_42_double: {
+ // return_type: "double",
+ // args: ["ptr"],
+ // },
+ cb_identity_42_uint8_t: {
+ return_type: "uint8_t",
+ args: ["ptr"],
+ },
+ cb_identity_neg_42_int8_t: {
+ return_type: "int8_t",
+ args: ["ptr"],
+ },
+ cb_identity_42_uint16_t: {
+ return_type: "uint16_t",
+ args: ["ptr"],
+ },
+ cb_identity_42_uint32_t: {
+ return_type: "uint32_t",
+ args: ["ptr"],
+ },
+ // cb_identity_42_uint64_t: {
+ // return_type: "uint64_t",
+ // args: ["ptr"],
+ // },
+ cb_identity_neg_42_int16_t: {
+ return_type: "int16_t",
+ args: ["ptr"],
+ },
+ cb_identity_neg_42_int32_t: {
+ return_type: "int32_t",
+ args: ["ptr"],
+ },
+ // cb_identity_neg_42_int64_t: {
+ // return_type: "int64_t",
+ // args: ["ptr"],
// },
+
+ return_a_function_ptr_to_function_that_returns_true: {
+ return_type: "ptr",
+ args: [],
+ },
};
const {
symbols: {
returns_true,
returns_false,
+ return_a_function_ptr_to_function_that_returns_true,
returns_42_char,
returns_42_float,
returns_42_double,
@@ -215,10 +315,24 @@ it("ffi run", () => {
add_int64_t,
add_uint8_t,
add_uint16_t,
+ identity_ptr,
add_uint32_t,
add_uint64_t,
does_pointer_equal_42_as_int32_t,
ptr_should_point_to_42_as_int32_t,
+ cb_identity_true,
+ cb_identity_false,
+ cb_identity_42_char,
+ cb_identity_42_float,
+ cb_identity_42_double,
+ cb_identity_42_uint8_t,
+ cb_identity_neg_42_int8_t,
+ cb_identity_42_uint16_t,
+ cb_identity_42_uint32_t,
+ cb_identity_42_uint64_t,
+ cb_identity_neg_42_int16_t,
+ cb_identity_neg_42_int32_t,
+ cb_identity_neg_42_int64_t,
},
close,
} = dlopen("/tmp/bun-ffi-test.dylib", types);
@@ -270,6 +384,105 @@ it("ffi run", () => {
);
expect(ptr(buffer)).toBe(cptr);
expect(new CString(cptr, 0, 1)).toBe("*");
+ expect(identity_ptr(cptr)).toBe(cptr);
+ const second_ptr = ptr(new Buffer(8));
+ expect(identity_ptr(second_ptr)).toBe(second_ptr);
+ function identityBool() {
+ console.log("hi");
+ return true;
+ }
+ globalThis.identityBool = identityBool;
+
+ const first = callback(
+ {
+ return_type: "bool",
+ },
+ identityBool
+ );
+
+ expect(
+ cb_identity_true(return_a_function_ptr_to_function_that_returns_true())
+ ).toBe(true);
+
+ expect(cb_identity_true(first)).toBe(true);
+ console.log("second");
+
+ expect(
+ cb_identity_false(
+ callback(
+ {
+ return_type: "bool",
+ },
+ () => false
+ )
+ )
+ ).toBe(false);
+
+ expect(
+ cb_identity_42_char(
+ callback(
+ {
+ return_type: "char",
+ },
+ () => 42
+ )
+ )
+ ).toBe(42);
+ expect(
+ cb_identity_42_uint8_t(
+ callback(
+ {
+ return_type: "uint8_t",
+ },
+ () => 42
+ )
+ )
+ ).toBe(42);
+
+ cb_identity_neg_42_int8_t(
+ callback(
+ {
+ return_type: "int8_t",
+ },
+ () => -42
+ )
+ ).toBe(-42);
+
+ cb_identity_42_uint16_t(
+ callback(
+ {
+ return_type: "uint16_t",
+ },
+ () => 42
+ )
+ ).toBe(42);
+
+ cb_identity_42_uint32_t(
+ callback(
+ {
+ return_type: "uint32_t",
+ },
+ () => 42
+ )
+ ).toBe(42);
+
+ cb_identity_neg_42_int16_t(
+ callback(
+ {
+ return_type: "int16_t",
+ },
+ () => -42
+ )
+ ).toBe(-42);
+
+ cb_identity_neg_42_int32_t(
+ callback(
+ {
+ return_type: "int32_t",
+ },
+ () => -42
+ )
+ ).toBe(-42);
+
close();
});
-``;
diff --git a/src/javascript/jsc/api/FFI.h b/src/javascript/jsc/api/FFI.h
index 10552b604..44259eb3b 100644
--- a/src/javascript/jsc/api/FFI.h
+++ b/src/javascript/jsc/api/FFI.h
@@ -6,6 +6,9 @@
// This file is only compatible with 64 bit CPUs
// It must be kept in sync with JSCJSValue.h
// https://github.com/Jarred-Sumner/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458
+#ifdef IS_CALLBACK
+#define INJECT_BEFORE printf("bun_call %p cachedJSContext %p cachedCallbackFunction %p\n", &bun_call, cachedJSContext, cachedCallbackFunction);
+#endif
#ifdef USES_FLOAT
#include <math.h>
@@ -15,23 +18,27 @@
#define USE_JSVALUE64 1
#define USE_JSVALUE32_64 0
-/* 7.18.1.1 Exact-width integer types */
-typedef signed char int8_t;
-typedef unsigned char uint8_t;
-typedef char int8_t;
-typedef short int16_t;
-typedef unsigned short uint16_t;
-typedef int int32_t;
-typedef unsigned uint32_t;
-typedef long long int64_t;
-typedef unsigned long long uint64_t;
-typedef int64_t intptr_t;
-typedef uint64_t uintptr_t;
-typedef uintptr_t size_t;
+#include <stdint.h>
+#include <stdio.h>
+// #include <tcclib.h>
+
+// // /* 7.18.1.1 Exact-width integer types */
+// typedef unsigned char uint8_t;
+// typedef signed char int8_t;
+// typedef short int16_t;
+// typedef unsigned short uint16_t;
+// typedef int int32_t;
+// typedef unsigned int uint32_t;
+// typedef long long int64_t;
+// typedef unsigned long long uint64_t;
+// typedef uint64_t size_t;
+// typedef long intptr_t;
+// typedef uint64_t uintptr_t;
+typedef _Bool bool;
#define true 1
#define false 0
-#define bool _Bool
+
// This value is 2^49, used to encode doubles such that the encoded value will
// begin with a 15-bit pattern within the range 0x0002..0xFFFC.
@@ -77,6 +84,15 @@ typedef union EncodedJSValue {
EncodedJSValue ValueUndefined = { TagValueUndefined };
EncodedJSValue ValueTrue = { TagValueTrue };
+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;
+#endif
+
static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__));
static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__));
static EncodedJSValue FLOAT_TO_JSVALUE(float val) __attribute__((__always_inline__));
@@ -100,8 +116,6 @@ static EncodedJSValue PTR_TO_JSVALUE(void* ptr) {
return val;
}
-
-
static int32_t JSVALUE_TO_INT32(EncodedJSValue val) {
return val.asInt64;
}
@@ -147,18 +161,5 @@ static bool JSVALUE_TO_BOOL(EncodedJSValue val) {
}
-typedef void* JSContext;
-typedef EncodedJSValue* JSException;
-
-
-// typedef void* (^ArrayBufferLikeGetPtrFunction)(JSContext, EncodedJSValue);
-// static ArrayBufferLikeGetPtrFunction JSArrayBufferGetPtr = (ArrayBufferLikeGetPtrFunction)MEMORY_ADDRESS_FOR_GET_ARRAY_BUFFER_FUNCTION;
-// (*JSObjectCallAsFunctionCallback) (JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
-
-// This is an example of a function which does the bare minimum
-void* Bun__CallbackFunctionPlaceholder(JSContext ctx, EncodedJSValue function, EncodedJSValue thisObject, size_t argumentCount, const EncodedJSValue arguments[], JSException exception);
-void* Bun__CallbackFunctionPlaceholder(JSContext ctx, EncodedJSValue function, EncodedJSValue thisObject, size_t argumentCount, const EncodedJSValue arguments[], JSException exception) {
- return (void*)123;
-}
// --- Generated Code ---
diff --git a/src/javascript/jsc/api/bun.zig b/src/javascript/jsc/api/bun.zig
index 1481bb689..010fe5e58 100644
--- a/src/javascript/jsc/api/bun.zig
+++ b/src/javascript/jsc/api/bun.zig
@@ -1120,7 +1120,7 @@ pub const Class = NewClass(
.ts = d.ts{},
},
.sha = .{
- .rfn = JSC.wrapWithHasContainer(Crypto.SHA512_256, "hash", false, false),
+ .rfn = JSC.wrapWithHasContainer(Crypto.SHA512_256, "hash", false, false, true),
},
},
.{
@@ -1240,7 +1240,7 @@ pub const Crypto = struct {
@This(),
.{
.hash = .{
- .rfn = JSC.wrapWithHasContainer(@This(), "hash", false, false),
+ .rfn = JSC.wrapWithHasContainer(@This(), "hash", false, false, true),
},
.constructor = .{ .rfn = constructor },
},
@@ -2257,19 +2257,22 @@ pub const FFI = struct {
},
.{
.viewSource = .{
- .rfn = JSC.wrapWithHasContainer(JSC.FFI, "print", false, false),
+ .rfn = JSC.wrapWithHasContainer(JSC.FFI, "print", false, false, true),
},
.dlopen = .{
- .rfn = JSC.wrapWithHasContainer(JSC.FFI, "open", false, false),
+ .rfn = JSC.wrapWithHasContainer(JSC.FFI, "open", false, false, true),
+ },
+ .callback = .{
+ .rfn = JSC.wrapWithHasContainer(JSC.FFI, "callback", false, false, false),
},
.ptr = .{
- .rfn = JSC.wrapWithHasContainer(@This(), "ptr", false, false),
+ .rfn = JSC.wrapWithHasContainer(@This(), "ptr", false, false, true),
},
.toBuffer = .{
- .rfn = JSC.wrapWithHasContainer(@This(), "toBuffer", false, false),
+ .rfn = JSC.wrapWithHasContainer(@This(), "toBuffer", false, false, true),
},
.toArrayBuffer = .{
- .rfn = JSC.wrapWithHasContainer(@This(), "toArrayBuffer", false, false),
+ .rfn = JSC.wrapWithHasContainer(@This(), "toArrayBuffer", false, false, true),
},
},
.{
@@ -2329,8 +2332,7 @@ pub const FFI = struct {
return JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref());
}
- // truncate to 56 bits to clear any pointer tags
- return JSC.JSValue.jsNumber(@bitCast(f64, @as(usize, @truncate(u56, addr))));
+ return JSC.JSValue.jsNumber(@bitCast(f64, @as(usize, addr)));
}
const ValueOrError = union(enum) {
@@ -2440,6 +2442,22 @@ pub const FFI = struct {
}
}
+ pub fn toCStringBuffer(
+ globalThis: *JSGlobalObject,
+ value: JSValue,
+ byteOffset: ?JSValue,
+ valueLength: ?JSValue,
+ ) JSC.JSValue {
+ switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
+ .err => |erro| {
+ return erro;
+ },
+ .slice => |slice| {
+ return JSC.JSValue.createBuffer(globalThis, slice, null);
+ },
+ }
+ }
+
pub fn getter(
_: void,
ctx: js.JSContextRef,
diff --git a/src/javascript/jsc/api/ffi.zig b/src/javascript/jsc/api/ffi.zig
index 45893eb7d..124b0f533 100644
--- a/src/javascript/jsc/api/ffi.zig
+++ b/src/javascript/jsc/api/ffi.zig
@@ -79,6 +79,28 @@ const ComptimeStringMap = @import("../../../comptime_string_map.zig").ComptimeSt
const TCC = @import("../../../../tcc.zig");
+/// This is the entry point for generated FFI callback functions
+/// We want to avoid potentially causing LLVM to not inline our regular calls to JSC.C.JSObjectCallAsFunction
+/// to do that, we use a different pointer for the callback function
+/// which is this noinline wrapper
+noinline fn bun_call(
+ ctx: JSC.C.JSContextRef,
+ function: JSC.C.JSObjectRef,
+ count: usize,
+ argv: [*c]const JSC.C.JSValueRef,
+) callconv(.C) JSC.C.JSObjectRef {
+ var exception = [1]JSC.C.JSValueRef{null};
+ Output.debug("[bun_call] {d} args\n", .{count});
+ return JSC.C.JSObjectCallAsFunction(ctx, function, JSC.JSValue.jsUndefined().asObjectRef(), count, argv, &exception);
+}
+
+comptime {
+ if (!JSC.is_bindgen) {
+ _ = bun_call;
+ @export(bun_call, .{ .name = "bun_call" });
+ }
+}
+
pub const FFI = struct {
dylib: std.DynLib,
functions: std.StringArrayHashMapUnmanaged(Function) = .{},
@@ -87,10 +109,51 @@ pub const FFI = struct {
pub const Class = JSC.NewClass(
FFI,
.{ .name = "class" },
- .{ .call = JSC.wrapWithHasContainer(FFI, "close", false, true) },
+ .{ .call = JSC.wrapWithHasContainer(FFI, "close", false, true, true) },
.{},
);
+ pub fn callback(globalThis: *JSGlobalObject, interface: JSC.JSValue, js_callback: JSC.JSValue) JSValue {
+ if (!interface.isObject()) {
+ return JSC.toInvalidArguments("Expected object", .{}, globalThis.ref());
+ }
+
+ if (js_callback.isEmptyOrUndefinedOrNull() or !js_callback.isCallable(globalThis.vm())) {
+ return JSC.toInvalidArguments("Expected callback function", .{}, globalThis.ref());
+ }
+
+ const allocator = VirtualMachine.vm.allocator;
+ var function: Function = undefined;
+ var func = &function;
+
+ if (generateSymbolForFunction(globalThis, allocator, interface, func) catch ZigString.init("Out of memory").toErrorInstance(globalThis)) |val| {
+ return val;
+ }
+
+ // TODO: WeakRefHandle that automatically frees it?
+ JSC.C.JSValueProtect(globalThis.ref(), js_callback.asObjectRef());
+ func.base_name = "";
+ func.compileCallback(allocator, globalThis, js_callback.asObjectRef().?) catch return ZigString.init("Out of memory").toErrorInstance(globalThis);
+ switch (func.step) {
+ .failed => |err| {
+ JSC.C.JSValueUnprotect(globalThis.ref(), js_callback.asObjectRef());
+ const message = ZigString.init(err).toErrorInstance(globalThis);
+ func.deinit(allocator);
+ return message;
+ },
+ .pending => {
+ JSC.C.JSValueUnprotect(globalThis.ref(), js_callback.asObjectRef());
+ func.deinit(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.jsNumber(@bitCast(f64, @as(usize, @ptrToInt(function_.step.compiled.ptr))));
+ },
+ }
+ }
+
pub fn close(this: *FFI) JSValue {
if (this.closed) {
return JSC.JSValue.jsUndefined();
@@ -101,19 +164,46 @@ pub const FFI = struct {
const allocator = VirtualMachine.vm.allocator;
for (this.functions.values()) |*val| {
- allocator.free(bun.constStrToU8(std.mem.span(val.base_name)));
-
- val.arg_types.deinit(allocator);
+ val.deinit(allocator);
}
this.functions.deinit(allocator);
return JSC.JSValue.jsUndefined();
}
- pub fn print(global: *JSGlobalObject, object: JSC.JSValue) JSValue {
+ pub fn printCallback(global: *JSGlobalObject, object: JSC.JSValue) JSValue {
const allocator = VirtualMachine.vm.allocator;
if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
+ return JSC.toInvalidArguments("Expected an object", .{}, global.ref());
+ }
+
+ var function: Function = undefined;
+ if (generateSymbolForFunction(global, allocator, object, &function) catch ZigString.init("Out of memory").toErrorInstance(global)) |val| {
+ return val;
+ }
+
+ var arraylist = std.ArrayList(u8).init(allocator);
+ defer arraylist.deinit();
+ var writer = arraylist.writer();
+
+ function.base_name = "my_callback_function";
+
+ function.printCallbackSourceCode(&writer) catch {
+ return ZigString.init("Error while printing code").toErrorInstance(global);
+ };
+ return ZigString.init(arraylist.items).toValueGC(global);
+ }
+
+ pub fn print(global: *JSGlobalObject, object: JSC.JSValue, is_callback_val: ?JSC.JSValue) JSValue {
+ const allocator = VirtualMachine.vm.allocator;
+ if (is_callback_val) |is_callback| {
+ if (is_callback.toBoolean()) {
+ return printCallback(global, object);
+ }
+ }
+
+ if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global.ref());
}
@@ -142,11 +232,11 @@ pub const FFI = struct {
for (symbols.values()) |*function_| {
function_.arg_types.deinit(allocator);
}
+
symbols.clearAndFree(allocator);
- allocator.free(zig_strings);
return ZigString.init("Error while printing code").toErrorInstance(global);
};
- zig_strings[i] = ZigString.init(arraylist.toOwnedSlice());
+ zig_strings[i] = ZigString.init(arraylist.items);
}
const ret = JSC.JSValue.createStringArray(global, zig_strings.ptr, zig_strings.len, true);
@@ -264,9 +354,9 @@ pub const FFI = struct {
return ZigString.init("Failed to compile (nothing happend!)").toErrorInstance(global);
},
.compiled => |compiled| {
- var callback = JSC.C.JSObjectMakeFunctionWithCallback(global.ref(), null, @ptrCast(JSC.C.JSObjectCallAsFunctionCallback, compiled.ptr));
+ var cb = JSC.C.JSObjectMakeFunctionWithCallback(global.ref(), null, @ptrCast(JSC.C.JSObjectCallAsFunctionCallback, compiled.ptr));
- obj.put(global, &ZigString.init(std.mem.span(function.base_name)), JSC.JSValue.cast(callback));
+ obj.put(global, &ZigString.init(std.mem.span(function.base_name)), JSC.JSValue.cast(cb));
},
}
}
@@ -281,6 +371,83 @@ pub const FFI = struct {
return JSC.JSValue.createObject2(global, &ZigString.init("close"), &ZigString.init("symbols"), close_object, obj);
}
+ pub fn generateSymbolForFunction(global: *JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, function: *Function) !?JSValue {
+ var abi_types = std.ArrayListUnmanaged(ABIType){};
+
+ if (value.get(global, "args")) |args| {
+ if (args.isEmptyOrUndefinedOrNull() or !args.jsType().isArray()) {
+ return ZigString.init("Expected an object with \"args\" as an array").toErrorInstance(global);
+ }
+
+ var array = args.arrayIterator(global);
+
+ try abi_types.ensureTotalCapacityPrecise(allocator, array.len);
+ while (array.next()) |val| {
+ if (val.isEmptyOrUndefinedOrNull()) {
+ abi_types.clearAndFree(allocator);
+ return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
+ }
+
+ if (val.isAnyInt()) {
+ const int = val.toInt32();
+ switch (int) {
+ 0...13 => {
+ abi_types.appendAssumeCapacity(@intToEnum(ABIType, int));
+ continue;
+ },
+ else => {
+ abi_types.clearAndFree(allocator);
+ return ZigString.init("invalid ABI type").toErrorInstance(global);
+ },
+ }
+ }
+
+ if (!val.jsType().isStringLike()) {
+ abi_types.clearAndFree(allocator);
+ return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
+ }
+
+ var type_name = val.toSlice(global, allocator);
+ defer type_name.deinit();
+ abi_types.appendAssumeCapacity(ABIType.label.get(type_name.slice()) orelse {
+ abi_types.clearAndFree(allocator);
+ return JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_ARG_VALUE, "Unknown type {s}", .{type_name.slice()}, global.ref());
+ });
+ }
+ }
+ // var function
+ var return_type = ABIType.@"void";
+
+ if (value.get(global, "return_type")) |ret_value| brk: {
+ if (ret_value.isAnyInt()) {
+ const int = ret_value.toInt32();
+ switch (int) {
+ 0...13 => {
+ return_type = @intToEnum(ABIType, int);
+ break :brk;
+ },
+ else => {
+ abi_types.clearAndFree(allocator);
+ return ZigString.init("invalid ABI type").toErrorInstance(global);
+ },
+ }
+ }
+
+ var ret_slice = ret_value.toSlice(global, allocator);
+ defer ret_slice.deinit();
+ return_type = ABIType.label.get(ret_slice.slice()) orelse {
+ abi_types.clearAndFree(allocator);
+ return JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_ARG_VALUE, "Unknown return type {s}", .{ret_slice.slice()}, global.ref());
+ };
+ }
+
+ function.* = Function{
+ .base_name = "",
+ .arg_types = abi_types,
+ .return_type = return_type,
+ };
+ return null;
+ }
pub fn generateSymbols(global: *JSGlobalObject, symbols: *std.StringArrayHashMapUnmanaged(Function), object: JSC.JSValue) !?JSValue {
const allocator = VirtualMachine.vm.allocator;
@@ -303,66 +470,12 @@ pub const FFI = struct {
return JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_ARG_VALUE, "Expected an object for key \"{s}\"", .{prop}, global.ref());
}
- var abi_types = std.ArrayListUnmanaged(ABIType){};
-
- if (value.get(global, "params")) |params| {
- if (params.isEmptyOrUndefinedOrNull() or !params.jsType().isArray()) {
- return ZigString.init("Expected an object with \"params\" as an array").toErrorInstance(global);
- }
-
- var array = params.arrayIterator(global);
-
- try abi_types.ensureTotalCapacityPrecise(allocator, array.len);
- while (array.next()) |val| {
- if (val.isEmptyOrUndefinedOrNull()) {
- abi_types.clearAndFree(allocator);
- return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
- }
-
- if (val.isAnyInt()) {
- const int = val.toInt32();
- switch (int) {
- 0...13 => {
- abi_types.appendAssumeCapacity(@intToEnum(ABIType, int));
- continue;
- },
- else => {
- abi_types.clearAndFree(allocator);
- return ZigString.init("invalid ABI type").toErrorInstance(global);
- },
- }
- }
-
- if (!val.jsType().isStringLike()) {
- abi_types.clearAndFree(allocator);
- return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
- }
-
- var type_name = val.toSlice(global, allocator);
- defer type_name.deinit();
- abi_types.appendAssumeCapacity(ABIType.label.get(type_name.slice()) orelse {
- abi_types.clearAndFree(allocator);
- return JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_ARG_VALUE, "Unknown type {s}", .{type_name.slice()}, global.ref());
- });
- }
- }
- // var function
- var return_type = ABIType.@"void";
-
- if (value.get(global, "return_type")) |ret_value| {
- var ret_slice = ret_value.toSlice(global, allocator);
- defer ret_slice.deinit();
- return_type = ABIType.label.get(ret_slice.slice()) orelse {
- abi_types.clearAndFree(allocator);
- return JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_ARG_VALUE, "Unknown return type {s}", .{ret_slice.slice()}, global.ref());
- };
+ var function: Function = undefined;
+ if (try generateSymbolForFunction(global, allocator, value, &function)) |val| {
+ return val;
}
+ function.base_name = try allocator.dupeZ(u8, prop);
- const function = Function{
- .base_name = try allocator.dupeZ(u8, prop),
- .arg_types = abi_types,
- .return_type = return_type,
- };
symbols.putAssumeCapacity(std.mem.span(function.base_name), function);
}
@@ -372,16 +485,41 @@ pub const FFI = struct {
pub const Function = struct {
symbol_from_dynamic_library: ?*anyopaque = null,
base_name: [:0]const u8 = "",
+ state: ?*TCC.TCCState = null,
return_type: ABIType,
arg_types: std.ArrayListUnmanaged(ABIType) = .{},
step: Step = Step{ .pending = {} },
+ pub fn deinit(val: *Function, allocator: std.mem.Allocator) void {
+ if (std.mem.span(val.base_name).len > 0) allocator.free(bun.constStrToU8(std.mem.span(val.base_name)));
+
+ val.arg_types.deinit(allocator);
+
+ if (val.state) |state| {
+ TCC.tcc_delete(state);
+ val.state = null;
+ }
+
+ if (val.step == .compiled) {
+ // allocator.free(val.step.compiled.buf);
+ if (val.step.compiled.js_function) |js_function| {
+ JSC.C.JSValueUnprotect(@ptrCast(JSC.C.JSContextRef, val.step.compiled.js_context.?), @ptrCast(JSC.C.JSObjectRef, js_function));
+ }
+ }
+
+ if (val.step == .failed) {
+ allocator.free(val.step.failed);
+ }
+ }
+
pub const Step = union(enum) {
pending: void,
compiled: struct {
ptr: *anyopaque,
buf: []u8,
+ js_function: ?*anyopaque = null,
+ js_context: ?*anyopaque = null,
},
failed: []const u8,
};
@@ -415,6 +553,8 @@ pub const FFI = struct {
extern fn pthread_jit_write_protect_np(enable: bool) callconv(.C) void;
+ const tcc_options = "-std=c11 -Wl,--export-all-symbols";
+
pub fn compile(
this: *Function,
allocator: std.mem.Allocator,
@@ -422,12 +562,20 @@ pub const FFI = struct {
var source_code = std.ArrayList(u8).init(allocator);
var source_code_writer = source_code.writer();
try this.printSourceCode(&source_code_writer);
+
try source_code.append(0);
defer source_code.deinit();
var state = TCC.tcc_new() orelse return error.TCCMissing;
- TCC.tcc_set_options(state, "-std=c11");
+ TCC.tcc_set_options(state, tcc_options);
TCC.tcc_set_error_func(state, this, handleTCCError);
- // defer TCC.tcc_delete(state);
+ this.state = state;
+ defer {
+ if (this.step == .failed) {
+ TCC.tcc_delete(state);
+ this.state = null;
+ }
+ }
+
_ = TCC.tcc_set_output_type(state, TCC.TCC_OUTPUT_MEMORY);
const compilation_result = TCC.tcc_compile_string(
@@ -447,44 +595,123 @@ pub const FFI = struct {
_ = TCC.tcc_add_symbol(state, this.base_name, this.symbol_from_dynamic_library.?);
- // i don't fully understand this, why it needs two calls
- // but that is the API
var relocation_size = TCC.tcc_relocate(state, null);
- if (relocation_size > 0) {
- var bytes: []u8 = try allocator.rawAlloc(@intCast(usize, relocation_size), 16, 16, 0);
- if (comptime Environment.isAarch64 and Environment.isMac) {
- pthread_jit_write_protect_np(false);
- }
- _ = TCC.tcc_relocate(state, bytes.ptr);
- if (comptime Environment.isAarch64 and Environment.isMac) {
- pthread_jit_write_protect_np(true);
- }
+ if (relocation_size == 0) return;
+ var bytes: []u8 = try allocator.rawAlloc(@intCast(usize, relocation_size), 16, 16, 0);
+ defer {
if (this.step == .failed) {
allocator.free(bytes);
- return;
}
+ }
- var formatted_symbol_name = try std.fmt.allocPrintZ(allocator, "bun_gen_{s}", .{std.mem.span(this.base_name)});
- defer allocator.free(formatted_symbol_name);
- var symbol = TCC.tcc_get_symbol(state, formatted_symbol_name) orelse {
- this.step = .{ .failed = "missing generated symbol in source code" };
- allocator.free(bytes);
+ if (comptime Environment.isAarch64 and Environment.isMac) {
+ pthread_jit_write_protect_np(false);
+ }
+ _ = TCC.tcc_relocate(state, bytes.ptr);
+ if (comptime Environment.isAarch64 and Environment.isMac) {
+ pthread_jit_write_protect_np(true);
+ }
+
+ var formatted_symbol_name = try std.fmt.allocPrintZ(allocator, "bun_gen_{s}", .{std.mem.span(this.base_name)});
+ defer allocator.free(formatted_symbol_name);
+ var symbol = TCC.tcc_get_symbol(state, formatted_symbol_name) orelse {
+ this.step = .{ .failed = "missing generated symbol in source code" };
+
+ return;
+ };
+
+ this.step = .{
+ .compiled = .{
+ .ptr = symbol,
+ .buf = bytes,
+ },
+ };
+ return;
+ }
+
+ pub fn compileCallback(
+ this: *Function,
+ allocator: std.mem.Allocator,
+ js_context: *anyopaque,
+ js_function: *anyopaque,
+ ) !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", .{});
+ try source_code.append(0);
+ // defer source_code.deinit();
+ var state = TCC.tcc_new() orelse return error.TCCMissing;
- return;
- };
+ TCC.tcc_set_options(state, tcc_options);
+ TCC.tcc_set_error_func(state, this, handleTCCError);
+ this.state = state;
+ defer {
if (this.step == .failed) {
- allocator.free(bytes);
- return;
+ TCC.tcc_delete(state);
+ this.state = null;
}
+ }
+
+ _ = TCC.tcc_set_output_type(state, TCC.TCC_OUTPUT_MEMORY);
+
+ const compilation_result = TCC.tcc_compile_string(
+ state,
+ source_code.items.ptr,
+ );
+ Output.debug("compile", .{});
+ // did tcc report an error?
+ if (this.step == .failed) {
+ return;
+ }
+
+ // did tcc report failure but never called the error callback?
+ if (compilation_result == -1) {
+ this.step = .{ .failed = "tcc returned -1, which means it failed" };
- this.step = .{
- .compiled = .{
- .ptr = symbol,
- .buf = bytes,
- },
- };
return;
}
+ Output.debug("here", .{});
+
+ _ = TCC.tcc_add_symbol(state, "bun_call", JSC.C.JSObjectCallAsFunction);
+ _ = TCC.tcc_add_symbol(state, "cachedJSContext", js_context);
+ _ = TCC.tcc_add_symbol(state, "cachedCallbackFunction", js_function);
+
+ var relocation_size = TCC.tcc_relocate(state, null);
+ if (relocation_size == 0) return;
+ var bytes: []u8 = try allocator.rawAlloc(@intCast(usize, relocation_size), 16, 16, 0);
+ defer {
+ if (this.step == .failed) {
+ allocator.free(bytes);
+ }
+ }
+
+ if (comptime Environment.isAarch64 and Environment.isMac) {
+ pthread_jit_write_protect_np(false);
+ }
+ _ = TCC.tcc_relocate(state, bytes.ptr);
+ if (comptime Environment.isAarch64 and Environment.isMac) {
+ pthread_jit_write_protect_np(true);
+ }
+
+ var symbol = TCC.tcc_get_symbol(state, "my_callback_function") orelse {
+ this.step = .{ .failed = "missing generated symbol in source code" };
+
+ return;
+ };
+ Output.debug("symbol: {*}", .{symbol});
+ Output.debug("bun_call: {*}", .{&bun_call});
+ Output.debug("js_function: {*}", .{js_function});
+
+ this.step = .{
+ .compiled = .{
+ .ptr = symbol,
+ .buf = &[_]u8{},
+ .js_function = js_function,
+ .js_context = js_context,
+ },
+ };
}
pub fn printSourceCode(
@@ -532,11 +759,16 @@ pub const FFI = struct {
// -- Generate JavaScriptCore's C wrapper function
try writer.writeAll("/* ---- Your Wrapper Function ---- */\nvoid* bun_gen_");
try writer.writeAll(std.mem.span(this.base_name));
- try writer.writeAll("(JSContext ctx, EncodedJSValue function, EncodedJSValue thisObject, size_t argumentCount, const EncodedJSValue arguments[], void* exception);\n\n");
+ try writer.writeAll("(JSContext ctx, void* function, void* thisObject, size_t argumentCount, const EncodedJSValue arguments[], void* exception);\n\n");
try writer.writeAll("void* bun_gen_");
try writer.writeAll(std.mem.span(this.base_name));
- try writer.writeAll("(JSContext ctx, EncodedJSValue function, EncodedJSValue thisObject, size_t argumentCount, const EncodedJSValue arguments[], void* exception) {\n\n");
+ try writer.writeAll("(JSContext ctx, void* function, void* thisObject, size_t argumentCount, const EncodedJSValue arguments[], void* exception) {\n\n");
+ if (comptime Environment.isDebug) {
+ try writer.writeAll("#ifdef INJECT_BEFORE\n");
+ try writer.writeAll("INJECT_BEFORE;\n");
+ try writer.writeAll("#endif\n");
+ }
var arg_buf: [512]u8 = undefined;
arg_buf[0.."arguments[".len].* = "arguments[".*;
for (this.arg_types.items) |arg, i| {
@@ -576,6 +808,117 @@ pub const FFI = struct {
try writer.writeAll(";\n}\n\n");
}
+
+ pub fn printCallbackSourceCode(
+ this: *Function,
+ writer: anytype,
+ ) !void {
+ try writer.writeAll("#define IS_CALLBACK 1\n");
+
+ brk: {
+ if (this.return_type.isFloatingPoint()) {
+ try writer.writeAll("#define USES_FLOAT 1\n");
+ break :brk;
+ }
+
+ for (this.arg_types.items) |arg| {
+ // conditionally include math.h
+ if (arg.isFloatingPoint()) {
+ try writer.writeAll("#define USES_FLOAT 1\n");
+ break;
+ }
+ }
+ }
+
+ if (comptime Environment.isRelease) {
+ try writer.writeAll(std.mem.span(FFI_HEADER));
+ } else {
+ try writer.writeAll(ffiHeader());
+ }
+
+ // -- Generate the FFI function symbol
+ try writer.writeAll("\n \n/* --- The Callback Function */\n");
+ try writer.writeAll("/* --- The Callback Function */\n");
+ try this.return_type.typename(writer);
+ try writer.writeAll(" my_callback_function");
+ try writer.writeAll("(");
+ var first = true;
+ for (this.arg_types.items) |arg, i| {
+ if (!first) {
+ try writer.writeAll(", ");
+ }
+ first = false;
+ try arg.typename(writer);
+ try writer.print(" arg{d}", .{i});
+ }
+ try writer.writeAll(");\n\n");
+
+ try this.return_type.typename(writer);
+
+ try writer.writeAll(" my_callback_function");
+ try writer.writeAll("(");
+ for (this.arg_types.items) |arg, i| {
+ if (!first) {
+ try writer.writeAll(", ");
+ }
+ first = false;
+ try arg.typename(writer);
+ try writer.print(" arg{d}", .{i});
+ }
+ try writer.writeAll(") {\n");
+
+ if (comptime Environment.isDebug) {
+ try writer.writeAll("#ifdef INJECT_BEFORE\n");
+ try writer.writeAll("INJECT_BEFORE;\n");
+ try writer.writeAll("#endif\n");
+ }
+
+ 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| {
+ try arg.typename(writer);
+ 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.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, ");
+ if (this.arg_types.items.len > 0) {
+ try writer.print("{d}, arguments, 0)", .{this.arg_types.items.len});
+ } else {
+ try writer.writeAll("0, arguments, (void*)0)");
+ }
+
+ if (this.return_type != .void) {
+ try writer.print("}};\n return {}", .{this.return_type.toC("return_value")});
+ }
+
+ try writer.writeAll(";\n}\n\n");
+ }
};
pub const ABIType = enum(i32) {
@@ -768,7 +1111,7 @@ pub const FFI = struct {
.uint32_t => "uint32_t",
.int64_t => "int64_t",
.uint64_t => "uint64_t",
- .double => "float",
+ .double => "double",
.float => "float",
.char => "char",
.void => "void",
diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig
index 430d60253..2e0a723ea 100644
--- a/src/javascript/jsc/base.zig
+++ b/src/javascript/jsc/base.zig
@@ -2735,7 +2735,7 @@ pub fn wrap(
comptime name: string,
comptime maybe_async: bool,
) MethodType(Container, true) {
- return wrapWithHasContainer(Container, name, maybe_async, true);
+ return wrapWithHasContainer(Container, name, maybe_async, true, true);
}
pub fn wrapWithHasContainer(
@@ -2743,11 +2743,13 @@ pub fn wrapWithHasContainer(
comptime name: string,
comptime maybe_async: bool,
comptime has_container: bool,
+ comptime auto_protect: bool,
) MethodType(Container, has_container) {
return struct {
const FunctionType = @TypeOf(@field(Container, name));
const FunctionTypeInfo: std.builtin.TypeInfo.Fn = @typeInfo(FunctionType).Fn;
const Args = std.meta.ArgsTuple(FunctionType);
+ const eater = if (auto_protect) JSC.Node.ArgumentsSlice.protectEatNext else JSC.Node.ArgumentsSlice.nextEat;
pub fn callback(
this: if (has_container) *Container else void,
@@ -2763,6 +2765,7 @@ pub fn wrapWithHasContainer(
comptime var i: usize = 0;
inline while (i < FunctionTypeInfo.args.len) : (i += 1) {
const ArgType = comptime FunctionTypeInfo.args[i].arg_type.?;
+
switch (comptime ArgType) {
*Container => {
args[i] = this;
@@ -2818,7 +2821,7 @@ pub fn wrapWithHasContainer(
}
},
ZigString => {
- var string_value = iter.protectEatNext() orelse {
+ var string_value = eater(&iter) orelse {
JSC.throwInvalidArguments("Missing argument", .{}, ctx, exception);
iter.deinit();
return null;
@@ -2842,7 +2845,7 @@ pub fn wrapWithHasContainer(
}
},
*Response => {
- args[i] = (iter.protectEatNext() orelse {
+ args[i] = (eater(&iter) orelse {
JSC.throwInvalidArguments("Missing Response object", .{}, ctx, exception);
iter.deinit();
return null;
@@ -2853,7 +2856,7 @@ pub fn wrapWithHasContainer(
};
},
*Request => {
- args[i] = (iter.protectEatNext() orelse {
+ args[i] = (eater(&iter) orelse {
JSC.throwInvalidArguments("Missing Request object", .{}, ctx, exception);
iter.deinit();
return null;
@@ -2875,7 +2878,7 @@ pub fn wrapWithHasContainer(
args[i] = exception;
},
JSValue => {
- const val = iter.protectEatNext() orelse {
+ const val = eater(&iter) orelse {
JSC.throwInvalidArguments("Missing argument", .{}, ctx, exception);
iter.deinit();
return null;
@@ -2883,7 +2886,7 @@ pub fn wrapWithHasContainer(
args[i] = val;
},
?JSValue => {
- args[i] = iter.protectEatNext();
+ args[i] = eater(&iter);
},
else => @compileError("Unexpected Type " ++ @typeName(ArgType)),
}
diff --git a/src/javascript/jsc/ffi.exports.js b/src/javascript/jsc/ffi.exports.js
index 59af47d4f..ce72297ff 100644
--- a/src/javascript/jsc/ffi.exports.js
+++ b/src/javascript/jsc/ffi.exports.js
@@ -3,5 +3,6 @@ export const toBuffer = globalThis.Bun.FFI.toBuffer;
export const toArrayBuffer = globalThis.Bun.FFI.toArrayBuffer;
export const CString = globalThis.Bun.FFI.CString;
export const dlopen = globalThis.Bun.FFI.dlopen;
+export const callback = globalThis.Bun.FFI.callback;
export const viewSource = globalThis.Bun.FFI.viewSource;
// --- FFIType ---
diff --git a/src/output.zig b/src/output.zig
index 1323cfa5c..a82b28f2f 100644
--- a/src/output.zig
+++ b/src/output.zig
@@ -310,7 +310,8 @@ pub fn println(comptime fmt: string, args: anytype) void {
pub inline fn debug(comptime fmt: string, args: anytype) void {
if (comptime Environment.isRelease) return;
- return prettyErrorln("\n<d>DEBUG:<r> " ++ fmt, args);
+ prettyErrorln("\n<d>DEBUG:<r> " ++ fmt, args);
+ flush();
}
pub fn _debug(comptime fmt: string, args: anytype) void {
diff --git a/types/bun/ffi.d.ts b/types/bun/ffi.d.ts
index 16a197643..718c97432 100644
--- a/types/bun/ffi.d.ts
+++ b/types/bun/ffi.d.ts
@@ -1,13 +1,12 @@
/**
* `bun:ffi` lets you efficiently call C functions & FFI functions from JavaScript
- * without writing any C code yourself.
+ * without writing bindings yourself.
*
* ```js
* import {dlopen, CString, ptr} from 'bun:ffi';
*
- * const lib = dlopen('libsqlite3', {});
- *
- *
+ * const lib = dlopen('libsqlite3', {
+ * });
* ```
*
* This is powered by just-in-time compiling C wrappers
@@ -314,68 +313,104 @@ declare module "bun:ffi" {
*/
void = 13,
}
+ export type FFITypeOrString =
+ | FFIType
+ | "char"
+ | "int8_t"
+ | "i8"
+ | "uint8_t"
+ | "u8"
+ | "int16_t"
+ | "i16"
+ | "uint16_t"
+ | "u16"
+ | "int32_t"
+ | "i32"
+ | "int"
+ | "uint32_t"
+ | "u32"
+ | "int64_t"
+ | "i64"
+ | "uint64_t"
+ | "u64"
+ | "double"
+ | "f64"
+ | "float"
+ | "f32"
+ | "bool"
+ | "ptr"
+ | "pointer"
+ | "void";
- type Symbols = Record<
- string,
- {
- /**
- * Arguments to a FFI function (C ABI)
- *
- * Defaults to an empty array, which means no arguments.
- *
- * To pass a pointer, use "ptr" or "pointer" as the type name. To get a pointer, see {@link ptr}.
- *
- * @example
- * From JavaScript:
- * ```js
- * const lib = dlopen('add', {
- * // FFIType can be used or you can pass string labels.
- * args: [FFIType.i32, "i32"],
- * return_type: "i32",
- * });
- * lib.symbols.add(1, 2)
- * ```
- * In C:
- * ```c
- * int add(int a, int b) {
- * return a + b;
- * }
- * ```
- */
- args?: FFIType[];
- /**
- * Return type to a FFI function (C ABI)
- *
- * Defaults to an empty array, which means no arguments.
- *
- * To pass a pointer, use "ptr" or "pointer" as the type name. To get a pointer, see {@link ptr}.
- *
- * @example
- * From JavaScript:
- * ```js
- * const lib = dlopen('add', {
- * // FFIType can be used or you can pass string labels.
- * args: [FFIType.i32, "i32"],
- * return_type: "i32",
- * });
- * lib.symbols.add(1, 2)
- * ```
- * In C:
- * ```c
- * int add(int a, int b) {
- * return a + b;
- * }
- * ```
- */
- return_type?: FFIType;
- }
- >;
+ interface FFIFunction {
+ /**
+ * Arguments to a FFI function (C ABI)
+ *
+ * Defaults to an empty array, which means no arguments.
+ *
+ * To pass a pointer, use "ptr" or "pointer" as the type name. To get a pointer, see {@link ptr}.
+ *
+ * @example
+ * From JavaScript:
+ * ```js
+ * const lib = dlopen('add', {
+ * // FFIType can be used or you can pass string labels.
+ * args: [FFIType.i32, "i32"],
+ * return_type: "i32",
+ * });
+ * lib.symbols.add(1, 2)
+ * ```
+ * In C:
+ * ```c
+ * int add(int a, int b) {
+ * return a + b;
+ * }
+ * ```
+ */
+ args?: FFITypeOrString[];
+ /**
+ * Return type to a FFI function (C ABI)
+ *
+ * Defaults to {@link FFIType.void}
+ *
+ * To pass a pointer, use "ptr" or "pointer" as the type name. To get a pointer, see {@link ptr}.
+ *
+ * @example
+ * From JavaScript:
+ * ```js
+ * const lib = dlopen('z', {
+ * version: {
+ * return_type: "ptr",
+ * }
+ * });
+ * console.log(new CString(lib.symbols.version()));
+ * ```
+ * In C:
+ * ```c
+ * char* version()
+ * {
+ * return "1.0.0";
+ * }
+ * ```
+ */
+ return_type?: FFITypeOrString;
+ }
+
+ type Symbols = Record<string, FFIFunction>;
+
+ /**
+ * Compile a callback function
+ *
+ * Returns a function pointer
+ *
+ */
+ export function callback(ffi: FFIFunction, cb: Function): number;
export interface Library {
symbols: Record<string, CallableFunction>;
/**
- * `dlclose` the library, unloading the symbols and freeing memory allocated.
+ * `dlclose` the library, unloading the symbols and freeing allocated memory.
*
* Once called, the library is no longer usable.
*
@@ -444,11 +479,11 @@ declare module "bun:ffi" {
* ```js
* const array = new Uint8Array(10);
* const rawPtr = ptr(array);
- * myCFunction(rawPtr);
+ * myFFIFunction(rawPtr);
* ```
* To C:
* ```c
- * void myCFunction(char* rawPtr) {
+ * void myFFIFunction(char* rawPtr) {
* // Do something with rawPtr
* }
* ```
@@ -519,5 +554,6 @@ declare module "bun:ffi" {
* You probably won't need this unless there's a bug in the FFI bindings
* generator or you're just curious.
*/
- export function viewSource(symbols: Symbols): string[];
+ export function viewSource(symbols: Symbols, is_callback?: false): string[];
+ export function viewSource(callback: FFIFunction, is_callback: true): string;
}