Repository navigation
gc: gc-moving-witnesses needs a dependency-scale RUNTIME witness (#7280 ask 3) #7717
Description
The one unmet ask from #7280, refiled on its own.
Done and closed with #7280: ask 1 (keep the stopgap) and ask 2 (fix the overclaiming comment).
Still missing: ask 3 — a dependency-scale RUNTIME witness in .github/workflows/gc-moving-witnesses.yml. Everything that gates this family today is either a synthetic fixture or a static check over emitted IR. Neither can observe the failure mode #7154's residual actually presents as.
Why a runtime witness specifically
The #7154 class is invisible to every static instrument and to every runtime GC probe at the moment of collection — there is nothing for the collector to find. It surfaces cycles later, in a different function, as TypeError: value is not a function. The evidence that scale matters is in #7280 itself: the loop-polls configuration was red 0/30, and stock zod alone failed 5/40 — i.e. real dependency-shaped code found what the fixtures did not.
scripts/gc_root_dominance_check.py reads LLVM IR, so a runtime-side cache holding a raw heap pointer is structurally invisible to it. That population now has its own inventory and gate (#7231 / #7695), but an inventory is not a witness.
What the witness must assert, or it is worth nothing
This is the trap that has cost this repo the most, so treat it as the acceptance criterion rather than a nicety:
- Prove the collector RAN. Since gc: zeal + compiled loop polls performs zero copying minors on compute-only benches — green instrument runs may be vacuous #7604 a zeal run prints
[gc-zeal] forced_collections=N copying_minors=M moved_objects=Kat exit and exits 70 if N or M is zero. Use it. A run with zero copying minors witnesses nothing, andPERRY_GC_PROTECT_FROMSPACE=1on such a run protects nothing while exiting clean — I hit exactly that today on gc: js_native_call_method reuses one copy of its rooted receiver across ~1200 lines of allocating probes #7528 and nearly recorded the wrong conclusion. - Compile with
PERRY_GC_MOVING_LOOP_POLLS=1(default off since fix(gc): disable evacuating minor by default pending #7154 (use-after-free on dynamically-added fields) #7161), or the loops emit no back-edge poll and zeal never fires inside them. - Watch
PERRY_GC_PROTECT_FROMSPACE_DEPTH. The default of 4 is far too small for real code — GC: evacuating minor drops an old-to-young field[1] edge, crashing with 'value is not a function' #7154's own reproducer needed 800, because the value crossed 600 polls between its last valid observation and its stale use.
The four ways this gate could ship unable to fail
Per CLAUDE.md, all four have bitten this repo: continue-on-error: true; absent from branch protection's required contexts; concurrency with unconditional cancel-in-progress (scope it to pull_request, let main runs queue); and the gate running while its subject does not. Also: a new gate has never been green, so promoting it to required immediately blocks every open PR — run it once on main, then promote. Leaving that second step undone is exactly how gc-stress ended up reporting failures without blocking anything.
Activity
test(gc): dependency-scale runtime witness for the moving collector (#…
test(gc): dependency-scale runtime witness for the moving collector (#…
test(gc): dependency-scale runtime witness for the moving collector (#…
test(gc): dependency-scale runtime witness for the moving collector (#…
proggeramlug commentedon Aug 10, 2026
Closed by #7793 (merged ae6cd77, v0.5.1460): the dependency-scale runtime witness now runs the 81-module zod corpus under the rate-1 seeded schedule + from-space quarantine in gc-moving-witnesses.yml, with fail-closed liveness assertions verified by sabotage. Bonus: its quarantine-off sabotage arm flushed out a latent rooting-shaped TypeError on main, filed as #7803.