From f6b0029249028b089e719994ff01684336aa34be Mon Sep 17 00:00:00 2001 From: agra Date: Fri, 12 Jun 2026 19:11:18 +0300 Subject: [PATCH] fix: AOT build cache disabled for programs with top-level #run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The JIT path already guards its object cache with hasTopLevelRun (the #run interp executes during codegen; a cache hit skips codegen and loses its effects). The build path had no such guard, so a second 'sx build --cache' of any app with a '#run configure_build()' block linked WITHOUT the build.sx config — no link flags (m3te: undefined SDL3 symbols), and on a binary-level hit the output path and bundling would have been wrong too. Both cache levels and both save sites now share the guard; #run-free programs keep full cache behavior (verified: second build hits the binary cache in <1ms; m3te's build/--cache/build sequence now links and bundles both times). --- src/main.zig | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main.zig b/src/main.zig index 0ab7a5c..f426d3f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -577,14 +577,19 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons const obj_path = try std.fmt.allocPrintSentinel(allocator, "{s}/main.o", .{tmp_dir}, 0); - // Cache: compute key and check for cached binary/.o + // Cache: compute key and check for cached binary/.o. + // Disabled for programs with top-level #run (same guard as the JIT + // path): the #run interp runs during codegen, and skipping codegen + // loses its effects — build config (link flags, frameworks, output + // path, bundling) and print side effects alike. + const use_cache = enable_cache and !hasTopLevelRun(root); const key = computeCacheKey(source, &comp.import_sources, target_config); const cache_obj = try cachePath(allocator, key, "o"); const cache_bin = try cachePath(allocator, key, "bin"); // Level 1: Try cached binary (skip everything — no codegen, no link). // Skipped under --emit-obj, which needs the freshly-emitted object kept. - if (enable_cache and !target_config.emit_obj) bin_cache: { + if (use_cache and !target_config.emit_obj) bin_cache: { std.Io.Dir.copyFile(.cwd(), cache_bin, .cwd(), output_path, io, .{}) catch break :bin_cache; timer.record("cache"); return; @@ -592,7 +597,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons // Level 2: Try cached .o (skip codegen+emit, still need link) const used_obj_cache = blk: { - if (!enable_cache) break :blk false; + if (!use_cache) break :blk false; std.Io.Dir.copyFile(.cwd(), cache_obj, .cwd(), obj_path, io, .{}) catch break :blk false; break :blk true; }; @@ -614,7 +619,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons timer.record("emit"); // Save .o to cache - if (enable_cache) { + if (use_cache) { std.Io.Dir.copyFile(.cwd(), obj_path, .cwd(), cache_obj, io, .{ .make_path = true }) catch {}; } } @@ -783,7 +788,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons } // Save linked binary to cache - if (enable_cache) { + if (use_cache) { std.Io.Dir.copyFile(.cwd(), output_path, .cwd(), cache_bin, io, .{ .make_path = true }) catch {}; }