mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
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:
File diff suppressed because it is too large
Load Diff
324
src/bun.js/api/ffi/abi_type.zig
Normal file
324
src/bun.js/api/ffi/abi_type.zig
Normal 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",
|
||||
};
|
||||
}
|
||||
};
|
||||
61
src/bun.js/api/ffi/common.zig
Normal file
61
src/bun.js/api/ffi/common.zig
Normal 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;
|
||||
}
|
||||
};
|
||||
467
src/bun.js/api/ffi/compile.zig
Normal file
467
src/bun.js/api/ffi/compile.zig
Normal 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 = "";
|
||||
}
|
||||
};
|
||||
112
src/bun.js/api/ffi/compiler_rt.zig
Normal file
112
src/bun.js/api/ffi/compiler_rt.zig
Normal 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;
|
||||
}
|
||||
};
|
||||
608
src/bun.js/api/ffi/function.zig
Normal file
608
src/bun.js/api/ffi/function.zig
Normal 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;
|
||||
}
|
||||
};
|
||||
57
src/bun.js/api/ffi/string_array.zig
Normal file
57
src/bun.js/api/ffi/string_array.zig
Normal 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);
|
||||
}
|
||||
};
|
||||
13
src/bun.js/api/ffi/symbols_map.zig
Normal file
13
src/bun.js/api/ffi/symbols_map.zig
Normal 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);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user