fix(vcr-ra): local promotion must never cause a compile failure (#474)#475
Merged
Conversation
v0.14.0 made i32 local promotion default-on. Promotion pins eligible locals into
callee-saved r4-r8, halving the operand-stack temp pool; on a dense function that
tips register allocation past what it can recover, turning a working compile into
a hard "register exhaustion" skip — a regression from v0.12.0 (reported on a real
engine-control function: compiled on 0.12.0, skipped on 0.14.0).
Fix (arm_backend.rs `select_direct`): factor the exhaustion-recovery ladder over a
`local_promote` flag and run it with promotion FIRST — so every function that
compiles today is bit-identical (frozen byte gate stays green) — then, only if it
still ends in register exhaustion, fall back to the promotion-off ladder
automatically. That is exactly what the `SYNTH_NO_LOCAL_PROMOTE=1` workaround does
by hand, now automatic. Promotion is an optimization; it must never be the *reason*
a function fails to compile. The fallback keys on "register exhaustion" generally,
so it covers both the single-register and i64-spill-pool exhaustion classes
promotion can trigger.
Verified:
- frozen byte gate green — every currently-compiling fixture bit-identical (the
fallback is reached ONLY by functions that exhaust WITH promotion);
- the generic repro (promotion_exhaustion_fallback.wat) FAILS to compile without
the fix ("register exhaustion: i64 spill-slot pool exhausted") and COMPILES with
it, .text byte-identical to the SYNTH_NO_LOCAL_PROMOTE=1 build (proving the
fallback fired and landed on the v0.12.0 frame-slot path);
- new CI regression test promotion_never_causes_compile_failure_474.
Refs #474, #462, #242
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jun 24, 2026
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
avrabe
added a commit
that referenced
this pull request
Jun 24, 2026
Roadmap artifact, no codegen change. The north star (#242) replaces synth's single-pass register allocator with a verified one; the clearest spec of what that allocator must do is the failure-recovery ladder the current allocator has accreted (arm_backend.rs select_direct): base attempt -> optimized-decline (#120) -> spill-on-exhaustion (#242 3b-lite) -> param frame-backing (#204) -> promotion-off fallback (#474/#475). Each rung is reached only after a recoverable register exhaustion, so the base-attempt corpus is untouched (frozen gate green). Maps each rung to the issue that added it and the failure it catches, ties fixtures to rungs (only rung 4 / promotion_exhaustion_fallback is confirmed load-bearing; the rest are by design intent pending a per-rung counter), and states the VCR-RA acceptance implication: a correct-by-construction allocator with spilling collapses rungs 2-4 into "allocation succeeds" — the ladder then survives only as a never-firing defense-in-depth assertion. Refs #242, #474 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #474 — a v0.14.0 regression: default-on i32 local promotion turns a working compile into a hard
register exhaustionskip on dense functions (fine on v0.12.0). Promotion pins eligible locals into callee-saved r4-r8, halving the operand-stack temp pool; past a density threshold the allocator can't recover.Fix
arm_backend.rs::select_direct: factor the exhaustion-recovery ladder over alocal_promoteflag. Run it with promotion first (so every function that compiles today is bit-identical), then — only if it still ends in register exhaustion — fall back to the promotion-off ladder automatically. That's exactly what theSYNTH_NO_LOCAL_PROMOTE=1workaround does by hand, now automatic. Promotion is an optimization; it must never be the reason a function fails to compile. Keys on"register exhaustion"generally, covering both the single-register and i64-spill-pool exhaustion classes promotion can trigger.Verified
scripts/repro/promotion_exhaustion_fallback.wat): fails without the fix (register exhaustion: i64 spill-slot pool exhausted), compiles with it,.textbyte-identical to theSYNTH_NO_LOCAL_PROMOTE=1build (proving the fallback fired and landed on the v0.12.0 frame-slot path).promotion_never_causes_compile_failure_474.I couldn't obtain gale's exact
control_step_packed, so the end-to-end rescue is demonstrated on the generic fixture (same promotion-induced-exhaustion class); gale offered to re-confirm oncontrol_step_packed/ G474RE once this lands.Bug-fix release candidate (v0.15.1).
Refs #474, #462, #242
🤖 Generated with Claude Code