cse322, programming languages and compilers 1 6/15/2015 lecture #12, may 15, 2007 basic blocks,...

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

Post on 20-Dec-2015

213 views

Category:

Documents


0 download

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;