diff options
-rw-r--r-- | integration/bunjs-only-snippets/ffi-test.c | 33 | ||||
-rw-r--r-- | integration/bunjs-only-snippets/ffi.test.js | 305 | ||||
-rw-r--r-- | src/javascript/jsc/api/FFI.h | 59 | ||||
-rw-r--r-- | src/javascript/jsc/api/bun.zig | 36 | ||||
-rw-r--r-- | src/javascript/jsc/api/ffi.zig | 541 | ||||
-rw-r--r-- | src/javascript/jsc/base.zig | 15 | ||||
-rw-r--r-- | src/javascript/jsc/ffi.exports.js | 1 | ||||
-rw-r--r-- | src/output.zig | 3 | ||||
-rw-r--r-- | types/bun/ffi.d.ts | 162 |
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; } |