diff options
author | 2022-03-07 19:11:12 -0800 | |
---|---|---|
committer | 2022-03-07 19:11:12 -0800 | |
commit | ce081f15e97055ae6641acc5fb7627acaf215602 (patch) | |
tree | 886f28866e8d0ecf1b180416f4f8ccb6928b7cc2 | |
parent | da9a19037f8bcca0ca2fa641446d04cd827f702f (diff) | |
download | bun-ce081f15e97055ae6641acc5fb7627acaf215602.tar.gz bun-ce081f15e97055ae6641acc5fb7627acaf215602.tar.zst bun-ce081f15e97055ae6641acc5fb7627acaf215602.zip |
Optimize sourcemaps
-rw-r--r-- | Makefile | 95 | ||||
-rw-r--r-- | src/javascript/jsc/api/transpiler.zig | 18 | ||||
-rw-r--r-- | src/logger.zig | 2 | ||||
-rw-r--r-- | src/options.zig | 6 | ||||
-rw-r--r-- | src/runtime.zig | 8 | ||||
-rw-r--r-- | src/sourcemap/sourcemap.zig | 171 |
6 files changed, 210 insertions, 90 deletions
@@ -161,7 +161,16 @@ MACOS_MIN_FLAG= POSIX_PKG_MANAGER=sudo apt -STRIP ?= $(shell which llvm-strip || which llvm-strip-13 || echo "Missing llvm-strip. Please pass it in the STRIP environment var"; exit 1;) +STRIP= + +ifeq ($(OS_NAME),darwin) +STRIP=strip -u -r +endif + +ifeq ($(OS_NAME),linux) +STRIP=$(which llvm-strip || echo "Missing strip") +endif + HOMEBREW_PREFIX ?= $(BREW_PREFIX_PATH) @@ -234,17 +243,18 @@ CLANG_FLAGS = $(INCLUDE_DIRS) \ -DBUILDING_JSCONLY__ \ -DASSERT_ENABLED=0 \ -fvisibility=hidden \ - -fvisibility-inlines-hidden \ - -fno-omit-frame-pointer $(CFLAGS) + -fvisibility-inlines-hidden + +PLATFORM_LINKER_FLAGS = # This flag is only added to webkit builds on Apple platforms # It has something to do with ICU ifeq ($(OS_NAME), darwin) -CLANG_FLAGS += -DDU_DISABLE_RENAMING=1 \ +PLATFORM_LINKER_FLAGS += -DDU_DISABLE_RENAMING=1 \ -lstdc++ \ + -fno-keep-static-consts \ -ffunction-sections \ -fdata-sections \ - -Wl,-no_eh_labels \ -Wl,-dead_strip \ -Wl,-dead_strip_dylibs endif @@ -256,12 +266,11 @@ ARCHIVE_FILES_WITHOUT_LIBCRYPTO = $(MIMALLOC_FILE_PATH) \ $(BUN_DEPS_OUT_DIR)/libarchive.a \ $(BUN_DEPS_OUT_DIR)/libssl.a \ $(BUN_DEPS_OUT_DIR)/picohttpparser.o \ - $(BUN_DEPS_OUT_DIR)/libbacktrace.a ARCHIVE_FILES = $(ARCHIVE_FILES_WITHOUT_LIBCRYPTO) $(BUN_DEPS_OUT_DIR)/libcrypto.boring.a -PLATFORM_LINKER_FLAGS = + STATIC_MUSL_FLAG ?= @@ -277,24 +286,23 @@ PLATFORM_LINKER_FLAGS = \ -fdata-sections \ -static-libstdc++ \ -static-libgcc \ + -fno-omit-frame-pointer $(CFLAGS) \ -Wl,--compress-debug-sections,zlib \ ${STATIC_MUSL_FLAG} -endif -ifeq ($(OS_NAME), darwin) -PLATFORM_LINKER_FLAGS = \ - -Wl,-keep_private_externs +ARCHIVE_FILES_WITHOUT_LIBCRYPTO += $(BUN_DEPS_OUT_DIR)/libbacktrace.a endif -BUN_LLD_FLAGS = $(OBJ_FILES) \ - ${ICU_FLAGS} \ - ${JSC_FILES} \ - $(ARCHIVE_FILES) \ +BUN_LLD_FLAGS_WITHOUT_JSC = $(ARCHIVE_FILES) \ $(LIBICONV_PATH) \ $(CLANG_FLAGS) \ $(DEFAULT_LINKER_FLAGS) \ - $(PLATFORM_LINKER_FLAGS) + $(PLATFORM_LINKER_FLAGS) + + + +BUN_LLD_FLAGS = $(OBJ_FILES) $(JSC_FILES) ${ICU_FLAGS} $(BUN_LLD_FLAGS_WITHOUT_JSC) CLANG_VERSION = $(shell $(CC) --version | awk '/version/ {for(i=1; i<=NF; i++){if($$i=="version"){split($$(i+1),v,".");print v[1]}}}') @@ -402,9 +410,22 @@ build-obj-wasm: -o packages/bun-freestanding-wasm32/bun-wasm.wasm cp packages/bun-freestanding-wasm32/bun-wasm.wasm src/api/demo/public/bun-wasm.wasm +build-obj-wasm-small: + $(ZIG) build bun-wasm -Drelease-small -Dtarget=wasm32-freestanding --prominent-compile-errors + emcc -sEXPORTED_FUNCTIONS="['_bun_free', '_cycleStart', '_cycleEnd', '_bun_malloc', '_scan', '_transform', '_init']" \ + -g -s ERROR_ON_UNDEFINED_SYMBOLS=0 -DNDEBUG \ + $(BUN_DEPS_DIR)/libmimalloc.a.wasm \ + packages/bun-freestanding-wasm32/bun-wasm.o -Oz --no-entry --allow-undefined -s ASSERTIONS=0 -s ALLOW_MEMORY_GROWTH=1 -s WASM_BIGINT=1 \ + -o packages/bun-freestanding-wasm32/bun-wasm.wasm + cp packages/bun-freestanding-wasm32/bun-wasm.wasm src/api/demo/public/bun-wasm.wasm + + build-obj-safe: $(ZIG) build obj -Drelease-safe + + + sign-macos-x64: gon sign.macos-x64.json @@ -755,10 +776,11 @@ jsc-build-mac-compile: mkdir -p $(WEBKIT_RELEASE_DIR) $(WEBKIT_DIR); cd $(WEBKIT_RELEASE_DIR) && \ ICU_INCLUDE_DIRS="$(HOMEBREW_PREFIX)opt/icu4c/include" \ - CMAKE_BUILD_TYPE=Release cmake \ + cmake \ -DPORT="JSCOnly" \ -DENABLE_STATIC_JSC=ON \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_BUILD_TYPE=Release \ -DUSE_THIN_ARCHIVES=OFF \ -DENABLE_FTL_JIT=ON \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ @@ -806,7 +828,7 @@ jsc-build-mac-copy: cp $(WEBKIT_RELEASE_DIR)/lib/libbmalloc.a $(BUN_DEPS_OUT_DIR)/libbmalloc.a clean-jsc: - cd src/javascript/jsc/WebKit && rm -rf **/CMakeCache.txt **/CMakeFiles + cd src/javascript/jsc/WebKit && rm -rf **/CMakeCache.txt **/CMakeFiles && rm -rf src/javascript/jsc/WebKit/WebKitBuild clean-bindings: rm -rf $(OBJ_DIR)/*.o @@ -824,7 +846,16 @@ jsc-bindings-mac: $(OBJ_FILES) # mimalloc is built as object files so that it can overload the system malloc mimalloc: rm -rf $(BUN_DEPS_DIR)/mimalloc/CMakeCache* $(BUN_DEPS_DIR)/mimalloc/CMakeFiles - cd $(BUN_DEPS_DIR)/mimalloc; CFLAGS="$(CFLAGS)" cmake $(CMAKE_FLAGS) -DMI_SKIP_COLLECT_ON_EXIT=1 -DMI_BUILD_SHARED=OFF -DMI_BUILD_STATIC=ON -DMI_BUILD_TESTS=OFF -DMI_BUILD_OBJECT=ON ${MIMALLOC_OVERRIDE_FLAG} -DMI_USE_CXX=ON .; make; + cd $(BUN_DEPS_DIR)/mimalloc; \ + CFLAGS="$(CFLAGS)" cmake $(CMAKE_FLAGS) \ + -DMI_SKIP_COLLECT_ON_EXIT=1 \ + -DMI_BUILD_SHARED=OFF \ + -DMI_BUILD_STATIC=ON \ + -DMI_BUILD_TESTS=OFF \ + -DMI_BUILD_OBJECT=ON \ + ${MIMALLOC_OVERRIDE_FLAG} \ + -DMI_USE_CXX=OFF .\ + && make -j $(CPUS); cp $(BUN_DEPS_DIR)/mimalloc/$(MIMALLOC_INPUT_PATH) $(BUN_DEPS_OUT_DIR)/$(MIMALLOC_FILE) @@ -837,9 +868,23 @@ bun-link-lld-debug: -g \ $(DEBUG_BIN)/bun-debug.o \ -W \ - -o $(DEBUG_BIN)/bun-debug \ + -o $(DEBUG_BIN)/bun-debug + +bun-link-lld-debug-no-jsc: + $(CXX) $(BUN_LLD_FLAGS_WITHOUT_JSC) \ + -g \ + $(DEBUG_BIN)/bun-debug.o \ + -W \ + -o $(DEBUG_BIN)/bun-debug +bun-link-lld-release-no-jsc: + $(CXX) $(BUN_LLD_FLAGS_WITHOUT_JSC) \ + -g \ + $(BUN_RELEASE_BIN).o \ + -W \ + -o $(BUN_RELEASE_BIN) -Wl,-undefined,dynamic_lookup -Wl,-why_load + bun-relink-copy: cp /tmp/bun-$(PACKAGE_JSON_VERSION).o $(BUN_RELEASE_BIN).o @@ -851,7 +896,6 @@ bun-link-lld-release: -o $(BUN_RELEASE_BIN) \ -W \ -flto \ - -ftls-model=initial-exec \ -O3 rm -rf $(BUN_RELEASE_BIN).dSYM cp $(BUN_RELEASE_BIN) $(BUN_RELEASE_BIN)-profile @@ -859,7 +903,7 @@ bun-link-lld-release: ifeq ($(OS_NAME),darwin) bun-link-lld-release-dsym: $(DSYMUTIL) -o $(BUN_RELEASE_BIN).dSYM $(BUN_RELEASE_BIN) - # -$(STRIP) $(BUN_RELEASE_BIN) + -$(STRIP) $(BUN_RELEASE_BIN) mv $(BUN_RELEASE_BIN).o /tmp/bun-$(PACKAGE_JSON_VERSION).o copy-to-bun-release-dir-dsym: @@ -885,10 +929,11 @@ wasm-return1: # The C compilation stuff with build.zig is really slow and we don't need to run this as often as the rest $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp $(CXX) -c -o $@ $< \ - $(CLANG_FLAGS) $(PLATFORM_LINKER_FLAGS) \ + $(CLANG_FLAGS) \ + $(MACOS_MIN_FLAG) \ -O3 \ - -fvectorize \ - -w -g \ + -fno-exceptions \ + -ffunction-sections -fdata-sections -g \ -ferror-limit=1000 sizegen: diff --git a/src/javascript/jsc/api/transpiler.zig b/src/javascript/jsc/api/transpiler.zig index 5863f0a69..5f66247bb 100644 --- a/src/javascript/jsc/api/transpiler.zig +++ b/src/javascript/jsc/api/transpiler.zig @@ -481,6 +481,24 @@ fn transformOptionsFromJSC(ctx: JSC.C.JSContextRef, temp_allocator: std.mem.Allo transpiler.runtime.allow_runtime = flag.toBoolean(); } + if (object.get(globalThis, "sourcemap")) |flag| { + if (flag.isBoolean() or flag.isUndefinedOrNull()) { + if (flag.toBoolean()) { + transpiler.transform.source_map = Api.SourceMapMode.external; + } else { + transpiler.transform.source_map = Api.SourceMapMode.inline_into_file; + } + } else { + var sourcemap = flag.toSlice(globalThis, allocator); + if (options.SourceMapOption.map.get(sourcemap.slice())) |source| { + transpiler.transform.source_map = source.toAPI(); + } else { + JSC.throwInvalidArguments("sourcemap must be one of \"inline\", \"external\", or \"none\"", .{}, ctx, exception); + return transpiler; + } + } + } + return transpiler; } diff --git a/src/logger.zig b/src/logger.zig index 63590fcda..bab017ebc 100644 --- a/src/logger.zig +++ b/src/logger.zig @@ -80,7 +80,7 @@ pub const Loc = packed struct { pub const Empty = Loc{ .start = -1 }; - pub inline fn eql(loc: *Loc, other: Loc) bool { + pub inline fn eql(loc: Loc, other: Loc) bool { return loc.start == other.start; } diff --git a/src/options.zig b/src/options.zig index d635b674a..80034db12 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1092,10 +1092,10 @@ pub const SourceMapOption = enum { }; } - pub fn toAPI(source_map: SourceMapOption) Api.SourceMapMode { - return switch (source_map orelse Api.SourceMapMode._none) { + pub fn toAPI(source_map: ?SourceMapOption) Api.SourceMapMode { + return switch (source_map orelse .none) { .external => .external, - .@"inline" => .@"inline", + .@"inline" => .@"inline_into_file", else => ._none, }; } diff --git a/src/runtime.zig b/src/runtime.zig index 32f45ffad..222b89919 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -33,9 +33,7 @@ pub const ErrorCSS = struct { var paths = [_]string{ dirname, BUN_ROOT, ErrorCSSPathDev }; const file = std.fs.cwd().openFile( resolve_path.joinAbsString(dirname, std.mem.span(&paths), .auto), - .{ - .read = true, - }, + .{ .mode = .read_only }, ) catch @panic("Missing packages/bun-error/bun-error.css. Please run \"make bun_error\""); defer file.close(); return file.readToEndAlloc(default_allocator, (file.stat() catch unreachable).size) catch unreachable; @@ -59,9 +57,7 @@ pub const ErrorJS = struct { var paths = [_]string{ dirname, BUN_ROOT, ErrorJSPath }; const file = std.fs.cwd().openFile( resolve_path.joinAbsString(dirname, std.mem.span(&paths), .auto), - .{ - .read = true, - }, + .{ .mode = .read_only }, ) catch @panic("Missing " ++ ErrorJSPath ++ ". Please run \"make bun_error\""); defer file.close(); return file.readToEndAlloc(default_allocator, (file.stat() catch unreachable).size) catch unreachable; diff --git a/src/sourcemap/sourcemap.zig b/src/sourcemap/sourcemap.zig index cf96afa4e..3b6048985 100644 --- a/src/sourcemap/sourcemap.zig +++ b/src/sourcemap/sourcemap.zig @@ -14,22 +14,19 @@ const Joiner = @import("../string_joiner.zig"); const JSPrinter = @import("../js_printer.zig"); const URL = @import("../query_string_map.zig").URL; const FileSystem = @import("../fs.zig").FileSystem; -const base64_lut: [std.math.maxInt(u8)]u8 = brk: { - @setEvalBranchQuota(9999); - var bytes = [_]u8{255} ** std.math.maxInt(u8); - - for (base64) |c, i| { - bytes[c] = i; - } - break :brk bytes; -}; const SourceMap = @This(); -// One VLQ value never exceeds 20 bits of data, so 15 is more than enough +const vlq_max_in_bytes = 8; pub const VLQ = struct { - bytes: [15]u8, - len: u8 = 0, + // We only need to worry about i32 + // That means the maximum VLQ-encoded value is 8 bytes + // because there are only 4 bits of number inside each VLQ value + // and it expects i32 + // therefore, it can never be more than 32 bits long + // I believe the actual number is 7 bytes long, however we can add an extra byte to be more cautious + bytes: [vlq_max_in_bytes]u8, + len: u4 = 0, }; /// Coordinates in source maps are stored using relative offsets for size @@ -182,6 +179,62 @@ pub fn appendSourceMapChunk(j: *Joiner, prev_end_state_: SourceMapState, start_s j.append(source_map_.list.items, @truncate(u32, @ptrToInt(source_map.ptr) - @ptrToInt(source_map_.list.items.ptr)), source_map_.allocator); } +const vlq_lookup_table: [256]VLQ = brk: { + var entries: [256]VLQ = undefined; + var i: usize = 0; + var j: i32 = 0; + while (i < 256) : (i += 1) { + entries[i] = encodeVLQ(j); + j += 1; + } + break :brk entries; +}; + +pub fn encodeVLQWithLookupTable( + value: i32, +) VLQ { + return if (value >= 0 and value <= 255) + vlq_lookup_table[@intCast(usize, value)] + else + encodeVLQ(value); +} + +test "encodeVLQ" { + const fixtures = .{ + .{ 2_147_483_647, "+/////D" }, + .{ -2_147_483_647, "//////D" }, + .{ 0, "A" }, + .{ 1, "C" }, + .{ -1, "D" }, + .{ 123, "2H" }, + .{ 123456789, "qxmvrH" }, + }; + inline for (fixtures) |fixture| { + const result = encodeVLQ(fixture[0]); + try std.testing.expectEqualStrings(fixture[1], result.bytes[0..result.len]); + } +} + +test "decodeVLQ" { + const fixtures = .{ + .{ 2_147_483_647, "+/////D" }, + .{ -2_147_483_647, "//////D" }, + .{ 0, "A" }, + .{ 1, "C" }, + .{ -1, "D" }, + .{ 123, "2H" }, + .{ 123456789, "qxmvrH" }, + .{ 8, "Q" }, + }; + inline for (fixtures) |fixture| { + const result = decodeVLQ(fixture[1], 0); + try std.testing.expectEqual( + result.value, + fixture[0], + ); + } +} + // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, // the next four bits are the actual value, and the 6th bit is the continuation @@ -197,27 +250,19 @@ pub fn appendSourceMapChunk(j: *Joiner, prev_end_state_: SourceMapState, start_s pub fn encodeVLQ( value: i32, ) VLQ { - var vlq: i32 = 0; - var bytes = std.mem.zeroes([15]u8); - var len: u8 = 0; - if (value < 0) { - vlq = ((-value) << 1) | 1; - } else { - vlq = value << 1; - } + var len: u4 = 0; + var bytes: [vlq_max_in_bytes]u8 = undefined; - if ((vlq >> 5) == 0) { - const digit = @intCast(u8, vlq & 31); - bytes[0] = base64[digit]; - return .{ .bytes = bytes, .len = 1 }; - } + var vlq: u32 = if (value >= 0) + @bitCast(u32, value << 1) + else + @bitCast(u32, (-value << 1) | 1); - // i32 contains 32 bits - // since - // the maximum possible number is @maxInt(u20) - // - while (true) { - var digit = @intCast(u8, vlq & 31); + // The max amount of digits a VLQ value for sourcemaps can contain is 9 + // therefore, we can unroll the loop + comptime var i: usize = 0; + inline while (i < vlq_max_in_bytes) : (i += 1) { + var digit = vlq & 31; vlq >>= 5; // If there are still more digits in this value, we must make sure the @@ -230,11 +275,11 @@ pub fn encodeVLQ( len += 1; if (vlq == 0) { - break; + return .{ .bytes = bytes, .len = len }; } } - return .{ .bytes = bytes, .len = len }; + return .{ .bytes = bytes, .len = 0 }; } pub const VLQResult = struct { @@ -242,33 +287,45 @@ pub const VLQResult = struct { start: usize = 0, }; +const base64_lut: [std.math.maxInt(u7)]u7 = brk: { + @setEvalBranchQuota(9999); + var bytes = [_]u7{std.math.maxInt(u7)} ** std.math.maxInt(u7); + + for (base64) |c, i| { + bytes[c] = i; + } + + break :brk bytes; +}; + fn decodeVLQ(encoded: []const u8, start: usize) VLQResult { - var shift: i32 = 0; - var vlq: i32 = 0; - var len: usize = encoded.len; - var i: usize = start; + var shift: u8 = 0; + var vlq: u32 = 0; - while (i < len) { - const index = @as(i32, base64_lut[encoded[i]]); - if (index == std.math.maxInt(u8)) break; + // it will never exceed 9 + // by doing it this way, we can hint to the compiler that it will not exceed 9 + const encoded_ = encoded[start..][0..@minimum(encoded.len - start, comptime (vlq_max_in_bytes + 1))]; + + for (encoded_) |c, i| { + const index = @as(u32, base64_lut[@truncate(u7, c)]); // decode a byte - vlq |= (index & 31) << shift; - i += 1; + vlq |= (index & 31) << @truncate(u5, shift); shift += 5; // Stop if there's no continuation bit if ((index & 32) == 0) { - break; + return VLQResult{ + .start = i + start, + .value = if ((vlq & 1) == 0) + @intCast(i32, vlq >> 1) + else + -@intCast(i32, (vlq >> 1)), + }; } } - var value = vlq >> 1; - if ((vlq & 1) != 0) { - value = -value; - } - - return VLQResult{ .start = i, .value = value }; + return VLQResult{ .start = start + encoded_.len, .value = 0 }; } pub const LineOffsetTable = struct { @@ -473,6 +530,9 @@ pub fn appendSourceMappingURLRemote( try writer.writeAll(strings.withoutTrailingSlash(origin.href)); if (asset_prefix_path.len > 0) try writer.writeAll(asset_prefix_path); + if (source.path.pretty.len > 0 and source.path.pretty[0] != '/') { + try writer.writeAll("/"); + } try writer.writeAll(source.path.pretty); try writer.writeAll(".map"); } @@ -483,13 +543,13 @@ pub fn appendMappingToBuffer(buffer_: MutableString, last_byte: u8, prev_state: const vlq = [_]VLQ{ // Record the generated column (the line is recorded using ';' elsewhere) - encodeVLQ(current_state.generated_column - prev_state.generated_column), + encodeVLQWithLookupTable(current_state.generated_column - prev_state.generated_column), // Record the generated source - encodeVLQ(current_state.source_index - prev_state.source_index), + encodeVLQWithLookupTable(current_state.source_index - prev_state.source_index), // Record the original line - encodeVLQ(current_state.original_line - prev_state.original_line), + encodeVLQWithLookupTable(current_state.original_line - prev_state.original_line), // Record the original column - encodeVLQ(current_state.original_column - prev_state.original_column), + encodeVLQWithLookupTable(current_state.original_column - prev_state.original_column), }; // Count exactly how many bytes we need to write @@ -687,7 +747,8 @@ pub const Chunk = struct { } pub fn addSourceMapping(b: *Builder, loc: Logger.Loc, output: []const u8) void { - if (b.prev_loc.eql(loc)) { + // exclude generated code from source + if (b.prev_loc.eql(loc) or loc.start == Logger.Loc.Empty.start) { return; } @@ -699,7 +760,7 @@ pub const Chunk = struct { // Use the line to compute the column var original_column = loc.start - @intCast(i32, line.byte_offset_to_start_of_line); if (line.columns_for_non_ascii.len > 0 and original_column >= @intCast(i32, line.byte_offset_to_first_non_ascii)) { - original_column = line.columns_for_non_ascii.slice()[@intCast(u32, original_column) - line.byte_offset_to_first_non_ascii]; + original_column = line.columns_for_non_ascii.ptr[@intCast(u32, original_column) - line.byte_offset_to_first_non_ascii]; } b.updateGeneratedLineAndColumn(output); |