diff options
author | 2022-04-30 23:56:31 -0700 | |
---|---|---|
committer | 2022-04-30 23:56:31 -0700 | |
commit | 5e270f9d77666890bba7bb5ecc84008f6e2b3115 (patch) | |
tree | 29fe1f2f021ef4f4182ee5f96721f8e32356b0b6 | |
parent | 7e13d6cbfe3875c8a9749212a4f9e0a48f1cfea3 (diff) | |
download | bun-5e270f9d77666890bba7bb5ecc84008f6e2b3115.tar.gz bun-5e270f9d77666890bba7bb5ecc84008f6e2b3115.tar.zst bun-5e270f9d77666890bba7bb5ecc84008f6e2b3115.zip |
[bun ffi] Remove dependency on libtcc1.a and improve error messages
-rw-r--r-- | Makefile | 18 | ||||
-rw-r--r-- | src/javascript/jsc/api/FFI.h | 56 | ||||
-rw-r--r-- | src/javascript/jsc/api/ffi.zig | 101 |
3 files changed, 139 insertions, 36 deletions
@@ -304,7 +304,7 @@ ARCHIVE_FILES_WITHOUT_LIBCRYPTO = $(MIMALLOC_FILE_PATH) \ -lssl \ -lbase64 \ -ltcc \ - -L/Users/jarred/Build/tinycc + -L$(TINYCC_DIR) ARCHIVE_FILES = $(ARCHIVE_FILES_WITHOUT_LIBCRYPTO) -lcrypto @@ -359,6 +359,15 @@ base64: $(CXX) $(CXXFLAGS) $(CFLAGS) -c neonbase64.cc -g -fPIC -emit-llvm && \ $(AR) rcvs $(BUN_DEPS_OUT_DIR)/libbase64.a ./*.bc +# Prevent dependency on libtcc1 so it doesn't do filesystem lookups +TINYCC_CFLAGS= -DTCC_LIBTCC1=\"\0\" + +tinycc: + cd $(TINYCC_DIR) && \ + make clean && \ + AR=$(AR) CC=$(CC) CFLAGS='$(CFLAGS) -emit-llvm $(TINYCC_CFLAGS)' ./configure --enable-static --cc=$(CC) --ar=$(AR) --config-predefs=yes && \ + make -j10 + generate-builtins: $(shell which python || which python2) $(realpath $(WEBKIT_DIR)/Source/JavaScriptCore/Scripts/generate-js-builtins.py) -i $(realpath src/javascript/jsc/bindings/builtins/js) -o $(realpath src/javascript/jsc/bindings) --framework WebCore @@ -781,11 +790,16 @@ ifeq ($(OS_NAME),linux) release-bin-push-dsym: endif +TINYCC_DIR ?= $(realpath $(HOME)/Build/tinycc) release-bin-push: release-bin-push-bin release-bin-push-dsym -release-bin-without-push: test-all release-bin-check release-bin-generate release-bin-codesign +generate-release-bin-as-zip: release-bin-generate release-bin-codesign +release-bin-without-push: test-all release-bin-check generate-release-bin-as-zip + release-bin: release-bin-without-push release-bin-push + + release-bin-dir: echo $(PACKAGE_DIR) diff --git a/src/javascript/jsc/api/FFI.h b/src/javascript/jsc/api/FFI.h index 44259eb3b..48e4db48d 100644 --- a/src/javascript/jsc/api/FFI.h +++ b/src/javascript/jsc/api/FFI.h @@ -7,38 +7,58 @@ // It must be kept in sync with JSCJSValue.h // https://github.com/Jarred-Sumner/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458 #ifdef IS_CALLBACK -#define INJECT_BEFORE printf("bun_call %p cachedJSContext %p cachedCallbackFunction %p\n", &bun_call, cachedJSContext, cachedCallbackFunction); -#endif - -#ifdef USES_FLOAT -#include <math.h> +// #define INJECT_BEFORE printf("bun_call %p cachedJSContext %p cachedCallbackFunction %p\n", &bun_call, cachedJSContext, cachedCallbackFunction); #endif #define IS_BIG_ENDIAN 0 #define USE_JSVALUE64 1 #define USE_JSVALUE32_64 0 -#include <stdint.h> -#include <stdio.h> +// #include <stdint.h> +#ifdef INJECT_BEFORE +// #include <stdio.h> +#endif // #include <tcclib.h> // // /* 7.18.1.1 Exact-width integer types */ -// typedef unsigned char uint8_t; -// typedef signed char int8_t; -// typedef short int16_t; -// typedef unsigned short uint16_t; -// typedef int int32_t; -// typedef unsigned int uint32_t; -// typedef long long int64_t; -// typedef unsigned long long uint64_t; -// typedef uint64_t size_t; -// typedef long intptr_t; -// typedef uint64_t uintptr_t; +typedef unsigned char uint8_t; +typedef signed char int8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; +typedef uint64_t size_t; +typedef long intptr_t; +typedef uint64_t uintptr_t; typedef _Bool bool; #define true 1 #define false 0 +#ifdef USES_FLOAT +// https://git.musl-libc.org/cgit/musl/tree/src/math/trunc.c +double trunc(double x); +double trunc(double x) +{ + union {double f; uint64_t i;} u = {x}; + int e = (int)(u.i >> 52 & 0x7ff) - 0x3ff + 12; + uint64_t m; + + if (e >= 52 + 12) + return x; + if (e < 12) + e = 1; + m = -1ULL >> e; + if ((u.i & m) == 0) + return x; + x + 0x1p120f; + u.i &= ~m; + return u.f; +} +#endif + // This value is 2^49, used to encode doubles such that the encoded value will // begin with a 15-bit pattern within the range 0x0002..0xFFFC. diff --git a/src/javascript/jsc/api/ffi.zig b/src/javascript/jsc/api/ffi.zig index 124b0f533..ac8f615d1 100644 --- a/src/javascript/jsc/api/ffi.zig +++ b/src/javascript/jsc/api/ffi.zig @@ -133,12 +133,15 @@ pub const FFI = struct { // TODO: WeakRefHandle that automatically frees it? JSC.C.JSValueProtect(globalThis.ref(), js_callback.asObjectRef()); func.base_name = ""; + func.compileCallback(allocator, globalThis, js_callback.asObjectRef().?) catch return ZigString.init("Out of memory").toErrorInstance(globalThis); switch (func.step) { .failed => |err| { JSC.C.JSValueUnprotect(globalThis.ref(), js_callback.asObjectRef()); - const message = ZigString.init(err).toErrorInstance(globalThis); + const message = ZigString.init(err.msg).toErrorInstance(globalThis); + func.deinit(allocator); + return message; }, .pending => { @@ -310,6 +313,7 @@ pub const FFI = struct { var obj = JSC.JSValue.c(JSC.C.JSObjectMake(global.ref(), null, null)); JSC.C.JSValueProtect(global.ref(), obj.asObjectRef()); + // var has_checked = false; defer JSC.C.JSValueUnprotect(global.ref(), obj.asObjectRef()); for (symbols.values()) |*function| { var resolved_symbol = dylib.lookup(*anyopaque, function.base_name) orelse { @@ -324,8 +328,19 @@ pub const FFI = struct { }; function.symbol_from_dynamic_library = resolved_symbol; - function.compile(allocator) catch { - const ret = JSC.toInvalidArguments("Failed to compile symbol \"{s}\" in \"{s}\"", .{ std.mem.span(function.base_name), name_slice.slice() }, global.ref()); + // if (!has_checked and VirtualMachine.vm.rareData().needs_to_copy_libtcc1 orelse true) { + // has_checked = true; + // VirtualMachine.vm.rareData().needs_to_copy_libtcc1 = false; + // if (VirtualMachine.vm.bundler.env.get("BUN_INSTALL")) |bun_install_dir| { + // maybeCopyLibtcc1(bun_install_dir); + // } + // } + function.compile(allocator) catch |err| { + const ret = JSC.toInvalidArguments("{s} when compiling symbol \"{s}\" in \"{s}\"", .{ + std.mem.span(@errorName(err)), + std.mem.span(function.base_name), + name_slice.slice(), + }, global.ref()); for (symbols.values()) |*value| { allocator.free(bun.constStrToU8(std.mem.span(value.base_name))); value.arg_types.clearAndFree(allocator); @@ -342,7 +357,9 @@ pub const FFI = struct { } symbols.clearAndFree(allocator); dylib.close(); - return ZigString.init(err).toErrorInstance(global); + const res = ZigString.init(err.msg).toErrorInstance(global); + function.deinit(allocator); + return res; }, .pending => { for (symbols.values()) |*value| { @@ -491,6 +508,8 @@ pub const FFI = struct { arg_types: std.ArrayListUnmanaged(ABIType) = .{}, step: Step = Step{ .pending = {} }, + pub var lib_dirZ: [*:0]const u8 = ""; + pub fn deinit(val: *Function, allocator: std.mem.Allocator) void { if (std.mem.span(val.base_name).len > 0) allocator.free(bun.constStrToU8(std.mem.span(val.base_name))); @@ -508,8 +527,8 @@ pub const FFI = struct { } } - if (val.step == .failed) { - allocator.free(val.step.failed); + if (val.step == .failed and val.step.failed.allocated) { + allocator.free(val.step.failed.msg); } } @@ -521,7 +540,10 @@ pub const FFI = struct { js_function: ?*anyopaque = null, js_context: ?*anyopaque = null, }, - failed: []const u8, + failed: struct { + msg: []const u8, + allocated: bool = false, + }, }; const FFI_HEADER: string = @embedFile("./FFI.h"); @@ -548,12 +570,23 @@ pub const FFI = struct { pub fn handleTCCError(ctx: ?*anyopaque, message: [*c]const u8) callconv(.C) void { var this = bun.cast(*Function, ctx.?); - this.step = .{ .failed = std.mem.span(message) }; + 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 = VirtualMachine.vm.allocator.dupe(u8, msg) catch unreachable, .allocated = true } }; } extern fn pthread_jit_write_protect_np(enable: bool) callconv(.C) void; - const tcc_options = "-std=c11 -Wl,--export-all-symbols"; + const tcc_options = "-std=c11 -nostdlib -Wl,--export-all-symbols"; pub fn compile( this: *Function, @@ -565,8 +598,10 @@ pub const FFI = struct { try source_code.append(0); defer source_code.deinit(); + var state = TCC.tcc_new() orelse return error.TCCMissing; TCC.tcc_set_options(state, tcc_options); + // addSharedLibPaths(state); TCC.tcc_set_error_func(state, this, handleTCCError); this.state = state; defer { @@ -589,14 +624,25 @@ pub const FFI = struct { // did tcc report failure but never called the error callback? if (compilation_result == -1) { - this.step = .{ .failed = "tcc returned -1, which means it failed" }; + this.step = .{ .failed = .{ .msg = "tcc returned -1, which means it failed" } }; return; } - + CompilerRT.inject(state); _ = TCC.tcc_add_symbol(state, this.base_name, this.symbol_from_dynamic_library.?); + if (this.step == .failed) { + return; + } var relocation_size = TCC.tcc_relocate(state, null); - if (relocation_size == 0) return; + if (this.step == .failed) { + return; + } + + if (relocation_size < 0) { + this.step = .{ .failed = .{ .msg = "tcc_relocate returned a negative value" } }; + return; + } + var bytes: []u8 = try allocator.rawAlloc(@intCast(usize, relocation_size), 16, 16, 0); defer { if (this.step == .failed) { @@ -615,7 +661,7 @@ pub const FFI = struct { var formatted_symbol_name = try std.fmt.allocPrintZ(allocator, "bun_gen_{s}", .{std.mem.span(this.base_name)}); defer allocator.free(formatted_symbol_name); var symbol = TCC.tcc_get_symbol(state, formatted_symbol_name) orelse { - this.step = .{ .failed = "missing generated symbol in source code" }; + this.step = .{ .failed = .{ .msg = "missing generated symbol in source code" } }; return; }; @@ -629,6 +675,29 @@ pub const FFI = struct { return; } + const CompilerRT = struct { + noinline fn memset( + dest: [*]u8, + c: u8, + byte_count: usize, + ) callconv(.C) void { + @memset(dest, c, byte_count); + } + + noinline fn memcpy( + noalias dest: [*]u8, + noalias source: [*]const u8, + byte_count: usize, + ) callconv(.C) void { + @memcpy(dest, source, byte_count); + } + + pub fn inject(state: *TCC.TCCState) void { + _ = TCC.tcc_add_symbol(state, "memset", &memset); + _ = TCC.tcc_add_symbol(state, "memcpy", &memcpy); + } + }; + pub fn compileCallback( this: *Function, allocator: std.mem.Allocator, @@ -643,7 +712,6 @@ pub const FFI = struct { try source_code.append(0); // defer source_code.deinit(); var state = TCC.tcc_new() orelse return error.TCCMissing; - TCC.tcc_set_options(state, tcc_options); TCC.tcc_set_error_func(state, this, handleTCCError); this.state = state; @@ -655,6 +723,7 @@ pub const FFI = struct { } _ = TCC.tcc_set_output_type(state, TCC.TCC_OUTPUT_MEMORY); + CompilerRT.inject(state); const compilation_result = TCC.tcc_compile_string( state, @@ -668,7 +737,7 @@ pub const FFI = struct { // did tcc report failure but never called the error callback? if (compilation_result == -1) { - this.step = .{ .failed = "tcc returned -1, which means it failed" }; + this.step = .{ .failed = .{ .msg = "tcc returned -1, which means it failed" } }; return; } @@ -696,7 +765,7 @@ pub const FFI = struct { } var symbol = TCC.tcc_get_symbol(state, "my_callback_function") orelse { - this.step = .{ .failed = "missing generated symbol in source code" }; + this.step = .{ .failed = .{ .msg = "missing generated symbol in source code" } }; return; }; |