Refactor: Split ffi.zig into multiple files for better maintainability

The previous ffi.zig was a massive 2454-line file that was difficult to
navigate and maintain. This commit splits it into logical modules:

- ffi/common.zig: Common utilities (getDlError, JIT protections, Offsets)
- ffi/string_array.zig: StringArray utility type
- ffi/symbols_map.zig: SymbolsMap type
- ffi/abi_type.zig: ABIType enum and related functions (320 lines)
- ffi/compiler_rt.zig: CompilerRT struct for runtime support
- ffi/compile.zig: CompileC struct for TinyCC compilation
- ffi/function.zig: Function struct for FFI function compilation

The main ffi.zig now acts as a clean facade that re-exports these modules
and contains the main FFI struct with high-level API methods.

This reorganization:
- Reduces the main file from 2454 lines to 889 lines (64% reduction)
- Improves code organization and readability
- Makes it easier to maintain and extend FFI functionality
- Preserves all existing functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2025-10-18 03:32:13 +00:00
parent a7816cfb23
commit 24da5fc37f
8 changed files with 1656 additions and 1580 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,324 @@
const std = @import("std");
const bun = @import("bun");
const string = []const u8;
// Must be kept in sync with JSFFIFunction.h version
pub const ABIType = enum(i32) {
char = 0,
int8_t = 1,
uint8_t = 2,
int16_t = 3,
uint16_t = 4,
int32_t = 5,
uint32_t = 6,
int64_t = 7,
uint64_t = 8,
double = 9,
float = 10,
bool = 11,
ptr = 12,
void = 13,
cstring = 14,
i64_fast = 15,
u64_fast = 16,
function = 17,
napi_env = 18,
napi_value = 19,
buffer = 20,
pub const max = @intFromEnum(ABIType.napi_value);
/// Types that we can directly pass through as an `int64_t`
pub fn needsACastInC(this: ABIType) bool {
return switch (this) {
.char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => false,
else => true,
};
}
const map = .{
.{ "bool", ABIType.bool },
.{ "c_int", ABIType.int32_t },
.{ "c_uint", ABIType.uint32_t },
.{ "char", ABIType.char },
.{ "char*", ABIType.ptr },
.{ "double", ABIType.double },
.{ "f32", ABIType.float },
.{ "f64", ABIType.double },
.{ "float", ABIType.float },
.{ "i16", ABIType.int16_t },
.{ "i32", ABIType.int32_t },
.{ "i64", ABIType.int64_t },
.{ "i8", ABIType.int8_t },
.{ "int", ABIType.int32_t },
.{ "int16_t", ABIType.int16_t },
.{ "int32_t", ABIType.int32_t },
.{ "int64_t", ABIType.int64_t },
.{ "int8_t", ABIType.int8_t },
.{ "isize", ABIType.int64_t },
.{ "u16", ABIType.uint16_t },
.{ "u32", ABIType.uint32_t },
.{ "u64", ABIType.uint64_t },
.{ "u8", ABIType.uint8_t },
.{ "uint16_t", ABIType.uint16_t },
.{ "uint32_t", ABIType.uint32_t },
.{ "uint64_t", ABIType.uint64_t },
.{ "uint8_t", ABIType.uint8_t },
.{ "usize", ABIType.uint64_t },
.{ "size_t", ABIType.uint64_t },
.{ "buffer", ABIType.buffer },
.{ "void*", ABIType.ptr },
.{ "ptr", ABIType.ptr },
.{ "pointer", ABIType.ptr },
.{ "void", ABIType.void },
.{ "cstring", ABIType.cstring },
.{ "i64_fast", ABIType.i64_fast },
.{ "u64_fast", ABIType.u64_fast },
.{ "function", ABIType.function },
.{ "callback", ABIType.function },
.{ "fn", ABIType.function },
.{ "napi_env", ABIType.napi_env },
.{ "napi_value", ABIType.napi_value },
};
pub const label = bun.ComptimeStringMap(ABIType, map);
const EnumMapFormatter = struct {
name: []const u8,
entry: ABIType,
pub fn format(self: EnumMapFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try writer.writeAll("['");
// these are not all valid identifiers
try writer.writeAll(self.name);
try writer.writeAll("']:");
try std.fmt.formatInt(@intFromEnum(self.entry), 10, .lower, .{}, writer);
try writer.writeAll(",'");
try std.fmt.formatInt(@intFromEnum(self.entry), 10, .lower, .{}, writer);
try writer.writeAll("':");
try std.fmt.formatInt(@intFromEnum(self.entry), 10, .lower, .{}, writer);
}
};
pub const map_to_js_object = brk: {
var count: usize = 2;
for (map, 0..) |item, i| {
const fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" };
count += std.fmt.count("{}", .{fmt});
count += @intFromBool(i > 0);
}
var buf: [count]u8 = undefined;
buf[0] = '{';
buf[buf.len - 1] = '}';
var end: usize = 1;
for (map, 0..) |item, i| {
const fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" };
if (i > 0) {
buf[end] = ',';
end += 1;
}
end += (std.fmt.bufPrint(buf[end..], "{}", .{fmt}) catch unreachable).len;
}
break :brk buf;
};
pub fn isFloatingPoint(this: ABIType) bool {
return switch (this) {
.double, .float => true,
else => false,
};
}
const ToCFormatter = struct {
symbol: string,
tag: ABIType,
exact: bool = false,
pub fn format(self: ToCFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
switch (self.tag) {
.void => {
return;
},
.bool => {
if (self.exact)
try writer.writeAll("(bool)");
try writer.writeAll("JSVALUE_TO_BOOL(");
},
.char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => {
if (self.exact)
try writer.print("({s})", .{bun.asByteSlice(@tagName(self.tag))});
try writer.writeAll("JSVALUE_TO_INT32(");
},
.i64_fast, .int64_t => {
if (self.exact)
try writer.writeAll("(int64_t)");
try writer.writeAll("JSVALUE_TO_INT64(");
},
.u64_fast, .uint64_t => {
if (self.exact)
try writer.writeAll("(uint64_t)");
try writer.writeAll("JSVALUE_TO_UINT64(");
},
.function, .cstring, .ptr => {
if (self.exact)
try writer.writeAll("(void*)");
try writer.writeAll("JSVALUE_TO_PTR(");
},
.double => {
if (self.exact)
try writer.writeAll("(double)");
try writer.writeAll("JSVALUE_TO_DOUBLE(");
},
.float => {
if (self.exact)
try writer.writeAll("(float)");
try writer.writeAll("JSVALUE_TO_FLOAT(");
},
.napi_env => {
try writer.writeAll("((napi_env)&Bun__thisFFIModuleNapiEnv)");
return;
},
.napi_value => {
try writer.writeAll(self.symbol);
try writer.writeAll(".asNapiValue");
return;
},
.buffer => {
try writer.writeAll("JSVALUE_TO_TYPED_ARRAY_VECTOR(");
},
}
try writer.writeAll(self.symbol);
try writer.writeAll(")");
}
};
const ToJSFormatter = struct {
symbol: []const u8,
tag: ABIType,
pub fn format(self: ToJSFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
switch (self.tag) {
.void => {},
.bool => {
try writer.print("BOOLEAN_TO_JSVALUE({s})", .{self.symbol});
},
.char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t => {
try writer.print("INT32_TO_JSVALUE((int32_t){s})", .{self.symbol});
},
.uint32_t => {
try writer.print("UINT32_TO_JSVALUE({s})", .{self.symbol});
},
.i64_fast => {
try writer.print("INT64_TO_JSVALUE(JS_GLOBAL_OBJECT, (int64_t){s})", .{self.symbol});
},
.int64_t => {
try writer.print("INT64_TO_JSVALUE_SLOW(JS_GLOBAL_OBJECT, {s})", .{self.symbol});
},
.u64_fast => {
try writer.print("UINT64_TO_JSVALUE(JS_GLOBAL_OBJECT, {s})", .{self.symbol});
},
.uint64_t => {
try writer.print("UINT64_TO_JSVALUE_SLOW(JS_GLOBAL_OBJECT, {s})", .{self.symbol});
},
.function, .cstring, .ptr => {
try writer.print("PTR_TO_JSVALUE({s})", .{self.symbol});
},
.double => {
try writer.print("DOUBLE_TO_JSVALUE({s})", .{self.symbol});
},
.float => {
try writer.print("FLOAT_TO_JSVALUE({s})", .{self.symbol});
},
.napi_env => {
try writer.writeAll("((napi_env)&Bun__thisFFIModuleNapiEnv)");
},
.napi_value => {
try writer.print("((EncodedJSValue) {{.asNapiValue = {s} }} )", .{self.symbol});
},
.buffer => {
try writer.writeAll("0");
},
}
}
};
pub fn toC(this: ABIType, symbol: string) ToCFormatter {
return ToCFormatter{ .tag = this, .symbol = symbol };
}
pub fn toCExact(this: ABIType, symbol: string) ToCFormatter {
return ToCFormatter{ .tag = this, .symbol = symbol, .exact = true };
}
pub fn toJS(
this: ABIType,
symbol: string,
) ToJSFormatter {
return ToJSFormatter{
.tag = this,
.symbol = symbol,
};
}
pub fn typename(this: ABIType, writer: anytype) !void {
try writer.writeAll(this.typenameLabel());
}
pub fn typenameLabel(this: ABIType) []const u8 {
return switch (this) {
.buffer, .function, .cstring, .ptr => "void*",
.bool => "bool",
.int8_t => "int8_t",
.uint8_t => "uint8_t",
.int16_t => "int16_t",
.uint16_t => "uint16_t",
.int32_t => "int32_t",
.uint32_t => "uint32_t",
.i64_fast, .int64_t => "int64_t",
.u64_fast, .uint64_t => "uint64_t",
.double => "double",
.float => "float",
.char => "char",
.void => "void",
.napi_env => "napi_env",
.napi_value => "napi_value",
};
}
pub fn paramTypename(this: ABIType, writer: anytype) !void {
try writer.writeAll(this.typenameLabel());
}
pub fn paramTypenameLabel(this: ABIType) []const u8 {
return switch (this) {
.function, .cstring, .ptr => "void*",
.bool => "bool",
.int8_t => "int8_t",
.uint8_t => "uint8_t",
.int16_t => "int16_t",
.uint16_t => "uint16_t",
// see the comment in ffi.ts about why `uint32_t` acts as `int32_t`
.int32_t,
.uint32_t,
=> "int32_t",
.i64_fast, .int64_t => "int64_t",
.u64_fast, .uint64_t => "uint64_t",
.double => "double",
.float => "float",
.char => "char",
.void => "void",
.napi_env => "napi_env",
.napi_value => "napi_value",
.buffer => "buffer",
};
}
};

View File

@@ -0,0 +1,61 @@
const std = @import("std");
const bun = @import("bun");
const Environment = bun.Environment;
const Output = bun.Output;
const debug = Output.scoped(.TCC, .visible);
extern fn pthread_jit_write_protect_np(enable: c_int) void;
/// Get the last dynamic library loading error message in a cross-platform way.
/// On POSIX systems, this calls dlerror().
/// On Windows, this uses GetLastError() and formats the error message.
/// Returns an allocated string that must be freed by the caller.
pub fn getDlError(allocator: std.mem.Allocator) ![]const u8 {
if (Environment.isWindows) {
// On Windows, we need to use GetLastError() and FormatMessageW()
const err = bun.windows.GetLastError();
const err_int = @intFromEnum(err);
// For now, just return the error code as we'd need to implement FormatMessageW in Zig
// This is still better than a generic message
return try std.fmt.allocPrint(allocator, "error code {d}", .{err_int});
} else {
// On POSIX systems, use dlerror() to get the actual system error
const msg = if (std.c.dlerror()) |err_ptr|
std.mem.span(err_ptr)
else
"unknown error";
// Return a copy since dlerror() string is not stable
return try allocator.dupe(u8, msg);
}
}
/// Run a function that needs to write to JIT-protected memory.
///
/// This is dangerous as it allows overwriting executable regions of memory.
/// Do not pass in user-defined functions (including JSFunctions).
pub fn dangerouslyRunWithoutJitProtections(R: type, func: anytype, args: anytype) R {
const has_protection = (Environment.isAarch64 and Environment.isMac);
if (comptime has_protection) pthread_jit_write_protect_np(@intFromBool(false));
defer if (comptime has_protection) pthread_jit_write_protect_np(@intFromBool(true));
return @call(.always_inline, func, args);
}
pub const Offsets = extern struct {
JSArrayBufferView__offsetOfLength: u32,
JSArrayBufferView__offsetOfByteOffset: u32,
JSArrayBufferView__offsetOfVector: u32,
JSCell__offsetOfType: u32,
extern "c" var Bun__FFI__offsets: Offsets;
extern "c" fn Bun__FFI__ensureOffsetsAreLoaded() void;
fn loadOnce() void {
Bun__FFI__ensureOffsetsAreLoaded();
}
var once = std.once(loadOnce);
pub fn get() *const Offsets {
once.call();
return &Bun__FFI__offsets;
}
};

View File

@@ -0,0 +1,467 @@
const Fs = @import("../../../fs.zig");
const TCC = @import("../../../deps/tcc.zig");
const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("bun");
const Environment = bun.Environment;
const Output = bun.Output;
const strings = bun.strings;
const jsc = bun.jsc;
const JSGlobalObject = jsc.JSGlobalObject;
const debug = Output.scoped(.TCC, .visible);
const StringArray = @import("./string_array.zig").StringArray;
const SymbolsMap = @import("./symbols_map.zig").SymbolsMap;
const CompilerRT = @import("./compiler_rt.zig").CompilerRT;
const Function = @import("./function.zig").Function;
pub const CompileC = struct {
source: Source = .{ .file = "" },
current_file_for_errors: [:0]const u8 = "",
libraries: StringArray = .{},
library_dirs: StringArray = .{},
include_dirs: StringArray = .{},
symbols: SymbolsMap = .{},
define: std.ArrayListUnmanaged([2][:0]const u8) = .{},
// Flags to replace the default flags
flags: [:0]const u8 = "",
deferred_errors: std.ArrayListUnmanaged([]const u8) = .{},
const Source = union(enum) {
file: [:0]const u8,
files: std.ArrayListUnmanaged([:0]const u8),
pub fn first(this: *const Source) [:0]const u8 {
return switch (this.*) {
.file => this.file,
.files => this.files.items[0],
};
}
pub fn deinit(this: *Source, allocator: Allocator) void {
switch (this.*) {
.file => if (this.file.len > 0) allocator.free(this.file),
.files => {
for (this.files.items) |file| {
allocator.free(file);
}
this.files.deinit(allocator);
},
}
this.* = .{ .file = "" };
}
pub fn add(this: *Source, state: *TCC.State, current_file_for_errors: *[:0]const u8) !void {
switch (this.*) {
.file => {
current_file_for_errors.* = this.file;
state.addFile(this.file) catch return error.CompilationError;
current_file_for_errors.* = "";
},
.files => {
for (this.files.items) |file| {
current_file_for_errors.* = file;
state.addFile(file) catch return error.CompilationError;
current_file_for_errors.* = "";
}
},
}
}
};
const stdarg = struct {
extern "c" fn ffi_vfprintf(*anyopaque, [*:0]const u8, ...) callconv(.C) c_int;
extern "c" fn ffi_vprintf([*:0]const u8, ...) callconv(.C) c_int;
extern "c" fn ffi_fprintf(*anyopaque, [*:0]const u8, ...) callconv(.C) c_int;
extern "c" fn ffi_printf([*:0]const u8, ...) callconv(.C) c_int;
extern "c" fn ffi_fscanf(*anyopaque, [*:0]const u8, ...) callconv(.C) c_int;
extern "c" fn ffi_scanf([*:0]const u8, ...) callconv(.C) c_int;
extern "c" fn ffi_sscanf([*:0]const u8, [*:0]const u8, ...) callconv(.C) c_int;
extern "c" fn ffi_vsscanf([*:0]const u8, [*:0]const u8, ...) callconv(.C) c_int;
extern "c" fn ffi_fopen([*:0]const u8, [*:0]const u8) callconv(.C) *anyopaque;
extern "c" fn ffi_fclose(*anyopaque) callconv(.C) c_int;
extern "c" fn ffi_fgetc(*anyopaque) callconv(.C) c_int;
extern "c" fn ffi_fputc(c: c_int, *anyopaque) callconv(.C) c_int;
extern "c" fn ffi_feof(*anyopaque) callconv(.C) c_int;
extern "c" fn ffi_fileno(*anyopaque) callconv(.C) c_int;
extern "c" fn ffi_ungetc(c: c_int, *anyopaque) callconv(.C) c_int;
extern "c" fn ffi_ftell(*anyopaque) callconv(.C) c_long;
extern "c" fn ffi_fseek(*anyopaque, c_long, c_int) callconv(.C) c_int;
extern "c" fn ffi_fflush(*anyopaque) callconv(.C) c_int;
extern "c" fn calloc(nmemb: usize, size: usize) callconv(.C) ?*anyopaque;
extern "c" fn perror([*:0]const u8) callconv(.C) void;
const mac = if (Environment.isMac) struct {
var ffi_stdinp: *anyopaque = @extern(*anyopaque, .{ .name = "__stdinp" });
var ffi_stdoutp: *anyopaque = @extern(*anyopaque, .{ .name = "__stdoutp" });
var ffi_stderrp: *anyopaque = @extern(*anyopaque, .{ .name = "__stderrp" });
pub fn inject(state: *TCC.State) void {
state.addSymbolsComptime(.{
.__stdinp = ffi_stdinp,
.__stdoutp = ffi_stdoutp,
.__stderrp = ffi_stderrp,
}) catch @panic("Failed to add macos symbols");
}
} else struct {
pub fn inject(_: *TCC.State) void {}
};
pub fn inject(state: *TCC.State) void {
state.addSymbolsComptime(.{
// printf family
.vfprintf = ffi_vfprintf,
.vprintf = ffi_vprintf,
.fprintf = ffi_fprintf,
.printf = ffi_printf,
.fscanf = ffi_fscanf,
.scanf = ffi_scanf,
.sscanf = ffi_sscanf,
.vsscanf = ffi_vsscanf,
// files
.fopen = ffi_fopen,
.fclose = ffi_fclose,
.fgetc = ffi_fgetc,
.fputc = ffi_fputc,
.feof = ffi_feof,
.fileno = ffi_fileno,
.fwrite = std.c.fwrite,
.ungetc = ffi_ungetc,
.ftell = ffi_ftell,
.fseek = ffi_fseek,
.fflush = ffi_fflush,
.fread = std.c.fread,
// memory
.malloc = std.c.malloc,
.realloc = std.c.realloc,
.calloc = calloc,
.free = std.c.free,
// error
.perror = perror,
}) catch @panic("Failed to add std.c symbols");
if (Environment.isPosix) {
state.addSymbolsComptime(.{
.posix_memalign = std.c.posix_memalign,
.dlopen = std.c.dlopen,
.dlclose = std.c.dlclose,
.dlsym = std.c.dlsym,
.dlerror = std.c.dlerror,
}) catch @panic("Failed to add posix symbols");
}
mac.inject(state);
}
};
pub fn handleCompilationError(this_: ?*CompileC, message: ?[*:0]const u8) callconv(.C) void {
const this = this_ orelse return;
var msg = std.mem.span(message orelse "");
if (msg.len == 0) return;
var offset: usize = 0;
// the message we get from TCC sometimes has garbage in it
// i think because we're doing in-memory compilation
while (offset < msg.len) : (offset += 1) {
if (msg[offset] > 0x20 and msg[offset] < 0x7f) break;
}
msg = msg[offset..];
bun.handleOom(this.deferred_errors.append(bun.default_allocator, bun.handleOom(bun.default_allocator.dupe(u8, msg))));
}
const DeferredError = error{DeferredErrors};
inline fn hasDeferredErrors(this: *CompileC) bool {
return this.deferred_errors.items.len > 0;
}
/// Returns DeferredError if any errors from tinycc were registered
/// via `handleCompilationError`
inline fn errorCheck(this: *CompileC) DeferredError!void {
if (this.deferred_errors.items.len > 0) {
return error.DeferredErrors;
}
}
pub const default_tcc_options: [:0]const u8 = "-std=c11 -Wl,--export-all-symbols -g -O2";
var cached_default_system_include_dir: [:0]const u8 = "";
var cached_default_system_library_dir: [:0]const u8 = "";
var cached_default_system_include_dir_once = std.once(getSystemRootDirOnce);
fn getSystemRootDirOnce() void {
if (Environment.isMac) {
var which_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
var process = bun.spawnSync(&.{
.stdout = .buffer,
.stdin = .ignore,
.stderr = .ignore,
.argv = &.{
bun.which(&which_buf, bun.sliceTo(std.c.getenv("PATH") orelse "", 0), Fs.FileSystem.instance.top_level_dir, "xcrun") orelse "/usr/bin/xcrun",
"-sdk",
"macosx",
"-show-sdk-path",
},
// ?[*:null]?[*:0]const u8
// [*:null]?[*:0]u8
.envp = @ptrCast(std.c.environ),
}) catch return;
if (process == .result) {
defer process.result.deinit();
if (process.result.isOK()) {
const stdout = process.result.stdout.items;
if (stdout.len > 0) {
cached_default_system_include_dir = bun.default_allocator.dupeZ(u8, strings.trim(stdout, "\n\r")) catch return;
}
}
}
} else if (Environment.isLinux) {
// On Debian/Ubuntu, the lib and include paths are suffixed with {arch}-linux-gnu
// e.g. x86_64-linux-gnu or aarch64-linux-gnu
// On Alpine and RHEL-based distros, the paths are not suffixed
if (Environment.isX64) {
if (bun.FD.cwd().directoryExistsAt("/usr/include/x86_64-linux-gnu").isTrue()) {
cached_default_system_include_dir = "/usr/include/x86_64-linux-gnu";
} else if (bun.FD.cwd().directoryExistsAt("/usr/include").isTrue()) {
cached_default_system_include_dir = "/usr/include";
}
if (bun.FD.cwd().directoryExistsAt("/usr/lib/x86_64-linux-gnu").isTrue()) {
cached_default_system_library_dir = "/usr/lib/x86_64-linux-gnu";
} else if (bun.FD.cwd().directoryExistsAt("/usr/lib64").isTrue()) {
cached_default_system_library_dir = "/usr/lib64";
}
} else if (Environment.isAarch64) {
if (bun.FD.cwd().directoryExistsAt("/usr/include/aarch64-linux-gnu").isTrue()) {
cached_default_system_include_dir = "/usr/include/aarch64-linux-gnu";
} else if (bun.FD.cwd().directoryExistsAt("/usr/include").isTrue()) {
cached_default_system_include_dir = "/usr/include";
}
if (bun.FD.cwd().directoryExistsAt("/usr/lib/aarch64-linux-gnu").isTrue()) {
cached_default_system_library_dir = "/usr/lib/aarch64-linux-gnu";
} else if (bun.FD.cwd().directoryExistsAt("/usr/lib64").isTrue()) {
cached_default_system_library_dir = "/usr/lib64";
}
}
}
}
fn getSystemIncludeDir() ?[:0]const u8 {
cached_default_system_include_dir_once.call();
if (cached_default_system_include_dir.len == 0) return null;
return cached_default_system_include_dir;
}
fn getSystemLibraryDir() ?[:0]const u8 {
cached_default_system_include_dir_once.call();
if (cached_default_system_library_dir.len == 0) return null;
return cached_default_system_library_dir;
}
pub fn compile(this: *CompileC, globalThis: *JSGlobalObject) !struct { *TCC.State, []u8 } {
const compile_options: [:0]const u8 = if (this.flags.len > 0)
this.flags
else if (bun.getenvZ("BUN_TCC_OPTIONS")) |tcc_options|
@ptrCast(tcc_options)
else
default_tcc_options;
// TODO: correctly handle invalid user-provided options
const state = TCC.State.init(CompileC, .{
.options = compile_options,
.err = .{ .ctx = this, .handler = &handleCompilationError },
}, true) catch |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
else => {
bun.debugAssert(this.hasDeferredErrors());
return error.DeferredErrors;
},
};
var pathbuf: [bun.MAX_PATH_BYTES]u8 = undefined;
if (CompilerRT.dir()) |compiler_rt_dir| {
state.addSysIncludePath(compiler_rt_dir) catch {
debug("TinyCC failed to add sysinclude path", .{});
};
}
if (Environment.isMac) {
add_system_include_dir: {
const dirs_to_try = [_][]const u8{
bun.getenvZ("SDKROOT") orelse "",
getSystemIncludeDir() orelse "",
};
for (dirs_to_try) |sdkroot| {
if (sdkroot.len > 0) {
const include_dir = bun.path.joinAbsStringBufZ(sdkroot, &pathbuf, &.{ "usr", "include" }, .auto);
state.addSysIncludePath(include_dir) catch return globalThis.throw("TinyCC failed to add sysinclude path", .{});
const lib_dir = bun.path.joinAbsStringBufZ(sdkroot, &pathbuf, &.{ "usr", "lib" }, .auto);
state.addLibraryPath(lib_dir) catch return globalThis.throw("TinyCC failed to add library path", .{});
break :add_system_include_dir;
}
}
}
if (Environment.isAarch64) {
if (bun.FD.cwd().directoryExistsAt("/opt/homebrew/include").isTrue()) {
state.addSysIncludePath("/opt/homebrew/include") catch {
debug("TinyCC failed to add library path", .{});
};
}
if (bun.FD.cwd().directoryExistsAt("/opt/homebrew/lib").isTrue()) {
state.addLibraryPath("/opt/homebrew/lib") catch {
debug("TinyCC failed to add library path", .{});
};
}
}
} else if (Environment.isLinux) {
if (getSystemIncludeDir()) |include_dir| {
state.addSysIncludePath(include_dir) catch {
debug("TinyCC failed to add sysinclude path", .{});
};
}
if (getSystemLibraryDir()) |library_dir| {
state.addLibraryPath(library_dir) catch {
debug("TinyCC failed to add library path", .{});
};
}
}
if (Environment.isPosix) {
if (bun.FD.cwd().directoryExistsAt("/usr/local/include").isTrue()) {
state.addSysIncludePath("/usr/local/include") catch {
debug("TinyCC failed to add sysinclude path", .{});
};
}
if (bun.FD.cwd().directoryExistsAt("/usr/local/lib").isTrue()) {
state.addLibraryPath("/usr/local/lib") catch {
debug("TinyCC failed to add library path", .{});
};
}
}
try this.errorCheck();
for (this.include_dirs.items) |include_dir| {
state.addSysIncludePath(include_dir) catch {
bun.debugAssert(this.hasDeferredErrors());
return error.DeferredErrors;
};
}
try this.errorCheck();
CompilerRT.define(state);
try this.errorCheck();
for (this.symbols.map.values()) |*symbol| {
if (symbol.needsNapiEnv()) {
state.addSymbol("Bun__thisFFIModuleNapiEnv", globalThis.makeNapiEnvForFFI()) catch return error.DeferredErrors;
break;
}
}
for (this.define.items) |define| {
state.defineSymbol(define[0], define[1]);
try this.errorCheck();
}
this.source.add(state, &this.current_file_for_errors) catch {
if (this.deferred_errors.items.len > 0) {
return error.DeferredErrors;
} else {
if (!globalThis.hasException()) {
return globalThis.throw("TinyCC failed to compile", .{});
}
return error.JSError;
}
};
CompilerRT.inject(state);
stdarg.inject(state);
try this.errorCheck();
for (this.library_dirs.items) |library_dir| {
// register all, even if some fail. Only fail after all have been registered.
state.addLibraryPath(library_dir) catch {
debug("TinyCC failed to add library path", .{});
};
}
try this.errorCheck();
for (this.libraries.items) |library| {
// register all, even if some fail.
state.addLibrary(library) catch {};
}
try this.errorCheck();
const relocation_size = state.relocate(null) catch {
bun.debugAssert(this.hasDeferredErrors());
return error.DeferredErrors;
};
const bytes: []u8 = try bun.default_allocator.alloc(u8, @as(usize, @intCast(relocation_size)));
// We cannot free these bytes, evidently.
const dangerouslyRunWithoutJitProtections = @import("./common.zig").dangerouslyRunWithoutJitProtections;
_ = dangerouslyRunWithoutJitProtections(TCC.Error!usize, TCC.State.relocate, .{ state, bytes.ptr }) catch return error.DeferredErrors;
// if errors got added, we would have returned in the relocation catch.
bun.debugAssert(this.deferred_errors.items.len == 0);
for (this.symbols.map.keys(), this.symbols.map.values()) |symbol, *function| {
// FIXME: why are we duping here? can we at least use a stack
// fallback allocator?
const duped = bun.handleOom(bun.default_allocator.dupeZ(u8, symbol));
defer bun.default_allocator.free(duped);
function.symbol_from_dynamic_library = state.getSymbol(duped) orelse {
return globalThis.throw("{} is missing from {s}. Was it included in the source code?", .{ bun.fmt.quote(symbol), this.source.first() });
};
}
try this.errorCheck();
return .{ state, bytes };
}
pub fn deinit(this: *CompileC) void {
this.symbols.deinit();
this.libraries.deinit();
this.library_dirs.deinit();
this.include_dirs.deinit();
for (this.deferred_errors.items) |deferred_error| {
bun.default_allocator.free(deferred_error);
}
this.deferred_errors.clearAndFree(bun.default_allocator);
for (this.define.items) |define| {
bun.default_allocator.free(define[0]);
if (define[1].len > 0) bun.default_allocator.free(define[1]);
}
this.define.clearAndFree(bun.default_allocator);
this.source.deinit(bun.default_allocator);
if (this.flags.len > 0) bun.default_allocator.free(this.flags);
this.flags = "";
}
};

View File

@@ -0,0 +1,112 @@
const Fs = @import("../../../fs.zig");
const TCC = @import("../../../deps/tcc.zig");
const std = @import("std");
const bun = @import("bun");
const Environment = bun.Environment;
const jsc = bun.jsc;
const JSValue = jsc.JSValue;
const Offsets = @import("./common.zig").Offsets;
pub const CompilerRT = struct {
var compiler_rt_dir: [:0]const u8 = "";
const compiler_rt_sources = struct {
pub const @"stdbool.h" = @embedFile("./ffi-stdbool.h");
pub const @"stdarg.h" = @embedFile("./ffi-stdarg.h");
pub const @"stdnoreturn.h" = @embedFile("./ffi-stdnoreturn.h");
pub const @"stdalign.h" = @embedFile("./ffi-stdalign.h");
pub const @"tgmath.h" = @embedFile("./ffi-tgmath.h");
pub const @"stddef.h" = @embedFile("./ffi-stddef.h");
pub const @"varargs.h" = "// empty";
};
fn createCompilerRTDir() void {
const tmpdir = Fs.FileSystem.instance.tmpdir() catch return;
var bunCC = tmpdir.makeOpenPath("bun-cc", .{}) catch return;
defer bunCC.close();
inline for (comptime std.meta.declarations(compiler_rt_sources)) |decl| {
const source = @field(compiler_rt_sources, decl.name);
bunCC.writeFile(.{
.sub_path = decl.name,
.data = source,
}) catch {};
}
var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
compiler_rt_dir = bun.handleOom(bun.default_allocator.dupeZ(u8, bun.getFdPath(.fromStdDir(bunCC), &path_buf) catch return));
}
var create_compiler_rt_dir_once = std.once(createCompilerRTDir);
pub fn dir() ?[:0]const u8 {
create_compiler_rt_dir_once.call();
if (compiler_rt_dir.len == 0) return null;
return compiler_rt_dir;
}
const MyFunctionSStructWorkAround = struct {
JSVALUE_TO_INT64: *const fn (JSValue0: jsc.JSValue) callconv(.C) i64,
JSVALUE_TO_UINT64: *const fn (JSValue0: jsc.JSValue) callconv(.C) u64,
INT64_TO_JSVALUE: *const fn (arg0: *jsc.JSGlobalObject, arg1: i64) callconv(.C) jsc.JSValue,
UINT64_TO_JSVALUE: *const fn (arg0: *jsc.JSGlobalObject, arg1: u64) callconv(.C) jsc.JSValue,
bun_call: *const @TypeOf(jsc.C.JSObjectCallAsFunction),
};
const headers = JSValue.exposed_to_ffi;
var workaround: MyFunctionSStructWorkAround = .{
.JSVALUE_TO_INT64 = headers.JSVALUE_TO_INT64,
.JSVALUE_TO_UINT64 = headers.JSVALUE_TO_UINT64,
.INT64_TO_JSVALUE = headers.INT64_TO_JSVALUE,
.UINT64_TO_JSVALUE = headers.UINT64_TO_JSVALUE,
.bun_call = &jsc.C.JSObjectCallAsFunction,
};
noinline fn memset(
dest: [*]u8,
c: u8,
byte_count: usize,
) callconv(.C) void {
@memset(dest[0..byte_count], c);
}
noinline fn memcpy(
noalias dest: [*]u8,
noalias source: [*]const u8,
byte_count: usize,
) callconv(.C) void {
@memcpy(dest[0..byte_count], source[0..byte_count]);
}
pub fn define(state: *TCC.State) void {
if (comptime Environment.isX64) {
state.defineSymbol("NEEDS_COMPILER_RT_FUNCTIONS", "1");
state.compileString(@embedFile(("libtcc1.c"))) catch {
if (bun.Environment.isDebug) {
@panic("Failed to compile libtcc1.c");
}
};
}
const Sizes = @import("../../bindings/sizes.zig");
const offsets = Offsets.get();
state.defineSymbolsComptime(.{
.Bun_FFI_PointerOffsetToArgumentsList = Sizes.Bun_FFI_PointerOffsetToArgumentsList,
.JSArrayBufferView__offsetOfLength = offsets.JSArrayBufferView__offsetOfLength,
.JSArrayBufferView__offsetOfVector = offsets.JSArrayBufferView__offsetOfVector,
.JSCell__offsetOfType = offsets.JSCell__offsetOfType,
.JSTypeArrayBufferViewMin = @intFromEnum(jsc.JSValue.JSType.min_typed_array),
.JSTypeArrayBufferViewMax = @intFromEnum(jsc.JSValue.JSType.max_typed_array),
});
}
pub fn inject(state: *TCC.State) void {
state.addSymbol("memset", &memset) catch unreachable;
state.addSymbol("memcpy", &memcpy) catch unreachable;
state.addSymbol("NapiHandleScope__open", &bun.api.napi.NapiHandleScope.NapiHandleScope__open) catch unreachable;
state.addSymbol("NapiHandleScope__close", &bun.api.napi.NapiHandleScope.NapiHandleScope__close) catch unreachable;
state.addSymbol("JSVALUE_TO_INT64_SLOW", workaround.JSVALUE_TO_INT64) catch unreachable;
state.addSymbol("JSVALUE_TO_UINT64_SLOW", workaround.JSVALUE_TO_UINT64) catch unreachable;
state.addSymbol("INT64_TO_JSVALUE_SLOW", workaround.INT64_TO_JSVALUE) catch unreachable;
state.addSymbol("UINT64_TO_JSVALUE_SLOW", workaround.UINT64_TO_JSVALUE) catch unreachable;
}
};

View File

@@ -0,0 +1,608 @@
const TCC = @import("../../../deps/tcc.zig");
const napi = @import("../../../napi/napi.zig");
const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("bun");
const Environment = bun.Environment;
const jsc = bun.jsc;
const JSValue = jsc.JSValue;
const ZigString = jsc.ZigString;
const string = []const u8;
const ABIType = @import("./abi_type.zig").ABIType;
const CompilerRT = @import("./compiler_rt.zig").CompilerRT;
pub const Function = struct {
symbol_from_dynamic_library: ?*anyopaque = null,
base_name: ?[:0]const u8 = null,
state: ?*TCC.State = null,
return_type: ABIType = ABIType.void,
arg_types: std.ArrayListUnmanaged(ABIType) = .{},
step: Step = Step{ .pending = {} },
threadsafe: bool = false,
allocator: Allocator,
pub var lib_dirZ: [*:0]const u8 = "";
pub fn needsHandleScope(val: *const Function) bool {
for (val.arg_types.items) |arg| {
if (arg == ABIType.napi_env or arg == ABIType.napi_value) {
return true;
}
}
return val.return_type == ABIType.napi_value;
}
extern "c" fn FFICallbackFunctionWrapper_destroy(*anyopaque) void;
pub fn deinit(val: *Function, globalThis: *jsc.JSGlobalObject) void {
jsc.markBinding(@src());
if (val.base_name) |base_name| {
if (bun.asByteSlice(base_name).len > 0) {
val.allocator.free(@constCast(bun.asByteSlice(base_name)));
}
}
val.arg_types.clearAndFree(val.allocator);
if (val.state) |state| {
state.deinit();
val.state = null;
}
if (val.step == .compiled) {
// val.allocator.free(val.step.compiled.buf);
if (val.step.compiled.js_function != .zero) {
_ = globalThis;
// _ = jsc.untrackFunction(globalThis, val.step.compiled.js_function);
val.step.compiled.js_function = .zero;
}
if (val.step.compiled.ffi_callback_function_wrapper) |wrapper| {
FFICallbackFunctionWrapper_destroy(wrapper);
val.step.compiled.ffi_callback_function_wrapper = null;
}
}
if (val.step == .failed and val.step.failed.allocated) {
val.allocator.free(val.step.failed.msg);
}
}
pub const Step = union(enum) {
pending: void,
compiled: struct {
ptr: *anyopaque,
buf: []u8,
js_function: JSValue = JSValue.zero,
js_context: ?*anyopaque = null,
ffi_callback_function_wrapper: ?*anyopaque = null,
},
failed: struct {
msg: []const u8,
allocated: bool = false,
},
};
fn fail(this: *Function, comptime msg: []const u8) void {
if (this.step != .failed) {
@branchHint(.likely);
this.step = .{ .failed = .{ .msg = msg, .allocated = false } };
}
}
pub fn ffiHeader() string {
return if (Environment.codegen_embed)
@embedFile("./FFI.h")
else
bun.runtimeEmbedFile(.src, "bun.js/api/FFI.h");
}
pub fn handleTCCError(ctx: ?*Function, message: [*c]const u8) callconv(.C) void {
var this = ctx.?;
var msg = std.mem.span(message);
if (msg.len > 0) {
var offset: usize = 0;
// the message we get from TCC sometimes has garbage in it
// i think because we're doing in-memory compilation
while (offset < msg.len) : (offset += 1) {
if (msg[offset] > 0x20 and msg[offset] < 0x7f) break;
}
msg = msg[offset..];
}
this.step = .{ .failed = .{ .msg = this.allocator.dupe(u8, msg) catch unreachable, .allocated = true } };
}
const tcc_options = "-std=c11 -nostdlib -Wl,--export-all-symbols" ++ if (Environment.isDebug) " -g" else "";
pub fn compile(this: *Function, napiEnv: ?*napi.NapiEnv) !void {
var source_code = std.ArrayList(u8).init(this.allocator);
var source_code_writer = source_code.writer();
try this.printSourceCode(&source_code_writer);
try source_code.append(0);
defer source_code.deinit();
const state = TCC.State.init(Function, .{
.options = tcc_options,
.err = .{ .ctx = this, .handler = handleTCCError },
}, false) catch return error.TCCMissing;
this.state = state;
defer {
if (this.step == .failed) {
state.deinit();
this.state = null;
}
}
if (napiEnv) |env| {
_ = state.addSymbol("Bun__thisFFIModuleNapiEnv", env) catch {
this.fail("Failed to add NAPI env symbol");
return;
};
}
CompilerRT.define(state);
state.compileString(@ptrCast(source_code.items)) catch {
this.fail("Failed to compile source code");
return;
};
CompilerRT.inject(state);
state.addSymbol(this.base_name.?, this.symbol_from_dynamic_library.?) catch {
bun.debugAssert(this.step == .failed);
return;
};
const relocation_size = state.relocate(null) catch {
this.fail("tcc_relocate returned a negative value");
return;
};
const bytes: []u8 = try this.allocator.alloc(u8, relocation_size);
defer {
if (this.step == .failed) this.allocator.free(bytes);
}
const dangerouslyRunWithoutJitProtections = @import("./common.zig").dangerouslyRunWithoutJitProtections;
_ = dangerouslyRunWithoutJitProtections(TCC.Error!usize, TCC.State.relocate, .{ state, bytes.ptr }) catch {
this.fail("tcc_relocate returned a negative value");
return;
};
const symbol = state.getSymbol("JSFunctionCall") orelse {
this.fail("missing generated symbol in source code");
return;
};
this.step = .{
.compiled = .{
.ptr = symbol,
.buf = bytes,
},
};
return;
}
pub fn compileCallback(
this: *Function,
js_context: *jsc.JSGlobalObject,
js_function: JSValue,
is_threadsafe: bool,
) !void {
jsc.markBinding(@src());
var source_code = std.ArrayList(u8).init(this.allocator);
var source_code_writer = source_code.writer();
const ffi_wrapper = Bun__createFFICallbackFunction(js_context, js_function);
try this.printCallbackSourceCode(js_context, ffi_wrapper, &source_code_writer);
if (comptime Environment.isDebug and Environment.isPosix) {
debug_write: {
const fd = std.posix.open("/tmp/bun-ffi-callback-source.c", .{ .CREAT = true, .ACCMODE = .WRONLY }, 0o644) catch break :debug_write;
_ = std.posix.write(fd, source_code.items) catch break :debug_write;
std.posix.ftruncate(fd, source_code.items.len) catch break :debug_write;
std.posix.close(fd);
}
}
try source_code.append(0);
// defer source_code.deinit();
const state = TCC.State.init(Function, .{
.options = tcc_options,
.err = .{ .ctx = this, .handler = handleTCCError },
}, false) catch |e| switch (e) {
error.OutOfMemory => return error.TCCMissing,
// 1. .Memory is always a valid option, so InvalidOptions is
// impossible
// 2. other throwable functions arent called, so their errors
// aren't possible
else => unreachable,
};
this.state = state;
defer {
if (this.step == .failed) {
state.deinit();
this.state = null;
}
}
if (this.needsNapiEnv()) {
state.addSymbol("Bun__thisFFIModuleNapiEnv", js_context.makeNapiEnvForFFI()) catch {
this.fail("Failed to add NAPI env symbol");
return;
};
}
CompilerRT.define(state);
state.compileString(@ptrCast(source_code.items)) catch {
this.fail("Failed to compile source code");
return;
};
CompilerRT.inject(state);
_ = state.addSymbol(
"FFI_Callback_call",
// TODO: stage2 - make these ptrs
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,
3 => FFI_Callback_call_3,
4 => FFI_Callback_call_4,
5 => FFI_Callback_call_5,
6 => FFI_Callback_call_6,
7 => FFI_Callback_call_7,
else => FFI_Callback_call,
},
) catch {
this.fail("Failed to add FFI callback symbol");
return;
};
const relocation_size = state.relocate(null) catch {
this.fail("tcc_relocate returned a negative value");
return;
};
const bytes: []u8 = try this.allocator.alloc(u8, relocation_size);
defer {
if (this.step == .failed) {
this.allocator.free(bytes);
}
}
const dangerouslyRunWithoutJitProtections = @import("./common.zig").dangerouslyRunWithoutJitProtections;
_ = dangerouslyRunWithoutJitProtections(TCC.Error!usize, TCC.State.relocate, .{ state, bytes.ptr }) catch {
this.fail("tcc_relocate returned a negative value");
return;
};
const symbol = state.getSymbol("my_callback_function") orelse {
this.fail("missing generated symbol in source code");
return;
};
this.step = .{
.compiled = .{
.ptr = symbol,
.buf = bytes,
.js_function = js_function,
.js_context = js_context,
.ffi_callback_function_wrapper = ffi_wrapper,
},
};
}
pub fn printSourceCode(
this: *Function,
writer: anytype,
) !void {
if (this.arg_types.items.len > 0) {
try writer.writeAll("#define HAS_ARGUMENTS\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;
}
}
}
try writer.writeAll(ffiHeader());
// -- Generate the FFI function symbol
try writer.writeAll("/* --- The Function To Call */\n");
try this.return_type.typename(writer);
try writer.writeAll(" ");
try writer.writeAll(bun.asByteSlice(this.base_name.?));
try writer.writeAll("(");
var first = true;
for (this.arg_types.items, 0..) |arg, i| {
if (!first) {
try writer.writeAll(", ");
}
first = false;
try arg.paramTypename(writer);
try writer.print(" arg{d}", .{i});
}
try writer.writeAll(
\\);
\\
\\/* ---- Your Wrapper Function ---- */
\\ZIG_REPR_TYPE JSFunctionCall(void* JS_GLOBAL_OBJECT, void* callFrame) {
\\
);
if (this.needsHandleScope()) {
try writer.writeAll(
\\ void* handleScope = NapiHandleScope__open(&Bun__thisFFIModuleNapiEnv, false);
\\
);
}
if (this.arg_types.items.len > 0) {
try writer.writeAll(
\\ LOAD_ARGUMENTS_FROM_CALL_FRAME;
\\
);
for (this.arg_types.items, 0..) |arg, i| {
if (arg == .napi_env) {
try writer.print(
\\ napi_env arg{d} = (napi_env)&Bun__thisFFIModuleNapiEnv;
\\ argsPtr++;
\\
,
.{
i,
},
);
} else if (arg == .napi_value) {
try writer.print(
\\ EncodedJSValue arg{d} = {{ .asInt64 = *argsPtr++ }};
\\
,
.{
i,
},
);
} else if (arg.needsACastInC()) {
if (i < this.arg_types.items.len - 1) {
try writer.print(
\\ EncodedJSValue arg{d} = {{ .asInt64 = *argsPtr++ }};
\\
,
.{
i,
},
);
} else {
try writer.print(
\\ EncodedJSValue arg{d};
\\ arg{d}.asInt64 = *argsPtr;
\\
,
.{
i,
i,
},
);
}
} else {
if (i < this.arg_types.items.len - 1) {
try writer.print(
\\ int64_t arg{d} = *argsPtr++;
\\
,
.{
i,
},
);
} else {
try writer.print(
\\ int64_t arg{d} = *argsPtr;
\\
,
.{
i,
},
);
}
}
}
}
// try writer.writeAll(
// "(JSContext ctx, void* function, void* thisObject, size_t argumentCount, const EncodedJSValue arguments[], void* exception);\n\n",
// );
var arg_buf: [512]u8 = undefined;
try writer.writeAll(" ");
if (!(this.return_type == .void)) {
try this.return_type.typename(writer);
try writer.writeAll(" return_value = ");
}
try writer.print("{s}(", .{bun.asByteSlice(this.base_name.?)});
first = true;
arg_buf[0..3].* = "arg".*;
for (this.arg_types.items, 0..) |arg, i| {
if (!first) {
try writer.writeAll(", ");
}
first = false;
try writer.writeAll(" ");
const lengthBuf = std.fmt.bufPrintIntToSlice(arg_buf["arg".len..], i, 10, .lower, .{});
const argName = arg_buf[0 .. 3 + lengthBuf.len];
if (arg.needsACastInC()) {
try writer.print("{any}", .{arg.toC(argName)});
} else {
try writer.writeAll(argName);
}
}
try writer.writeAll(");\n");
if (!first) try writer.writeAll("\n");
try writer.writeAll(" ");
if (this.needsHandleScope()) {
try writer.writeAll(
\\ NapiHandleScope__close(&Bun__thisFFIModuleNapiEnv, handleScope);
\\
);
}
try writer.writeAll("return ");
if (!(this.return_type == .void)) {
try writer.print("{any}.asZigRepr", .{this.return_type.toJS("return_value")});
} else {
try writer.writeAll("ValueUndefined.asZigRepr");
}
try writer.writeAll(";\n}\n\n");
}
extern fn FFI_Callback_call(*anyopaque, usize, [*]JSValue) JSValue;
extern fn FFI_Callback_call_0(*anyopaque, usize, [*]JSValue) JSValue;
extern fn FFI_Callback_call_1(*anyopaque, usize, [*]JSValue) JSValue;
extern fn FFI_Callback_call_2(*anyopaque, usize, [*]JSValue) JSValue;
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;
pub fn printCallbackSourceCode(
this: *Function,
globalObject: ?*jsc.JSGlobalObject,
context_ptr: ?*anyopaque,
writer: anytype,
) !void {
{
const ptr = @intFromPtr(globalObject);
const fmt = bun.fmt.hexIntUpper(ptr);
try writer.print("#define JS_GLOBAL_OBJECT (void*)0x{any}ULL\n", .{fmt});
}
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;
}
}
}
try writer.writeAll(ffiHeader());
// -- Generate the FFI function symbol
try writer.writeAll("\n \n/* --- The Callback Function */\n");
var first = true;
try this.return_type.typename(writer);
try writer.writeAll(" my_callback_function");
try writer.writeAll("(");
for (this.arg_types.items, 0..) |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) {
var arg_buf: [512]u8 = undefined;
try writer.print(" ZIG_REPR_TYPE arguments[{d}];\n", .{this.arg_types.items.len});
arg_buf[0.."arg".len].* = "arg".*;
for (this.arg_types.items, 0..) |arg, i| {
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("arguments[{d}] = {any}.asZigRepr;\n", .{ i, arg.toJS(arg_name) });
}
}
try writer.writeAll(" ");
var inner_buf_: [372]u8 = undefined;
var inner_buf: []u8 = &.{};
{
const ptr = @intFromPtr(context_ptr);
const fmt = bun.fmt.hexIntUpper(ptr);
if (this.arg_types.items.len > 0) {
inner_buf = try std.fmt.bufPrint(
inner_buf_[1..],
"FFI_Callback_call((void*)0x{any}ULL, {d}, arguments)",
.{ fmt, this.arg_types.items.len },
);
} else {
inner_buf = try std.fmt.bufPrint(
inner_buf_[1..],
"FFI_Callback_call((void*)0x{any}ULL, 0, (ZIG_REPR_TYPE*)0)",
.{fmt},
);
}
}
if (this.return_type == .void) {
try writer.writeAll(inner_buf);
} else {
const len = inner_buf.len + 1;
inner_buf = inner_buf_[0..len];
inner_buf[0] = '_';
try writer.print("return {s}", .{this.return_type.toCExact(inner_buf)});
}
try writer.writeAll(";\n}\n\n");
}
pub fn needsNapiEnv(this: *const Function) bool {
for (this.arg_types.items) |arg| {
if (arg == .napi_env or arg == .napi_value) {
return true;
}
}
return false;
}
};

View File

@@ -0,0 +1,57 @@
const std = @import("std");
const bun = @import("bun");
const jsc = bun.jsc;
pub const StringArray = struct {
items: []const [:0]const u8 = &.{},
pub fn deinit(this: *StringArray) void {
for (this.items) |item| {
// Attempting to free an empty null-terminated slice will crash if it was a default value
bun.debugAssert(item.len > 0);
bun.default_allocator.free(@constCast(item));
}
if (this.items.len > 0)
bun.default_allocator.free(this.items);
}
pub fn fromJSArray(globalThis: *jsc.JSGlobalObject, value: jsc.JSValue, comptime property: []const u8) bun.JSError!StringArray {
var iter = try value.arrayIterator(globalThis);
var items = std.ArrayList([:0]const u8).init(bun.default_allocator);
while (try iter.next()) |val| {
if (!val.isString()) {
for (items.items) |item| {
bun.default_allocator.free(@constCast(item));
}
items.deinit();
return globalThis.throwInvalidArgumentTypeValue(property, "array of strings", val);
}
const str = try val.getZigString(globalThis);
if (str.isEmpty()) continue;
bun.handleOom(items.append(bun.handleOom(str.toOwnedSliceZ(bun.default_allocator))));
}
return .{ .items = items.items };
}
pub fn fromJSString(globalThis: *jsc.JSGlobalObject, value: jsc.JSValue, comptime property: []const u8) bun.JSError!StringArray {
if (value.isUndefined()) return .{};
if (!value.isString()) {
return globalThis.throwInvalidArgumentTypeValue(property, "array of strings", value);
}
const str = try value.getZigString(globalThis);
if (str.isEmpty()) return .{};
var items = std.ArrayList([:0]const u8).init(bun.default_allocator);
bun.handleOom(items.append(bun.handleOom(str.toOwnedSliceZ(bun.default_allocator))));
return .{ .items = items.items };
}
pub fn fromJS(globalThis: *jsc.JSGlobalObject, value: jsc.JSValue, comptime property: []const u8) bun.JSError!StringArray {
if (value.isArray()) {
return fromJSArray(globalThis, value, property);
}
return fromJSString(globalThis, value, property);
}
};

View File

@@ -0,0 +1,13 @@
const std = @import("std");
const bun = @import("bun");
const Function = @import("./function.zig").Function;
pub const SymbolsMap = struct {
map: bun.StringArrayHashMapUnmanaged(Function) = .{},
pub fn deinit(this: *SymbolsMap) void {
for (this.map.keys()) |key| {
bun.default_allocator.free(@constCast(key));
}
this.map.clearAndFree(bun.default_allocator);
}
};