Say I have this intermediate representation of some code:
t1 = 1
t2 = 2
t3 = 3
t4 = t1 + t2
t5 = t3 + t4
use t5
The ultimate goal is to do register assignment using only two ARM registers, r0
and r1
, and possibly spill some symbols.
The first step is to compute live ranges for each instruction:
t1 = 1 |
t2 = 2 | t1
t3 = 3 | t1, t2
t4 = t1 + t2 | t1, t2, t3 (this is going to become a problem)
t5 = t3 + t4 | t3, t4
use t5 | t5
Register interference graph
t2
/ \
/ \
/ \
t1----------t3
|
|
t5 t4
Register assignment via graph coloring
Now use Chaitin's algorithm on it to color it with the two registers:
- Find node with fewer than 2 edges
- Found =>
t5
- Remove it from the graph
- Push it onto the stack:
[t5]
- Found =>
- Find node with fewer than 2 edges
- Found =>
t4
- Remove it from graph, push:
[t5, t4]
- Found =>
- No nodes with fewer than 2 edges! (we're looking at the
t1 - t2 - t3
triangle)- Pick one at random (or with the highest degree (2 for all remaining nodes)) =>
t3
(this might be a problem) - Remove it from graph, push:
[t5, t4, t3]
- Pick one at random (or with the highest degree (2 for all remaining nodes)) =>
- Find node with fewer than 2 edges
- Found
t2
- Remove it from graph, push:
[t5, t4, t3, t2]
- Found
- Push the last node:
[t5, t4, t3, t2, t1]
Now assign registers in reversed order:
- Pop symbol =>
t1
- Conflicting nodes: none
- Assign:
t1 => r0
- Put
t1
back in the graph
- Pop
t2
- Conflicting nodes:
{t1: r0}
- We have one more free register
- Assign:
t2 => r1
- Put
t2
back in the graph
- Conflicting nodes:
- Pop
t3
- Conflicting nodes:
{t1: r0, t2: r1}
- OOPS! No more free registers!
- Spill it =>
t3 => m0
- Put
t3
back in the graph
- Conflicting nodes:
- No more spills required!
Final assignment:
t1 -> r0
t2 -> r1
t3 -> m0 (spilled)
t4 -> r0 (or r1)
t5 -> r0 (or r1)
Inserting spills
Now, as per this (slide 29) and this (slide 4):
Before each operation that uses [the spilled symbol
t3
], insertt3 := load m0
After each operation that defines [the spilled symbol
t3
], insertstore t3, m0
Let's do this and also compute the live ranges:
t1 = 1 |
t2 = 2 | t1
t3 = 3 | t1, t2
// STORE
store t3, m0 | t1, t2, t3 (STILL 3 SYMBOLS!)
t4 = t1 + t2 | t1, t2
// LOAD
t3 = load m0 | t4
t5 = t3 + t4 | t3, t4
use t5 | t5
Problem
As you can see, we still have an instruction where three symbols interfere, and we'll also get the same RIG as before.
So the spill didn't work!
Now, I brute-forced the possible candidates for spilling by hand and found out that the RIG remains not 2-colorable if we spill any one symbol, but it becomes 2-colorable if we spill any of these pairs of symbols:
t1
andt3
t2
andt3
...and also t1
, t2
and t3
, but spilling only two symbols looks simpler.
Actual question
How do I decide what to spill? What heuristic do I use? I'd also like the algorithm to do global register assignment, so plain live ranges and linear-scan assignment (as opposed to building the RIG) seems to be a pretty cumbersome approach.
Also, if I re-run the same algorithm on the code with spills, the result will be "spill t3
" again, even though it's been done already, so the code will loop forever. Even more, if I continue spilling t3
and inserting load
/store
code, I'll get more and more instructions where t1
, t2
and t3
interfere, so the situation will be getting worse.
And that's assuming that I can just t3 = load m0
, while on ARM (that I'm targeting) m0
should be stored in its own register first.