Chapters 11 and 12
Ch. 11
- Register Allocation
- an application of graph coloring
- graph coloring: assign colors to all nodes such that no two adjacent
nodes have the same color but we use a minimum number of colors
- classic application: color states/countries on a map
- 4-color theorem: if can draw graph on paper without crossing lines,
need at most 4 colors
- application to register allocation: colors = register names
- if need more colors than registers, need to spill registers
into memory
- issue: graph coloring is "NP Complete"
- all known (perfect) algorithms are exponential: try all colorings with
2, 3, ... N (where N is the number of nodes in the graph) until find one
which works
- each trial: consider all Nk possible colorings for k colors
- furthermore, can reduce this problem (in polynomial time) to a large
set of other problems which seem to require exponential time
- a polynomial-time solution to any one of the problems would lead to
polynomial-time solutions to all of them
- no one can yet prove polynomial-time solutions do not exist
- people have tried to find polynomial-time solutions to these problems
but have failed
- consequence: "perfect" register allocation is not practical
- given 20 variables and 10 registers, may need to examine 1020
combinations
- at 1012 combinations per second, need 108 seconds
or 3+ years to evaluate all!
Practical Register Allocation
- linear-time approximation algorithm giving good results
- phases:
- Build: construct interference graph
- Simplify: color graph using heuristic
- Let K be the number of registers; goal is to find a coloring
that uses no more than K distinct colors/registers
- Observation: if node m in G has fewer than K
neighbors (where K is the number of registers) and G' = G -
{m} can be K - 1 colored, then can color G
- Algorithm: find a node with fewer than K neighbors, push that
node on a stack, remove it, then recursively look for a node
with fewer than K neighbors. Stop when all nodes removed or no
node has fewer than K neighbors.
- Spill: if all nodes have too many neighbors (so we may need to
spill some node to memory), pick one and mark it to be spilled
and push that node on the stack. Then go back to Simplify.
- Select: When graph is empty, we rebuild the graph by picking the
topmost node from the stack, allocating it to a register. When we reach a
node which was marked for spill, we determine if there is a possible
allocation after all - that is, its neighbors already use all
registers. If it can be allocated, we do so and simply continue. Of not,
we go on to identify other variables that also need to be spilled. This
step terminates with either all variables allocated as a register or some
variables must be spilled.
- Start Over: If some variables must be spilled, rewrite the
program to fetch the variables from memory just before using them and
go back to Build.
- the rewrite results in different live ranges, hence a different
interference graph
- in practice: one or two iterations is almost always is sufficient
do example on p. 221 (graphs 11.1, 11.2, 11.3)
- 4 registers: apply simplify by removing k, d, j, e, etc.
(this is not the same order as the book)
Coalescing
- joining nodes which do not interfere
- example: source and destination in a move instruction
- used to eliminate move instructions
- issue: increase the degree of the remaining node
- graph may be colorable with K colors before but not after coalescing
- coalescing strategies which do not make a graph uncolorable
- Briggs Strategy: coalesce nodes a and b if the resulting node ab has
fewer than K neighbors with a degree >= K
- George Strategy: coalesce a and b if every neighbor t of a is also a
neighbor of b or has an "insignificant" degree
- Both are conservative: guaranteed to not make graph non-K-colorable
- algorithm: mix coalescing with simplify
Precolored Nodes
- Pre-allocated registers can be dealt with by coloring them initially
- don't simplify, spill precolored node
- keep live ranges short by generating move instructions
- caller & callee-save registers
- those live across several procedure calls: allocate to callee-save
- not live across procedure calls: caller-save
- implementation: callee-save registers are live at entry point and exit
Other Notes
- implementation: see book
- register allocation for trees: see book
Ch. 12: Full Compiler
- structured l-values: copying more than one word of data
- intermediate representation
- no representation for procedure prolog, epilogue
- things like object creation "hidden" as calls to malloc
- result: not ideal for optimizations, esp. inter-procedural optimization
- tree language really written for ease-of-code generation, not other
issues
- register allocation
- why implement such a complicated process?
- other parts would be more complicated (more careful allocation of
temporaries, etc.)
Review
- register allocation as graph coloring
- NP-complete problems
- coloring by simplification
- coalescing
- precolored nodes
- Mini-Java compiler as a project