1 / 33

Lecture #12, May 15, 2007

Lecture #12, May 15, 2007. Basic Blocks, Control flow graphs, Liveness using data flow, dataflow equations, Using fixed-points. Assignments. Reading – chapter 9. Sections 9.1 and 9.2 Liveness analysis. pp 433-452 Possible quiz Wednesday or next Monday. Basic Blocks.

linore
Download Presentation

Lecture #12, May 15, 2007

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Lecture #12, May 15, 2007 • Basic Blocks, • Control flow graphs, • Liveness using data flow, • dataflow equations, • Using fixed-points.

  2. Assignments • Reading – • chapter 9. Sections 9.1 and 9.2 Liveness analysis. pp 433-452 • Possible quiz Wednesday or next Monday

  3. 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).

  4. Calculating Basic Blocks • To calculate basic blocks: • Determine BB leaders (→) : • First statement in routine • Target of any jump (conditional or unconditional). • Statement following any jump. • Basic block extends from leader to (but not including) next leader (or end of routine).

  5. 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. ---

  6. ML code Strategy – 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)

  7. Example 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 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

  8. 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.

  9. Algorithm • For each IR instruction x := y op z • If y isn't in a register, load it into a free one, updating descriptors. • Similarly for z . • If y and/or z are no longer live following this instruction, mark their registers as free. • Choose a free register for x , updating descriptors. • Generate instruction MOVE(rx,BINOP(op,ry,rz))

  10. 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?!?

  11. 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

  12. Algorithm trace Descriptors after Instructions IR 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

  13. 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.

  14. 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

  15. 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] = {}

  16. 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.

  17. 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

  18. 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

  19. 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}

  20. 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]

  21. 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.

  22. 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] succ[1] = {2} succ[2] = {3} succ[3] = {4} succ[4] = {5} succ[5] = {6,2} succ[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}

  23. Example live-in[n] = useN[n] U (live-out[n] – defN[n]) live-out[n] = U s  succ(n) live-in[s] succ[1] = {2} succ[2] = {3} succ[3] = {4} succ[4] = {5} succ[5] = {6,2} succ[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} 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]

  24. 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]

  25. 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.

  26. 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)

  27. 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

  28. Normalization fun 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 [])

  29. liveness algorithm fun 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)

  30. 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"],[]|]

  31. 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

  32. Second try fun 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.

  33. Keep applying fun 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;

More Related