Summary
DEFAULT_ROOT_SPILL_RELOCATIONS (4,000,000, codegen/helpers.rs) looks too low: on a synthetic entry function that estimates ~8M relocations, spilling made the compile slower than just letting RS4GC fan out — so the default fires on moderate cases that don't need it and pays the shadow-frame overhead for nothing.
Data (single runs, contended host — treat as rough)
Synthetic: N=2000 top-level object bindings, all live via a prev chain, each built through a call → ~2000 roots × ~2000 safepoints ≈ 4–8M estimated relocations.
| config |
compile |
| no outline, spill OFF (plain RS4GC fan-out) |
180s |
no outline, spill ON (4M default → spills main, 8M est) |
303s |
The spilled build emits ~4001 js_shadow_slot_* for the frame; that IR is itself expensive to build, and here it cost more than the fan-out it avoided. RS4GC fan-out only becomes catastrophic at much higher relocation counts (the Claude Code cli.js @main: ~74M est, never finishes at -Os).
Recommendation
Raise the default so spilling fires only in the genuinely-catastrophic regime. The exact crossover is unmeasured — it's between ~8M (fan-out fine at 180s) and ~74M (fan-out never finishes) — so bracket it with a couple of synthetic points (e.g. 16M / 32M / 48M) and set the default just below where fan-out stops finishing in bounded time. Raising is backstopped by the post-RS4GC instruction-budget assertion (#8586), which fails loudly rather than hanging if the new threshold is ever too high for a given function.
Note
This is compile-time only — spilling main (run-once init) does not affect the emitted binary's runtime. Lower priority than the runtime-perf work, but a real compile-time regression on moderate-fan-out functions. Context: #8583 (the regression), #8589 (the spill), #8586 (the budget backstop).
Summary
DEFAULT_ROOT_SPILL_RELOCATIONS(4,000,000,codegen/helpers.rs) looks too low: on a synthetic entry function that estimates ~8M relocations, spilling made the compile slower than just letting RS4GC fan out — so the default fires on moderate cases that don't need it and pays the shadow-frame overhead for nothing.Data (single runs, contended host — treat as rough)
Synthetic: N=2000 top-level object bindings, all live via a
prevchain, each built through a call → ~2000 roots × ~2000 safepoints ≈ 4–8M estimated relocations.main, 8M est)The spilled build emits ~4001
js_shadow_slot_*for the frame; that IR is itself expensive to build, and here it cost more than the fan-out it avoided. RS4GC fan-out only becomes catastrophic at much higher relocation counts (the Claude Codecli.js@main: ~74M est, never finishes at-Os).Recommendation
Raise the default so spilling fires only in the genuinely-catastrophic regime. The exact crossover is unmeasured — it's between ~8M (fan-out fine at 180s) and ~74M (fan-out never finishes) — so bracket it with a couple of synthetic points (e.g. 16M / 32M / 48M) and set the default just below where fan-out stops finishing in bounded time. Raising is backstopped by the post-RS4GC instruction-budget assertion (#8586), which fails loudly rather than hanging if the new threshold is ever too high for a given function.
Note
This is compile-time only — spilling
main(run-once init) does not affect the emitted binary's runtime. Lower priority than the runtime-perf work, but a real compile-time regression on moderate-fan-out functions. Context: #8583 (the regression), #8589 (the spill), #8586 (the budget backstop).