aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-11-02 13:30:40 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-11-02 13:35:49 -0700
commit006a2f37ddb133f61c6fa672652663204c2d2e54 (patch)
treeee5f0252eee105f2f7b4e80a0bf291724fe75c16
parent65b543fba63c1c1ece5100d0b76644f2f115a906 (diff)
downloadbun-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.zig21
-rw-r--r--src/bun.js/bindings/JSFFIFunction.cpp26
-rw-r--r--src/bun.js/ffi.exports.js3
-rw-r--r--test/bun.js/ffi.test.js37
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);
}
}