[JSC] GreedyRegAlloc: Add loop-aware live range splitting (disabled by default)
Component: JSC | 899099b
JSTests/stress/regalloc-loop-splitting.js
B3 is JSC's optimizing JIT compiler and Air is its low-level assembly IR, where physical register assignment happens. The greedy register allocator assigns physical registers to virtual registers (tmps) by processing live ranges in priority order; a tmp that cannot be allocated is either spilled or split into independently-allocatable sub-ranges. Loop-aware splitting is one such strategy: a tmp live across a loop but unused inside it wastes a register for the loop's duration, so splitting frees that register within the body. This commit implements it, splitting a tmp whose live range crosses a loop boundary into loop and non-loop portions allocated independently, with fixup code at loop entry and exit moving values between the portions using Air's Shuffle instruction — which handles parallel moves including cycles such as an A↔B swap. A new CFG normalization pass, ensureDedicatedLoopEntryExitBlocks, is required because fixup code can only be safely inserted on edges where the source block has exactly one successor and the target exactly one predecessor; without it, inserting a fixup block would change control flow for unrelated predecessors. The whole feature is gated behind airGreedyRegAllocSplitAroundLoops and disabled by default.
Before split:
[pre-loop] ──► [loop header] ──────────────── [loop body] ──► [post-loop]
tmp0 live ──────────────────────────────────────────────────► tmp0 live
(register held across entire loop even if unused inside)
After split (with CFG normalization + fixup):
[pre-loop] ──► [entry fixup] ──► [loop header] ──► [loop body] ──► [exit fixup] ──► [post-loop]
tmp0 reg ──► Shuffle(tmp0→spill) spill live spill live Shuffle(spill→tmp0) tmp0 reg
(physical register freed inside loop body for other tmps)
Significance
New JIT infrastructure — CFG transformation, live range rewriting, and parallel-move fixup code generation — lands with splitting policy and priority-ordering integration explicitly incomplete. The commit says so itself, which makes subtle miscompilations plausible in exactly the area where they are hardest to detect.
Audit directions
Shuffle cycle resolution at loop boundaries: when multiple tmps are split around the same loop, their entry and exit fixups emit parallel Shuffle instructions that must handle register cycles — tmp0 and tmp1 swapping physical registers, for instance. Cycle resolution is notoriously subtle, and a bug produces silent value corruption in JIT output rather than a crash. Every other emitter of parallel moves in Air inherits the same risk profile.
CFG normalization edge cases: ensureDedicatedLoopEntryExitBlocks inserts new basic blocks at loop entry and exit. The problematic shapes are loops with multiple back-edges, irreducible loops, loops whose header block is also the only exit, and interaction with OSR entry/exit; a malformed CFG post-normalization could silently miscompile or produce exploitable JIT artifacts.
Incomplete allocation ordering: the commit states that improvements to the splitting policy and integration with the allocator's priority-driven allocation order are still needed. That acknowledged gap means the allocator can make ordering decisions conflicting with split-range assumptions under adversarially crafted inputs.
Split tmp use/def rewrite correctness: rewriteCoalescedTmps and addSplitTmp must update every use and def site of the original tmp to reference the correct sub-range. A missed rewrite — particularly at a def site the liveness analysis treats as a boundary — corrupts register assignment silently.
trySplitAroundClobbers interaction: that function splits around call clobber sites. When both clobber-splitting and loop-splitting apply to the same tmp, which split takes priority and how their fixup blocks are ordered is a potential source of conflict, and the general pattern — two independent transformations claiming the same live range — is worth checking wherever a second splitting heuristic is added.