mirror of
https://github.com/oven-sh/bun
synced 2026-02-13 12:29:07 +00:00
wip
This commit is contained in:
@@ -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 ---
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,18 +164,45 @@ 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: Function = undefined;
|
||||
if (try generateSymbolForFunction(global, allocator, value, &function)) |val| {
|
||||
return val;
|
||||
}
|
||||
// var function
|
||||
var return_type = ABIType.@"void";
|
||||
function.base_name = try allocator.dupeZ(u8, prop);
|
||||
|
||||
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());
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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" };
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
this.step = .{
|
||||
.compiled = .{
|
||||
.ptr = symbol,
|
||||
.buf = bytes,
|
||||
},
|
||||
};
|
||||
_ = 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" };
|
||||
|
||||
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",
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
Reference in New Issue
Block a user