Repository navigation
GC backlog re-verification sweep, 2026-08-02 #7262
Description
Re-verification sweep of the open GC/repsel/lint backlog against current main, prompted by the #7194 precedent: a real, deterministic GC bug that was already fixed by #7249 and sat open only because nobody re-tested it. The backlog grew faster than verification could retire it largely because gc-stress reported skipped on 12/12 nightly main runs until #7253 fixed its if:.
10 issues are fixed. 17 still reproduce. 2 cannot be concluded either way, and that is the most important finding here.
Method, and the bar every FIXED claim had to clear
- Base:
main=d4ac65f21(refactor: split the 16 files over the 2000-line lint gate #7256), built--profile perry-dev -p perry -p perry-runtime-static -p perry-stdlib-staticin an isolated target dir,PERRY_RUNTIME_DIRpinned to it,PERRY_NO_AUTO_OPTIMIZE=1. Oracle: node26.5.1, matching.node-version. - Acceptance is the allocation-point arm: compile with no compile-time GC env, run with
PERRY_GC_HEAP_LIMIT=8 PERRY_GC_INCREMENTAL=0 PERRY_CONSERVATIVE_STACK_SCAN=off. fix(gc): run the globalThis bootstrap in a no-move window (#7217) #7249 established thatPERRY_GC_MOVING_LOOP_POLLS=1cannot expose an unrooted local in runtime code that does not re-enter user JS, so a loop-polls-green result was not accepted as evidence for anything. - 10 runs per cell, byte-compared against the oracle.
- Movement confirmed per issue. Every green cell reports its own
copy-minorcount and summedcopied_objectsfromPERRY_GC_DIAG=1on the same binary and the same arm. A green cell withcopied_objects=0was not accepted as a fix — see the two NOT VERIFIABLE rows, which is exactly the CLAUDE.md hazard "the gate runs but its subject never did".
Results
| # | bucket | evidence | reason |
|---|---|---|---|
| #7200 | FIXED | test_gap_gc_spread_accessor_rooting 10/10 byte-exact; 5 copy-minors, 17 861 copied |
Was exit=139 10/10. Fixed by #7207 (js_object_assign_one rooting) + #7249 (bootstrap no-move window). |
| #7201 | FIXED | test_gap_gc_static_block_this_rooting 10/10; 5 copy-minors, 17 364 copied |
Was bad 1 10/10. Fixed by #7207 (the PutValueSet key, not the static-this cell) + #7249. |
| #7202 | FIXED | lower_call/new.rs:1164 now root_entry_alloca(ctx, &this_slot); --unrooted-allocas --moving-only 0 violations over 138 modules / 5 779 gc-capable allocas |
Both named instances closed: inline-ctor this_slot by #7207, closure this/new.target by #7216. Residual staging array is #7210 §2. |
| #7208 | FIXED | test_gap_gc_closure_this_capture_rooting 10/10; 5 copy-minors, 17 325 copied |
Was exit=139 3/3. Fixed by #7216 — closure.rs now reserves and binds the +1 slot method.rs always had. |
| #7209 | FIXED | test_gap_gc_catch_param_rooting 10/10; 6 copy-minors, 1 614 copied |
Was message 8 field 8 of 400. Fixed by #7216 — the reserved catch-param index is now bound. |
| #7211 | FIXED | gc_root_dominance_check.py --moving-only: 5 hits → 0, over 2 282 functions / 138 modules / 2 950 root stores, --seeded-violations 40 → 40 planted, 40 caught, 0 MISSED |
Fixed by #7226 (7f29b804c): protect_handle now includes !named_statics.is_empty(). The seeded arm proves the checker still parses the IR, so the 0 is a verdict, not a parse failure. |
| #7215 | FIXED | object/alloc.rs:1171 let owned: String = s.to_string(); before the loop |
Fixed by #7216. No runtime witness exists and the issue says so; the code change is the claim and it is present. |
| #6991 | FIXED | test_gap_repsel_ptr_shape_locals 10/10 on alloc-point, +force_evac, +force+verify (6 076 copied); zeal 3/3 at 6 copy-minors / 15 724 copied vs the 4 640 cited in the report |
Was SIGSEGV in a compiled ctor across the globalThis-population collection — literally #7249's root cause. Green at 3.4× the cited movement. |
| #6992 | FIXED | test_gap_repsel_proven_this_frozen 10/10 on the same three arms (6 049 copied); zeal 3/3 at 5 copy-minors / 15 623 copied vs the 4 614 cited |
Was TypeError: bump is not a function. Same cause, same fix. Green at 3.4× the cited movement. |
| #7066 | FIXED (duplicate) | same witness as #7208 | Textually the same defect as #7208 (closure captured this in an unbound alloca), fixed by the same commit #7216. |
| #7246 | STILL OPEN | B 2 10/10 (oracle B 0); 88 copy-minors, 15 504 copied. Also on +PERRY_GC_FORCE_EVACUATE=1, 10/10 |
Survives #7243 as the issue predicted. alloc_symbol still gc_malloc(..., GC_TYPE_STRING); no GC_TYPE_SYMBOL exists in the tree, so nothing can trace the description pointer. |
| #7247 | STILL OPEN | test_gap_gc_regexp_receiver_rooting exit=139 10/10 |
Unchanged by #7249. js_regexp_new still opens with string_as_str(pattern)/string_as_str(flags). |
| #7248 | STILL OPEN — and its numbers are now wrong | bad char 2 count 2 6/10, bad char 3 count 3 4/10 |
See the correction comment on the issue: it is neither 1 nor deterministic on current main. |
| #7251 | STILL OPEN | no GcSuppressScope in global_this/generator.rs or global_this/typed_array.rs |
Deliberately not shipped with #7249; the two tower builders are still ungated and unwindowed. |
| #7254 | STILL OPEN | 3/3 rc=134, gc evacuation verification failed: stale forwarded pointer in shadow stack roots |
Reproduces exactly as written on d4ac65f21. |
| #7255 | STILL OPEN | default and verify_evac arms: 0 copy-minors on 5/5 files tried; evac_minor on the same binaries: 1, 2, 5, 5, 8, 88 |
The requires=scavenge arms are inert. This is the root blocker for two other rows below. |
| #7245 | STILL OPEN | cargo test -p perry-codegen --test native_proof_regressions: 235 passed, 16 failed — the same 15 invalidation::* plus typed_f64_receiver_method_clone_raw_loads_after_composed_guards |
Byte-identical failure list. |
| #7257 | STILL OPEN | benchmarks/ci_public_baseline_check.py → public artifact benchmark inputs changed |
Byte-identical. Still gating steps 4–10 of lint. |
| #7258 | STILL OPEN | scripts/gc_store_site_inventory.py exit 1, 19 findings |
Byte-identical finding list. |
| #7259 | STILL OPEN | scripts/addr_class_inventory.py exit 1 |
Byte-identical: both ratchet regressions (child_process/value_util.rs 3/1, fs/dirent.rs 3/2) plus the O_SYMLINK allowlist substring miss. |
| #7183 | STILL OPEN | PERRY_GC_MOVING_LOOP_POLLS present in object_cache.rs:908, absent from build_cache.rs's key list (lines 24–53) |
Exactly the state the issue describes. |
| #7210 | STILL OPEN | codegen/helpers.rs:1275 still emit_raw("alloca [N x double]"), still filled interleaved |
§1's class-keys bullet is now a known false-positive class — see the correction comment. |
| #7231 | PARTIALLY FIXED | test_gap_gc_process_env_cache_rooting 10/10, 5 copy-minors / 16 750 copied; scan_process_env_cache_roots_mut registered at gc/mod.rs:639 |
#7239 rooted the three independently-verified caches and seven more. The unverified tail is still unrooted: no gc/ reference for SHAPE_CACHE, BUILTIN_MAP_SET_VALUE_BITS, MODULE_PATH_REGISTRY, RECOVERABLE_ERRORS, GLOB_ITERATORS, internally_handled. |
| #7261 | STILL OPEN | strip_dedup.rs removes extract_dir at 10 sites, never tmp_base |
The perry_strip_<pid> dir itself is never removed. |
| #6984 | STILL OPEN | PERRY_PTR_SHAPE_LOCALS=0 → TypeError: Cannot read properties of undefined (reading 'area'), rc=1, exact 4-line diff (p5a-iface/named/inherit/collide) |
Unchanged. This is also #6976 item 2. |
| #7016 | STILL OPEN | test_gap_repsel_p4a3_numarray_growth: 0 gc-diag lines at HEAP_LIMIT=8, +INCREMENTAL=0, and HEAP_LIMIT=2 |
Matches the issue's table exactly. |
| #6978 | STILL OPEN | test_gap_repsel_canonical_i32: 0 cycles at HEAP_LIMIT=8 alone, 1 with +INCREMENTAL=0 |
The issue's own acceptance signal (cycles > 0 with no other GC env) is still unmet. |
| #6982 | NOT VERIFIABLE | 10/10 byte-exact — but 0 copy-minors, and still 0 with PERRY_GC_FORCE_EVACUATE=1 added |
INCREMENTAL=0) no longer runs a copying minor at all, so a green run says nothing about a crash inside a copying minor. Blocked on #7255. |
| #7018 | NOT VERIFIABLE | PERRY_GC_TRACE=1 5/5 rc=0, +force_evac 5/5 rc=0 — but 0 copy-minors with AND without the flag |
copied_objects of 261 251–379 072 for this file; this build reaches zero. The subject never ran. Blocked on #7255. |
The two NOT VERIFIABLE rows are the finding
#6982 and #7018 both go green, 10/10 and 5/5, byte-exact against the oracle — and both are worthless as evidence, because copied_objects=0. Closing them on those runs would have been the single most damaging outcome of this sweep: two real crash reports retired on the strength of an arm that had gone inert underneath them.
That inertness is #7255, and it is now load-bearing for more than a stale header comment. Until the requires=scavenge arms relocate again, any "fixed under the shipped configuration" claim about the copying minor is unfalsifiable. #7255 should be treated as the top of this queue, ahead of the individual rooting bugs.
What genuinely remains
Bugs with a live, reproduced witness — #7246, #7247, #7248, #7254, #6984 (= #6976 §2), #7251, #7210, #7231 (tail), #7261
Gate problems — a gate that cannot fail, or cannot run — #7255 (four PR arms inert), #7257 → #7258 → #7259 (lint red at step 3, so steps 4–10 including the new gc_gate_wiring_check.py are unreachable), #7245 (16 red, not per-PR gated), #7016 (a corpus row that asserts nothing), #6978 (shipped config performs zero collections), #7183 (build-cache key)
Blocked on a gate before they can be judged — #6982, #7018
What was NOT tested, explicitly
- The repsel design/engineering issues (repsel row 1: scoping Ptr<Shape> beyond locals — measured, promotion is 0 on the motivating workload; build --opt-report (#6952) first #7034, repsel: promotion census — make "zero values promoted" a visible, gated state instead of an invisible one #7106, --opt-report: a function whose call sites were all inlined away never enters the spec-ABI decision loop, so the report cannot say "moot" #7111, --opt-report: candidate-generation pre-filters for Ptr<Shape> / Ptr<NumArray> record nothing, so "0 candidates" cannot be read #7112, Ptr<Shape>: a scalar-replaced object is still counted as a promotion, and emits nothing #7115, repsel: bound a loop accumulator by trip count x step magnitude, so a bare accumulator can take canonical i32 #7123, repsel Phase 5a: the
deleteshape barrier is module-scoped, but a proven-thisreceiver is aliased across modules by construction #7143, repsel: #7132's profitability refusal over-fires on x86-64 (fccmp-fusion term is AArch64-specific) — cost model needs a per-target term #7146, repsel: deforestation hides the record-producer shape from the Ptr<Shape> element rule #7150, repsel: Ptr<Shape> element reads — the four forms #7034 §3 does not cover #7151, repsel: dependency JS is walled by rule 1 (unbound allocations), not containment — 506 of 746 candidates #7152, repsel: rule-1 provenance for unbound allocations — the 506 are 96% record literals, and Perry's own CJS IIFE is the wall (#7152 follow-up) #7170, repsel: --opt-report panics on real dependency JS — RULE2 carries two tiers and render.rs asserts one #7234) and the GC engineering tracks (gc(measurement): poll-time moving buys ~nothing the 16 MB nursery cap does not already buy — delete the loop back-edge poll, do not build pin-based poll moving #7056, Stack maps vs the codegen shadow stack: viability experiment (Q1 passes; 0.44 MB text saved for 4.5-16.6 MB metadata) #7108, gc: port the native stack-map scanner to ELF/Linux (CI coverage + small-hardware numbers) #7173, gc: RewriteStatepointsForGC over managed-pointer SSA — the measured-only road to native-root file-size parity #7174, gc/perf: classify_heap_generation is 19% of batch.ts (57.4% total GC bookkeeping) with ZERO collections running — write-barrier tower needs its own lever (#5094 evidence) #7187, Sound recursive-numeric specialization to recover the ~20% #7238 cost on 05_fibonacci #7244, exp: in-process LLVM backend — stop shipping IR as text (Phase 0: feasible, byte-identical objects) #7241) — these are tasks and decisions, not reproducible defect reports, and nothing in the last 24h could have closed them. - gc-matrix: the collect-arm liveness predicate (cycles > 0) cannot tell a mid-program collection from a teardown one that frees nothing #7017, test(gc): temp-root codegen contract lives only in crates/perry-codegen/tests/, which does not run per-PR #6988, GC testing: PERRY_GC_FORCE_EVACUATE is inert for gc()-driven tests (full mark-sweep + forced conservative scan) — stress claims may be unsupported #6946, gc: PERRY_GC_VERIFY_EVACUATION reports clean on a demonstrable missing rewrite (verifier only self-checks the rewrite pass's own enumeration) #7035 — instrument/gate-design issues with no runnable pass/fail reproducer as written.
- runtime: a fourth unrooted-operand family — js_string_coerce as a plain ToString argument coercion (string built-ins, constructors) #6949, gc: constructor arguments on lower_new's non-class branches (Readline / imported ctor / new Function) and builtin.rs are still not precise roots #6986, codegen: shadow-frame is sized by HashMap len while slot indices come from a counter — duplicate qualifying Stmt::Let can assign an index past the frame end #6995, codegen: pointer_locals types Expr::Uint8ArrayGet as Number unconditionally — a symbol-keyed read binds a heap value into a local with no shadow slot #6998 — statically-argued codegen/runtime rooting issues with no reproducer in the body. They were not structurally re-checked here.
- gc: perry_ffi::alloc_object + later copying minor SIGSEGVs in scan_slot on a bogus slot (0x9) — documented-FFI-only repro #7164 — needs an ext-crate FFI harness, not a
.tsfile. - buffer fast path: Uint32Array writes are lost after constructing Uint8Array(words.buffer) #7219, --verify-native-regions accepts a stale TypedArray buffer-view pointer #7220, spec-ABI misses a monomorphic Float64Array helper; helper form is ~3x slower than inline #7221 — non-GC buffer/spec-ABI issues outside this sweep's scope.
- The full
gc_repsel_matrix.sh --arms allcorpus run. gc-matrix: four of the six PR-gating arms run ZERO copying minors corpus-wide (default/verify_evac/cons_scan_off at copy-minor 0/49; header still claims 12/22) #7255 was confirmed from five files, not all 49. - Linux. Everything here is macOS arm64.
Activity
proggeramlug commentedon Aug 5, 2026
A dated snapshot whose conclusions no longer describe
main. Its own stated top-of-queue blocker #7255 is closed, ~30 further GC PRs landed on 08-03/08-04, and its two "NOT VERIFIABLE" rows (#6982, #7018) are now judgeable. What is needed is a successor sweep, not this record.