You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 6abb9ee
Browse filesBrowse the repository at this point in the historyBrowse files
fix(gc): D-556 root cause — persist analysis roots past their bracket + conservative tree_walk stack scan
Smell-audited: 2: the whack-a-mole arc itself was the smell — three
structural layers landed instead of more per-site brackets: (1) the ROOT
CAUSE, (2) a conservative backstop, (3) the D-555 sweep as the standing
detection net. D-557 (a separate, GC-independent tree_walk shadow/slot
bug the verification uncovered) is filed with a deterministic repro.
ROOT CAUSE (the deftest-BFS "integer overflow" panic + most of the D-555
family): fn-body Node literals live in the heap-lifetime analyzer arena,
but the ADR-0169 AnalysisFrame DROPPED its roots at form end — correct
only for Nodes that die with their form. A (defn f ...) body Node
survives via Function.body, so its literal pool (name strings, quoted
collections — the swept-string probe showed "map"/"clojure.core"/"*in*"
being swept) was unrooted from the first post-definition collect on. The
vm masks the EXECUTING chunk via the A1 pool root; a not-yet-executed
fn's pool had the same latent hole on BOTH backends.
FIX: root_set.endAnalysisPersist — production seams (driver x2, loader,
repl, nrepl, evaluator oracle) persist bracket roots into
gc.persisted_analysis_roots (a GcHeap field walked after
permanent_roots; the same lifetime the Nodes already have). Test
fixtures keep the dropping endAnalysis.
STRUCTURAL BACKSTOP: mark_sweep.conservativeStackScan — at collect, walk
the collecting thread's native stack from the ADR-0157 anchor (now
shared: root_set.conservative_stack_top) + a setjmp register spill
(Boehm GC_push_regs idiom), decode words via the heapHeader membrane
(zero-address pre-filter), validate against a per-collect live-set hash,
pin matches. Sound under non-moving mark-sweep; tree_walk-only (the vm
never sets the anchor).
VERIFIED: the original rush-hour repro passes 3/3 at default AND 1MB
thresholds on tree_walk; sym-meta GC e2e green; vm smoke green
(gc_torture step included).
Copy file name to clipboardExpand all lines: .dev/debt.yaml
+8-3Lines changed: 8 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -24,11 +24,16 @@
24
24
# md-table-align no longer pads it to ~800 KB of whitespace.
25
25
26
26
active:
27
+
- id: "D-557"
28
+
status: "open (2026-07-07, found DURING the D-556 verification — a SEPARATE, GC-independent tree_walk bug). The rewritten cw-arcade rush-hour solver (two-list amortized queue: 3-arity self-call chain + loop [front back visited] + (recur (reverse back) () visited) + a let that destructures [state path] (first front) and SHADOWS front with (rest front)) fails on the tree_walk backend with `-nth on Long` at the destructure line — (first front) returns a Long, i.e. a slot/shadow resolution defect. DETERMINISTIC; vm green on the identical code; CLJW_GC_THRESHOLD_MB=100000 (GC off) still fails -> NOT a rooting issue. Direct call fails too (no clojure.test needed). Simple synthetic shadow shapes pass — the trigger needs the real solver's combination (multi-arity self-call + reverse/list queue + same-name shadow + for). REPRO: scratchpad rhmin copy (or cw-arcade apps/rush-hour post-2026-07-07-edit sources), tree_walk build, cljw -cp src run_direct.clj. Fix by minimizing from the real solver; land the distilled case as a diff-oracle case (the F-012 net SHOULD have caught this — its absence is the gap D-555's cadence work addresses)."
29
+
category: "runtime"
30
+
barrier: "none — deterministic repro in hand (tree_walk build + the rhmin copy). Minimize -> analyzer slot-allocation fix -> diff case."
31
+
last_reviewed: "2026-07-07"
27
32
- id: "D-556"
28
-
status: "RE-NARROWED 2026-07-06 (late session): the panic REPRODUCES — the split observation is explained: the repro binary was a -Dbackend=tree_walk build (the --version fingerprint shows \", tree_walk\"); the vm default never panics. ROOT CLASS confirmed by bisection: CLJW_GC_THRESHOLD_MB=100000 (auto-collect off) -> 3/3 tests pass; default threshold -> deterministic panic; 1MB threshold -> an EARLIER, cleaner corruption (\"+: expected number, got nil\" inside board.clj occupied). So: tree_walk holds evaluation intermediates ONLY on the C stack, and a mid-eval ADR-0164 auto-collect sweeps them — the corrupted-string usize overflow in Writer.drain is the downstream symptom. SEVEN real unrooted sites were found+fixed this session (A6 locals: callMethodImpl + driver.evalForm + evaluator oracle; A6b progressive-publish ArgsFrame: evalCall callee+args, ctor/instance/static interop args, vector/map/set literal accumulators, evalRecur pending buf, evalBinding pass-1 inits) — symbol-meta GC loss is FIXED — but the rush-hour repro STILL panics: at least one more unrooted path remains (the 1MB trace points at a closure/lazy intermediate in a some-fn/for chain). FINISHED-FORM DECISION RECORDED: stop the whack-a-mole — implement a CONSERVATIVE C-STACK SCAN for the tree_walk backend: at collect, walk the native stack words from the ADR-0157 stack_base anchor (already threadlocal!) down to the collector frame, decode each word as a potential NaN-boxed heap Value, validate the pointer against a per-collect hash set of live objects (built in one O(live) pass over the live list), and pin matches. Non-moving mark-sweep makes conservative pinning sound (false positives just delay a free). ~100-150 lines in root_set/mark_sweep + a tree_walk-registered stack-range provider. This makes ALL C-stack intermediates safe structurally and lets the explicit-bracket sites become optimizations rather than correctness requirements. Repro: cw-arcade rush-hour solver-test on a tree_walk build (see the private handover note); ALSO reachable via bash scripts/check_vm_parity.sh."
33
+
status: "ROOT CAUSE FOUND + FIXED 2026-07-07 (the deftest-BFS integer-overflow panic + the D-555 GC family). The swept-string probe showed the FIRST post-bootstrap collect sweeping foundation strings (\"map\", \"clojure.core\", \"*in*\") on the tree_walk backend: fn-body Node literals live in the heap-lifetime analyzer arena, but the ADR-0169 AnalysisFrame bracket DROPPED its roots at form end — correct only for Nodes that die with their form; a (defn f …) body Node survives via Function.body and its literal pool was unrooted from the first collect on (the vm masks the EXECUTING chunk via the A1 pool root; a not-yet-executed fn had the same latent hole). FIX: root_set.endAnalysisPersist — production seams (driver x2, loader, repl, nrepl, evaluator oracle) persist their bracket roots into gc.persisted_analysis_roots (a GcHeap field, walked after permanent_roots; same lifetime the Nodes already have). ALSO landed in the same arc: the conservative C-stack scan (mark_sweep.conservativeStackScan: stack words from the ADR-0157 anchor + a setjmp register spill (Boehm GC_push_regs idiom), validated against a per-collect live-set hash, pinned — sound under non-moving mark-sweep) as the STRUCTURAL backstop for tree_walk's C-stack intermediates, and the 7 explicit A6/A6b brackets. VERIFIED: the original repro (rush-hour solver-test, pre-edit sources preserved in the scratchpad rhmin copy) passes 3/3 at BOTH default and 1MB thresholds on tree_walk; sym-meta GC e2e green; vm untouched. REMAINING out of scope: registered-WORKER-thread stacks are not conservatively scanned (parked at alloc-prologue; agent+tree_walk is the known D-418-adjacent area)."
29
34
category: "runtime"
30
-
barrier: "none — live deterministic repro (tree_walk build + cw-arcade rush-hour solver-test; vm default unaffected). NEXT UNIT = the conservative C-stack scan (the finished form; design in status). The 7 explicit-bracket fixes landed 2026-07-06 remain correct and useful regardless."
31
-
last_reviewed: "2026-07-06"
35
+
barrier: "FIXED for the reported class — keep open until the D-555 re-sweep (check_vm_parity.sh) confirms which of the 6 findings cleared, then discharge with the sweep numbers."
36
+
last_reviewed: "2026-07-07"
32
37
- id: "D-555"
33
38
status: "RE-NARROWED 2026-07-06 (same day): finding (1) watch_recursion SEGFAULT is FIXED — the ADR-0157 native-stack guard (threadlocal stack_base anchor + 6MiB budget) existed ONLY in vm.zig; the tree_walk arm is now ported (per-node check in eval; a threadlocal read + two compares). SIGSEGV -> catchable stack_overflow, all watch/validator/reducer reentry cases green on BOTH backends. The e2e 500-deep legit-recursion cases were vm-shaped: tree_walk pays ~30KB native per HOF step / ~13KB per direct step (ReleaseSafe), so 500 PHYSICALLY exceeds the 6MiB budget (pre-guard: raw SIGSEGV) — cases (6)/(7) are now backend-aware (100/300 deep on tree_walk; --version gained a \", tree_walk\" marker for detection, vm banner unchanged). FORWARD: raising tree_walk depth to clj-like levels needs build.zig stack_size + an ADR-0157 budget amendment (depth-2) — take up if the oracle depth becomes limiting. REMAINING (4 findings): (2) phase16_gc_torture dies; (3) phase14_symbol_metadata sym_meta_gc_survives nil-want-1 (GC rooting gap, tree_walk-only); (4) phase14_seq_helpers2 interleave_large got-empty (possibly the SAME budget physics on a deep lazy realize — triage vs a rooting bug); (5) phase15_dynamic_var guard + (6) phase15_require_fs missing, both got-2-want-1 (exit-code shaped). ORIGINAL: six e2e fail on -Dbackend=tree_walk (D-553 first sweep), corpus green, F-012 oracle green."
0 commit comments