ReviewOS

zig-utils/zig-js

GC: fix stale precise roots during shared JIT collection

#510
Closed chris opened this 9 days ago ·
9 days ago

Parent: #476 Blocks: #460 Related: #482

Failure

The clean full V15 representative publication run deterministically panics in the four-lane shared collections row:

env LC_ALL=C bench-comparison-zig-js shared representative_collections 450 7 4

The precise young-collection root scan reaches markValue with a string-tagged value whose pointer is not a GC or valid unmanaged string cell (GC mark of non-GC cell). The stack crosses a VM/JIT native-operation call and serviceGcSafepoint; both current f5363e46 and its literal parent 423b9ba0 reproduce, so this predates the generator-tracing recovery fix.

The benchmark collector failed closed and wrote no raw/report evidence.

Acceptance

  • Identify the exact registered interpreter root and ownership/lifetime violation.
  • Fix the root contract without suppressing validation, disabling GC/JIT, changing the scored workload, or weakening the shared path.
  • Add a focused deterministic regression that exercises the JIT/VM/shared young-collection boundary.
  • Pass the exact four-lane seven-sample collections command repeatedly.
  • Pass focused TSan plus relevant parallel GC/threadfuzz gates.
  • Resume the clean full #460 publication run and link the resulting evidence.
9 days ago

Fixed on main in 9d0227a3.

Root cause: VM closures retain lexical bindings in arena-owned Function.frame chains, but Function tracing and relocation covered managed Function fields and bytecode constants without visiting those captured frame slots. A moving minor collection could move/free a captured managed string while the suspended frame kept its old address. Re-entering the closure made that stale frame an active VM ancestor, and the next shared/JIT young collection correctly rejected the non-GC header.

The fix makes the captured chain a precise Function edge. Concurrent marking takes the existing escaped-frame slot lock used by upvalue mutation; world-stopped relocation rewrites the same slots after all realm mutators park. No GC/JIT mode, workload, validation, or strict header check was weakened.

Validation:

  • literal parent 423b9ba0 and pre-fix f5363e46 both reproduce the exact four-lane collections panic;
  • fixed exact row passes 21/21 samples across three consecutive runs, checksum 450464400 throughout;
  • new required-bytecode moving-nursery regression passes in Debug and TSan;
  • structural Function trace/relocation test covers a two-frame captured chain;
  • existing shared exact-rendezvous moving-nursery witness passes under TSan;
  • CI-equivalent TSan mid-GC profile: 90 programs from seeds 1-2, 0 failures;
  • full sharded unit suite: 1,533 passed, 1 expected skip, 0 failed/leaked.

Test262 was not rerun because this slice changes GC reachability/relocation ownership rather than JavaScript semantics or corpus expectations.

9 days ago

The Promise-job follow-up uncovered after captured-frame relocation is fixed on main by 33150087, closing #511.

#510 fixed suspended VM frame edges; #511 fixes native Promise-job locals across the same moving safepoint boundary. Together they make the exact bounded eight-lane async/microtask workload pass repeatedly with a stable checksum. The remaining full-scale retention/performance issue is tracked separately in #512.

Sign in to comment on this issue.