Problem
Fresh Linux x86-64 validation of #628 and #633 reaches past both prior allocation fixes, then exhausts the unchanged 4 MiB recovery witness while duplicating the binding name for the loop body’s const o: unit shard 0.
The tree walker allocates a new GC Environment, binding map, and duplicated names every time a lexical block is entered. Fresh lexical-environment identity is observable only when a closure captures that environment. For an uncaptured repeated block, retaining each dead side store until the next successful collection needlessly consumes the fail-closed realm budget; on Linux the next tiny binding-name allocation is the first request after no recoverable slack remains.
Required implementation
- Recycle an exited block environment only when it was not captured and has no pending disposable state.
- Fully reset and reclaim its binding tables, duplicated names, side tables, values, flags, and parent before reuse; do not retain prior lexical state.
- Keep the reusable environment per interpreter, root it precisely while cached, and relocate its pointer at moving GC checkpoints.
- Preserve fresh environments for captured closures, nested captures, function-body scopes,
using/abrupt disposal, TDZ, Annex B block functions, and no-GIL ownership. - Do not raise the heap cap, bypass accounting, force a collection on every block, or use a process-global/AST-mutating cache.
Acceptance
- The unchanged
Context heap_limit_bytes never publishes a torn object under recovery pressurewitness passes on exact Zig dev.1818 Linux and under TSan. - Focused tests prove an uncaptured repeated lexical block reuses bounded storage while closures from distinct entries retain distinct bindings.
- Existing block-scope, TDZ, Annex B, disposal, interpreter/VM identity, and moving-GC witnesses pass.
- Matching full unit shards report zero failures/leaks attributable to this change.