Register Allocator
Many live values, few registers. Reduce the register count until the allocator has to spill, and see which value it chooses and why — under two different algorithms.
Both allocators are implemented in src/compilers/sim/regalloc.ts and run on the optimized SSA of the program you type. They work on live ranges without holes: a value dead in the middle of its range still occupies a register here, where a production allocator would model the hole and reuse it. That makes linear scan slightly more pessimistic than it really is. Register names are x86-64 System V.
This program needs 10 at its busiest point. Below that, some value must live in memory — no allocator can do better.
Graph colouring
Chaitin-Briggs. Better allocations, more compile time — what an ahead-of-time compiler uses.
- %2 is live across 9 instructions with only 1 use, so it costs the least to keep in memory.
- %6 is live across 8 instructions with only 1 use, so it costs the least to keep in memory.
- %8 is live across 8 instructions with only 1 use, so it costs the least to keep in memory.
- %10 is live across 8 instructions with only 1 use, so it costs the least to keep in memory.
- %12 is live across 8 instructions with only 1 use, so it costs the least to keep in memory.
- %14 is live across 8 instructions with only 1 use, so it costs the least to keep in memory.
Linear scan
Sort by start point and sweep. Much faster, worse code — what a JIT uses, because the compile happens while the user waits.
- %0 stays live until instruction 16, longer than %8, so it blocks a register for longer. Linear scan spills the later-ending interval.
- %8 stays live until instruction 11, longer than %19, so it blocks a register for longer. Linear scan spills the later-ending interval.
- No register was free at instruction 5, and every active value ends later than %10 does.
- No register was free at instruction 6, and every active value ends later than %12 does.
- No register was free at instruction 7, and every active value ends later than %14 does.
- No register was free at instruction 8, and every active value ends later than %16 does.
What came out
A spilled value becomes a stack slot, and the extra memory traffic is visible in the listing.
.globl spreadspread:push rbp ; establish the frame pointermov rbp, rspsub rsp, 48 ; 6 spilled values, 8 bytes each.Lspread_b0: ; entrymov rsi, rdi ; parameter 0: nlea [rbp-8], [rsi+1] ; add with a distinct destination selected as lealea rax, [rsi+2] ; add with a distinct destination selected as lealea [rbp-16], [rsi+3] ; add with a distinct destination selected as lealea [rbp-24], [rsi+4] ; add with a distinct destination selected as lealea [rbp-32], [rsi+5] ; add with a distinct destination selected as lealea [rbp-40], [rsi+6] ; add with a distinct destination selected as lealea [rbp-48], [rsi+7] ; add with a distinct destination selected as lealea rdx, [rsi+8] ; add with a distinct destination selected as leamov rcx, [rbp-8]add rcx, raxmov rax, rcxadd rax, [rbp-16]mov rcx, raxadd rcx, [rbp-24]mov rax, rcxadd rax, [rbp-32]mov rcx, raxadd rcx, [rbp-40]mov rax, rcxadd rax, [rbp-48]mov rcx, raxadd rcx, rdxmov rax, rcxadd rax, rsimov rsp, rbppop rbpret