Cse322, Programming Languages and Compilers 1 6/14/2015 Lecture #13, May 15, 2007 Control flow...

41
Cse322, Programming Languages and Compilers 1 03/27/22 Lecture #13, May 15, 2007 Control flow graphs, Liveness using data flow, dataflow equations, Using fixed-points. Dynamic Liveness, The halting problem, Register Interference Graphs, Graph coloring, Flow graph relations dominators.
  • date post

    19-Dec-2015
  • Category

    Documents

  • view

    214
  • download

    0

Transcript of Cse322, Programming Languages and Compilers 1 6/14/2015 Lecture #13, May 15, 2007 Control flow...

Cse322, Programming Languages and Compilers

104/18/23

Lecture #13, May 15, 2007•Control flow graphs,•Liveness using data flow,•dataflow equations,•Using fixed-points.•Dynamic Liveness,•The halting problem,•Register Interference Graphs,•Graph coloring,•Flow graph relations•dominators.

Cse322, Programming Languages and Compilers

204/18/23

Assignments

• Project #2 is Due on Monday, May 22, 2006– The project template is ready.

– Please notify me, and I will email you the template.

• Reading – Same as on Monday– chapter 9. Sections 9.1 and 9.2 Liveness analysis. pp 433-452

– Possible quiz next Monday

Cse322, Programming Languages and Compilers

304/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

404/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

504/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

604/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

704/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

804/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

904/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

1004/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

1104/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

1204/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

1304/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

1404/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

1504/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

1604/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

1704/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

1804/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

1904/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

2004/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

2104/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

2204/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

2304/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;

Cse322, Programming Languages and Compilers

2404/18/23

Static vs Dynamic Liveness | | 1 ▼ .---------. | a = b*b | `---------' | | | 2 ▼ .---------. | c = a+b | `---------' | | 3 ▼ .-----------. | c >= b ? | `-----------' / \ / \ 4 ▼ 5 ▼ .-----------. .----------. | return a | | return c | `-----------' `----------'

Consider the graph

Is a live-out at node 2?

Cse322, Programming Languages and Compilers

2504/18/23

Some thoughts• It depends on whether control flow ever reaches

node 4.

• A smart compiler could answer no.

• A smarter compiler could answer similar questions about more complicated programs.

• But no compiler can ever always answer such questions correctly. This is a consequence of the uncomputability of the Halting Problem.

• So we must be content with static liveness, which talks about paths of control-flow edges, and is just a conservative approximation of dynamic liveness, which talks about actual execution paths.

Cse322, Programming Languages and Compilers

2604/18/23

The Halting Problem• Theorem: There is no program H that takes as input any

program P and its input X, and (without infinite-looping) returns true if P(X) halts and false if P(X) infinite-loops.

• Proof: Suppose there were such an H. From it, construct the function F(Y) = if H(Y,Y) then (while true do ()) else 1

• Now consider F(F).– If F(F) halts, then, by the definition of H, H(F,F)is true, so the

then clause executes, so F(F) does not halt.

– But, if F(F) loops forever, then H(F,F) is false, so the else clause is taken, so F(F) halts.

– Hence F(F) halts if any only if it doesn't halt.

• Since we've reached a contradiction, the initial assumption is wrong: there can be no such H.

Cse322, Programming Languages and Compilers

2704/18/23

Consequence

• Corollary: No program H'(P,X,L) can tell, for any program P, input X, and label L within P, whether L is ever reached on an execution of P on X.

• Proof: If we had H', we could construct H. Consider a program transformation T that, from any program P constructs a new program by putting a label L at the end of the program, and changing every halt to goto L$. Then H(P,X) = H'(T(P),X,L).

Cse322, Programming Languages and Compilers

2804/18/23

Register Interference Graphs• Mixing instruction selection and register allocation gets

confusing;• We need a more systematic way to look at the problem.• Initially generate code assuming an infinite number of

``logical'' registers; calculate live ranges Live after instr.ld a,t0 ; a:t0 t0

ld b,t1 ; b:t1 t0 t1

sub t0,t1,t2 ; t:t2 t0 t2

ld c,t3 ; c:t3 t0 t2 t3

sub t0,t3,t4 ; u:t4 t2 t4

add t2,t4,t5 ; v:t5 t4 t5

add t5,t4,t6 ; d:t6 t6

st t6,d

• Build a register interference graph, which has– a node for each logical register.

– an edge between two nodes if the corresponding registers are simultaneously live.

Cse322, Programming Languages and Compilers

2904/18/23

ExampleLive after instr.

ld a,t0 ; a:t0 t0

ld b,t1 ; b:t1 t0 t1

sub t0,t1,t2 ; t:t2 t0 t2

ld c,t3 ; c:t3 t0 t2 t3

sub t0,t3,t4 ; u:t4 t2 t4

add t2,t4,t5 ; v:t5 t4 t5

add t5,t4,t6 ; d:t6 t6

st t6,d

t0 t1t2

t3t4 t5

t6

Cse322, Programming Languages and Compilers

3004/18/23

• A coloring of a graph is an assignment of colors to nodes such that no two connected nodes have the same color. (Like coloring a map, where nodes=countries and edges connect countries with common border.

• Suppose we have k physical registers available. Then aim is to color interference graph with k or fewer colors. This implies we can allocate logical registers to physical registers without spilling.

• In general case, determining whether a graph can be k-colored is hard (N.P. Complete, and hence probably exponential).

• But a simple heuristic will usually find a k-coloring if there is one.

t0 t1t2

t3t4 t5

t6

Cse322, Programming Languages and Compilers

3104/18/23

Graph Coloring Heuristic

• 1. Choose a node with fewer than k neighbors.

• 2. Remove that node. Note that if we can color the resulting graph with k colors, we can also color the original graph, by giving the deleted node a color different from all its neighbors.

• Repeat until either – There are no nodes with fewer than k neighbors, in which case we

must spill;

– or

– The graph is gone, in which case we can color the original graph by adding the deleted nodes back in one at a time and coloring them.

Cse322, Programming Languages and Compilers

3204/18/23

Example: Find a 3-coloring

t0 t1t2

t3t4 t5

t0 t1t2

t3

t0 t1t2

t3t4

remove 6

remove 5

remove 4

t0 t1 t2

t3 t4 t5

t6

Cse322, Programming Languages and Compilers

3304/18/23

t0 t1

t3

remove 2

t0 t1t2

t3

t0

t3

remove 1

t0 remove 3

t0 t1t2

t3t4 t5

t6

Cse322, Programming Languages and Compilers

3404/18/23

• There cannot be a 2-coloring (why not?).

t0 t1t2

t3t4 t5

t6

Cse322, Programming Languages and Compilers

3504/18/23

More about Flow Graphs• Nodes: basic blocks• Edges: branches between

blocks

• Example: factorial

(1) f := 1

(2) i := 2

(3) if i>n then goto (7)

(4) f := f*i

(5) i := i+1

(6) goto (3)

(7) return

Cse322, Programming Languages and Compilers

3604/18/23

Flow Graphs (cont.)

(1) f:=1(2) i:=2

(3) if i>n then goto (7) (#4)

(4) f:=f*i(5) i:= i+1(6) goto (3) (#2)

(7) return

#1

#2

#3

#4

2

3 4

1

Cse322, Programming Languages and Compilers

3704/18/23

• Successor– j `succ` i if (i,j) is an edge in the flow graph

• Predecessor– inverse of successor

• Dominator– i `dominates` j if i is on every path from 1 (the initial node) to j

• Immediate dominator– i `idom` j if i `dom` j and there is no other node k such that

– k `idom` k and k `dom` j.

Flow Graph Relations

2

3 4

1

2 `succ` 1, 1 `pred` 2, 3 `succ` 2, etc

1 `dom` 2, 1 `dom` 3, 1 `dom` 4; 2 `dom` 3, 2 `dom` 4

1 `idom` 2; 2 `idom` 3, 2 `idom` 4

Cse322, Programming Languages and Compilers

3804/18/23

Flow Graph Relations (cont.)

• Each node has a unique immediate dominator• If i `idom` k then for all m,

(m `dom` k) => m `dom` i• Consequence:

– there exists a dominator tree (with the same nodes as the flow graph, but different edges) where each node in the dominator tree has an out-edge only to the nodes it immediately dominates.

2

3 4

1

3

2

4

1flow graph

dominatortree

Cse322, Programming Languages and Compilers

3904/18/23

Flow Graph Relations (cont.) • The dominator tree edges are not

necessarily flow graph edges:

1

2 3 4

1

2 3

4

original flow graph

dominator tree

note: every path from 1 to 4 must go through 1, but can go through either 2 or 3. So 2 & 3 do not immediately dominate 4.

Cse322, Programming Languages and Compilers

4004/18/23

Flow Graph Relations (cont.)• Flow graph application: finding loops.• A edge from B to A (in the flow graph) is a back-edge

iff A dominates B (i.e. exists a path from A to B in dominator tree).

• If we remove all back-edges, only forward edges remain. If this graph has no cycles (i.e. it’s a Dag) then the original flow graph is known as a a reducible graph.

• In a reducible graph:– every loop contains a back edge

– there are no jumps from outside into the middle of the loop

2

1

3

Non-reducible graph (rare; must use goto):

Cse322, Programming Languages and Compilers

4104/18/23

Next time

• Next time we’ll use flow graphs to implmement some more optimizations.