aboutsummaryrefslogtreecommitdiff
path: root/src/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'src/javascript')
-rw-r--r--src/javascript/jsc/api/FFI.h59
-rw-r--r--src/javascript/jsc/api/bun.zig36
-rw-r--r--src/javascript/jsc/api/ffi.zig541
-rw-r--r--src/javascript/jsc/base.zig15
-rw-r--r--src/javascript/jsc/ffi.exports.js1
5 files changed, 509 insertions, 143 deletions
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 ---