From 116e2b3669183e258070fb167ced34ba5d244d4f Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Fri, 5 Dec 2025 16:43:25 +0900 Subject: [PATCH] Fix JSC build and add bytecode cache timestamp validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove -ffat-lto-objects and --whole-archive flags from build-jsc.ts to fix libgcc multiple definition linker errors on Linux - Add timestamp validation in transpiler.zig to invalidate bytecode cache when source file is newer than cached bytecode 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- scripts/build-jsc.ts | 16 ++++++++++------ src/transpiler.zig | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/scripts/build-jsc.ts b/scripts/build-jsc.ts index 4da6617402..5c02f1ffb3 100755 --- a/scripts/build-jsc.ts +++ b/scripts/build-jsc.ts @@ -134,16 +134,20 @@ const getBuildFlags = (config: BuildConfig) => { const getBuildEnv = () => { const env = { ...process.env }; - const cflags = ["-ffat-lto-objects"]; - const cxxflags = ["-ffat-lto-objects"]; + const cflags: string[] = []; + const cxxflags: string[] = []; if (IS_LINUX && buildConfig !== "lto") { - cflags.push("-Wl,--whole-archive"); - cxxflags.push("-Wl,--whole-archive", "-DUSE_BUN_JSC_ADDITIONS=ON", "-DUSE_BUN_EVENT_LOOP=ON"); + // Note: -ffat-lto-objects and --whole-archive removed due to libgcc multiple definition errors + cxxflags.push("-DUSE_BUN_JSC_ADDITIONS=ON", "-DUSE_BUN_EVENT_LOOP=ON"); } - env.CFLAGS = (env.CFLAGS || "") + " " + cflags.join(" "); - env.CXXFLAGS = (env.CXXFLAGS || "") + " " + cxxflags.join(" "); + if (cflags.length > 0) { + env.CFLAGS = (env.CFLAGS || "") + " " + cflags.join(" "); + } + if (cxxflags.length > 0) { + env.CXXFLAGS = (env.CXXFLAGS || "") + " " + cxxflags.join(" "); + } if (IS_MAC) { env.ICU_INCLUDE_DIRS = `${HOMEBREW_PREFIX}opt/icu4c/include`; diff --git a/src/transpiler.zig b/src/transpiler.zig index f3980e3dd9..c2f8b7c1ae 100644 --- a/src/transpiler.zig +++ b/src/transpiler.zig @@ -1162,7 +1162,44 @@ pub const Transpiler = struct { var path_buf2: bun.PathBuffer = undefined; @memcpy(path_buf2[0..path.text.len], path.text); path_buf2[path.text.len..][0..bun.bytecode_extension.len].* = bun.bytecode_extension.*; - const bytecode = bun.sys.File.toSourceAt(dirname_fd.unwrapValid() orelse bun.FD.cwd(), path_buf2[0 .. path.text.len + bun.bytecode_extension.len], bun.default_allocator, .{}).asValue() orelse break :brk default_value; + + const dir_fd = dirname_fd.unwrapValid() orelse bun.FD.cwd(); + // Add null terminator for bytecode path + path_buf2[path.text.len + bun.bytecode_extension.len] = 0; + const bytecode_path: [:0]const u8 = path_buf2[0 .. path.text.len + bun.bytecode_extension.len :0]; + + // Check timestamps: if source file is newer than bytecode cache, invalidate cache + // This ensures the cache is regenerated when source changes + // Create null-terminated source path + var source_path_buf: bun.PathBuffer = undefined; + @memcpy(source_path_buf[0..path.text.len], path.text); + source_path_buf[path.text.len] = 0; + const source_path: [:0]const u8 = source_path_buf[0..path.text.len :0]; + + const source_stat = bun.sys.fstatat(dir_fd, source_path); + const bytecode_stat = bun.sys.fstatat(dir_fd, bytecode_path); + + if (source_stat == .result and bytecode_stat == .result) { + const source_mtime = source_stat.result.mtime(); + const bytecode_mtime = bytecode_stat.result.mtime(); + + // Compare timestamps: if source is newer than bytecode by more than 1 second, skip cache + // We use 1 second tolerance because bytecode is written just before the source file + // during Bun.build, so there can be sub-second timing differences + // (source_mtime > bytecode_mtime + 1 second) + if (source_mtime.sec > bytecode_mtime.sec + 1 or + (source_mtime.sec == bytecode_mtime.sec + 1 and source_mtime.nsec > bytecode_mtime.nsec)) + { + break :brk default_value; + } + } else { + // If we can't stat one of the files, skip cache + if (bytecode_stat == .err) { + break :brk default_value; + } + } + + const bytecode = bun.sys.File.toSourceAt(dir_fd, bytecode_path, bun.default_allocator, .{}).asValue() orelse break :brk default_value; if (bytecode.contents.len == 0) { break :brk default_value; }