diff options
author | 2022-11-02 13:30:40 -0700 | |
---|---|---|
committer | 2022-11-02 13:35:49 -0700 | |
commit | 006a2f37ddb133f61c6fa672652663204c2d2e54 (patch) | |
tree | ee5f0252eee105f2f7b4e80a0bf291724fe75c16 | |
parent | 65b543fba63c1c1ece5100d0b76644f2f115a906 (diff) | |
download | bun-006a2f37ddb133f61c6fa672652663204c2d2e54.tar.gz bun-006a2f37ddb133f61c6fa672652663204c2d2e54.tar.zst bun-006a2f37ddb133f61c6fa672652663204c2d2e54.zip |
[bun:ffi] Add `threadsafe` option to callbacks
-rw-r--r-- | src/bun.js/api/ffi.zig | 21 | ||||
-rw-r--r-- | src/bun.js/bindings/JSFFIFunction.cpp | 26 | ||||
-rw-r--r-- | src/bun.js/ffi.exports.js | 3 | ||||
-rw-r--r-- | test/bun.js/ffi.test.js | 37 |
4 files changed, 73 insertions, 14 deletions
diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig index 9ad3c4b46..0fe736258 100644 --- a/src/bun.js/api/ffi.zig +++ b/src/bun.js/api/ffi.zig @@ -119,7 +119,7 @@ pub const FFI = struct { func.base_name = ""; js_callback.ensureStillAlive(); - func.compileCallback(allocator, globalThis, js_callback) catch return ZigString.init("Out of memory").toErrorInstance(globalThis); + func.compileCallback(allocator, globalThis, js_callback, func.threadsafe) catch return ZigString.init("Out of memory").toErrorInstance(globalThis); switch (func.step) { .failed => |err| { const message = ZigString.init(err.msg).toErrorInstance(globalThis); @@ -551,6 +551,12 @@ pub const FFI = struct { // var function var return_type = ABIType.@"void"; + var threadsafe = false; + + if (value.get(global, "threadsafe")) |threadsafe_value| { + threadsafe = threadsafe_value.toBoolean(); + } + if (value.get(global, "returns")) |ret_value| brk: { if (ret_value.isAnyInt()) { const int = ret_value.toInt32(); @@ -574,10 +580,16 @@ pub const FFI = struct { }; } + if (function.threadsafe and return_type != ABIType.@"void") { + abi_types.clearAndFree(allocator); + return ZigString.static("Threadsafe functions must return void").toErrorInstance(global); + } + function.* = Function{ .base_name = null, .arg_types = abi_types, .return_type = return_type, + .threadsafe = threadsafe, }; if (value.get(global, "ptr")) |ptr| { @@ -635,6 +647,7 @@ pub const FFI = struct { return_type: ABIType = ABIType.@"void", arg_types: std.ArrayListUnmanaged(ABIType) = .{}, step: Step = Step{ .pending = {} }, + threadsafe: bool = false, pub var lib_dirZ: [*:0]const u8 = ""; @@ -911,6 +924,7 @@ pub const FFI = struct { allocator: std.mem.Allocator, js_context: *JSC.JSGlobalObject, js_function: JSValue, + is_threadsafe: bool, ) !void { var source_code = std.ArrayList(u8).init(allocator); var source_code_writer = source_code.writer(); @@ -954,7 +968,9 @@ pub const FFI = struct { state, "FFI_Callback_call", // TODO: stage2 - make these ptrs - switch (this.arg_types.items.len) { + if (is_threadsafe) + FFI_Callback_threadsafe_call + else switch (this.arg_types.items.len) { 0 => FFI_Callback_call_0, 1 => FFI_Callback_call_1, 2 => FFI_Callback_call_2, @@ -1166,6 +1182,7 @@ pub const FFI = struct { extern fn FFI_Callback_call_3(*anyopaque, usize, [*]JSValue) JSValue; extern fn FFI_Callback_call_4(*anyopaque, usize, [*]JSValue) JSValue; extern fn FFI_Callback_call_5(*anyopaque, usize, [*]JSValue) JSValue; + extern fn FFI_Callback_threadsafe_call(*anyopaque, usize, [*]JSValue) JSValue; extern fn FFI_Callback_call_6(*anyopaque, usize, [*]JSValue) JSValue; extern fn FFI_Callback_call_7(*anyopaque, usize, [*]JSValue) JSValue; extern fn Bun__createFFICallbackFunction(*JSC.JSGlobalObject, JSValue) *anyopaque; diff --git a/src/bun.js/bindings/JSFFIFunction.cpp b/src/bun.js/bindings/JSFFIFunction.cpp index 66fdc701d..c0fc34fc0 100644 --- a/src/bun.js/bindings/JSFFIFunction.cpp +++ b/src/bun.js/bindings/JSFFIFunction.cpp @@ -151,6 +151,32 @@ FFI_Callback_call(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::Enc return JSC::JSValue::encode(result); } +extern "C" void +FFI_Callback_threadsafe_call(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::EncodedJSValue* args) +{ + + auto* globalObject = wrapper.globalObject.get(); + WTF::Vector<JSC::EncodedJSValue, 8> argsVec; + for (size_t i = 0; i < argCount; ++i) + argsVec.append(args[i]); + + WebCore::ScriptExecutionContext::postTaskTo(globalObject->scriptExecutionContext()->identifier(), [argsVec = WTFMove(argsVec), wrapper](WebCore::ScriptExecutionContext& ctx) mutable { + auto* globalObject = JSC::jsCast<Zig::GlobalObject*>(ctx.jsGlobalObject()); + auto& vm = globalObject->vm(); + JSC::MarkedArgumentBuffer arguments; + auto* function = wrapper.m_function.get(); + for (size_t i = 0; i < argsVec.size(); ++i) + arguments.appendWithCrashOnOverflow(JSC::JSValue::decode(argsVec[i])); + WTF::NakedPtr<JSC::Exception> exception; + JSC::call(globalObject, function, JSC::getCallData(function), JSC::jsUndefined(), arguments, exception); + if (UNLIKELY(exception)) { + auto scope = DECLARE_THROW_SCOPE(vm); + scope.throwException(globalObject, exception); + return; + } + }); +} + extern "C" JSC::EncodedJSValue FFI_Callback_call_0(FFICallbackFunctionWrapper& wrapper, size_t argCount, JSC::EncodedJSValue* args) { diff --git a/src/bun.js/ffi.exports.js b/src/bun.js/ffi.exports.js index 68876bb6f..922920577 100644 --- a/src/bun.js/ffi.exports.js +++ b/src/bun.js/ffi.exports.js @@ -26,7 +26,8 @@ export class JSCallback { #ctx; [Symbol.toPrimitive]() { - return this.ptr; + const { ptr } = this; + return typeof ptr === "number" ? ptr : 0; } close() { diff --git a/test/bun.js/ffi.test.js b/test/bun.js/ffi.test.js index 6433f0161..65b29b788 100644 --- a/test/bun.js/ffi.test.js +++ b/test/bun.js/ffi.test.js @@ -509,12 +509,12 @@ function ffiRunner(fast) { for (let [returnName, returnValue] of Object.entries(typeMap)) { var roundtripFunction = new CFunction({ ptr: new JSCallback( + (input) => { + return input; + }, { returns: returnName, args: [returnName], - }, - (input) => { - return input; } ).ptr, returns: returnName, @@ -525,12 +525,12 @@ function ffiRunner(fast) { { var toClose = new JSCallback( + (input) => { + return input; + }, { returns: "bool", args: ["bool"], - }, - (input) => { - return input; } ); expect(toClose.ptr > 0).toBe(true); @@ -541,15 +541,30 @@ function ffiRunner(fast) { // Return types, no args for (let [name, value] of Object.entries(typeMap)) { var roundtripFunction = new CFunction({ + ptr: new JSCallback(() => value, { + returns: name, + }).ptr, + returns: name, + }); + expect(roundtripFunction()).toBe(value); + } + + // 1 arg, threadsafe + for (let [name, value] of Object.entries(typeMap)) { + var roundtripFunction = new CFunction({ ptr: new JSCallback( - { - returns: name, + (arg1) => { + expect(arg1).toBe(value); }, - () => value + { + args: [name], + threadsafe: true, + } ).ptr, - returns: name, + returns: "void", + args: [name], }); - expect(roundtripFunction()).toBe(value); + roundtripFunction(value); } } |