fix(runtime): keep old array growth targets out of nursery - #8651
fix(runtime): keep old array growth targets out of nursery#8651proggeramlug wants to merge 2 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 8 included reviews per hour; 4 remain after this review. 📝 WalkthroughWalkthroughArray growth now selects allocation by source generation. Old or non-nursery sources use old-born targets. Nursery sources use no-collection allocation with an old-born fallback. A regression test verifies old-generation forwarding targets. ChangesArray growth allocation
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to The change keeps array-growth targets in safe memory generations and adds regression coverage; no actionable merge-blocking risk remains beyond normal checks and review. Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
CI baseline comparison: the exact base commit The head-only signal is clean:
No failing check unique to the array-growth change was found. Local exact-commit release runtime validation remains 2,642 passed / 0 failed / 4 ignored, and the corrected ECS artifact passed 50/50 fresh Mac mini processes. |
* perf(codegen): materialize large constant array literals from a static descriptor + one bulk call (#8583 follow-up) A minified bundle data table is a giant nested constant array literal. The default lowering builds it procedurally — one `js_array_from_values` per sub-array plus the inline element stores — so the Claude Code bundle's `__33499` (a constant numeric array-of-arrays) lowered to 11,104 allocations and a 245k- instruction body that made `rewrite-statepoints-for-gc` fan out. This adds a codegen path that recognizes a LARGE, fully-constant array literal (number/int/bool/null/undefined, recursively nested arrays) and instead: * serializes the constant tree into a compact tagged blob emitted as module- private rodata, and * emits ONE call to a new runtime helper `js_value_from_const_descriptor` that materializes the whole nested structure in a single pass. The runtime builds a FRESH, mutable array each call (JS array literals are mutable, so the descriptor is a template, never a shared constant), under `GcSuppressScope` so the partially-built parents held across nested child allocations cannot be collected or moved — the same discipline `js_json_parse` and the lazy-array materializer use. All-number rows keep the raw-f64 layout; any pointer element downgrades the row via `store_array_slot`. Gated on a 256-node minimum, so small literals keep the fast inline bump-alloc path (no regression). `PERRY_CONST_ARRAY_DESCRIPTOR=0` reverts to the procedural path (A/B bisection + escape hatch). On a 3,000-row nested-array synthetic: the 3,000+ `js_array_from_values` calls collapse to one `js_value_from_const_descriptor` + a rodata blob; the compile drops from not-finishing-in-2min to 1.46s; output is byte-identical to the procedural build across the moving-GC matrix, with mutation-after-materialize and bool/null rows verified. Claude-Session: https://claude.ai/code/session_01TwxRkALrR9HKSF1zKLSTAF * docs(changelog): fragment for #8647 Claude-Session: https://claude.ai/code/session_01TwxRkALrR9HKSF1zKLSTAF * fix(runtime): complete computed property name reflection * perf(codegen): restrict the const-array descriptor to NESTED literals A flat constant scalar array (e.g. `[0; 2050]`) is already a single `js_array_alloc_literal` + inline stores — not the per-subarray fan-out the descriptor targets — and its inline path carries the precise per-slot write barriers a later push/store depends on (large_object_barriers). Gate the descriptor path on the literal containing at least one nested array element, so only genuine nested data tables (the __33499 shape) take it; flat arrays keep their existing path. Verified: the nested 3,000-row synthetic still collapses to one js_value_from_const_descriptor call, and large_object_barriers passes. Claude-Session: https://claude.ai/code/session_01TwxRkALrR9HKSF1zKLSTAF * fix(runtime): keep old array growth targets out of nursery * docs(changelog): note array growth generation fix * fix(codegen): guard block-creating lowerings against diverged (terminated) blocks (#8583) When a sub-expression provably diverges — a throwing operand (e.g. a captured TDZ access or const-reassignment) emits `js_throw_error_with_code` + `unreachable` — the current block is terminated. `LlBlock` silently drops any instruction emitted after a terminator (block.rs), so the setup instructions for the surrounding operation are discarded; but block-creating lowerings still emit fresh blocks that reference those dropped `%rN` registers, which the dialect builder rejects with "register %rN used but never defined" (dialect/mod.rs). The whole surrounding operation is unreachable on that path, so the fix is to emit nothing once the block is terminated. Two sites hit this in the Claude Code 2.1.112 bundle (both dead code after a proven-throwing operand): `lower_index_set_fast` (`a[i] = v`, closure `__44845`) and `emit_persistent_shadow_root_barrier` (a pointer root store, closure `__44449`). Each now returns early when `ctx.block().is_terminated()`. Also adds a `PERRY_DIALECT_DUMP=<dir>` diagnostic: on a dialect construction failure, `render_units_from_frozen` names the offending function and writes its full IR (typed insts rendered via `render_into`) — the failing unit never parses, so the normal `PERRY_SAVE_LL` post-parse dump cannot capture it. This is how the two sites above were located. Validated end-to-end: with these guards, the cli.js bundle codegens ALL 84 units with zero "used but never defined" errors (it previously failed at unit 25); the remaining blocker to a final binary is unrelated (host disk). Claude-Session: https://claude.ai/code/session_01TwxRkALrR9HKSF1zKLSTAF * docs(changelog): fragment for #8652 Claude-Session: https://claude.ai/code/session_01TwxRkALrR9HKSF1zKLSTAF * fix: complete test262 built-ins misc tail semantics * chore: fmt the stack and add the two missing changelog fragments #8646 and #8650 landed without a changelog.d fragment; #8650 also lowers the raw-handle ratchet 925 -> 923, which is recorded in its fragment. --------- Co-authored-by: Ralph Küpper <ralph@skelpo.com>
Summary
Root cause
js_array_growalways allocated the replacement backing array through the ordinary GC allocator. An old retainedGC_FLAG_FORWARDEDgrowth stub could therefore point into resetting nursery space. Copying minor GC does not trace that stub payload as a normal array slot, so the nursery target could be recycled while stale aliases still followed the old forwarding word. In the ECS workload this surfaced intermittently asCannot assign to read only property length of object.Validation
Nurserytarget) and passes after the fix (Oldtarget)cargo fmt --all -- --checkpython3 scripts/gc_store_site_inventory.py(1,542 files scanned)5000500050005000The broader
pre-tag-check.sh --quickrun passed the owned gates; its address-classification audit also reports three existing sites that are identical on currentmainand untouched by this PR.Summary by CodeRabbit
Bug Fixes
Tests