Audit-cycle finding (architectural drift in the allocation pool)
The RV32 temp allocator (synth-backend-riscv/src/selector.rs::alloc_temp) recycled the pool [t0..t6, s1..s6] with a monotonic next_temp round-robin counter. The code itself flagged it as a straight-line shortcut ("fine for straight-line code"). Even after #226 made it skip live registers, it kept marching forward into the callee-saved s-registers rather than reusing a just-freed low register.
Evidence (measured)
controller_step RV32 prologue, before:
addi sp, sp, -48
sw s1,12 sw s2,16 sw s3,20 sw s4,24 sw s5,28 sw s6,32 ← 6 callee-saved spills
The function never has more than ~4 values simultaneously live (the #226 liveness confirms this), yet round-robin walked into all six s-registers, each costing a save and restore (#220 prologue) plus frame space.
The drift
The pool accreted s-registers + a save/restore prologue (#220). With round-robin, the allocator consumed those reserves by accident. They should be a genuine overflow reserve.
Cleanup
alloc_temp returns the lowest-indexed free temp (caller-saved t-registers are listed first), keeping a function in t-registers whenever ≤7 values are live. Drops the next_temp field entirely.
Before / after
|
before |
after |
controller_step .text |
440 B |
368 B (−72 B, −18 instrs) |
| callee-saved spills |
6 |
0 |
| frame |
48 B |
16 B |
Oracle (behavior frozen)
All five differential fixtures bit-identical: ARM div_const 338/338, control_step 0x00210a55, flight_seam 0x07FDF307; RV32 control_step 0x00210a55, controller_step 0x05ff0000. 165 riscv unit tests pass (the #220 preservation test reworked to force s-register usage via 8 simultaneously-live values).
The broader local-regalloc / const-CSE Opt-3 work (gale #209) stays separate — this is only the round-robin → lowest-free revert.
Audit-cycle finding (architectural drift in the allocation pool)
The RV32 temp allocator (
synth-backend-riscv/src/selector.rs::alloc_temp) recycled the pool[t0..t6, s1..s6]with a monotonicnext_tempround-robin counter. The code itself flagged it as a straight-line shortcut ("fine for straight-line code"). Even after #226 made it skip live registers, it kept marching forward into the callee-saved s-registers rather than reusing a just-freed low register.Evidence (measured)
controller_stepRV32 prologue, before:The function never has more than ~4 values simultaneously live (the #226 liveness confirms this), yet round-robin walked into all six s-registers, each costing a save and restore (#220 prologue) plus frame space.
The drift
The pool accreted s-registers + a save/restore prologue (#220). With round-robin, the allocator consumed those reserves by accident. They should be a genuine overflow reserve.
Cleanup
alloc_tempreturns the lowest-indexed free temp (caller-saved t-registers are listed first), keeping a function in t-registers whenever ≤7 values are live. Drops thenext_tempfield entirely.Before / after
controller_step.textOracle (behavior frozen)
All five differential fixtures bit-identical: ARM
div_const338/338,control_step0x00210a55,flight_seam0x07FDF307; RV32control_step0x00210a55,controller_step0x05ff0000. 165 riscv unit tests pass (the #220 preservation test reworked to force s-register usage via 8 simultaneously-live values).The broader local-regalloc / const-CSE Opt-3 work (gale #209) stays separate — this is only the round-robin → lowest-free revert.