Register Allocation ( + Liveness, Dead code Elimination)
Note: newish-material�If something seems like a bug, it probably is…ask questions.
// rdi = load_local 0
// rdi = assert_integer rdi
call assert_integer
mov %rax %rdi
// rdi = get_integer rdi
call get_integer
mov %rax %rdi�
// rax = sub_const rdi 2
sub $2 %rdi
call new_integer
//return rax
// load_local 0
push %rdi�
// assert_integer
pop %rdi
call assert_integer
push %rax
// get_integer
pop %rdi
call get_integer
push %rax
// sub_const_2 2
pop %rdi
sub $2 %rdi
call new_integer
push %rax
// return
pop %rax
push %r12
mov <Address of Constants> %r12
// load_local 0
push %rdi
�// dup
pop %rax
push %rax
push %rax�
// assert_integer
pop %rdi
call assert_integer
// get_integer
pop %rdi
call get_integer
push %rax��// load_const 1
Mov 8(%r12) %rdx
push %rdx
�// dup
pop %rax
push %rax
push %rax
�// assert_integer
pop %rdi
call assert_integer
// get_integer
pop %rdi
call get_integer
push %rax
// sub
pop %rcx
pop %rdi
sub %rcx %rdi
call new_integer
push %rax
// ret
pop %rax
push %r12
mov <Address of Constants> %r12
push %r13
push %r14
�// load_local 0
push %rdi
// load_const 1
mov 8(%r12) %rdx
push %rdx
�// Pop right op (Value *)
pop %r13
// Check right op is integer
mov %r13 %rdi
call assert_integer
// Load right op value (2)
mov %r13 %rdi
call get_integer
mov %rax %r13
// Pop left op y
pop %r14
// Check left op is integer
mov %r14 %rdi
call assert_integer
// Load left op value (y)
mov %r14 %rdi�call get_integer
mov %rax %rdi
sub %r13 %rdi
call new_integer
push %rax
// ret
pop %rax
pop %r14
pop %r13
pop %r12
Instructions: 25
Instructions: 24
Instructions: 13
Instructions: 7
Mem: 12
Mem: 18
Mem: 9 (stack) + 1 (object)
Mem: 2 (stack) + 2 (object)
Alloc: 1
Original
Exposed IR Ops
Type and Value Analysis
Register Allocation
Numbers need update!
Unoptimized Code Generation
// load_local 0
push %rdi
// load_const 1
mov 8(%r12) %rdx
push %rdx
�// Pop right op (Value *)
pop %r13
// Check right op is integer
mov %r13 %rdi
call assert_integer
// Load right op value (2)
mov %r13 %rdi
call get_integer
mov %rax %r13
// Pop left op y
pop %r14
// Check left op is integer
mov %r14 %rdi
call assert_integer
// Load left op value (y)
mov %r14 %rdi�call get_integer
mov %rax %rdi
sub %r13 %rdi
call new_integer
push %rax
// ret
pop %rax
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
load_const 1
sub
return
]
}
Type Analysis
// load_local 0
push %rdi�
// assert_integer
pop %rdi
call assert_integer
push %rax
// get_integer
pop %rdi
call get_integer
push %rax
�// load_const 1
mov 8(%r12) %rdx
push %rdx
// get_integer
pop %rdi
call get_integer
push %rax
// sub
pop %rcx
pop %rdi
sub %rcx %rdi
call new_integer
push %rax
// ret
pop %rax
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer
sub_int32
return
]
}
Value Analysis
// load_local 0
push %rdi�
// assert_integer
pop %rdi
call assert_integer
push %rax
// get_integer
pop %rdi
call get_integer
push %rax
// get_integer 2
push $2
// sub
pop %rcx
pop %rdi
sub %rcx %rdi
call new_integer
push %rax
// ret
pop %rax
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
get_integer 2
sub
return
]
}
Value Analysis
// load_local 0
push %rdi�
// assert_integer
pop %rdi
call assert_integer
push %rax
// get_integer
pop %rdi
call get_integer
push %rax
// sub_int32_const 2
pop %rdi
sub $2 %rdi
call new_integer
push %rax
// return
pop %rax
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_int32_const 2
return
]
}
Stack Caching
�
�
push %r13
// r13 = load_local 0
mov %rdi %r13
// r13 = assert_integer r13
mov %r13 %rdi
call assert_integer
mov %rax %r13
// r13 = get_integer r13
mov %r13 %rdi
call get_integer
mov %rax %r13��// r13 = sub_const_2 r13 2
sub $2 %r13
mov %r13 %rdi
call new_integer
mov %rax %r13
// return r13
mov %r13 %rax
pop %r13
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
r13 = load_local 0
r13 = assert_integer r13
r13 = get_integer r13
r13 = sub_int32 r13 2
return r13
]
}
Register �Allocation
// rdi = load_local 0
// rdi = assert_integer rdi
call assert_integer
mov %rax %rdi
// rdi = get_integer rdi
call get_integer
mov %rax %rdi�
// rax = sub_const rdi 2
sub $2 %rdi
call new_integer
//return rax
push %r13
// r13 = load_local 0
mov %rdi %r13
// r13 = assert_integer r13
mov %r13 %rdi
call assert_integer
mov %rax %r13
// r13 = get_integer r13
mov %r13 %rdi
call get_integer
mov %rax %r13
�// r13 = sub_const_2 r13 2
sub $2 %r13
mov %r13 %rdi
call new_integer
mov %rax %r13
// return r13
mov %r13 %rax
pop %r13
New
Old
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
rdi = load_local 0
rdi = assert_integer rdi
rdi = get_integer rdi
rax = sub_int32 rdi 2
return rax
]
}
Value Analysis (Constant Propagation)
We statically know the value of a constant.
We do we need to load it dynamically? Nope!
A static analysis to statically reason about values
Idea: statically simulate execution of program�and keep track of what values are known �and which aren’t.
Various levels of precision: exact values, �signs, range
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer
sub_int32
return
]
}
Intermediate Representation (Illustrative)
Types
Instructions
Value Analysis
Stack |
|
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer
sub_int32
return
]
}
Value Analysis
Stack |
? |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer
sub_int32
return
]
}
Value Analysis
Stack |
? |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer
sub_int32
return
]
}
Value Analysis
Stack |
? |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer
sub_int32
return
]
}
Value Analysis
Stack |
? |
2 |
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer
sub_int32
return
]
}
Value Analysis
Stack |
? |
2 |
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer 2
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer
sub_int32
return
]
}
Value Analysis
Stack |
? |
2 |
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
get_integer 2
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer
sub_int32
return
]
}
The load_const is dead code
Value Analysis
Stack |
? |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
get_integer 2
sub_int32
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer
sub_int32
return
]
}
Value Analysis
Stack |
|
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
get_integer 2
sub_int32
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
load_const 1
get_integer
sub_int32
return
]
}
Intermediate Representation (Illustrative)
Types
Last Time (How?)
Register Allocation
Intermediate Representation (Illustrative)
Types
Stack to Three-Address Code
Stack |
t1 |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_const_2 2
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
]
}
(Code after Type and Constant Analysis)
Stack to Three-Address Code
Stack |
t1 |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_const_2 2
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
]
}
Stack to Three-Address Code
Stack |
t2 |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_const_2 2
return
]
}
Stack to Three-Address Code
Stack |
t3 |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_const_2 2
return
]
}
Stack to Three-Address Code
Stack |
t4 |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t4 = sub_int32 t3 2
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_const_2 2
return
]
}
Stack to Three-Address Code
Stack |
|
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_const_2 2
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t4 = sub_int32 t3 2
return t4
]
}
Virtual Machine Showdown: Stack Versus Registers
Yunhe Shi, David Gregg, Andrew Beatty, and M. Anton Ertl
Constant Propagation
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t4 = load_const 1
t5 = get_integer t4
t6 = sub_int32 t3 t5
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t4 = 2
t5 = get_integer t4
t6 = sub_int32 t3 t5
return
]
}
Constant Propagation
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t4 = load_const 1
t5 = get_integer t4
t6 = sub_int32 t3 t5
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t4 = 2
t5 = get_integer 2
t6 = sub_int32 t3 2
return
]
}
Dead Code
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t4 = load_const 1
t5 = get_integer t4
t6 = sub_int32 t3 t5
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t4 = 2
t5 = get_integer 2
t6 = sub_int32 t3 2
return
]
}
Neither t4 or t5 are live. Alternatively, they are dead.
Dead Code
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t4 = load_const 1
t5 = get_integer t4
t6 = sub_int32 t3 t5
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t6 = sub_int32 t3 2
return
]
}
Neither t4 or t5 are live. Alternatively, they are dead.
Liveness Analysis
Conceptual Idea of Analysis
Liveness Example
a = x+y;
t = a;
c = a+x;
x == 0
b = t+z;
c = y+1;
1 1 0 0 1 0 0
1 1 1 0 0 0 0
1 1 0 0 1 1 1
1 0 0 0 1 1 1
1 1 0 0 1 0 0
0 1 0 1 1 1 0
a b c x y z t
a b c x y z t
a b c x y z t
Liveness as Dataflow
111
011
101
110
010
001
000
100
P = { 000, 001, 010, 011, 100, 101, 110, 111}
(standard boolean lattice, also called hypercube)
Transfer functions
Saman Amarasinghe 38 6.035 ©MIT Fall 1998
Liveness Algorithm
for all nodes n in N - { Exit }
IN[n] = emptyset;
OUT[Exit] = emptyset;
IN[Exit] = use[Exit];
Changed = N - { Exit };
while (Changed != emptyset)
choose a node n in Changed;
Changed = Changed - { n };
OUT[n] = emptyset;
for all nodes s in successors(n)
OUT[n] = OUT[n] U IN[p];
IN[n] = use[n] U (out[n] - def[n]);
if (IN[n] changed)
for all nodes p in predecessors(n)
Changed = Changed U { p };
Register Allocation
Credit: CS143 @ Stanford
The Memory Hierarchy
●
Idea: Try to get the best of all worlds by using multiple types of memory.
The Memory Hierarchy
●
Idea: Try to get the best of all worlds by using multiple types of memory.
Registers
L1 Cache
L2 Cache
Main Memory
Hard Disk
Network
The Memory Hierarchy
●
Idea: Try to get the best of all worlds by using multiple types of memory.
L1 Cache
L2 Cache
Main Memory
Hard Disk
Network
256B - 8KB
16KB – 64KB
1MB - 4MB
4GB – 256GB
500GB+
HUGE
Registers
The Memory Hierarchy
●
Idea: Try to get the best of all worlds by using multiple types of memory.
L1 Cache
L2 Cache
Main Memory
Hard Disk
Network
256B - 8KB
16KB – 64KB
1MB - 4MB
4GB – 256GB 25ns – 100ns
500GB+
HUGE
0.25 – 1ns
1ns – 5ns 5ns – 25ns
3 – 10ms
10 – 2000ms
Registers
The Challenges of Code Generation
●
Almost all programming languages expose a coarse view of the memory hierarchy:
●
All variables live in “memory.”
Disk and network explicitly handled separately.
●
●
(Interesting exception: Stanford's Sequoia
programming language)
Challenges in code generation:
●
●
Position objects in a way that takes maximum advantage of the memory hierarchy.
Do so without hints from the programmer.
●
Registers
●
Most machines have a set of registers, dedicated memory locations that
●
can be accessed quickly,
can have computations performed on them, and exist in small quantity.
●
●
●
Using registers intelligently is a critical step in any compiler.
●
A good register allocator can generate code orders of magnitude better than a bad register allocator.
Register Allocation
●
On a physical machine there are a small number of registers:
●
amd64 has 16 general-purpose registers and a number of specialized registers.
●
Register allocation is the process of assigning variables to registers and managing data transfer in and out of registers.
Challenges in Register Allocation
●
Registers are scarce.
●
Often substantially more IR variables than registers. Need to find a way to reuse registers whenever possible.
●
●
Registers are complicated.
●
x86: Certain instructions must store their results in specific registers; can't store values there if you want to use those instructions.
Most architectures: Some registers must be preserved across function calls.
●
Stack Caching
Stack Caching
Stack |
|
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_int32_const 2
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
]
}
Stack Caching
Stack |
r13 |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_int32_const 2
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
r13 = load_local 0
]
}
Stack Caching
Stack |
r13 |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_int32_const 2
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
r13 = load_local 0
r13 = assert_integer r13
]
}
Stack Caching
Stack |
r13 |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_int32_const 2
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
r13 = load_local 0
r13 = assert_integer r13
r13 = get_integer r13
]
}
Stack Caching
Stack |
r13 |
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_int32_const 2
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
r13 = load_local 0
r13 = assert_integer r13
r13 = get_integer r13
r13 = sub_int32 r13 2
]
}
Stack Caching
Stack |
|
|
|
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
r13 = load_local 0
r13 = assert_integer r13
r13 = get_integer r13
r13 = sub_int32 r13 2
return r13
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_int32_const 2
return
]
}
Sub optimal
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
r13 = load_local 0
r13 = assert_integer r13
r13 = get_integer r13
r13 = sub_int32 r13 2
return r13
]
}
push %r13
// r13 = load_local 0
mov %rdi %r13
// r13 = assert_integer r13
mov %r13 %rdi
call assert_integer
mov %rax %r13
// r13 = get_integer r13
mov %r13 %rdi
call get_integer
mov %rax %r13
�// r13 = sub_const_2 r13 2
sub $2 %r13
mov %r13 %rdi
call new_integer
mov %rax %r13
// return r13
mov %r13 %rax
pop %r13
Sub optimal
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
r13 = load_local 0
r13 = assert_integer r13
r13 = get_integer r13
r13 = sub_int32 r13 2
return r13
]
}
push %r13
// r13 = load_local 0
mov %rdi %r13
// r13 = assert_integer r13
mov %r13 %rdi
call assert_integer
mov %rax %r13
// r13 = get_integer r13
mov %r13 %rdi
call get_integer
mov %rax %r13
�// r13 = sub_const_2 r13 2
sub $2 %r13
mov %r13 %rdi
call new_integer
mov %rax %r13
// return r13
mov %r13 %rax
pop %r13
Constrained Optimization Problem
Assumptions:
local_local 0 is in register rdi
Constraints:
assert_integer: t1 in rdi
get_integer: t2 in rdi
sub: t3 in rdi
return: t4 in rax
Minimize Cost:
- Memory accesses
- Instructions (register to register moves)
Solution (Register Assignment):
t1 in rdi
t2 in rdi
t3 in rdi
t4 in rax
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t4 = sub_int32 t3 2
return t4
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
rdi = load_local 0
rdi = assert_integer rdi
rdi = get_integer rdi
rax = sub_int32 rdi 2
return rax
]
}
Code
// rdi = load_local 0
// rdi = assert_integer rdi
call assert_integer
mov %rax %rdi
// rdi = get_integer rdi
call get_integer
mov %rax %rdi�
// rax = sub_const rdi 2
sub $2 %rdi
call new_integer
//return rax
push %r13
// r13 = load_local 0
mov %rdi %r13
// r13 = assert_integer r13
mov %r13 %rdi
call assert_integer
mov %rax %r13
// r13 = get_integer r13
mov %r13 %rdi
call get_integer
mov %rax %r13
�// r13 = sub_const_2 r13 2
sub $2 %r13
mov %r13 %rdi
call new_integer
mov %rax %r13
// return r13
mov %r13 %rax
pop %r13
New
Old
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
rdi = load_local 0
rdi = assert_integer rdi
rdi = get_integer rdi
rax = sub_int32 rdi 2
return rax
]
}
Goal
Explore two algorithms for register allocation:
Linear scan register allocation.
Graph-coloring register allocation.
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
t1 = load_local 0
t2 = assert_integer t1
t3 = get_integer t2
t4 = sub_int32 t3 2
return t4
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
load_local 0
assert_integer
get_integer
sub_int32_const 2
return
]
}
function {
parameter_count = 1
local_vars = [y, x],
constants = [None, 2],
instructions = [
rdi = load_local 0
rdi = assert_integer rdi
rdi = get_integer rdi
rax = sub_int32 rdi 2
return rax
]
}
Building a Better Allocator
●
Goal: Try to hold as many variables in registers as possible.
●
Reduces memory reads/writes. Reduces total memory usage.
●
●
We will need to address these questions:
●
Which registers do we put variables in? What do we do when we run out of registers?
●
Register Consistency
Does not mean that each variable is always stored in the same location!
●
At each program point, each variable must be in one location.
●
●
●
Can assign several variables the same register if no two of them ever will be read together.
At each program point, each register holds at most one live variable.
Live Ranges and Live Intervals
The live range for a variable is the set of program points at which that variable is live.
The live interval for a variable is the smallest contiguous range of program points that contains all of a variable's live ranges.
●
A variable is live at a particular program point if its value may be read later before it is written.
●
Can find this using liveness analysis.
●
●
●
Less precise than live ranges, but simpler to work with.
Live Ranges and Live Intervals
Live Ranges and Live Intervals
e | = | d | + | a | |
f | = | b | + | c | |
f | = | f | + | b | |
IfZ | e | Goto | _L0 | ||
d = | e | + f | | ||
Goto _L1;
_L0:
d = e - f
_L1:
g = d
Live Ranges and Live Intervals
e = d + a
f = b + c
f = f + b
d = e + f
d = e – f
g = d
e = d + a
f = b + c f = f + b
IfZ e Goto _L0 d = e + f Goto _L1;
_L0:
d = e - f
_L1:
g = d
Live Ranges and Live Intervals
e = d + a
f = b + c
f = f + b
d = e + f
d = e – f
e = d + a
f = b + c f = f + b
IfZ e Goto _L0 d = e + f Goto _L1;
_L0:
d = e - f
_L1:
g = d
g = d
{ g }
Live Ranges and Live Intervals
e = d + a
f = b + c
f = f + b
d = e + f
d = e – f
e = d + a
f = b + c f = f + b
IfZ e Goto _L0 d = e + f Goto _L1;
_L0:
d = e - f
_L1:
g = d
{ d }
g = d
{ g }
Live Ranges and Live Intervals
e = d + a
f = b + c
f = f + b
d = e + f
{ d }
d = e – f
e = d + a
f = b + c f = f + b
IfZ e Goto _L0 d = e + f Goto _L1;
_L0:
d = e - f
_L1:
g = d
{ d }
g = d
{ g }
Live Ranges and Live Intervals
e = d + a
f = b + c
f = f + b
{ e, f } d = e + f
{ d }
d = e – f
e = d + a
f = b + c f = f + b
IfZ e Goto _L0 d = e + f Goto _L1;
_L0:
d = e - f
_L1:
g = d
{ d }
g = d
{ g }
Live Ranges and Live Intervals
e = d + a
f = b + c
f = f + b
{ e, f } d = e + f
{ d }
d = e – f
{ d }
e = d + a
f = b + c f = f + b
IfZ e Goto _L0 d = e + f Goto _L1;
_L0:
d = e - f
_L1:
g = d
{ d }
g = d
{ g }
Live Ranges and Live Intervals
e = d + a
f = b + c
f = f + b
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
e = d + a
f = b + c f = f + b
IfZ e Goto _L0 d = e + f Goto _L1;
_L0:
d = e - f
_L1:
g = d
{ d }
g = d
{ g }
Live Ranges and Live Intervals
e = d + a
f = b + c
f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
e = d + a
f = b + c f = f + b
IfZ e Goto _L0 d = e + f Goto _L1;
_L0:
d = e - f
_L1:
g = d
{ d }
g = d
{ g }
Live Ranges and Live Intervals
e = d + a
f = b + c
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
e = d + a
f = b + c f = f + b
IfZ e Goto _L0 d = e + f Goto _L1;
_L0:
d = e - f
_L1:
g = d
{ d }
g = d
{ g }
Live Ranges and Live Intervals
e = d + a
f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
e = d + a
f = b + c f = f + b
IfZ e Goto _L0 d = e + f Goto _L1;
_L0:
d = e - f
_L1:
g = d
{ d }
g = d
{ g }
Live Ranges and Live Intervals
e = d + a
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
e | = | d | + | a | |
f | = | b | + | c | |
f | = | f | + | b | |
IfZ | e | Goto | _L0 | ||
d = | e | + f | | ||
{ d }
g = d
{ g }
Goto _L1;
_L0:
d = e - f
_L1:
g = d
Live Ranges and Live Intervals
e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
e | = | d | + | a | |
f | = | b | + | c | |
f | = | f | + | b | |
IfZ | e | Goto | _L0 | ||
d = | e | + f | | ||
{ d }
g = d
{ g }
Goto _L1;
_L0:
d = e - f
_L1:
g = d
Live Ranges and Live Intervals
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
e | = | d | + | a | |
f | = | b | + | c | |
f | = | f | + | b | |
IfZ | e | Goto | _L0 | ||
d = | e | + f | | ||
{ d }
g = d
{ g }
Goto _L1;
_L0:
d = e - f
_L1:
g = d
Live Ranges and Live Intervals
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
e | = | d | + | a | |
f | = | b | + | c | |
f | = | f | + | b | |
IfZ | e | Goto | _L0 | ||
d = | e | + f | | ||
d = e - f
Goto _L1;
_L0:
_L1:
g = d
{ d }
g = d
{ g }
Live Ranges and Live Intervals
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
| | |
e = d + a | | |
| ||
f = b + c | | |
f = f + b | | |
IfZ e Goto _L0 | | |
d = e + f | | |
Goto _L1; | | |
_L0: | | |
d = e - f | | |
_L1: | | |
g = d | | |
| | |
{ d }
g = d
{ g }
a
Live Ranges and Live Intervals
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a b
e = d + a
{ d }
g = d
{ g }
f = b + c
f = f + b
IfZ e Goto _L0
d = e + f
d = e - f
Goto _L1;
_L0:
_L1:
g = d
Live Ranges and Live Intervals
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a b c
e = d + a
{ d }
g = d
{ g }
f = b + c
f = f + b
IfZ e Goto _L0
d = e + f
d = e - f
Goto _L1;
_L0:
_L1:
g = d
Live Ranges and Live Intervals
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a b c d
e = d + a
{ d }
g = d
{ g }
f = b + c
f = f + b
IfZ e Goto _L0
d = e + f
d = e - f
Goto _L1;
_L0:
_L1:
g = d
Live Ranges and Live Intervals
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a b c d
e = d + a
{ d }
g = d
{ g }
f = b + c
f = f + b
IfZ e Goto _L0
d = e + f
d = e - f
Goto _L1;
_L0:
_L1:
g = d
Live Ranges and Live Intervals
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a b c d e
e = d + a
{ d }
g = d
{ g }
f = b + c
f = f + b
IfZ e Goto _L0
d = e + f
d = e - f
Goto _L1;
_L0:
_L1:
g = d
Live Ranges and Live Intervals
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
e = d + a
{ d }
g = d
{ g }
f = b + c
f = f + b
IfZ e Goto _L0
d = e + f
d = e - f
Goto _L1;
_L0:
_L1:
g = d
a b c d e f
Live Ranges and Live Intervals
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a b c d e f g
| | | | | | | |
e = d + a | | | | | | | |
| | ||||||
f = b + c | | | | | | | |
| | ||||||
f = f + b | | | | | | | |
| |||||||
IfZ e Goto _L0 | | | | | | | |
d = e + f | | | | | | | |
Goto _L1; | | | | | | | |
_L0: | | | | | | | |
d = e - f | | | | | | | |
| | ||||||
_L1: | | | | | | | |
g = d | | | | | | | |
| | ||||||
| | | | | | | |
{ d }
g = d
{ g }
Register Allocation with Live Intervals
●
Given the live intervals for all the variables in the program, we can allocate registers using a simple greedy algorithm.
Idea: Track which registers are free at each point.
When a live interval begins, give that variable a free register.
When a live interval ends, the register is once again free.
We can't always fit everything into a register; we'll see what do to in a minute.
●
●
●
●
| | | | | | |
| | |||||
| | |||||
| ||||||
| | |||||
| |
a b c d e f g
Register Allocation with Live Intervals
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Register Allocation with Live Intervals
R0 | R1 | R2 | R3 |
Free Registers
a
b c d e f g
Another Example
Another Example
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
3 registers instead of 4
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
What do we do now?
a
b c d e f g
Register Spilling
●
If a register cannot be found for a variable v, we may need to spill a variable.
When a variable is spilled, it is stored in memory rather than a register.
When we need a register for the spilled variable:
●
●
●
Evict some existing register to memory. Load the variable into the register.
When done, write the register back to memory and reload the register with its original value.
●
●
●
Spilling is slow, but sometimes necessary.
Another Example
R0 | R1 | R2 |
Free Registers
What do we do now?
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Another Example
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Linear Scan Register Allocation
●
This algorithm is called linear scan register allocation
and is a comparatively new algorithm (1999).
Advantages:
●
●
Very efficient (after computing live intervals, runs in linear time)
Produces good code in many instances.
Allocation step works in one pass; can generate code during iteration.
Often used in JIT compilers like Java HotSpot.
●
●
●
Disadvantages:
●
Imprecise due to use of live intervals rather than live ranges. Other techniques known to be superior in many cases.
●
Correctness Proof Sketch
Live intervals are conservative approximations of live ranges.
No two variables with overlapping live ranges placed in the same register.
●
No register holds two live variables at once:
●
●
●
At each program point, every variable is in the same location:
●
All variables assigned a unique location.
Second-Chance Bin Packing
●
A more aggressive version of linear-scan. Uses live ranges instead of live intervals.
If a variable must be spilled, don't spill all uses of it.
●
●
●
A later live range might still fit into a register.
●
Requires a final data-flow analysis to confirm variables are assigned consistent locations.
See “Quality and Speed in Linear-scan Register Allocation” by Traub, Holloway, and Smith.
●
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
Second-Chance Bin Packing
R0 | R1 | R2 |
Free Registers
a
b c d e f g
An Entirely Different Approach
An Entirely Different Approach
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
{ d }
g = d
{ g }
An Entirely Different Approach
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
What can we infer from all these variables being live at this point?
{ d }
g = d
{ g }
An Entirely Different Approach
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
{ d }
g = d
{ g }
An Entirely Different Approach
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a
b
c
d
e
f
g
{ d }
g = d
{ g }
An Entirely Different Approach
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a
b
c
d
e
f
g
{ d }
g = d
{ g }
An Entirely Different Approach
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a
b
c
d
e
f
g
{ d }
g = d
{ g }
An Entirely Different Approach
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a
b
c
d
e
f
g
{ d }
g = d
{ g }
An Entirely Different Approach
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a
b
c
d
e
f
g
R0 | R1 | R2 | R3 |
{ d }
g = d
{ g }
Registers
An Entirely Different Approach
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
a
b
c
d
e
f
g
R0 | R1 | R2 | R3 |
{ d }
g = d
{ g }
Registers
The Register Interference Graph
●
The register interference graph (RIG) of a control-flow graph is an undirected graph where
●
Each node is a variable.
There is an edge between two variables that are live at the same program point.
●
●
Perform register allocation by assigning each variable a different register from all of its neighbors.
There's just one catch...
●
The One Catch
This problem is equivalent to graph- coloring, which is NP-hard if there are at least three registers.
No good polynomial-time algorithms (or even good approximations!) are known for this problem.
Not quite. Polynomial for static single assignment form programs.
We have to be content with a heuristic that is good enough for RIGs that arise in practice.
●
●
●
The One Catch to The One Catch
The One Catch to The One Catch
If you can figure out a way to assign registers to arbitrary RIGs, you've just proven P = NP and will get a $1,000,000 check from the Clay Mathematics Institute.
The One Catch to The One Catch
If you can figure out a way to assign registers to arbitrary RIGs, you've just proven P = NP and will get a $1,000,000 check from the Clay Mathematics Institute.
Battling NP-Hardness
Chaitin's Algorithm (1982)
Suppose we are trying to k-color a graph and find a node with fewer than k edges.
If we delete this node from the graph and color what remains, we can find a color for this node if we add it back in.
Reason: With fewer than k neighbors, some color must be left over.
●
Intuition:
●
●
●
●
Algorithm:
●
Find a node with fewer than k outgoing edges. Remove it from the graph.
Recursively color the rest of the graph. Add the node back in.
Assign it a valid color.
●
●
●
●
Chaitin's Algorithm
Chaitin's Algorithm
a
b
c
d
e
g
f
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
d b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
d b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
e d b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
e d b c
a
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
e d b c
g a
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
e d b c
f g a
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
e d b c
g a
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
e d b c
g a
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
e d b c
a
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
e d b c
a
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
e d b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
e d b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
d b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
d b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
b c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
c
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
Registers
Chaitin's Algorithm
R0 | R1 | R2 | R3 |
a
b
c
d
e
g
f
Registers
One Problem
What if we can't find a node with fewer than k neighbors?
Choose and remove an arbitrary node, marking it “troublesome.”
Otherwise, we have to spill that node.
●
●
●
●
Use heuristics to choose which one.
●
When adding node back in, it may be possible to find a valid color.
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
a
b
c
d
e
f
g
Registers
<- Note 3 registers this time rather than 4
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
a
b
c
d
e
f
g
Registers
g
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
f
a
b
c
d
e
f g
Registers
g
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
e
f
a
b
c
d
e f g
Registers
g
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
d
e
f
a
b
c
d e f g
Registers
g
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
c
d
e
f
a
b
c d e f g
Registers
g
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
c
d
e
f
a
b
b c d e f g
g
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
c
d
e
f
a
b
a b c d e f g
g
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
c
d
e
f
a
b
b c d e f g
g
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
c
d
e
f
a
b
b c d e f g
g
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
c
d
e
f
a
b
c d e f g
g
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
c
d
e
f
a
b
c d e f g
g
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
d
e
f
a
b
d e f g
g
c
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
d
e
f
a
b
d e f g
g
c
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
d
e
f
a
b
e f g
g
c
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
d
e
f
a
b
e f g
g
c
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
e
f
a
b
e f g
g
d
c
Registers
(spilled)
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
f
a
b
f g
g
d
c
e
(spilled)
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
f
a
b
f g
g
d
c
e
(spilled)
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
f
a
b
g
g
d
c
e
(spilled)
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
f
a
b
g
g
d
c
e
(spilled)
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
f
a
b
g
d
c
e
(spilled)
Registers
Chaitin's Algorithm Reloaded
R0 | R1 | R2 |
f
a
b
g
d
c
e
(spilled)
Registers
A Smarter Algorithm
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
R0 | R1 | R2 |
Registers
f
a
b
g
d
c
(spilled)
e
{ d }
g = d
{ g }
A Smarter Algorithm
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d = e + f
{ d }
{ e, f } d = e – f
{ d }
R0 | R1 | R2 |
Registers
f
a
b
g
d
c
(spilled)
e
{ d }
g = d
{ g }
Concept of a web, includes:
- A definition and all reachable uses� - All definitions that reach the same use
A Smarter Algorithm
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d' = e + f
{ d' }
{ e, f } d' = e – f
{ d' }
R0 | R1 | R2 |
Registers
f
a
b
g
d
c
(spilled)
e
{ d' }
g = d'
{ g }
Concept of a web, includes:
- A definition and all reachable uses� - All definitions that reach the same use
A Smarter Algorithm
{ a, b, c, d } e = d + a
{ b, c, e }
{ b, c, e } f = b + c
{ b, e, f}
{ b, e, f } f = f + b
{ e, f }
{ e, f } d' = e + f
{ d' }
{ e, f } d' = e – f
{ d' }
R0 | R1 | R2 |
Registers
e
a
b
g
c
d'
f
d
{ d' }
g = d'
{ g }
(spilled)
Concept of a web, includes:
- A definition and all reachable uses� - All definitions that reach the same use
Another Example
Another Example
a
b
c
d
e
f
Another Example
R0 | R1 | R2 |
a
b
c
d
e
f
Registers
Another Example
R0 | R1 | R2 |
a
b
c
d
e
f
a
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
c a
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
b c a
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
f
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
d f
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
f
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
f
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
b c a
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
b c a
Registers
Another Example
R0 | R1 | R2 |
c
a
d
e
f
c a
b
Registers
Another Example
R0 | R1 | R2 |
c
a
d
e
f
c a
b
Registers
Another Example
R0 | R1 | R2 |
c
a
d
e
f
a
b
Registers
Another Example
R0 | R1 | R2 |
c
a
d
e
f
a
b
Registers
Another Example
R0 | R1 | R2 |
c
a
d
e
f
b
Registers
Another Example
R0 | R1 | R2 |
c
a
d
e
f
b
Registers
Another Example (Different Coloring)
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
d f
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
f
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
f
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
e
f
e b c a
Registers
Unlike last time, choose purple instead of yellow
Another Example
R0 | R1 | R2 |
c
a
b
d
f
b c a
Registers
e
Another Example
R0 | R1 | R2 |
c
a
b
d
f
b c a
Registers
e
Another Example
R0 | R1 | R2 |
c
a
b
d
f
c a
Registers
e
Another Example
R0 | R1 | R2 |
c
a
b
d
f
c a
Registers
e
Another Example
R0 | R1 | R2 |
c
a
b
d
f
a
e
Registers
Another Example
R0 | R1 | R2 |
c
a
b
d
f
a
Registers
e
(spilled)
Another Example
R0 | R1 | R2 |
c
a
b
d
f
e
Registers
(spilled)
Another Example
R0 | R1 | R2 |
c
a
b
d
f
e
Registers
(spilled)
(spilled)
Takeaway: fundamental nondeterminism �in color assignments and nodes to traverse = NP.�Heuristics may be suboptimal
Chaitin's Algorithm
●
Advantages:
●
For many control-flow graphs, finds an excellent assignment of variables to registers.
When distinguishing variables by use, produces a precise RIG.
Often used in production compilers like GCC.
●
●
●
Disadvantages:
●
Core approach based on the NP-hard graph coloring problem.
Heuristic may produce pathologically worst-case assignments.
●
Correctness Proof Sketch
●
No two variables live at some point are assigned the same register.
●
Forced by graph coloring.
●
At any program point each variable is always in one location.
●
Automatic if we assign each variable one register.
Requires a few tricks if we separate by use case.
●
Improvements to the Algorithm
●
Use heuristics (least-commonly used, greatest improvement, etc.) to determine what to spill.
Handle spilling intelligently.
●
●
When spilling a variable, recompute the RIG based on the spill and use a new coloring to find a register.
Summary of Register Allocation
●
Critical step in all optimizing compilers.
The linear scan algorithm uses live intervals to greedily assign variables to registers.
●
●
Often used in JIT compilers due to efficiency. �Original LLVM approach, now tweaked
●
Chaitin's algorithm uses the register interference graph (based on live ranges) and graph coloring to assign registers.
●
The basis for the technique used in GCC.
Takeaway of Register Allocation
Many many other tricks: live range splitting, rematerialization, coalescing, scalar replacement of aggregates, spill heuristics.
Design principle: a little bit of register allocation is a lot better than none. A lot of register allocation is…hard.��Don’t get lost in the details! Simple, robust approaches work well.