Skip to content

fix(selector): spill i64 register pairs + call results under pressure (#171)#198

Merged
avrabe merged 2 commits into
mainfrom
fix/i64-pair-spill-171
May 30, 2026
Merged

fix(selector): spill i64 register pairs + call results under pressure (#171)#198
avrabe merged 2 commits into
mainfrom
fix/i64-pair-spill-171

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 30, 2026

Summary

Closes #171 — the real i64 register-pair spill. i64-heavy functions exceeding the 5 consecutive register pairs hit "no consecutive pair" exhaustion and were dropped by the #168 skip net. #188's caller-saved preservation added pressure that regressed z_impl_k_sem_give from compiling (v0.11.6) to skipped (v0.11.8). This implements the spill so they compile.

Design

  • Operand stack Vec<Reg>Vec<StackVal> (Reg | Spilled { lo_slot }); stack_live_regs counts only register-resident entries (~200 sites converted, every handler's logic preserved).
  • alloc_consecutive_pair spills the deepest register-resident entry to a frame slot (STR lo/hi) when no pair is free, then retries; pop_operand/peek_operand reload a spilled value into a fresh pair (LDR lo/hi) on consume.
  • LocalLayout reserves an 8-slot i64 spill area (i64_spill_base) only when the function has i64 ops (i32-only frames unchanged); slots reused.
  • Call path under pressure: restore_caller_saved spills the call result to the frame (returns Spilled) when no callee-saved reg is free; emit_arg_moves acquires its cycle-break scratch lazily (only a genuine cycle needs it).

Verification

Note

Three background agents failed on this all-or-nothing refactor (each crashed mid-pass leaving the i64-critical selector non-compiling); it was completed directly. The related #197 (the optimized path doesn't preserve caller-saved across import calls — gale's z_impl uses that path) is a distinct fix tracked separately.

Closes #171.

🤖 Generated with Claude Code

avrabe and others added 2 commits May 30, 2026 22:36
…#171)

i64-heavy functions that kept more i64 values live than the 5 consecutive
register pairs hold hit "no consecutive pair" exhaustion and were dropped by the
#168 skip-and-continue net. #188's caller-saved preservation added pressure that
pushed z_impl_k_sem_give over the edge (it compiled on v0.11.6, was skipped on
v0.11.8). This implements the real spill.

- Operand stack `Vec<Reg>` → `Vec<StackVal>` (`Reg | Spilled { lo_slot }`).
  `stack_live_regs` counts only register-resident entries.
- `alloc_consecutive_pair` spills the deepest register-resident entry to a frame
  slot (STR lo/hi) when no pair is free, freeing it, and retries. `pop_operand` /
  `peek_operand` reload a spilled value into a fresh pair (LDR lo/hi) on consume.
- `LocalLayout` reserves an 8-slot i64 spill area (`i64_spill_base`), only when
  the function has i64 ops (keeps i32-only frames unchanged); slots are reused.
- Call path under pressure: `restore_caller_saved` spills the call result to the
  frame (returns `StackVal::Spilled`) when no callee-saved reg is free to park
  it; `emit_arg_moves` acquires its cycle-break scratch lazily (only for a
  genuine cycle), so an acyclic arg move needs no free reg.

Acceptance: the 6-live-i64 case now compiles via spill (was Err); a z_impl-shaped
i64+calls function compiles end-to-end (was skipped). 1292 workspace tests pass,
fmt + clippy clean; #188/#195/#170 non-regression confirmed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@avrabe avrabe merged commit d801d0b into main May 30, 2026
6 of 12 checks passed
@avrabe avrabe deleted the fix/i64-pair-spill-171 branch May 30, 2026 20:56
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.

arm regalloc: i64 consecutive-pair spill / coalescing (root of #168)

1 participant