Presentation is loading. Please wait.

Presentation is loading. Please wait.

Cse322, Programming Languages and Compilers 1 6/15/2015 Lecture #12, May 15, 2007 Basic Blocks, Control flow graphs, Liveness using data flow, dataflow.

Similar presentations


Presentation on theme: "Cse322, Programming Languages and Compilers 1 6/15/2015 Lecture #12, May 15, 2007 Basic Blocks, Control flow graphs, Liveness using data flow, dataflow."— Presentation transcript:

1 Cse322, Programming Languages and Compilers 1 6/15/2015 Lecture #12, May 15, 2007 Basic Blocks, Control flow graphs, Liveness using data flow, dataflow equations, Using fixed-points.

2 Cse322, Programming Languages and Compilers 2 6/15/2015 Assignments Reading – –chapter 9. Sections 9.1 and 9.2 Liveness analysis. pp 433-452 –Possible quiz Wednesday or next Monday

3 Cse322, Programming Languages and Compilers 3 6/15/2015 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 Cse322, Programming Languages and Compilers 4 6/15/2015 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).

5 Cse322, Programming Languages and Compilers 5 6/15/2015 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 Cse322, Programming Languages and Compilers 6 6/15/2015 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 Cse322, Programming Languages and Compilers 7 6/15/2015 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 Cse322, Programming Languages and Compilers 8 6/15/2015 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 Cse322, Programming Languages and Compilers 9 6/15/2015 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(r x,BINOP(op,r y,r z ))

10 Cse322, Programming Languages and Compilers 10 6/15/2015 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 Cse322, Programming Languages and Compilers 11 6/15/2015 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 Cse322, Programming Languages and Compilers 12 6/15/2015 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 Cse322, Programming Languages and Compilers 13 6/15/2015 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 n 1 to n 2 if control can ever flow directly from statement 1 to statement 2.

14 Cse322, Programming Languages and Compilers 14 6/15/2015 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 Cse322, Programming Languages and Compilers 15 6/15/2015 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 Cse322, Programming Languages and Compilers 16 6/15/2015 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 Cse322, Programming Languages and Compilers 17 6/15/2015 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 Cse322, Programming Languages and Compilers 18 6/15/2015 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 Cse322, Programming Languages and Compilers 19 6/15/2015 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 Cse322, Programming Languages and Compilers 20 6/15/2015 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 Cse322, Programming Languages and Compilers 21 6/15/2015 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 Cse322, Programming Languages and Compilers 22 6/15/2015 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] = {}

23 Cse322, Programming Languages and Compilers 23 6/15/2015 Example 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] = {} 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 Cse322, Programming Languages and Compilers 24 6/15/2015 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] nodeuse def1 st out in 2 nd out in 3 rd out in 6c c c c 5ac acac 4b aac bc 3bc bbc 2a bbc ac 1 aac c

25 Cse322, Programming Languages and Compilers 25 6/15/2015 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(N 4 ) worst-case, but between O(N) and O(N 2 ) 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 Cse322, Programming Languages and Compilers 26 6/15/2015 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 Cse322, Programming Languages and Compilers 27 6/15/2015 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 Cse322, Programming Languages and Compilers 28 6/15/2015 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 Cse322, Programming Languages and Compilers 29 6/15/2015 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 Cse322, Programming Languages and Compilers 30 6/15/2015 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 Cse322, Programming Languages and Compilers 31 6/15/2015 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 Cse322, Programming Languages and Compilers 32 6/15/2015 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 Cse322, Programming Languages and Compilers 33 6/15/2015 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;


Download ppt "Cse322, Programming Languages and Compilers 1 6/15/2015 Lecture #12, May 15, 2007 Basic Blocks, Control flow graphs, Liveness using data flow, dataflow."

Similar presentations


Ads by Google