Loops Given a CFG, formally define a *loop* as a set of CFG nodes S with header node h, such that (1) for all n in S, there is a path from n to h. (2) for all n in S, there is a path from h to n. (3) if n is in S and m not in S, there is no path from m to n that doesn't include h. A loop can have multiple exits, but only one entry (the header). Dominators Assume the CFG has a distinguished start node S, and has no disconnected subgraphs (nodes unreachable from S). Then we define that node d *dominates* node n if all paths from S to n include d. In particular, for all n, n dominates n. Fact: d dominates n iff d = n or d dominates all predecessors of n. So, the set D[n] of nodes that dominate n can be defined as: D[S] = { S } D[n] = { n } U (intersection over all p in pred[n] of D[p]) (where pred[n] = set of predecessors on n in CFG) Define the *immediate dominator* of n, idom(n) as follows: (1) idom(n) dominates n (2) idom(n) is not n (3) idom(n) does not dominate any other dominator of n (except n itself) Fact: every node (except S) has a unique immediate dominator. Hence the immediate dominator relation defined a tree, called the *dominator tree*, whose nodes are the nodes of the CFG, where the parent of a node is its immediate dominator. Have D[n] = {n} U (descendents of n in dominator tree) Fact: The dominator tree of a CFG can be computed in almost-linear time. Define a *back edge* of the CFG to be an edge whose source is dominated by its target (i.e., an edge whose source is a descendent of its target in the dominator tree). Every back edge n -> h defines a *natural loop* with header h, consisting of all nodes x dominated by h such that there is a path from x to n that doesn't include h. Note that a node h can be the header of more than one natural loop. For convenience, we alter the control flow graph to insert a *preheader* node above each loop header, with a single out-edge to the header node. Pointers that originally targeted the header node from outside the loop now target the pre-header node instead; pointers from inside the loop are unchanged. A CFG is *reducible* (or "well-structured") if, after we remove all back edges, the remaining graph is acyclic. This is true iff all cycles in the graph are natural loops. Loop Invariant Compuatations Consider a quad d: t <- a1 binop a2 that lies inside a loop. We say d is a *loop invariant* if, for each of i = 1 and 2 we have: (1) ai is a constant; or (2) all the definitions of ai that reach d are outside the loop; or (3) the sole definition of ai that reaches d is a loop invariant. (Note this is a recursive definition.) When is it safe to hoist such a loop invariant into the loop header (assuming that binop has no side-effects) ? (1) d dominates all loop exits at which t is live. (2) there is only one definition of t in the loop. (3) t is not live-out of the loop pre-header. Static Single Assignment (SSA) Form. - Every variable has one (static) definition (though defn. may be executed many times). - For straightline code, this is just like value numbering: Original Code: v <- 4 w <- v + 5 v <- 6 w <- v + 7 Code in SSA: v1 <- 4 w1 <- v1 + 5 v2 <- 6 w2 <- v2 + 6 - For general flow, must introduce phi-nodes. These are fictional operations, (usually) not intended to have exeuction significance. To interpret these nodes, must view code as CFG, with the in-edges to any node have well-defined order. Example 1: Original CFG: |--------| | P? | |--------| / \ / \ V V |------| |------| | v<-4 | | v<-5 | |------| |------| \ / \ / V V |------------| | w <- v + v | |------------| CFG in SSA form: |--------| | P? | |--------| / \ / \ V V |-------| |-------| | v1<-4 | | v2<-5 | |-------| |-------| \ / \ / V V |------------------| | v3 <- phi(v1,v2) | | w1 <- v3 + v3 | |------------------| Example 2: Original CFG: |-----------| | i <- 0 | | j <- 0 | ------------- | ---------------- | | | V V | |-----------| | | i > N ? | | |-----------| | /\ | / \ | EXIT \ | \ | |------------| | | j <- j + i | | | i <- j + 1 | | |------------| | | | |----------- CFG in SSA form: |-----------| | i1 <- 0 | | j1 <- 0 | ------------- | ---------------- 1 | | 2 | V V | |-----------------| | | i2 = phi(i1,i3) | | | j2 = phi(j1,j3) | | | i2 > N ? | | |-----------------| | /\ | / \ | EXIT \ | \ | |--------------| | | j3 <- j2 + i | | | i3 <- j2 + 1 | | |--------------| | | | |----------- Where should we put phi assignments, and for which variables? Simple answer: in every join node, for every variable in the program. Too expensive! Suffices to put a phi assignment for x in join nodes that aren't dominated by a single definition of x. More next time...