@@ -38,6 +38,7 @@ const finalization_cleanup_queue_reserve_granularity = 16;
3838const module_queue_reserve_granularity = 16;
3939const module_namespace_waiter_reserve_granularity = 16;
4040const runtime_gc_tenuring_age: u8 = 3;
41+ const precise_gc_nursery_threshold_bytes: usize = 2 * 1024 * 1024;
4142const gc_auto_compaction_min_reclaimable_bytes: usize = 512 * 1024;
4243
4344/// Serializes calls into an embedder allocator that may not itself be safe for
@@ -6252,8 +6253,14 @@ pub const Context = struct {
62526253 /// A full or concurrent cycle already in progress keeps ownership of the checkpoint.
62536254 fn collectYoungMidScriptIfNeeded(self: *Context, h: *GcHeap, machine: *interp.Interpreter) bool {
62546255 if (h.marking.load(.acquire) or h.concurrent.load(.acquire)) return false;
6255- if (!h.shouldCollectYoung()) return false;
62566256 const precise_roots = machine.gc_precise_safepoint;
6257+ // Exact single-mutator checkpoints have no conservative false roots,
6258+ // so reclaim a cache-local young batch before the wider general nursery
6259+ // threshold. Generic, concurrent, and shared checkpoints keep zig-gc's
6260+ // established cadence to avoid repeated promotion of ambiguous roots.
6261+ const precise_cache_batch =
6262+ precise_roots and !h.parallel and h.young_bytes >= precise_gc_nursery_threshold_bytes;
6263+ if (!h.shouldCollectYoung() and !precise_cache_batch) return false;
62576264 self.gc_scan_native_stack = !precise_roots;
62586265 defer self.gc_scan_native_stack = false;
62596266 self.gc_scan_parked_stacks = !precise_roots;
@@ -16245,6 +16252,9 @@ test "GC compaction preserves quiescent pointer-free baseline JIT tiers" {
1624516252 .enable_jit = true,
1624616253 });
1624716254 defer ctx.destroy();
16255+ // This test constructs tail fragmentation for an explicit full compaction;
16256+ // keep automatic nursery policy from relocating its witness first.
16257+ ctx.gc.?.nursery_threshold_bytes = std.math.maxInt(usize);
1624816258
1624916259 // Keep dead Object and Function cells live until the next evaluation has
1625016260 // started. The target function and witness are then allocated behind them,
@@ -16320,6 +16330,9 @@ test "GC requested compaction resumes the same baseline tier from a precise nati
1632016330 .enable_jit = true,
1632116331 });
1632216332 defer ctx.destroy();
16333+ // Preserve the deliberate pre-compaction placement independently of the
16334+ // production nursery threshold.
16335+ ctx.gc.?.nursery_threshold_bytes = std.math.maxInt(usize);
1632316336
1632416337 _ = try ctx.evaluate(
1632516338 \\globalThis.preciseDiscardFunctions = [];
@@ -18234,6 +18247,29 @@ test "enable_gc: mid-script collection reclaims garbage during a running loop (b
1823418247 try std.testing.expect(ctx.gc.?.live_cells < 20000);
1823518248}
1823618249
18250+ test "enable_gc: precise checkpoints reclaim a cache-local nursery batch" {
18251+ const ctx = try Context.createWith(std.testing.allocator, .{ .enable_gc = true });
18252+ defer ctx.destroy();
18253+ const heap = ctx.gc.?;
18254+ try std.testing.expect(heap.nursery_threshold_bytes > precise_gc_nursery_threshold_bytes);
18255+
18256+ const saved = gc_mod.setActiveContext(ctx);
18257+ defer gc_mod.restoreActiveContext(saved);
18258+ while (heap.young_bytes < precise_gc_nursery_threshold_bytes) {
18259+ const object = try heap.create(value.Object, .object);
18260+ object.* = .{};
18261+ object.initInlineSlots();
18262+ }
18263+ try std.testing.expect(!heap.shouldCollectYoung());
18264+
18265+ var machine = ctx.interpreter();
18266+ machine.gc_precise_safepoint = true;
18267+ const minor_before = heap.minor_collections;
18268+ try std.testing.expect(ctx.collectYoungMidScriptIfNeeded(heap, &machine));
18269+ try std.testing.expectEqual(minor_before + 1, heap.minor_collections);
18270+ try std.testing.expect(heap.young_bytes < precise_gc_nursery_threshold_bytes);
18271+ }
18272+
1823718273test "enable_gc: mid-script safepoints collect nursery before old heap" {
1823818274 const ctx = try Context.createWith(std.testing.allocator, .{ .enable_gc = true });
1823918275 defer ctx.destroy();
0 commit comments