mirror of
https://github.com/oven-sh/bun
synced 2026-02-18 06:41:50 +00:00
2455 lines
97 KiB
Zig
2455 lines
97 KiB
Zig
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.
|
|
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).
|
|
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(bun.callmod_inline, func, args);
|
|
}
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
pub const FFI = struct {
|
|
pub const js = jsc.Codegen.JSFFI;
|
|
pub const toJS = js.toJS;
|
|
pub const fromJS = js.fromJS;
|
|
pub const fromJSDirect = js.fromJSDirect;
|
|
|
|
dylib: ?std.DynLib = null,
|
|
relocated_bytes_to_free: ?[]u8 = null,
|
|
functions: bun.StringArrayHashMapUnmanaged(Function) = .{},
|
|
closed: bool = false,
|
|
shared_state: ?*TCC.State = null,
|
|
|
|
pub fn finalize(_: *FFI) callconv(.c) void {}
|
|
|
|
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.env_var.BUN_TCC_OPTIONS.get()) |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.env_var.SDKROOT.get() 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.
|
|
|
|
_ = 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("{f} 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 = "";
|
|
}
|
|
};
|
|
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);
|
|
}
|
|
};
|
|
|
|
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.array_list.Managed([: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.array_list.Managed([: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);
|
|
}
|
|
};
|
|
|
|
pub fn Bun__FFI__cc(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue {
|
|
const arguments = callframe.arguments_old(1).slice();
|
|
if (arguments.len == 0 or !arguments[0].isObject()) {
|
|
return globalThis.throwInvalidArguments("Expected object", .{});
|
|
}
|
|
const allocator = bun.default_allocator;
|
|
|
|
// Step 1. compile the user's code
|
|
|
|
const object = arguments[0];
|
|
|
|
var compile_c = CompileC{};
|
|
defer {
|
|
if (globalThis.hasException()) {
|
|
compile_c.deinit();
|
|
}
|
|
}
|
|
|
|
const symbols_object: JSValue = try object.getOwn(globalThis, "symbols") orelse .js_undefined;
|
|
if (!globalThis.hasException() and (symbols_object == .zero or !symbols_object.isObject())) {
|
|
return globalThis.throwInvalidArgumentTypeValue("symbols", "object", symbols_object);
|
|
}
|
|
|
|
if (globalThis.hasException()) {
|
|
return error.JSError;
|
|
}
|
|
|
|
// SAFETY: already checked that symbols_object is an object
|
|
if (try generateSymbols(globalThis, allocator, &compile_c.symbols.map, symbols_object.getObject().?)) |val| {
|
|
if (val != .zero and !globalThis.hasException())
|
|
return globalThis.throwValue(val);
|
|
return error.JSError;
|
|
}
|
|
|
|
if (compile_c.symbols.map.count() == 0) {
|
|
return globalThis.throw("Expected at least one exported symbol", .{});
|
|
}
|
|
|
|
if (try object.getOwn(globalThis, "library")) |library_value| {
|
|
compile_c.libraries = try StringArray.fromJS(globalThis, library_value, "library");
|
|
}
|
|
|
|
if (globalThis.hasException()) {
|
|
return error.JSError;
|
|
}
|
|
|
|
if (try object.getTruthy(globalThis, "flags")) |flags_value| {
|
|
if (flags_value.isArray()) {
|
|
var iter = try flags_value.arrayIterator(globalThis);
|
|
|
|
var flags = std.array_list.Managed(u8).init(allocator);
|
|
defer flags.deinit();
|
|
bun.handleOom(flags.appendSlice(CompileC.default_tcc_options));
|
|
|
|
while (try iter.next()) |value| {
|
|
if (!value.isString()) {
|
|
return globalThis.throwInvalidArgumentTypeValue("flags", "array of strings", value);
|
|
}
|
|
const slice = try value.toSlice(globalThis, allocator);
|
|
if (slice.len == 0) continue;
|
|
defer slice.deinit();
|
|
bun.handleOom(flags.append(' '));
|
|
bun.handleOom(flags.appendSlice(slice.slice()));
|
|
}
|
|
bun.handleOom(flags.append(0));
|
|
compile_c.flags = flags.items[0 .. flags.items.len - 1 :0];
|
|
flags = std.array_list.Managed(u8).init(allocator);
|
|
} else {
|
|
if (!flags_value.isString()) {
|
|
return globalThis.throwInvalidArgumentTypeValue("flags", "string", flags_value);
|
|
}
|
|
|
|
const str = try flags_value.getZigString(globalThis);
|
|
if (!str.isEmpty()) {
|
|
compile_c.flags = bun.handleOom(str.toOwnedSliceZ(allocator));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (globalThis.hasException()) {
|
|
return error.JSError;
|
|
}
|
|
|
|
if (try object.getTruthy(globalThis, "define")) |define_value| {
|
|
if (define_value.getObject()) |define_obj| {
|
|
const Iter = jsc.JSPropertyIterator(.{ .include_value = true, .skip_empty_name = true });
|
|
var iter = try Iter.init(globalThis, define_obj);
|
|
defer iter.deinit();
|
|
while (try iter.next()) |entry| {
|
|
const key = bun.handleOom(entry.toOwnedSliceZ(allocator));
|
|
var owned_value: [:0]const u8 = "";
|
|
if (!iter.value.isUndefinedOrNull()) {
|
|
if (iter.value.isString()) {
|
|
const value = try iter.value.getZigString(globalThis);
|
|
if (value.len > 0) {
|
|
owned_value = bun.handleOom(value.toOwnedSliceZ(allocator));
|
|
}
|
|
}
|
|
}
|
|
if (globalThis.hasException()) {
|
|
allocator.free(key);
|
|
return error.JSError;
|
|
}
|
|
|
|
bun.handleOom(compile_c.define.append(allocator, .{ key, owned_value }));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (globalThis.hasException()) {
|
|
return error.JSError;
|
|
}
|
|
|
|
if (try object.getTruthy(globalThis, "include")) |include_value| {
|
|
compile_c.include_dirs = try StringArray.fromJS(globalThis, include_value, "include");
|
|
}
|
|
|
|
if (globalThis.hasException()) {
|
|
return error.JSError;
|
|
}
|
|
|
|
if (try object.getOwn(globalThis, "source")) |source_value| {
|
|
if (source_value.isArray()) {
|
|
compile_c.source = .{ .files = .{} };
|
|
var iter = try source_value.arrayIterator(globalThis);
|
|
while (try iter.next()) |value| {
|
|
if (!value.isString()) {
|
|
return globalThis.throwInvalidArgumentTypeValue("source", "array of strings", value);
|
|
}
|
|
try compile_c.source.files.append(bun.default_allocator, try (try value.getZigString(globalThis)).toOwnedSliceZ(bun.default_allocator));
|
|
}
|
|
} else if (!source_value.isString()) {
|
|
return globalThis.throwInvalidArgumentTypeValue("source", "string", source_value);
|
|
} else {
|
|
const source_path = try (try source_value.getZigString(globalThis)).toOwnedSliceZ(bun.default_allocator);
|
|
compile_c.source.file = source_path;
|
|
}
|
|
}
|
|
|
|
if (globalThis.hasException()) {
|
|
return error.JSError;
|
|
}
|
|
|
|
// Now we compile the code with tinycc.
|
|
var tcc_state: ?*TCC.State, var bytes_to_free_on_error = compile_c.compile(globalThis) catch |err| {
|
|
switch (err) {
|
|
error.DeferredErrors => {
|
|
var combined = std.array_list.Managed(u8).init(bun.default_allocator);
|
|
defer combined.deinit();
|
|
var writer = combined.writer();
|
|
bun.handleOom(writer.print("{d} errors while compiling {s}\n", .{ compile_c.deferred_errors.items.len, if (compile_c.current_file_for_errors.len > 0) compile_c.current_file_for_errors else compile_c.source.first() }));
|
|
|
|
for (compile_c.deferred_errors.items) |deferred_error| {
|
|
bun.handleOom(writer.print("{s}\n", .{deferred_error}));
|
|
}
|
|
|
|
return globalThis.throw("{s}", .{combined.items});
|
|
},
|
|
error.JSError => |e| return e,
|
|
error.OutOfMemory => |e| return e,
|
|
error.JSTerminated => |e| return e,
|
|
}
|
|
};
|
|
defer {
|
|
if (tcc_state) |state| state.deinit();
|
|
|
|
// TODO: upgrade tinycc because they improved the way memory management works for this
|
|
// we are unable to free memory safely in certain cases here.
|
|
}
|
|
|
|
const napi_env = makeNapiEnvIfNeeded(compile_c.symbols.map.values(), globalThis);
|
|
|
|
var obj = jsc.JSValue.createEmptyObject(globalThis, compile_c.symbols.map.count());
|
|
for (compile_c.symbols.map.values()) |*function| {
|
|
const function_name = function.base_name.?;
|
|
|
|
function.compile(napi_env) catch |err| {
|
|
if (!globalThis.hasException()) {
|
|
const ret = globalThis.toInvalidArguments("{s} when translating symbol \"{s}\"", .{
|
|
@errorName(err),
|
|
function_name,
|
|
});
|
|
return globalThis.throwValue(ret);
|
|
}
|
|
return error.JSError;
|
|
};
|
|
switch (function.step) {
|
|
.failed => |err| {
|
|
const res = ZigString.init(err.msg).toErrorInstance(globalThis);
|
|
return globalThis.throwValue(res);
|
|
},
|
|
.pending => {
|
|
return globalThis.throw("Failed to compile (nothing happend!)", .{});
|
|
},
|
|
.compiled => |*compiled| {
|
|
const str = ZigString.init(bun.asByteSlice(function_name));
|
|
const cb = jsc.host_fn.NewRuntimeFunction(
|
|
globalThis,
|
|
&str,
|
|
@as(u32, @intCast(function.arg_types.items.len)),
|
|
bun.cast(*const jsc.JSHostFn, compiled.ptr),
|
|
false,
|
|
true,
|
|
function.symbol_from_dynamic_library,
|
|
);
|
|
compiled.js_function = cb;
|
|
obj.put(globalThis, &str, cb);
|
|
},
|
|
}
|
|
}
|
|
|
|
// TODO: pub const new = bun.TrivialNew(FFI)
|
|
var lib = bun.handleOom(bun.default_allocator.create(FFI));
|
|
lib.* = .{
|
|
.dylib = null,
|
|
.shared_state = tcc_state,
|
|
.functions = compile_c.symbols.map,
|
|
.relocated_bytes_to_free = bytes_to_free_on_error,
|
|
};
|
|
tcc_state = null;
|
|
bytes_to_free_on_error = "";
|
|
compile_c.symbols = .{};
|
|
|
|
const js_object = lib.toJS(globalThis);
|
|
jsc.Codegen.JSFFI.symbolsValueSetCached(js_object, globalThis, obj);
|
|
return js_object;
|
|
}
|
|
|
|
pub fn closeCallback(globalThis: *JSGlobalObject, ctx: JSValue) JSValue {
|
|
var function: *Function = @ptrFromInt(ctx.asPtrAddress());
|
|
function.deinit(globalThis);
|
|
return .js_undefined;
|
|
}
|
|
|
|
pub fn callback(globalThis: *JSGlobalObject, interface: jsc.JSValue, js_callback: jsc.JSValue) bun.JSError!JSValue {
|
|
jsc.markBinding(@src());
|
|
if (!interface.isObject()) {
|
|
return globalThis.toInvalidArguments("Expected object", .{});
|
|
}
|
|
|
|
if (js_callback.isEmptyOrUndefinedOrNull() or !js_callback.isCallable()) {
|
|
return globalThis.toInvalidArguments("Expected callback function", .{});
|
|
}
|
|
|
|
const allocator = VirtualMachine.get().allocator;
|
|
var function: Function = .{ .allocator = allocator };
|
|
var func = &function;
|
|
|
|
if (generateSymbolForFunction(globalThis, allocator, interface, func) catch ZigString.init("Out of memory").toErrorInstance(globalThis)) |val| {
|
|
return val;
|
|
}
|
|
|
|
// TODO: WeakRefHandle that automatically frees it?
|
|
func.base_name = "";
|
|
js_callback.ensureStillAlive();
|
|
|
|
func.compileCallback(globalThis, js_callback, func.threadsafe) catch return ZigString.init("Out of memory").toErrorInstance(globalThis);
|
|
switch (func.step) {
|
|
.failed => |err| {
|
|
const message = ZigString.init(err.msg).toErrorInstance(globalThis);
|
|
|
|
func.deinit(globalThis);
|
|
|
|
return message;
|
|
},
|
|
.pending => {
|
|
func.deinit(globalThis);
|
|
return ZigString.init("Failed to compile, but not sure why. Please report this bug").toErrorInstance(globalThis);
|
|
},
|
|
.compiled => {
|
|
const function_ = bun.default_allocator.create(Function) catch unreachable;
|
|
function_.* = func.*;
|
|
return JSValue.createObject2(
|
|
globalThis,
|
|
ZigString.static("ptr"),
|
|
ZigString.static("ctx"),
|
|
jsc.JSValue.fromPtrAddress(@intFromPtr(function_.step.compiled.ptr)),
|
|
jsc.JSValue.fromPtrAddress(@intFromPtr(function_)),
|
|
);
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn close(
|
|
this: *FFI,
|
|
globalThis: *jsc.JSGlobalObject,
|
|
_: *jsc.CallFrame,
|
|
) bun.JSError!JSValue {
|
|
jsc.markBinding(@src());
|
|
if (this.closed) {
|
|
return .js_undefined;
|
|
}
|
|
this.closed = true;
|
|
if (this.dylib) |*dylib| {
|
|
dylib.close();
|
|
this.dylib = null;
|
|
}
|
|
|
|
if (this.shared_state) |state| {
|
|
this.shared_state = null;
|
|
state.deinit();
|
|
}
|
|
|
|
const allocator = VirtualMachine.get().allocator;
|
|
|
|
for (this.functions.values()) |*val| {
|
|
val.deinit(globalThis);
|
|
}
|
|
this.functions.deinit(allocator);
|
|
|
|
// NOTE: `relocated_bytes_to_free` points to a memory region that was
|
|
// relocated by tinycc. Attempts to free it will cause a bus error,
|
|
// even if jit protections are disabled.
|
|
// if (this.relocated_bytes_to_free) |relocated_bytes_to_free| {
|
|
// this.relocated_bytes_to_free = null;
|
|
// bun.default_allocator.free(relocated_bytes_to_free);
|
|
// }
|
|
|
|
return .js_undefined;
|
|
}
|
|
|
|
pub fn printCallback(global: *JSGlobalObject, object: jsc.JSValue) JSValue {
|
|
jsc.markBinding(@src());
|
|
const allocator = VirtualMachine.get().allocator;
|
|
|
|
if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
|
|
return global.toInvalidArguments("Expected an object", .{});
|
|
}
|
|
|
|
var function: Function = .{ .allocator = allocator };
|
|
if (generateSymbolForFunction(global, allocator, object, &function) catch ZigString.init("Out of memory").toErrorInstance(global)) |val| {
|
|
return val;
|
|
}
|
|
|
|
var arraylist = std.array_list.Managed(u8).init(allocator);
|
|
defer arraylist.deinit();
|
|
var writer = arraylist.writer();
|
|
|
|
function.base_name = "my_callback_function";
|
|
|
|
function.printCallbackSourceCode(null, null, &writer) catch {
|
|
return ZigString.init("Error while printing code").toErrorInstance(global);
|
|
};
|
|
return ZigString.init(arraylist.items).toJS(global);
|
|
}
|
|
|
|
pub fn print(global: *JSGlobalObject, object: jsc.JSValue, is_callback_val: ?jsc.JSValue) bun.JSError!JSValue {
|
|
const allocator = bun.default_allocator;
|
|
if (is_callback_val) |is_callback| {
|
|
if (is_callback.toBoolean()) {
|
|
return printCallback(global, object);
|
|
}
|
|
}
|
|
|
|
if (object.isEmptyOrUndefinedOrNull()) return invalidOptionsArg(global);
|
|
const obj = object.getObject() orelse return invalidOptionsArg(global);
|
|
|
|
var symbols = bun.StringArrayHashMapUnmanaged(Function){};
|
|
if (generateSymbols(global, bun.default_allocator, &symbols, obj) catch jsc.JSValue.zero) |val| {
|
|
// an error while validating symbols
|
|
for (symbols.keys()) |key| {
|
|
allocator.free(@constCast(key));
|
|
}
|
|
symbols.clearAndFree(allocator);
|
|
return val;
|
|
}
|
|
jsc.markBinding(@src());
|
|
var strs = bun.handleOom(std.array_list.Managed(bun.String).initCapacity(allocator, symbols.count()));
|
|
defer {
|
|
for (strs.items) |str| {
|
|
str.deref();
|
|
}
|
|
strs.deinit();
|
|
}
|
|
for (symbols.values()) |*function| {
|
|
var arraylist = std.array_list.Managed(u8).init(allocator);
|
|
var writer = arraylist.writer();
|
|
function.printSourceCode(&writer) catch {
|
|
// an error while generating source code
|
|
for (symbols.keys()) |key| {
|
|
allocator.free(@constCast(key));
|
|
}
|
|
for (symbols.values()) |*function_| {
|
|
function_.arg_types.deinit(allocator);
|
|
}
|
|
|
|
symbols.clearAndFree(allocator);
|
|
return ZigString.init("Error while printing code").toErrorInstance(global);
|
|
};
|
|
strs.appendAssumeCapacity(bun.String.cloneUTF8(arraylist.items));
|
|
}
|
|
|
|
const ret = try bun.String.toJSArray(global, strs.items);
|
|
|
|
for (symbols.keys()) |key| {
|
|
allocator.free(@constCast(key));
|
|
}
|
|
for (symbols.values()) |*function_| {
|
|
function_.arg_types.deinit(allocator);
|
|
if (function_.step == .compiled) {
|
|
allocator.free(function_.step.compiled.buf);
|
|
}
|
|
}
|
|
symbols.clearAndFree(allocator);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/// Creates an Exception object indicating that options object is invalid.
|
|
/// The exception is not thrown on the VM.
|
|
fn invalidOptionsArg(global: *JSGlobalObject) JSValue {
|
|
return global.toInvalidArguments("Expected an options object with symbol names", .{});
|
|
}
|
|
|
|
pub fn open(global: *JSGlobalObject, name_str: ZigString, object_value: jsc.JSValue) jsc.JSValue {
|
|
jsc.markBinding(@src());
|
|
const vm = VirtualMachine.get();
|
|
var name_slice = name_str.toSlice(bun.default_allocator);
|
|
defer name_slice.deinit();
|
|
|
|
if (object_value.isEmptyOrUndefinedOrNull()) return invalidOptionsArg(global);
|
|
const object = object_value.getObject() orelse return invalidOptionsArg(global);
|
|
|
|
var filepath_buf: bun.PathBuffer = undefined;
|
|
const name = brk: {
|
|
if (jsc.ModuleLoader.resolveEmbeddedFile(
|
|
vm,
|
|
name_slice.slice(),
|
|
switch (Environment.os) {
|
|
.linux => "so",
|
|
.mac => "dylib",
|
|
.windows => "dll",
|
|
else => @compileError("TODO"),
|
|
},
|
|
)) |resolved| {
|
|
@memcpy(filepath_buf[0..resolved.len], resolved);
|
|
filepath_buf[resolved.len] = 0;
|
|
break :brk filepath_buf[0..resolved.len];
|
|
}
|
|
|
|
break :brk name_slice.slice();
|
|
};
|
|
|
|
if (name.len == 0) {
|
|
return global.toInvalidArguments("Invalid library name", .{});
|
|
}
|
|
|
|
var symbols = bun.StringArrayHashMapUnmanaged(Function){};
|
|
if (generateSymbols(global, bun.default_allocator, &symbols, object) catch jsc.JSValue.zero) |val| {
|
|
// an error while validating symbols
|
|
for (symbols.keys()) |key| {
|
|
bun.default_allocator.free(@constCast(key));
|
|
}
|
|
symbols.clearAndFree(bun.default_allocator);
|
|
return val;
|
|
}
|
|
if (symbols.count() == 0) {
|
|
return global.toInvalidArguments("Expected at least one symbol", .{});
|
|
}
|
|
|
|
var dylib: std.DynLib = brk: {
|
|
// First try using the name directly
|
|
break :brk std.DynLib.open(name) catch {
|
|
const backup_name = Fs.FileSystem.instance.abs(&[1]string{name});
|
|
// if that fails, try resolving the filepath relative to the current working directory
|
|
break :brk std.DynLib.open(backup_name) catch {
|
|
// Then, if that fails, report an error with the library name and system error
|
|
const dlerror_buf = getDlError(bun.default_allocator) catch null;
|
|
defer if (dlerror_buf) |buf| bun.default_allocator.free(buf);
|
|
const dlerror_msg = dlerror_buf orelse "unknown error";
|
|
|
|
const msg = bun.handleOom(std.fmt.allocPrint(
|
|
bun.default_allocator,
|
|
"Failed to open library \"{s}\": {s}",
|
|
.{ name, dlerror_msg },
|
|
));
|
|
defer bun.default_allocator.free(msg);
|
|
const system_error = jsc.SystemError{
|
|
.code = bun.String.cloneUTF8(@tagName(.ERR_DLOPEN_FAILED)),
|
|
.message = bun.String.cloneUTF8(msg),
|
|
.syscall = bun.String.cloneUTF8("dlopen"),
|
|
};
|
|
return system_error.toErrorInstance(global);
|
|
};
|
|
};
|
|
};
|
|
|
|
var size = symbols.values().len;
|
|
if (size >= 63) {
|
|
size = 0;
|
|
}
|
|
var obj = jsc.JSValue.createEmptyObject(global, size);
|
|
obj.protect();
|
|
defer obj.unprotect();
|
|
|
|
const napi_env = makeNapiEnvIfNeeded(symbols.values(), global);
|
|
|
|
for (symbols.values()) |*function| {
|
|
const function_name = function.base_name.?;
|
|
|
|
// optional if the user passed "ptr"
|
|
if (function.symbol_from_dynamic_library == null) {
|
|
const resolved_symbol = dylib.lookup(*anyopaque, function_name) orelse {
|
|
const ret = global.toInvalidArguments("Symbol \"{s}\" not found in \"{s}\"", .{ bun.asByteSlice(function_name), name });
|
|
for (symbols.values()) |*value| {
|
|
bun.default_allocator.free(@constCast(bun.asByteSlice(value.base_name.?)));
|
|
value.arg_types.clearAndFree(bun.default_allocator);
|
|
}
|
|
symbols.clearAndFree(bun.default_allocator);
|
|
dylib.close();
|
|
return ret;
|
|
};
|
|
|
|
function.symbol_from_dynamic_library = resolved_symbol;
|
|
}
|
|
|
|
function.compile(napi_env) catch |err| {
|
|
const ret = global.toInvalidArguments("{s} when compiling symbol \"{s}\" in \"{s}\"", .{
|
|
bun.asByteSlice(@errorName(err)),
|
|
bun.asByteSlice(function_name),
|
|
name,
|
|
});
|
|
for (symbols.values()) |*value| {
|
|
value.deinit(global);
|
|
}
|
|
symbols.clearAndFree(bun.default_allocator);
|
|
dylib.close();
|
|
return ret;
|
|
};
|
|
switch (function.step) {
|
|
.failed => |err| {
|
|
defer for (symbols.values()) |*other_function| {
|
|
other_function.deinit(global);
|
|
};
|
|
|
|
const res = ZigString.init(err.msg).toErrorInstance(global);
|
|
symbols.clearAndFree(bun.default_allocator);
|
|
dylib.close();
|
|
return res;
|
|
},
|
|
.pending => {
|
|
for (symbols.values()) |*other_function| {
|
|
other_function.deinit(global);
|
|
}
|
|
symbols.clearAndFree(bun.default_allocator);
|
|
dylib.close();
|
|
return ZigString.init("Failed to compile (nothing happend!)").toErrorInstance(global);
|
|
},
|
|
.compiled => |*compiled| {
|
|
const str = ZigString.init(bun.asByteSlice(function_name));
|
|
const cb = jsc.host_fn.NewRuntimeFunction(
|
|
global,
|
|
&str,
|
|
@as(u32, @intCast(function.arg_types.items.len)),
|
|
bun.cast(*const jsc.JSHostFn, compiled.ptr),
|
|
false,
|
|
true,
|
|
function.symbol_from_dynamic_library,
|
|
);
|
|
compiled.js_function = cb;
|
|
obj.put(global, &str, cb);
|
|
},
|
|
}
|
|
}
|
|
|
|
const lib = bun.new(FFI, .{
|
|
.dylib = dylib,
|
|
.functions = symbols,
|
|
});
|
|
|
|
const js_object = lib.toJS(global);
|
|
jsc.Codegen.JSFFI.symbolsValueSetCached(js_object, global, obj);
|
|
return js_object;
|
|
}
|
|
|
|
pub fn getSymbols(_: *FFI, _: *jsc.JSGlobalObject) jsc.JSValue {
|
|
// This shouldn't be called. The cachedValue is what should be called.
|
|
return .js_undefined;
|
|
}
|
|
|
|
pub fn linkSymbols(global: *JSGlobalObject, object_value: jsc.JSValue) jsc.JSValue {
|
|
jsc.markBinding(@src());
|
|
const allocator = VirtualMachine.get().allocator;
|
|
|
|
if (object_value.isEmptyOrUndefinedOrNull()) return invalidOptionsArg(global);
|
|
const object = object_value.getObject() orelse return invalidOptionsArg(global);
|
|
|
|
var symbols = bun.StringArrayHashMapUnmanaged(Function){};
|
|
if (generateSymbols(global, allocator, &symbols, object) catch jsc.JSValue.zero) |val| {
|
|
// an error while validating symbols
|
|
for (symbols.keys()) |key| {
|
|
allocator.free(@constCast(key));
|
|
}
|
|
symbols.clearAndFree(allocator);
|
|
return val;
|
|
}
|
|
if (symbols.count() == 0) {
|
|
return global.toInvalidArguments("Expected at least one symbol", .{});
|
|
}
|
|
|
|
var obj = JSValue.createEmptyObject(global, symbols.count());
|
|
obj.ensureStillAlive();
|
|
defer obj.ensureStillAlive();
|
|
|
|
const napi_env = makeNapiEnvIfNeeded(symbols.values(), global);
|
|
|
|
for (symbols.values()) |*function| {
|
|
const function_name = function.base_name.?;
|
|
|
|
if (function.symbol_from_dynamic_library == null) {
|
|
const ret = global.toInvalidArguments("Symbol \"{s}\" is missing a \"ptr\" field. When using linkSymbols() or CFunction(), you must provide a \"ptr\" field with the memory address of the native function.", .{bun.asByteSlice(function_name)});
|
|
for (symbols.values()) |*value| {
|
|
allocator.free(@constCast(bun.asByteSlice(value.base_name.?)));
|
|
value.arg_types.clearAndFree(allocator);
|
|
}
|
|
symbols.clearAndFree(allocator);
|
|
return ret;
|
|
}
|
|
|
|
function.compile(napi_env) catch |err| {
|
|
const ret = global.toInvalidArguments("{s} when compiling symbol \"{s}\"", .{
|
|
bun.asByteSlice(@errorName(err)),
|
|
bun.asByteSlice(function_name),
|
|
});
|
|
for (symbols.values()) |*value| {
|
|
value.deinit(global);
|
|
}
|
|
symbols.clearAndFree(allocator);
|
|
return ret;
|
|
};
|
|
switch (function.step) {
|
|
.failed => |err| {
|
|
for (symbols.values()) |*value| {
|
|
allocator.free(@constCast(bun.asByteSlice(value.base_name.?)));
|
|
value.arg_types.clearAndFree(allocator);
|
|
}
|
|
|
|
const res = ZigString.init(err.msg).toErrorInstance(global);
|
|
function.deinit(global);
|
|
symbols.clearAndFree(allocator);
|
|
return res;
|
|
},
|
|
.pending => {
|
|
for (symbols.values()) |*value| {
|
|
allocator.free(@constCast(bun.asByteSlice(value.base_name.?)));
|
|
value.arg_types.clearAndFree(allocator);
|
|
}
|
|
symbols.clearAndFree(allocator);
|
|
return ZigString.static("Failed to compile (nothing happend!)").toErrorInstance(global);
|
|
},
|
|
.compiled => |*compiled| {
|
|
const name = &ZigString.init(bun.asByteSlice(function_name));
|
|
|
|
const cb = jsc.host_fn.NewRuntimeFunction(
|
|
global,
|
|
name,
|
|
@as(u32, @intCast(function.arg_types.items.len)),
|
|
bun.cast(*jsc.JSHostFn, compiled.ptr),
|
|
false,
|
|
true,
|
|
function.symbol_from_dynamic_library,
|
|
);
|
|
compiled.js_function = cb;
|
|
|
|
obj.put(global, name, cb);
|
|
},
|
|
}
|
|
}
|
|
|
|
const lib = bun.new(FFI, .{
|
|
.dylib = null,
|
|
.functions = symbols,
|
|
});
|
|
|
|
const js_object = lib.toJS(global);
|
|
jsc.Codegen.JSFFI.symbolsValueSetCached(js_object, global, obj);
|
|
return js_object;
|
|
}
|
|
pub fn generateSymbolForFunction(global: *JSGlobalObject, allocator: std.mem.Allocator, value: jsc.JSValue, function: *Function) bun.JSError!?JSValue {
|
|
jsc.markBinding(@src());
|
|
|
|
var abi_types = std.ArrayListUnmanaged(ABIType){};
|
|
|
|
if (try value.getOwn(global, "args")) |args| {
|
|
if (args.isEmptyOrUndefinedOrNull() or !args.jsType().isArray()) {
|
|
return ZigString.static("Expected an object with \"args\" as an array").toErrorInstance(global);
|
|
}
|
|
|
|
var array = try args.arrayIterator(global);
|
|
|
|
try abi_types.ensureTotalCapacityPrecise(allocator, array.len);
|
|
while (try array.next()) |val| {
|
|
if (val.isEmptyOrUndefinedOrNull()) {
|
|
abi_types.clearAndFree(allocator);
|
|
return ZigString.static("param must be a string (type name) or number").toErrorInstance(global);
|
|
}
|
|
|
|
if (val.isAnyInt()) {
|
|
const int = val.to(i32);
|
|
switch (int) {
|
|
0...ABIType.max => {
|
|
abi_types.appendAssumeCapacity(@as(ABIType, @enumFromInt(int)));
|
|
continue;
|
|
},
|
|
else => {
|
|
abi_types.clearAndFree(allocator);
|
|
return ZigString.static("invalid ABI type").toErrorInstance(global);
|
|
},
|
|
}
|
|
}
|
|
|
|
if (!val.jsType().isStringLike()) {
|
|
abi_types.clearAndFree(allocator);
|
|
return ZigString.static("param must be a string (type name) or number").toErrorInstance(global);
|
|
}
|
|
|
|
var type_name = try val.toSlice(global, allocator);
|
|
defer type_name.deinit();
|
|
abi_types.appendAssumeCapacity(ABIType.label.get(type_name.slice()) orelse {
|
|
abi_types.clearAndFree(allocator);
|
|
return global.toTypeError(.INVALID_ARG_VALUE, "Unknown type {s}", .{type_name.slice()});
|
|
});
|
|
}
|
|
}
|
|
// var function
|
|
var return_type = ABIType.void;
|
|
|
|
var threadsafe = false;
|
|
|
|
if (try value.getTruthy(global, "threadsafe")) |threadsafe_value| {
|
|
threadsafe = threadsafe_value.toBoolean();
|
|
}
|
|
|
|
if (try value.getTruthy(global, "returns")) |ret_value| brk: {
|
|
if (ret_value.isAnyInt()) {
|
|
const int = ret_value.toInt32();
|
|
switch (int) {
|
|
0...ABIType.max => {
|
|
return_type = @as(ABIType, @enumFromInt(int));
|
|
break :brk;
|
|
},
|
|
else => {
|
|
abi_types.clearAndFree(allocator);
|
|
return ZigString.static("invalid ABI type").toErrorInstance(global);
|
|
},
|
|
}
|
|
}
|
|
|
|
var ret_slice = try ret_value.toSlice(global, allocator);
|
|
defer ret_slice.deinit();
|
|
return_type = ABIType.label.get(ret_slice.slice()) orelse {
|
|
abi_types.clearAndFree(allocator);
|
|
return global.toTypeError(.INVALID_ARG_VALUE, "Unknown return type {s}", .{ret_slice.slice()});
|
|
};
|
|
}
|
|
|
|
if (return_type == ABIType.napi_env) {
|
|
abi_types.clearAndFree(allocator);
|
|
return ZigString.static("Cannot return napi_env to JavaScript").toErrorInstance(global);
|
|
}
|
|
|
|
if (return_type == .buffer) {
|
|
abi_types.clearAndFree(allocator);
|
|
return ZigString.static("Cannot return a buffer to JavaScript (since byteLength and byteOffset are unknown)").toErrorInstance(global);
|
|
}
|
|
|
|
if (function.threadsafe and return_type != ABIType.void) {
|
|
abi_types.clearAndFree(allocator);
|
|
return ZigString.static("Threadsafe functions must return void").toErrorInstance(global);
|
|
}
|
|
|
|
function.* = Function{
|
|
.base_name = null,
|
|
.arg_types = abi_types,
|
|
.return_type = return_type,
|
|
.threadsafe = threadsafe,
|
|
.allocator = allocator,
|
|
};
|
|
|
|
if (try value.get(global, "ptr")) |ptr| {
|
|
if (ptr.isNumber()) {
|
|
const num = ptr.asPtrAddress();
|
|
if (num > 0)
|
|
function.symbol_from_dynamic_library = @as(*anyopaque, @ptrFromInt(num));
|
|
} else {
|
|
const num = ptr.toUInt64NoTruncate();
|
|
if (num > 0) {
|
|
function.symbol_from_dynamic_library = @as(*anyopaque, @ptrFromInt(num));
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn generateSymbols(global: *JSGlobalObject, allocator: Allocator, symbols: *bun.StringArrayHashMapUnmanaged(Function), object: *jsc.JSObject) bun.JSError!?JSValue {
|
|
jsc.markBinding(@src());
|
|
|
|
var symbols_iter = try jsc.JSPropertyIterator(.{
|
|
.skip_empty_name = true,
|
|
|
|
.include_value = true,
|
|
}).init(global, object);
|
|
defer symbols_iter.deinit();
|
|
|
|
try symbols.ensureTotalCapacity(allocator, symbols_iter.len);
|
|
|
|
while (try symbols_iter.next()) |prop| {
|
|
const value = symbols_iter.value;
|
|
|
|
if (value.isEmptyOrUndefinedOrNull()) {
|
|
return global.toTypeError(.INVALID_ARG_VALUE, "Expected an object for key \"{f}\"", .{prop});
|
|
}
|
|
|
|
var function: Function = .{ .allocator = allocator };
|
|
if (try generateSymbolForFunction(global, allocator, value, &function)) |val| {
|
|
return val;
|
|
}
|
|
function.base_name = try prop.toOwnedSliceZ(allocator);
|
|
|
|
symbols.putAssumeCapacity(bun.asByteSlice(function.base_name.?), function);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
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.array_list.Managed(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);
|
|
}
|
|
|
|
_ = 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.array_list.Managed(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);
|
|
}
|
|
}
|
|
|
|
_ = 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.printInt(arg_buf["arg".len..], i, 10, .lower, .{});
|
|
const argName = arg_buf[0 .. 3 + lengthBuf];
|
|
if (arg.needsACastInC()) {
|
|
try writer.print("{f}", .{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("{f}.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{f}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.printInt(arg_buf["arg".len..], i, 10, .lower, .{});
|
|
const arg_name = arg_buf[0 .. "arg".len + printed];
|
|
try writer.print("arguments[{d}] = {f}.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{f}ULL, {d}, arguments)",
|
|
.{ fmt, this.arg_types.items.len },
|
|
);
|
|
} else {
|
|
inner_buf = try std.fmt.bufPrint(
|
|
inner_buf_[1..],
|
|
"FFI_Callback_call((void*)0x{f}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 {f}", .{this.return_type.toCExact(inner_buf)});
|
|
}
|
|
|
|
try writer.writeAll(";\n}\n\n");
|
|
}
|
|
|
|
fn needsNapiEnv(this: *const FFI.Function) bool {
|
|
for (this.arg_types.items) |arg| {
|
|
if (arg == .napi_env or arg == .napi_value) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// 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, writer: *std.Io.Writer) !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, writer: *std.Io.Writer) !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, writer: *std.Io.Writer) !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",
|
|
};
|
|
}
|
|
};
|
|
};
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
pub const Bun__FFI__cc = FFI.Bun__FFI__cc;
|
|
|
|
fn makeNapiEnvIfNeeded(functions: []const FFI.Function, globalThis: *JSGlobalObject) ?*napi.NapiEnv {
|
|
for (functions) |function| {
|
|
if (function.needsNapiEnv()) {
|
|
return globalThis.makeNapiEnvForFFI();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
const string = []const u8;
|
|
|
|
const Fs = @import("../../fs.zig");
|
|
const TCC = @import("../../deps/tcc.zig");
|
|
const napi = @import("../../napi/napi.zig");
|
|
const options = @import("../../options.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 = bun.jsc.JSGlobalObject;
|
|
const JSValue = bun.jsc.JSValue;
|
|
const VM = bun.jsc.VM;
|
|
const VirtualMachine = jsc.VirtualMachine;
|
|
const ZigString = bun.jsc.ZigString;
|