cse322, programming languages and compilers 1 6/15/2015 lecture #12, may 15, 2007 basic blocks,...
Post on 20-Dec-2015
213 views
TRANSCRIPT
Cse322, Programming Languages and Compilers
104/18/23
Lecture #12, May 15, 2007•Basic Blocks,•Control flow graphs,•Liveness using data flow,•dataflow equations,•Using fixed-points.
Cse322, Programming Languages and Compilers
204/18/23
Assignments
• Reading –– chapter 9. Sections 9.1 and 9.2 Liveness analysis. pp 433-452
– Possible quiz Wednesday or next Monday
Cse322, Programming Languages and Compilers
304/18/23
Basic Blocks
• Extend analysis of register use to program units larger than expressions but still completely analyzable at compile time.
• Basic Block = sequence of instructions with single entry & exit.
• If first instruction of BB is executed, so is remainder of block (in order).
Cse322, Programming Languages and Compilers
404/18/23
Calculating Basic Blocks
• To calculate basic blocks:
1. Determine BB leaders (→) :1. First statement in routine
2. Target of any jump (conditional or unconditional).
3. Statement following any jump.
2. Basic block extends from leader to (but not including) next leader (or end of routine).
Cse322, Programming Languages and Compilers
504/18/23
Basic Block Example prod := 0;
i := 1;
while i <= 20 do
prod := prod + a[i] * b[i];
i := i + 1
end
→ 1. prod := 0 2. i := 1→ 3. if i > 20 goto 14→ 4. t1 := i * 4 5. t2 := addr a 6. t3 := *(t2+t1) 7. t4 := i * 4 8. t5 := addr b 9. t6 := *(t5+t4) 10. t7 := t3 * t6 11. prod := prod + t7 12. i := i + 1 13. goto 3→ 14. ---
Cse322, Programming Languages and Compilers
604/18/23
ML codeStrategy – Move code from input to the current block list, until we see a
leader. Then add current as a new block to blocks, and reinitialize current to the empty list.
fun bb (LABEL n :: ss) current blocks = bb ss [LABEL n] (rev current :: blocks) | bb (JUMP n :: ss) current blocks = bb ss [] (rev (JUMP n :: current) :: blocks) | bb ((s as (CJUMP _)) :: ss) current blocks = bb ss [] (rev (s :: current) :: blocks) | bb ((s as (CALLST _)) :: ss) current blocks = bb ss [] (rev (s :: current) :: blocks) | bb (COMMENT(s,m):: ss) current blocks = bb (s::ss) current blocks | bb (s::ss) current blocks = bb ss (s::current) blocks | bb [] [] blocks = rev blocks | bb [] current blocks = rev((rev current) :: blocks)
Cse322, Programming Languages and Compilers
704/18/23
Example
T1 := 0L1: T2 := T1 + 1
T3 := T2 + T3
T1 := (2 * T2)
if T1 < 1000 GOTO L1
return T3
T1 := 0
L1: T2 := T1 + 1
T3 := T2 + T3
T1 := (2 * T2)
if T1 < 1000 GOTO L1
return T3
T1 := 0
L1:
T2 := T1 + 1 L1:
T3 := T2 + T3 T2 := T1 + 1 L1:
T1 := (2 * T2) T3 := T2 + T3 T2 := T1 + 1 L1:
The current block grows in reverse order
with most recursive calls
Cse322, Programming Languages and Compilers
804/18/23
BB Code Generation using Liveness
• Can combine code generation with ``greedy'' register allocation:
– Bring each variable into a register when first needed, and leave it there as long as it's needed (if possible).
• Maintain register descriptors saying which variable is in each register, and address descriptors saying where (in memory and/or a register) each variable is.
Cse322, Programming Languages and Compilers
904/18/23
Algorithm• For each IR instruction x := y op z
1. If y isn't in a register, load it into a free one, updating descriptors.
2. Similarly for z .
3. If y and/or z are no longer live following this instruction, mark their registers as free.
4. Choose a free register for x , updating descriptors.
5. Generate instruction MOVE(rx,BINOP(op,ry,rz))
Cse322, Programming Languages and Compilers
1004/18/23
Notes
• For the special case x := y , load y into a register, if necessary, and then mark that register as holding x too.
• Must now be careful not to free a register unless none of its associated variables is live.
• Registers behave like a cache for memory locations.
• Nasty problems for source-level debuggers and dump utilities – where is that variable?!?
Cse322, Programming Languages and Compilers
1104/18/23
Example
Source code: d := (a-b) + (a-c) + (a-c)
Live after inst:
a b c
IR: t := a - b a c t
u := a - c t u
v := t + u u v
d := v + u d
Cse322, Programming Languages and Compilers
1204/18/23
Algorithm trace Descriptors after InstructionsIR Statement Code Regs Addrs
- a,b,c:mem
t := a - b ld a,r0 r0:a a:r0,mem ld b,r1 r1:t b,c:mem sub r0,r1,r1 t:r1
u := a - c ld c,r2 r0:u a,b,c:mem sub r0,r2,r0 r1:t u:r0 t:r1
v := t + u add r1,r0,r1 r0:u a,b,c:mem r1:v u:r0 v:r1
d := v + u add r0,r1,r0 r0:d a,b,c:mem st r0,d r1:v d:r0,mem
Cse322, Programming Languages and Compilers
1304/18/23
Control Flow Graphs
• To assign registers on a per-procedure basis, need to perform liveness analysis on entire procedure, not just basic blocks.
• To analyze the properties of entire procedures with multiple basic blocks, we use a control-flow graph.
• In simplest form, control flow graph has one node per statement, and an edge from n1 to n2 if control can ever flow directly from statement 1 to statement 2.
Cse322, Programming Languages and Compilers
1404/18/23
• We write pred[n] for the set of predecessors of node n,
• and succ[n] for the set of successors.
• (In practice, usually build control-flow graphs where each node is a basic block, rather than a single statement.)
• Example routine: a = 0
L: b = a + 1
c = c + b
a = b * 2
if a < N goto L
return c
Cse322, Programming Languages and Compilers
1504/18/23
Example |
|
1 ▼
.-------.
| a = 0 |
`-------’
|
| .------.
| | |
2 C ▼ |
.-----------. |
| b = a + 1 | |
`-----------’ |
| |
| |
3 ▼ |
.-----------. |
| c = c + b | |
`-----------’ |
| |
| |
4 ▼ |
.-----------. |
| a = b * 2 | |
`-----------’ |
| |
| |
5 ▼ |
.-------. |
| a < N | |
`-------’ |
| T | |
F | `-------’
|
6 ▼
.----------.
| return c |
`----------’
pred[1] = ?
pred[2] = {1,5}
pred[3] = {2}
pred[4] = {3}
pred[5] = {4}
pred[6] = {5}
succ[1] = {2}
succ[2] = {3}
succ[3] = {4}
succ[4] = {5}
succ[5] = {6,2}
succ[6] = {}
Cse322, Programming Languages and Compilers
1604/18/23
Liveness Analysis using Dataflow• Working from the future to the past, we can
determine the edges over which each variable is live.
• In the example:
• b is live on 2 → 3 and on 3 → 4.
• a is live from on 1 → 2, on 4 → 5, and on 5 → 2 (but not on 2 → 3 → 4).
• c is live throughout (including on entry → 1).
• We can see that two registers suffice to hold a, b and c.
Cse322, Programming Languages and Compilers
1704/18/23
Dataflow equations
• We can do liveness analysis (and many other analyses) via dataflow analysis.
• A node defines a variable if its corresponding statement assigns to it.
• A node uses a variable if its corresponding statement mentions that variable in an expression (e.g., on the rhs of assignment).
– Recall our ML function varsOf
Cse322, Programming Languages and Compilers
1804/18/23
Definitions
• For any variable v define:– defV[v] = set of graph nodes that define v
– useV[v] = set of graph nodes that use v
• Similarly, for any node n, define– defN[n] = set of variables defined by node n
– useN[n] = set of variables used by node n
Cse322, Programming Languages and Compilers
1904/18/23
Example |
|
1 ▼
.-------.
| a = 0 |
`-------’
|
| .------.
| | |
2 C ▼ |
.-----------. |
| b = a + 1 | |
`-----------’ |
| |
| |
3 ▼ |
.-----------. |
| c = c + b | |
`-----------’ |
| |
| |
4 ▼ |
.-----------. |
| a = b * 2 | |
`-----------’ |
| |
| |
5 ▼ |
.-------. |
| a < N | |
`-------’ |
| T | |
F | `-------’
|
6 ▼
.----------.
| return c |
`----------’
defV[a] = {1,4}
defV[b] = {2}
defV[c] = {?,3}
useV[a] = {2,5}
useV[b] = {3,4}
useV[c] = {3,6}
defN[1] = {a}
defN[2] = {b}
defN[3] = {c}
defN[4] = {a}
defN[5] = {}
defN[6] = {}
useN[1] = {}
useN[2] = {a}
useN[3] = {c,b}
useN[4] = {b}
useN[5] = {a}
useN[6] = {c}
Cse322, Programming Languages and Compilers
2004/18/23
Setting up equations– A variable is live on an edge if there is a directed path from that
edge to a use of the variable that does not go through any def.
– A variable is live-in at a node if it is live on any in-edge of that node;
– It is live-out if it is live on any out-edge.
• Then the following equations hold
live-in[n] = useN[n] U (live-out[n] – defN[n])
live-out[n] = U s succ(n) live-in[s]
Cse322, Programming Languages and Compilers
2104/18/23
Computing• We want the least fixed point of these
equations: the• smallest live-in and live-out sets such that
the equations hold.
• We can find this solution by iteration:
– Start with empty sets for live-in and live-out
– Use equations to add variables to sets, one node at a time.
– Repeat until sets don't change any more.
• Adding additional variables to the sets is safe, as long as the sets still obey the equations, but inaccurately suggests that more live variables exist than actually do.
Cse322, Programming Languages and Compilers
2204/18/23
The Problem
• We want to compute: live-in and live-out• We know:
• by using:
live-in[n] = useN[n] U (live-out[n] – defN[n])
live-out[n] = U s succ(n) live-in[s]
defN[1] = {a}
defN[2] = {b}
defN[3] = {c}
defN[4] = {a}
defN[5] = {}
defN[6] = {}
useN[1] = {}
useN[2] = {a}
useN[3] = {c,b}
useN[4] = {b}
useN[5] = {a}
useN[6] = {c}
succ[1] = {2}
succ[2] = {3}
succ[3] = {4}
succ[4] = {5}
succ[5] = {6,2}
succ[6] = {}
Cse322, Programming Languages and Compilers
2304/18/23
Examplelive-in[n] = useN[n] U (live-out[n] – defN[n])
live-out[n] = U s succ(n) live-in[s]
defN[1] = {a}
defN[2] = {b}
defN[3] = {c}
defN[4] = {a}
defN[5] = {}
defN[6] = {}
useN[1] = {}
useN[2] = {a}
useN[3] = {c,b}
useN[4] = {b}
useN[5] = {a}
useN[6] = {c}
succ[1] = {2}
succ[2] = {3}
succ[3] = {4}
succ[4] = {5}
succ[5] = {6,2}
succ[6] = {}
Lets do node 5
live-out[5] = { } live-in[5]={ }
live-out[5] = U s {6,2} live-in[s]
so now we need to do live-in[6] and live-in[2]
Cse322, Programming Languages and Compilers
2404/18/23
Solution• For correctness, order in which we take nodes doesn't matter, but it turns
out to be fastest to take them in roughly reverse order:
– live-in[n] = use[n] U (live-out[n] – def[n])– live-out[n] = U s succ(n) live-in[s]
node use def
1st
out in
2nd
out in
3rd
out in
6 c c
c
c
5 a c ac
ac ac
ac ac
4 b a
ac bc
ac bc
ac bc
3 bc b
bc bc
bc bc
bc bc
2 a b
bc ac
bc ac
bc ac
1 a
ac c
ac c
ac c
Cse322, Programming Languages and Compilers
2504/18/23
Implementation issues• Algorithm always terminates, because each
iteration must enlarge at least one set, but sets are limited in size (by total number of variables).
• Time complexity is O(N4) worst-case, but between O(N) and O(N2) in practice.
• Typically do analysis using entire basic blocks as nodes.
• Can compute liveness for all variables in parallel (as here) or independently for each variable, on demand.
• Sets can be represented as bit vectors or linked lists; best choice depends on set density.
Cse322, Programming Languages and Compilers
2604/18/23
ML code
• First we need operations over sets– union
– setMinus
– normalization
fun union [] ys = ys
| union (x::xs) ys =
if List.exists (fn z => z=x) ys
then union xs ys
else x :: (union xs ys)
Cse322, Programming Languages and Compilers
2704/18/23
SetMinus
fun remove x [] = []
| remove x (y::ys) =
if x=y
then ys
else y :: remove x ys;
fun setMinus xs [] = xs
| setMinus xs (y::ys) =
setMinus (remove y xs) ys
Cse322, Programming Languages and Compilers
2804/18/23
Normalizationfun sort' comp [] ans = ans | sort' comp [x] ans = x :: ans | sort' comp (x::xs) ans = let fun LE x y = case comp(x,y) of GREATER => false | _ => true fun GT x y = case comp(x,y) of GREATER => true | _ => false val small = List.filter (GT x) xs val big = List.filter (LE x) xs in sort' comp small (x::(sort' comp big ans)) end;
fun nub [] = [] | nub [x] = [x] | nub (x::y::xs) = if x=y then nub (x::xs) else x::(nub (y::xs));
fun norm x = nub (sort' String.compare x [])
Cse322, Programming Languages and Compilers
2904/18/23
liveness algorithmfun computeInOut succ defN useN live_in live_out range = let open Array fun out n = let val nexts = sub(succ,n) fun getLive x = sub(live_in,x) val listOflists = map getLive nexts val all = norm(List.concat listOflists) in update(live_out,n,all) end fun inF n = let val ans = union (sub(useN,n)) (setMinus (sub(live_out,n)) (sub(defN,n))) in update(live_in,n,norm ans) end fun run i = (out i; inF i) in map run range end;
Array access functions x[i] == sub(x,i) x[i] = e == update(x,i,e)
Cse322, Programming Languages and Compilers
3004/18/23
val it = [|[],[],[],[],[],[],[]|] val it = [|[],[],[],[],[],[],[]|] - computeInOut succ defN useN live_in live_out [6,5,4,3,2,1];
val it = [|[],[],["a"],["b","c"],["b"],[],["c"]|] val it = [|[],[],[],[],[],["a"],[]|] - computeInOut succ defN useN live_in live_out [6,5,4,3,2,1];
val it = [|[],[],["a"],["b","c"],["b"],[],["c"]|] val it = [|[],["a"],["b","c"],["b"],[],["a","c"],[]|] - computeInOut succ defN useN live_in live_out [6,5,4,3,2,1];
val it = [|[],[],["a","c"],["b","c"],["b"],["a","c"],["c"]|]val it = [|[],["a"],["b","c"],["b"],[],["a","c"],[]|] - computeInOut succ defN useN live_in live_out [6,5,4,3,2,1];
val it = [|[],[],["a","c"],["b","c"],["b"],["a","c"],["c"]|]val it = [|[],["a","c"],["b","c"],["b"],["a","c"],["a","c"],[]|]- computeInOut succ defN useN live_in live_out [6,5,4,3,2,1];
val it = [|[],["c"],["a","c"],["b","c"],["b","c"],["a","c"],["c"]|]val it = [|[],["a","c"],["b","c"],["b"],["a","c"],["a","c"],[]|]- computeInOut succ defN useN live_in live_out [6,5,4,3,2,1];
val it = [|[],["c"],["a","c"],["b","c"],["b","c"],["a","c"],["c"]|]val it = [|[],["a","c"],["b","c"],["b","c"],["a","c"],["a","c"],[]|]- computeInOut succ defN useN live_in live_out [6,5,4,3,2,1];
val it = [|[],["c"],["a","c"],["b","c"],["b","c"],["a","c"],["c"]|]val it = [|[],["a","c"],["b","c"],["b","c"],["a","c"],["a","c"],[]|]
Cse322, Programming Languages and Compilers
3104/18/23
Fixed point algorithm• Repeat computeInOut until live_in and live_out remain unchanged after a full iteration.
• The comparison is expensive. • Since we never subtract any thing from one
of these arrays, we need only detect when we assign a value to a particular index that is different from the one already there.
• A full iteration with no changes, means we’ve reached a fixpoint.
fun change (array,index,value) = let val old = Array.sub(array,index) in Array.update(array,index,value) ; Bool.not(old=value) end;
Returns true only if we’ve made a change
Cse322, Programming Languages and Compilers
3204/18/23
Second tryfun computeInOut succ defN useN live_in live_out range = let open Array fun out n = let val nexts = sub(succ,n) fun getLive x = sub(live_in,x) val listOflists = map getLive nexts val all = norm(List.concat listOflists) in change(live_out,n,all) end fun inF n = let val ans = union (sub(useN,n)) (setMinus (sub(live_out,n)) (sub(defN,n))) in change(live_in,n,norm ans) end fun run(i,change) = (out i orelse inF i orelse change) in List.foldr run false range end;
returns true only if a change has
been made
iterates over all n and determines if any
change. Note change starts at false.
Cse322, Programming Languages and Compilers
3304/18/23
Keep applyingfun try succ defN useN = let val n = Array.length succ val live_in = Array.array(n,[]:string list) val live_out = Array.array(n,[]:string list) fun repeat () = if (computeInOut succ defN useN live_in live_out [6,5,4,3,2,1]) then repeat () else () in repeat(); (live_out,live_in) end;