diff options
-rw-r--r-- | bench/snippets/ffi-overhead.mjs | 465 | ||||
-rw-r--r-- | src/bun.js/ffi.exports.js | 61 | ||||
-rw-r--r-- | test/bun.js/ffi-test.c | 13 |
3 files changed, 537 insertions, 2 deletions
diff --git a/bench/snippets/ffi-overhead.mjs b/bench/snippets/ffi-overhead.mjs new file mode 100644 index 000000000..dcceef7ba --- /dev/null +++ b/bench/snippets/ffi-overhead.mjs @@ -0,0 +1,465 @@ +import { + viewSource, + dlopen, + CString, + ptr, + toBuffer, + toArrayBuffer, + FFIType, + callback, +} from "bun:ffi"; +import { bench, group, run } from "mitata"; + +const types = { + returns_true: { + returns: "bool", + args: [], + }, + returns_false: { + returns: "bool", + args: [], + }, + returns_42_char: { + returns: "char", + args: [], + }, + returns_42_float: { + returns: "float", + args: [], + }, + returns_42_double: { + returns: "double", + args: [], + }, + returns_42_uint8_t: { + returns: "uint8_t", + args: [], + }, + returns_neg_42_int8_t: { + returns: "int8_t", + args: [], + }, + returns_42_uint16_t: { + returns: "uint16_t", + args: [], + }, + returns_42_uint32_t: { + returns: "uint32_t", + args: [], + }, + // // returns_42_uint64_t: { + // // returns: "uint64_t", + // // args: [], + // // }, + returns_neg_42_int16_t: { + returns: "int16_t", + args: [], + }, + returns_neg_42_int32_t: { + returns: "int32_t", + args: [], + }, + // returns_neg_42_int64_t: { + // returns: "int64_t", + // args: [], + // }, + + identity_char: { + returns: "char", + args: ["char"], + }, + identity_float: { + returns: "float", + args: ["float"], + }, + identity_bool: { + returns: "bool", + args: ["bool"], + }, + identity_double: { + returns: "double", + args: ["double"], + }, + identity_int8_t: { + returns: "int8_t", + args: ["int8_t"], + }, + identity_int16_t: { + returns: "int16_t", + args: ["int16_t"], + }, + identity_int32_t: { + returns: "int32_t", + args: ["int32_t"], + }, + // identity_int64_t: { + // returns: "int64_t", + // args: ["int64_t"], + // }, + identity_uint8_t: { + returns: "uint8_t", + args: ["uint8_t"], + }, + identity_uint16_t: { + returns: "uint16_t", + args: ["uint16_t"], + }, + identity_uint32_t: { + returns: "uint32_t", + args: ["uint32_t"], + }, + // identity_uint64_t: { + // returns: "uint64_t", + // args: ["uint64_t"], + // }, + + add_char: { + returns: "char", + args: ["char", "char"], + }, + add_float: { + returns: "float", + args: ["float", "float"], + }, + add_double: { + returns: "double", + args: ["double", "double"], + }, + add_int8_t: { + returns: "int8_t", + args: ["int8_t", "int8_t"], + }, + add_int16_t: { + returns: "int16_t", + args: ["int16_t", "int16_t"], + }, + add_int32_t: { + returns: "int32_t", + args: ["int32_t", "int32_t"], + }, + // add_int64_t: { + // returns: "int64_t", + // args: ["int64_t", "int64_t"], + // }, + add_uint8_t: { + returns: "uint8_t", + args: ["uint8_t", "uint8_t"], + }, + add_uint16_t: { + returns: "uint16_t", + args: ["uint16_t", "uint16_t"], + }, + add_uint32_t: { + returns: "uint32_t", + args: ["uint32_t", "uint32_t"], + }, + + does_pointer_equal_42_as_int32_t: { + returns: "bool", + args: ["ptr"], + }, + + ptr_should_point_to_42_as_int32_t: { + returns: "ptr", + args: [], + }, + identity_ptr: { + returns: "ptr", + args: ["ptr"], + }, + // add_uint64_t: { + // returns: "uint64_t", + // args: ["uint64_t", "uint64_t"], + // }, + + cb_identity_true: { + returns: "bool", + args: ["ptr"], + }, + cb_identity_false: { + returns: "bool", + args: ["ptr"], + }, + cb_identity_42_char: { + returns: "char", + args: ["ptr"], + }, + // cb_identity_42_float: { + // returns: "float", + // args: ["ptr"], + // }, + // cb_identity_42_double: { + // returns: "double", + // args: ["ptr"], + // }, + cb_identity_42_uint8_t: { + returns: "uint8_t", + args: ["ptr"], + }, + cb_identity_neg_42_int8_t: { + returns: "int8_t", + args: ["ptr"], + }, + cb_identity_42_uint16_t: { + returns: "uint16_t", + args: ["ptr"], + }, + cb_identity_42_uint32_t: { + returns: "uint32_t", + args: ["ptr"], + }, + // cb_identity_42_uint64_t: { + // returns: "uint64_t", + // args: ["ptr"], + // }, + cb_identity_neg_42_int16_t: { + returns: "int16_t", + args: ["ptr"], + }, + cb_identity_neg_42_int32_t: { + returns: "int32_t", + args: ["ptr"], + }, + // cb_identity_neg_42_int64_t: { + // returns: "int64_t", + // args: ["ptr"], + // }, + + return_a_function_ptr_to_function_that_returns_true: { + returns: "ptr", + args: [], + }, +}; + +var opened; +try { + opened = dlopen("/tmp/bun-ffi-test.dylib", types); +} catch (e) { + throw new Error( + "Please run `make compile-ffi-test` to compile the ffi test library" + ); +} + +const { + symbols: { + returns_true, + returns_false, + return_a_function_ptr_to_function_that_returns_true, + returns_42_char, + returns_42_float, + returns_42_double, + returns_42_uint8_t, + returns_neg_42_int8_t, + returns_42_uint16_t, + returns_42_uint32_t, + returns_42_uint64_t, + returns_neg_42_int16_t, + returns_neg_42_int32_t, + returns_neg_42_int64_t, + identity_char, + identity_float, + identity_bool, + identity_double, + identity_int8_t, + identity_int16_t, + identity_int32_t, + identity_int64_t, + identity_uint8_t, + identity_uint16_t, + identity_uint32_t, + identity_uint64_t, + add_char, + add_float, + add_double, + add_int8_t, + add_int16_t, + add_int32_t, + 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, +} = opened; + +group("add_int16_t", () => { + bench("add_int16_t (raw)", () => raw_add_int16_t(1, 1)); + bench("add_int16_t", () => add_int16_t(1, 1)); +}); + +group("add_char", () => { + bench("add_char (raw)", () => raw_add_char(1, 1)); + bench("add_char", () => add_char(1, 1)); +}); +group("add_int16_t", () => { + bench("add_int16_t (raw)", () => raw_add_int16_t(1, 1)); + bench("add_int16_t", () => add_int16_t(1, 1)); +}); +group("add_int32_t", () => { + bench("add_int32_t (raw)", () => raw_add_int32_t(1, 1)); + bench("add_int32_t", () => add_int32_t(1, 1)); +}); +group("add_int8_t", () => { + bench("add_int8_t (raw)", () => raw_add_int8_t(1, 1)); + bench("add_int8_t", () => add_int8_t(1, 1)); +}); +group("add_uint16_t", () => { + bench("add_uint16_t (raw)", () => raw_add_uint16_t(1, 1)); + bench("add_uint16_t", () => add_uint16_t(1, 1)); +}); +group("add_uint32_t", () => { + bench("add_uint32_t (raw)", () => raw_add_uint32_t(1, 1)); + bench("add_uint32_t", () => add_uint32_t(1, 1)); +}); +group("add_uint8_t", () => { + bench("add_uint8_t (raw)", () => raw_add_uint8_t(1, 1)); + bench("add_uint8_t", () => add_uint8_t(1, 1)); +}); +group("identity_bool", () => { + bench("identity_bool (raw)", () => raw_identity_bool(false)); + bench("identity_bool", () => identity_bool(true)); +}); +group("identity_char", () => { + bench("identity_char (raw)", () => raw_identity_char(10)); + bench("identity_char", () => identity_char(10)); +}); +group("identity_int16_t", () => { + bench("identity_int16_t (raw)", () => raw_identity_int16_t(10)); + bench("identity_int16_t", () => identity_int16_t(10)); +}); +group("identity_int32_t", () => { + bench("identity_int32_t (raw)", () => raw_identity_int32_t(10)); + bench("identity_int32_t", () => identity_int32_t(10)); +}); +group("identity_int8_t", () => { + bench("identity_int8_t (raw)", () => raw_identity_int8_t(10)); + bench("identity_int8_t", () => identity_int8_t(10)); +}); +group("identity_uint16_t", () => { + bench("identity_uint16_t (raw)", () => raw_identity_uint16_t(10)); + bench("identity_uint16_t", () => identity_uint16_t(10)); +}); +group("identity_uint32_t", () => { + bench("identity_uint32_t (raw)", () => raw_identity_uint32_t(10)); + bench("identity_uint32_t", () => identity_uint32_t(10)); +}); +group("identity_uint8_t", () => { + bench("identity_uint8_t (raw)", () => raw_identity_uint8_t(10)); + bench("identity_uint8_t", () => identity_uint8_t(10)); +}); +group("returns_42_char", () => { + bench("returns_42_char (raw)", () => raw_returns_42_char()); + bench("returns_42_char", () => returns_42_char()); +}); +group("returns_42_uint16_t", () => { + bench("returns_42_uint16_t (raw)", () => raw_returns_42_uint16_t()); + bench("returns_42_uint16_t", () => returns_42_uint16_t()); +}); +group("returns_42_uint32_t", () => { + bench("returns_42_uint32_t (raw)", () => raw_returns_42_uint32_t()); + bench("returns_42_uint32_t", () => returns_42_uint32_t()); +}); +group("returns_42_uint8_t", () => { + bench("returns_42_uint8_t (raw)", () => raw_returns_42_uint8_t()); + bench("returns_42_uint8_t", () => returns_42_uint8_t()); +}); +group("returns_false", () => { + bench("returns_false (raw)", () => raw_returns_false()); + bench("returns_false", () => returns_false()); +}); +group("returns_neg_42_int16_t", () => { + bench("returns_neg_42_int16_t (raw)", () => raw_returns_neg_42_int16_t()); + bench("returns_neg_42_int16_t", () => returns_neg_42_int16_t()); +}); +group("returns_neg_42_int32_t", () => { + bench("returns_neg_42_int32_t (raw)", () => raw_returns_neg_42_int32_t()); + bench("returns_neg_42_int32_t", () => returns_neg_42_int32_t()); +}); +group("returns_neg_42_int8_t", () => { + bench("returns_neg_42_int8_t (raw)", () => raw_returns_neg_42_int8_t()); + bench("returns_neg_42_int8_t", () => returns_neg_42_int8_t()); +}); +group("returns_true", () => { + bench("returns_true (raw)", () => raw_returns_true()); + bench("returns_true", () => returns_true()); +}); + +group("return_a_function_ptr_to_function_that_returns_true", () => { + bench("return_a_function_ptr_to_function_that_returns_true (raw)", () => + raw_return_a_function_ptr_to_function_that_returns_true() + ); + bench("return_a_function_ptr_to_function_that_returns_true", () => + return_a_function_ptr_to_function_that_returns_true() + ); +}); +group("returns_42_float", () => { + bench("returns_42_float (raw)", () => raw_returns_42_float()); + bench("returns_42_float", () => returns_42_float()); +}); +group("returns_42_double", () => { + bench("returns_42_double (raw)", () => raw_returns_42_double(42)); + bench("returns_42_double", () => returns_42_double()); +}); +group("identity_float", () => { + bench("identity_float (raw)", () => raw_identity_float(42.42)); + bench("identity_float", () => identity_float()); +}); +group("identity_double", () => { + bench("identity_double (raw)", () => raw_identity_double(42.42)); + bench("identity_double", () => identity_double()); +}); + +var raw_return_a_function_ptr_to_function_that_returns_true = + return_a_function_ptr_to_function_that_returns_true.native ?? + return_a_function_ptr_to_function_that_returns_true; +var raw_returns_42_float = returns_42_float.native ?? returns_42_float; +var raw_returns_42_double = returns_42_double.native ?? returns_42_double; +var raw_identity_float = identity_float.native ?? identity_float; +var raw_identity_double = identity_double.native ?? identity_double; +var raw_returns_true = returns_true.native ?? returns_true; +var raw_returns_false = returns_false.native ?? returns_false; +var raw_returns_42_char = returns_42_char.native ?? returns_42_char; +var raw_returns_42_uint8_t = returns_42_uint8_t.native ?? returns_42_uint8_t; +var raw_returns_neg_42_int8_t = + returns_neg_42_int8_t.native ?? returns_neg_42_int8_t; +var raw_returns_42_uint16_t = returns_42_uint16_t.native ?? returns_42_uint16_t; +var raw_returns_42_uint32_t = returns_42_uint32_t.native ?? returns_42_uint32_t; +var raw_returns_neg_42_int16_t = + returns_neg_42_int16_t.native ?? returns_neg_42_int16_t; +var raw_returns_neg_42_int32_t = + returns_neg_42_int32_t.native ?? returns_neg_42_int32_t; +var raw_identity_char = identity_char.native ?? identity_char; +var raw_identity_bool = identity_bool.native ?? identity_bool; +var raw_identity_bool = identity_bool.native ?? identity_bool; +var raw_identity_int8_t = identity_int8_t.native ?? identity_int8_t; +var raw_identity_int16_t = identity_int16_t.native ?? identity_int16_t; +var raw_identity_int32_t = identity_int32_t.native ?? identity_int32_t; +var raw_identity_uint8_t = identity_uint8_t.native ?? identity_uint8_t; +var raw_identity_uint16_t = identity_uint16_t.native ?? identity_uint16_t; +var raw_identity_uint32_t = identity_uint32_t.native ?? identity_uint32_t; +var raw_add_char = add_char.native ?? add_char; +var raw_add_int8_t = add_int8_t.native ?? add_int8_t; +var raw_add_int16_t = add_int16_t.native ?? add_int16_t; +var raw_add_int32_t = add_int32_t.native ?? add_int32_t; +var raw_add_uint8_t = add_uint8_t.native ?? add_uint8_t; +var raw_add_uint16_t = add_uint16_t.native ?? add_uint16_t; +var raw_add_uint32_t = add_uint32_t.native ?? add_uint32_t; + +run({ collect: false, percentiles: true }); diff --git a/src/bun.js/ffi.exports.js b/src/bun.js/ffi.exports.js index 217e208b6..f24844155 100644 --- a/src/bun.js/ffi.exports.js +++ b/src/bun.js/ffi.exports.js @@ -226,9 +226,66 @@ function FFIBuilder(params, returnType, functionToCall, name) { Object.defineProperty(func, "name", { value: name, }); - const wrap = (...args) => func(functionToCall, ...args); - wrap.native = functionToCall; + // variadic arguments can be expensive + // most FFI functions are going to be < 5 arguments + // so we just inline it + var wrap; + switch (paramNames.length) { + case 0: + wrap = () => func(functionToCall); + break; + case 1: + wrap = (arg1) => func(functionToCall, arg1); + break; + case 2: + wrap = (arg1, arg2) => func(functionToCall, arg1, arg2); + break; + case 3: + wrap = (arg1, arg2, arg3) => func(functionToCall, arg1, arg2, arg3); + break; + case 4: + wrap = (arg1, arg2, arg3, arg4) => + func(functionToCall, arg1, arg2, arg3, arg4); + break; + case 5: + wrap = (arg1, arg2, arg3, arg4, arg5) => + func(functionToCall, arg1, arg2, arg3, arg4, arg5); + case 6: + wrap = (arg1, arg2, arg3, arg4, arg5, arg6) => + func(functionToCall, arg1, arg2, arg3, arg4, arg5, arg6); + break; + case 7: + wrap = (arg1, arg2, arg3, arg4, arg5, arg6, arg7) => + func(functionToCall, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + break; + case 8: + wrap = (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) => + func(functionToCall, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + break; + case 9: + wrap = (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) => + func( + functionToCall, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8, + arg9 + ); + break; + default: { + wrap = (...args) => func(functionToCall, ...args); + break; + } + } + + wrap.native = functionToCall; + wrap.ptr = functionToCall.ptr; return wrap; } diff --git a/test/bun.js/ffi-test.c b/test/bun.js/ffi-test.c index cc87d0528..e508b2c11 100644 --- a/test/bun.js/ffi-test.c +++ b/test/bun.js/ffi-test.c @@ -105,6 +105,19 @@ void *ptr_should_point_to_42_as_int32_t() { return ptr; } +static uint8_t buffer_with_deallocator[128]; +static int deallocatorCalled; +void deallocator(void *ptr, void *userData) { deallocatorCalled++; } +void *getDeallocatorCallback() { + deallocatorCalled = 0; + return &deallocator; +} +void *getDeallocatorBuffer() { + deallocatorCalled = 0; + return &buffer_with_deallocator; +} +int getDeallocatorCalledCount() { return deallocatorCalled; } + bool does_pointer_equal_42_as_int32_t(int32_t *ptr); bool does_pointer_equal_42_as_int32_t(int32_t *ptr) { return *ptr == 42; } |