diff options
-rw-r--r-- | integration/bunjs-only-snippets/ffi.test.fixture.callback.c | 3 | ||||
-rw-r--r-- | integration/bunjs-only-snippets/ffi.test.fixture.receiver.c | 3 | ||||
-rw-r--r-- | integration/bunjs-only-snippets/ffi.test.js | 41 | ||||
-rw-r--r-- | packages/bun-types/types.d.ts | 452 | ||||
-rw-r--r-- | src/javascript/jsc/api/bun.zig | 259 | ||||
-rw-r--r-- | src/javascript/jsc/api/ffi.zig | 23 | ||||
-rw-r--r-- | src/javascript/jsc/ffi.exports.js | 60 | ||||
-rw-r--r-- | types/bun/ffi.d.ts | 24 |
8 files changed, 664 insertions, 201 deletions
diff --git a/integration/bunjs-only-snippets/ffi.test.fixture.callback.c b/integration/bunjs-only-snippets/ffi.test.fixture.callback.c index 97932e255..3a557e7d5 100644 --- a/integration/bunjs-only-snippets/ffi.test.fixture.callback.c +++ b/integration/bunjs-only-snippets/ffi.test.fixture.callback.c @@ -10,7 +10,6 @@ #ifdef IS_CALLBACK #define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call #endif - #define IS_BIG_ENDIAN 0 #define USE_JSVALUE64 1 #define USE_JSVALUE32_64 0 @@ -39,8 +38,6 @@ typedef _Bool bool; #endif // #include <tcclib.h> - - // 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. #define DoubleEncodeOffsetBit 49 diff --git a/integration/bunjs-only-snippets/ffi.test.fixture.receiver.c b/integration/bunjs-only-snippets/ffi.test.fixture.receiver.c index 07d44c3a6..8a75e12f9 100644 --- a/integration/bunjs-only-snippets/ffi.test.fixture.receiver.c +++ b/integration/bunjs-only-snippets/ffi.test.fixture.receiver.c @@ -11,7 +11,6 @@ #ifdef IS_CALLBACK #define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call #endif - #define IS_BIG_ENDIAN 0 #define USE_JSVALUE64 1 #define USE_JSVALUE32_64 0 @@ -40,8 +39,6 @@ typedef _Bool bool; #endif // #include <tcclib.h> - - // 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. #define DoubleEncodeOffsetBit 49 diff --git a/integration/bunjs-only-snippets/ffi.test.js b/integration/bunjs-only-snippets/ffi.test.js index 258ee93ec..9198df3f7 100644 --- a/integration/bunjs-only-snippets/ffi.test.js +++ b/integration/bunjs-only-snippets/ffi.test.js @@ -58,8 +58,10 @@ it("ffi print", async () => { ).toBe(true); }); -it("ffi run", () => { - const types = { +function getTypes(fast) { + const int64_t = fast ? "i64_fast" : "int64_t"; + const uint64_t = fast ? "u64_fast" : "uint64_t"; + return { returns_true: { returns: "bool", args: [], @@ -97,7 +99,7 @@ it("ffi run", () => { args: [], }, returns_42_uint64_t: { - returns: "uint64_t", + returns: uint64_t, args: [], }, returns_neg_42_int16_t: { @@ -109,7 +111,7 @@ it("ffi run", () => { args: [], }, returns_neg_42_int64_t: { - returns: "int64_t", + returns: int64_t, args: [], }, @@ -142,8 +144,8 @@ it("ffi run", () => { args: ["int32_t"], }, identity_int64_t: { - returns: "int64_t", - args: ["int64_t"], + returns: int64_t, + args: [int64_t], }, identity_uint8_t: { returns: "uint8_t", @@ -158,8 +160,8 @@ it("ffi run", () => { args: ["uint32_t"], }, identity_uint64_t: { - returns: "uint64_t", - args: ["uint64_t"], + returns: uint64_t, + args: [uint64_t], }, add_char: { @@ -187,8 +189,8 @@ it("ffi run", () => { args: ["int32_t", "int32_t"], }, add_int64_t: { - returns: "int64_t", - args: ["int64_t", "int64_t"], + returns: int64_t, + args: [int64_t, int64_t], }, add_uint8_t: { returns: "uint8_t", @@ -217,8 +219,8 @@ it("ffi run", () => { args: ["ptr"], }, add_uint64_t: { - returns: "uint64_t", - args: ["uint64_t", "uint64_t"], + returns: uint64_t, + args: [uint64_t, uint64_t], }, cb_identity_true: { @@ -258,7 +260,7 @@ it("ffi run", () => { args: ["ptr"], }, cb_identity_42_uint64_t: { - returns: "uint64_t", + returns: uint64_t, args: ["ptr"], }, cb_identity_neg_42_int16_t: { @@ -270,7 +272,7 @@ it("ffi run", () => { args: ["ptr"], }, cb_identity_neg_42_int64_t: { - returns: "int64_t", + returns: int64_t, args: ["ptr"], }, @@ -279,6 +281,9 @@ it("ffi run", () => { args: [], }, }; +} + +function ffiRunner(types) { const { symbols: { returns_true, @@ -514,4 +519,12 @@ it("ffi run", () => { // ).toBe(-42); close(); +} + +it("run ffi fast", () => { + ffiRunner(getTypes(true)); +}); + +it("run ffi", () => { + ffiRunner(getTypes(false)); }); diff --git a/packages/bun-types/types.d.ts b/packages/bun-types/types.d.ts index b3dfbc605..87db8a016 100644 --- a/packages/bun-types/types.d.ts +++ b/packages/bun-types/types.d.ts @@ -818,6 +818,16 @@ declare module "bun" { } /** + * Nanoseconds since Bun.js was started as an integer. + * + * This uses a high-resolution monotonic system timer. + * + * After 14 weeks of consecutive uptime, this function + * returns a `bigint` to prevent overflow + */ + export function nanoseconds(): number | bigint; + + /** * Generate a heap snapshot for seeing where the heap is being used */ export function generateHeapSnapshot(): HeapSnapshot; @@ -1035,14 +1045,13 @@ declare var Bun: typeof import("bun"); /** * `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 @@ -1054,17 +1063,158 @@ declare var Bun: typeof import("bun"); declare module "bun:ffi" { export enum FFIType { char = 0, - + /** + * 8-bit signed integer + * + * Must be a value between -127 and 127 + * + * When passing to a FFI function (C ABI), type coercsion is not performed. + * + * In C: + * ```c + * signed char + * char // on x64 & aarch64 macOS + * ``` + * + * In JavaScript: + * ```js + * var num = 0; + * ``` + */ int8_t = 1, + /** + * 8-bit signed integer + * + * Must be a value between -127 and 127 + * + * When passing to a FFI function (C ABI), type coercsion is not performed. + * + * In C: + * ```c + * signed char + * char // on x64 & aarch64 macOS + * ``` + * + * In JavaScript: + * ```js + * var num = 0; + * ``` + */ i8 = 1, + /** + * 8-bit unsigned integer + * + * Must be a value between 0 and 255 + * + * When passing to a FFI function (C ABI), type coercsion is not performed. + * + * In C: + * ```c + * unsigned char + * ``` + * + * In JavaScript: + * ```js + * var num = 0; + * ``` + */ uint8_t = 2, + /** + * 8-bit unsigned integer + * + * Must be a value between 0 and 255 + * + * When passing to a FFI function (C ABI), type coercsion is not performed. + * + * In C: + * ```c + * unsigned char + * ``` + * + * In JavaScript: + * ```js + * var num = 0; + * ``` + */ u8 = 2, + /** + * 16-bit signed integer + * + * Must be a value between -32768 and 32767 + * + * When passing to a FFI function (C ABI), type coercsion is not performed. + * + * In C: + * ```c + * in16_t + * short // on arm64 & x64 + * ``` + * + * In JavaScript: + * ```js + * var num = 0; + * ``` + */ int16_t = 3, + /** + * 16-bit signed integer + * + * Must be a value between -32768 and 32767 + * + * When passing to a FFI function (C ABI), type coercsion is not performed. + * + * In C: + * ```c + * in16_t + * short // on arm64 & x64 + * ``` + * + * In JavaScript: + * ```js + * var num = 0; + * ``` + */ i16 = 3, + /** + * 16-bit unsigned integer + * + * Must be a value between 0 and 65535, inclusive. + * + * When passing to a FFI function (C ABI), type coercsion is not performed. + * + * In C: + * ```c + * uint16_t + * unsigned short // on arm64 & x64 + * ``` + * + * In JavaScript: + * ```js + * var num = 0; + * ``` + */ uint16_t = 4, + /** + * 16-bit unsigned integer + * + * Must be a value between 0 and 65535, inclusive. + * + * When passing to a FFI function (C ABI), type coercsion is not performed. + * + * In C: + * ```c + * uint16_t + * unsigned short // on arm64 & x64 + * ``` + * + * In JavaScript: + * ```js + * var num = 0; + * ``` + */ u16 = 4, /** @@ -1083,69 +1233,246 @@ declare module "bun:ffi" { * 32-bit signed integer * * The same as `int` in C + * + * ```c + * int + * ``` */ int = 5, + /** + * 32-bit unsigned integer + * + * The same as `unsigned int` in C (on x64 & arm64) + * + * C: + * ```c + * unsigned int + * ``` + * JavaScript: + * ```js + * ptr(new Uint32Array(1)) + * ``` + */ uint32_t = 6, + /** + * 32-bit unsigned integer + * + * Alias of {@link FFIType.uint32_t} + */ u32 = 6, + /** + * int64 is a 64-bit signed integer + * + * This is not implemented yet! + */ int64_t = 7, + /** + * i64 is a 64-bit signed integer + * + * This is not implemented yet! + */ i64 = 7, + /** + * 64-bit unsigned integer + * + * This is not implemented yet! + */ uint64_t = 8, + /** + * 64-bit unsigned integer + * + * This is not implemented yet! + */ u64 = 8, + /** + * Doubles are not supported yet! + */ double = 9, + /** + * Doubles are not supported yet! + */ f64 = 9, - + /** + * Floats are not supported yet! + */ float = 10, + /** + * Floats are not supported yet! + */ f32 = 10, + /** + * Booelan value + * + * Must be `true` or `false`. `0` and `1` type coercion is not supported. + * + * In C, this corresponds to: + * ```c + * bool + * _Bool + * ``` + * + * + */ bool = 11, + /** + * Pointer value + * + * See {@link Bun.FFI.ptr} for more information + * + * In C: + * ```c + * void* + * ``` + * + * In JavaScript: + * ```js + * ptr(new Uint8Array(1)) + * ``` + */ ptr = 12, + /** + * Pointer value + * + * alias of {@link FFIType.ptr} + */ pointer = 12, + /** + * void value + * + * void arguments are not supported + * + * void return type is the default return type + * + * In C: + * ```c + * void + * ``` + * + */ void = 13, + + /** + * When used as a `returns`, this will automatically become a {@link CString}. + * + * When used in `args` it is equivalent to {@link FFIType.pointer} + * + */ + cstring = 14, + } + 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" + | "cstring"; + + 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"], + * returns: "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: { + * returns: "ptr", + * } + * }); + * console.log(new CString(lib.symbols.version())); + * ``` + * In C: + * ```c + * char* version() + * { + * return "1.0.0"; + * } + * ``` + */ + returns?: FFITypeOrString; } - type Symbols = Record< - string, - { - /** - * Arguments to a C function - * - * 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?: FFIType; - } - >; + 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>; + symbols: Record< + string, + CallableFunction & { + /** + * The function without a wrapper + */ + native: 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. * @@ -1214,11 +1541,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 * } * ``` @@ -1251,7 +1578,8 @@ declare module "bun:ffi" { * reading beyond the bounds of the pointer will crash the program or cause * undefined behavior. Use with care! */ - export interface CString { + + export class CString extends String { /** * Get a string from a UTF-8 encoded C string * If `byteLength` is not provided, the string is assumed to be null-terminated. @@ -1279,7 +1607,25 @@ declare module "bun:ffi" { * reading beyond the bounds of the pointer will crash the program or cause * undefined behavior. Use with care! */ - new (ptr: number, byteOffset?: number, byteLength?: number): string; + constructor(ptr: number, byteOffset?: number, byteLength?: number): string; + + /** + * The ptr to the C string + * + * This `CString` instance is a clone of the string, so it + * is safe to continue using this instance after the `ptr` has been + * freed. + */ + ptr: number; + byteOffset?: number; + byteLength?: number; + + /** + * Get the {@link ptr} as an `ArrayBuffer` + * + * `null` or empty ptrs returns an `ArrayBuffer` with `byteLength` 0 + */ + get arrayBuffer(): ArrayBuffer; } /** @@ -1288,7 +1634,25 @@ 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; + + /** + * Platform-specific file extension name for dynamic libraries + * + * "." is not included + * + * @example + * ```js + * "dylib" // macOS + * ``` + * + * @example + * ```js + * "so" // linux + * ``` + */ + export const suffix: string; } diff --git a/src/javascript/jsc/api/bun.zig b/src/javascript/jsc/api/bun.zig index 632d85500..afb3c9a60 100644 --- a/src/javascript/jsc/api/bun.zig +++ b/src/javascript/jsc/api/bun.zig @@ -981,121 +981,150 @@ pub const Class = NewClass( }, }, }, - .{ .match = .{ - .rfn = Router.match, - .ts = Router.match_type_definition, - }, .sleepSync = .{ - .rfn = sleepSync, - }, .fetch = .{ - .rfn = Fetch.call, - .ts = d.ts{}, - }, .getImportedStyles = .{ - .rfn = Bun.getImportedStyles, - .ts = d.ts{ - .name = "getImportedStyles", - .@"return" = "string[]", - }, - }, .inspect = .{ - .rfn = Bun.inspect, - .ts = d.ts{ - .name = "inspect", - .@"return" = "string", - }, - }, .getRouteFiles = .{ - .rfn = Bun.getRouteFiles, - .ts = d.ts{ - .name = "getRouteFiles", - .@"return" = "string[]", - }, - }, ._Path = .{ - .rfn = Bun.newPath, - .ts = d.ts{}, - }, .getRouteNames = .{ - .rfn = Bun.getRouteNames, - .ts = d.ts{ - .name = "getRouteNames", - .@"return" = "string[]", - }, - }, .readFile = .{ - .rfn = Bun.readFileAsString, - .ts = d.ts{ - .name = "readFile", - .@"return" = "string", - }, - }, .resolveSync = .{ - .rfn = Bun.resolveSync, - .ts = d.ts{ - .name = "resolveSync", - .@"return" = "string", - }, - }, .resolve = .{ - .rfn = Bun.resolve, - .ts = d.ts{ - .name = "resolve", - .@"return" = "string", - }, - }, .readFileBytes = .{ - .rfn = Bun.readFileAsBytes, - .ts = d.ts{ - .name = "readFile", - .@"return" = "Uint8Array", - }, - }, .getPublicPath = .{ - .rfn = Bun.getPublicPathJS, - .ts = d.ts{ - .name = "getPublicPath", - .@"return" = "string", - }, - }, .registerMacro = .{ - .rfn = Bun.registerMacro, - .ts = d.ts{ - .name = "registerMacro", - .@"return" = "undefined", - }, - .enumerable = false, - }, .fs = .{ - .rfn = Bun.createNodeFS, - .ts = d.ts{}, - .enumerable = false, - }, .jest = .{ - .rfn = @import("../test/jest.zig").Jest.call, - .ts = d.ts{}, - .enumerable = false, - }, .gc = .{ - .rfn = Bun.runGC, - .ts = d.ts{}, - }, .allocUnsafe = .{ - .rfn = Bun.allocUnsafe, - .ts = .{}, - }, .mmap = .{ - .rfn = Bun.mmapFile, - .ts = .{}, - }, .generateHeapSnapshot = .{ - .rfn = Bun.generateHeapSnapshot, - .ts = d.ts{}, - }, .shrink = .{ - .rfn = Bun.shrink, - .ts = d.ts{}, - }, .openInEditor = .{ - .rfn = Bun.openInEditor, - .ts = d.ts{}, - }, .readAllStdinSync = .{ - .rfn = Bun.readAllStdinSync, - .ts = d.ts{}, - }, .serve = .{ - .rfn = Bun.serve, - .ts = d.ts{}, - }, .file = .{ - .rfn = JSC.WebCore.Blob.constructFile, - .ts = d.ts{}, - }, .write = .{ - .rfn = JSC.WebCore.Blob.writeFile, - .ts = d.ts{}, - }, .sha = .{ - .rfn = JSC.wrapWithHasContainer(Crypto.SHA512_256, "hash", false, false, true), - }, .nanoseconds = .{ - .rfn = nanoseconds, - } }, + .{ + .match = .{ + .rfn = Router.match, + .ts = Router.match_type_definition, + }, + .sleepSync = .{ + .rfn = sleepSync, + }, + .fetch = .{ + .rfn = Fetch.call, + .ts = d.ts{}, + }, + .getImportedStyles = .{ + .rfn = Bun.getImportedStyles, + .ts = d.ts{ + .name = "getImportedStyles", + .@"return" = "string[]", + }, + }, + .inspect = .{ + .rfn = Bun.inspect, + .ts = d.ts{ + .name = "inspect", + .@"return" = "string", + }, + }, + .getRouteFiles = .{ + .rfn = Bun.getRouteFiles, + .ts = d.ts{ + .name = "getRouteFiles", + .@"return" = "string[]", + }, + }, + ._Path = .{ + .rfn = Bun.newPath, + .ts = d.ts{}, + }, + .getRouteNames = .{ + .rfn = Bun.getRouteNames, + .ts = d.ts{ + .name = "getRouteNames", + .@"return" = "string[]", + }, + }, + .readFile = .{ + .rfn = Bun.readFileAsString, + .ts = d.ts{ + .name = "readFile", + .@"return" = "string", + }, + }, + .resolveSync = .{ + .rfn = Bun.resolveSync, + .ts = d.ts{ + .name = "resolveSync", + .@"return" = "string", + }, + }, + .resolve = .{ + .rfn = Bun.resolve, + .ts = d.ts{ + .name = "resolve", + .@"return" = "string", + }, + }, + .readFileBytes = .{ + .rfn = Bun.readFileAsBytes, + .ts = d.ts{ + .name = "readFile", + .@"return" = "Uint8Array", + }, + }, + .getPublicPath = .{ + .rfn = Bun.getPublicPathJS, + .ts = d.ts{ + .name = "getPublicPath", + .@"return" = "string", + }, + }, + .registerMacro = .{ + .rfn = Bun.registerMacro, + .ts = d.ts{ + .name = "registerMacro", + .@"return" = "undefined", + }, + .enumerable = false, + }, + .fs = .{ + .rfn = Bun.createNodeFS, + .ts = d.ts{}, + .enumerable = false, + }, + .jest = .{ + .rfn = @import("../test/jest.zig").Jest.call, + .ts = d.ts{}, + .enumerable = false, + }, + .gc = .{ + .rfn = Bun.runGC, + .ts = d.ts{}, + }, + .allocUnsafe = .{ + .rfn = Bun.allocUnsafe, + .ts = .{}, + }, + .mmap = .{ + .rfn = Bun.mmapFile, + .ts = .{}, + }, + .generateHeapSnapshot = .{ + .rfn = Bun.generateHeapSnapshot, + .ts = d.ts{}, + }, + .shrink = .{ + .rfn = Bun.shrink, + .ts = d.ts{}, + }, + .openInEditor = .{ + .rfn = Bun.openInEditor, + .ts = d.ts{}, + }, + .readAllStdinSync = .{ + .rfn = Bun.readAllStdinSync, + .ts = d.ts{}, + }, + .serve = .{ + .rfn = Bun.serve, + .ts = d.ts{}, + }, + .file = .{ + .rfn = JSC.WebCore.Blob.constructFile, + .ts = d.ts{}, + }, + .write = .{ + .rfn = JSC.WebCore.Blob.writeFile, + .ts = d.ts{}, + }, + .sha = .{ + .rfn = JSC.wrapWithHasContainer(Crypto.SHA512_256, "hash", false, false, true), + }, + .nanoseconds = .{ + .rfn = nanoseconds, + }, + }, .{ .main = .{ .get = getMain, diff --git a/src/javascript/jsc/api/ffi.zig b/src/javascript/jsc/api/ffi.zig index c5ad6bf59..2e9173ee4 100644 --- a/src/javascript/jsc/api/ffi.zig +++ b/src/javascript/jsc/api/ffi.zig @@ -1118,6 +1118,9 @@ pub const FFI = struct { cstring = 14, + i64_fast = 15, + u64_fast = 16, + /// Types that we can directly pass through as an `int64_t` pub fn needsACastInC(this: ABIType) bool { return switch (this) { @@ -1160,6 +1163,8 @@ pub const FFI = struct { .{ "pointer", ABIType.ptr }, .{ "void", ABIType.@"void" }, .{ "cstring", ABIType.@"cstring" }, + .{ "i64_fast", ABIType.i64_fast }, + .{ "u64_fast", ABIType.u64_fast }, }; pub const label = ComptimeStringMap(ABIType, map); const EnumMapFormatter = struct { @@ -1221,10 +1226,10 @@ pub const FFI = struct { .char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => { try writer.print("JSVALUE_TO_INT32({s})", .{self.symbol}); }, - .int64_t => { + .i64_fast, .int64_t => { try writer.print("JSVALUE_TO_INT64({s})", .{self.symbol}); }, - .uint64_t => { + .u64_fast, .uint64_t => { try writer.print("JSVALUE_TO_UINT64(globalObject, {s})", .{self.symbol}); }, .cstring, .ptr => { @@ -1253,12 +1258,18 @@ pub const FFI = struct { .char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => { try writer.print("INT32_TO_JSVALUE({s})", .{self.symbol}); }, - .int64_t => { + .i64_fast => { try writer.print("INT64_TO_JSVALUE(globalObject, {s})", .{self.symbol}); }, - .uint64_t => { + .int64_t => { + try writer.print("INT64_TO_JSVALUE_SLOW(globalObject, {s})", .{self.symbol}); + }, + .u64_fast => { try writer.print("UINT64_TO_JSVALUE(globalObject, {s})", .{self.symbol}); }, + .uint64_t => { + try writer.print("UINT64_TO_JSVALUE_SLOW(globalObject, {s})", .{self.symbol}); + }, .cstring, .ptr => { try writer.print("PTR_TO_JSVALUE({s})", .{self.symbol}); }, @@ -1300,8 +1311,8 @@ pub const FFI = struct { .uint16_t => "uint16_t", .int32_t => "int32_t", .uint32_t => "uint32_t", - .int64_t => "int64_t", - .uint64_t => "uint64_t", + .i64_fast, .int64_t => "int64_t", + .u64_fast, .uint64_t => "uint64_t", .double => "double", .float => "float", .char => "char", diff --git a/src/javascript/jsc/ffi.exports.js b/src/javascript/jsc/ffi.exports.js index fea92e0cd..ae11326e5 100644 --- a/src/javascript/jsc/ffi.exports.js +++ b/src/javascript/jsc/ffi.exports.js @@ -52,7 +52,7 @@ Object.defineProperty(globalThis, "__GlobalBunCString", { configurable: false, }); -const ffiWrappers = new Array(15); +const ffiWrappers = new Array(16); var char = (val) => val | 0; ffiWrappers.fill(char); ffiWrappers[FFIType.uint8_t] = function uint8(val) { @@ -67,13 +67,14 @@ ffiWrappers[FFIType.uint16_t] = function uint16(val) { ffiWrappers[FFIType.int32_t] = function int32(val) { return val | 0; }; +// we never want to return NaN ffiWrappers[FFIType.uint32_t] = function uint32(val) { - return val <= 0 ? 0 : val >= 0xffffffff ? 0xffffffff : val; + return val <= 0 ? 0 : val >= 0xffffffff ? 0xffffffff : +val || 0; }; -ffiWrappers[FFIType.int64_t] = function int64(val) { +ffiWrappers[FFIType.i64_fast] = function int64(val) { if (typeof val === "bigint") { if (val < BigInt(Number.MAX_VALUE)) { - return Number(val).valueOf(); + return Number(val).valueOf() || 0; } } @@ -81,13 +82,13 @@ ffiWrappers[FFIType.int64_t] = function int64(val) { return 0; } - return val; + return +val || 0; }; -ffiWrappers[FFIType.uint64_t] = function int64(val) { +ffiWrappers[FFIType.u64_fast] = function u64_fast(val) { if (typeof val === "bigint") { if (val < BigInt(Number.MAX_VALUE) && val > 0) { - return Number(val).valueOf(); + return Number(val).valueOf() || 0; } } @@ -95,21 +96,48 @@ ffiWrappers[FFIType.uint64_t] = function int64(val) { return 0; } - return val; + return +val || 0; }; -ffiWrappers[FFIType.uint16_t] = function uint64(val) { +ffiWrappers[FFIType.int64_t] = function int64(val) { if (typeof val === "bigint") { - if (val < BigInt(Number.MAX_VALUE)) { - return Math.abs(Number(val).valueOf()); - } + return val; } - if (!val) { - return 0; + if (typeof val === "number") { + return BigInt(val); + } + + return BigInt(+val || 0); +}; + +ffiWrappers[FFIType.uint64_t] = function uint64(val) { + if (typeof val === "bigint") { + return val; } - return Math.abs(val); + if (typeof val === "number") { + return val <= 0 ? BigInt(0) : BigInt(val); + } + + return BigInt(+val || 0); +}; + +ffiWrappers[FFIType.u64_fast] = function u64_fast(val) { + if (typeof val === "bigint") { + return val < BigInt(Number.MAX_VALUE) + ? val <= BigInt(0) + ? 0 + : Number(val) + : val; + } + + return typeof val === "number" ? (val <= 0 ? 0 : +val || 0) : +val || 0; +}; + +ffiWrappers[FFIType.uint16_t] = function uint16(val) { + const ret = (typeof val === "bigint" ? Number(val) : val) | 0; + return ret <= 0 ? 0 : ret > 0xffff ? 0xffff : ret; }; ffiWrappers[FFIType.double] = function double(val) { @@ -231,7 +259,7 @@ export function dlopen(path, options) { ); } else { // consistentcy - result.native = result; + result.symbols[key].native = result.symbols[key]; } } diff --git a/types/bun/ffi.d.ts b/types/bun/ffi.d.ts index 96f6fa562..f27cbe551 100644 --- a/types/bun/ffi.d.ts +++ b/types/bun/ffi.d.ts @@ -320,6 +320,30 @@ declare module "bun:ffi" { * */ cstring = 14, + + /** + * Attempt to coerce `BigInt` into a `Number` if it fits. This improves performance + * but means you might get a `BigInt` or you might get a `number`. + * + * In C, this always becomes `int64_t` + * + * In JavaScript, this could be number or it could be BigInt, depending on what + * value is passed in. + * + */ + i64_fast = 15, + + /** + * Attempt to coerce `BigInt` into a `Number` if it fits. This improves performance + * but means you might get a `BigInt` or you might get a `number`. + * + * In C, this always becomes `uint64_t` + * + * In JavaScript, this could be number or it could be BigInt, depending on what + * value is passed in. + * + */ + u64_fast = 16, } export type FFITypeOrString = | FFIType |