Skip to content

merge: land #8633 (spill estimate sees allocating literals + call temporaries) - #8634

Merged
proggeramlug merged 3 commits into
mainfrom
merge/8633
Aug 23, 2026
Merged

merge: land #8633 (spill estimate sees allocating literals + call temporaries)#8634
proggeramlug merged 3 commits into
mainfrom
merge/8633

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Lands #8633 plus one test fix it needs.

Audit

The change is sound and well-evidenced. It closes a genuine two-axis under-count
in the RS4GC spill estimate:

  • allocating object/array literals were not counted as safepoints at all, so a
    minified data table (no Expr::Call anywhere) looked safepoint-free — that is
    the __33499 shape that fanned out for >3 h / ~30 GiB;
  • slot_count counted only named pointer locals, missing the call-result
    temporaries RS4GC actually relocates.

Both directions over-approximate toward spilling, which is the correct bias
here: a false-positive shadow frame is cheap, a missed fan-out is not.

Interaction with #8623 (which raised the threshold to 32M because spilling
moderate functions measured slower): this pushes the other way by raising
estimates, so it's worth stating where they meet. The crossover is roughly
4,000–5,700 allocating operations before a function starts spilling — module-init
data-table territory, not hot code. The PR's "~2000+" is a conservative
statement of the same bound.

The test fix

#8633 changes the production formula to (slot_count + sites) × sites, but
#8623's two endpoint tests still called root_relocation_estimate(slot, sites)
directly:

case test computed production now
moderate 8.0M 12.0M
catastrophic 84.3M 11.3B

Both still land on the correct side of the 32M threshold, so nothing was broken
— but those tests exist specifically to pin both ends of the threshold, and they
would no longer catch a regression in the live_roots composition this PR
introduces. Extracted spill_live_root_count() so production and the tests
share one definition, and routed both endpoint tests through it.

Validation

  • All nine ratchets + cargo fmt --all -- --check: pass.
  • perry-codegen lib: 1182 passed, 0 failed (1180 on main + the two new
    safepoint tests).
  • Five relevant tests asserted by name, not inferred from the aggregate:
    array_and_object_literals_are_safepoints, nested_array_literals_recurse,
    and the three root_spill_default_tests.
  • No version-file edits; changelog fragment present and PR-keyed.

Fork PR, so this lands as a branch rather than a push to the contributor's head ref.

Summary by CodeRabbit

  • Bug Fixes
    • Improved garbage-collection safety for functions containing object and array literals, spreads, assignments, and nested allocations.
    • Large functions now more reliably preserve live references during runtime memory collection.
    • Enhanced diagnostics provide clearer information when references are moved to protected memory frames.

Ralph Küpper added 3 commits August 23, 2026 08:12
… the RS4GC root-spill estimate (#8583)

A minified bundle data table compiles as one giant array-of-arrays literal:
the Claude Code 2.1.112 bundle's `__33499` lowered to 11,104 `js_array_from_values`
allocations (one per sub-array) and ~20k GC safepoints. The shadow-frame spill
estimate (`slot_count * safepoint_sites`, #8583) missed it on two counts, so the
function stayed on native statepoints and `rewrite-statepoints-for-gc` fanned out
for >3h / ~30GiB — never reaching the post-rewrite budget assertion, which only
fires after a rewrite that here never finished:

1. `count_safepoint_sites` counted only Call/New-family expressions, not
   allocating object/array literals, which lower to a collecting runtime call
   (`js_array_from_values` / `js_object_*`) and each get an RS4GC statepoint.
2. `slot_count` counted only named pointer locals (the shadow-slot map), not the
   ~one pointer temporary each call result leaves live across later safepoints —
   the roots RS4GC actually relocates.

The estimate now counts allocating literals as safepoints and adds per-safepoint
temporaries to the root count, so a data-table-shaped function spills to the
shadow frame like the module entry does. Over-approximation biased toward
spilling (a false positive is a cheap shadow frame; a missed fan-out is not); a
function needs ~2000+ allocating ops to cross the default threshold, i.e. only
genuinely huge (usually module-init) functions. Verified on a 3,000-row
nested-array synthetic: estimate 3001x3001 -> spilled, compiled in 64s instead of
not finishing, byte-correct output.

Claude-Session: https://claude.ai/code/session_01TwxRkALrR9HKSF1zKLSTAF
#8633 changes the estimate to (slot_count + sites) x sites, but #8623's two
endpoint tests still called root_relocation_estimate(slot, sites) directly, so
they pinned a formula production no longer uses. Both still reached the right
verdict (moderate 8.0M vs 12.0M, catastrophic 84.3M vs 11.3B -- same side of
the 32M threshold either way), but they would no longer catch a regression in
the live_roots composition this PR introduces.

Extract spill_live_root_count() so production and the tests share one
definition, and route both endpoint tests through it.
@proggeramlug
proggeramlug merged commit ad25921 into main Aug 23, 2026
19 checks passed
@proggeramlug
proggeramlug deleted the merge/8633 branch August 23, 2026 06:32
@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 784b769e-27ea-4da1-b54f-b00b73be5207

📥 Commits

Reviewing files that changed from the base of the PR and between 38dac3b and 7359426.

📒 Files selected for processing (3)
  • changelog.d/8633-spill-inline-aware-estimate.md
  • crates/perry-codegen/src/codegen/helpers.rs
  • crates/perry-codegen/src/collectors/safepoint_sites.rs

📝 Walkthrough

Walkthrough

The codegen now counts allocating object and array expressions as safepoints. Root-spill estimates include one pointer temporary per safepoint and use the expanded count for spill decisions and diagnostics.

Changes

Root spill estimation

Layer / File(s) Summary
Allocating expression safepoints
crates/perry-codegen/src/collectors/safepoint_sites.rs
Object and array literals, including spread forms, now count as safepoints. Tests cover nested allocations.
Expanded root spill calculation
crates/perry-codegen/src/codegen/helpers.rs, changelog.d/8633-spill-inline-aware-estimate.md
spill_live_root_count adds safepoint sites to named root slots. Spill decisions and diagnostics use the expanded estimate. Tests use the shared formula and updated expected values.

Estimated code review effort: 3 (Moderate) | ~20 minutes

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch merge/8633

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant