Dominance Properties 1. If x is the i'th argument of a phi-function in CFG block n, then the definition of x dominates the i'th predecessor of n. 2. If x is used in a non-phi statement in block n, then the definition of x dominates n. -------- Say x *strictly dominates* w if x dominates w and x is not w. The *dominance frontier* of a definition x, DF(x), is the set of nodes {w | x dominates an (immediate) predecessor of w, but x does not strictly dominate w}. Intuition: Any node in DF(x) is the join point of 2 disjoint paths from x and from the ENTRY node. Note: can compute DF(x) easily given dominator tree. Dominance Frontier Criterion for placing phi-nodes: If node x defines a, then any node x in DF(x) requires a phi-function for a. Since such a phi-function is itself a definition of a, must (in general) iterate until there are no more phi-functions to place. Example: CFG: |-------|0 | ENTER | |-------| | V |--------|1 | k <- 0 | | i <- 1 | | j <- 2 | |--------| | -----------------| | | V V | |--------|2 | | i <= N?| | |--------| | / \ | / \ | / \ | V V | |---------|3 |-----------|4 | | k <- 1 | | k > 0 ? | | | i <- i+1| |-----------| | |---------| / \ | | / \ | | V V |------| |---------|5 |-----------|6 | i <- 0 | | i<- i+1 | |---------| |-----------| \ / \ / V V |-----------|7 | EXIT | |-----------| Dominator tree: 0 | 1 | 2 / \ 3 4 /|\ / | \ 5 6 7 n DF(n) 0 {} 1 {} 2 {2} 3 {2} 4 {} 5 {7} 6 {7} 7 {} By dominance frontier criterion, must place phi-nodes for i and k in node 2, and for i in node 7, and then rename variables: Resulting CFG: |-------|0 | ENTER | |-------| | V |---------|1 | k1 <- 0 | | i1 <- 1 | | j1 <- 2 | |---------| | -----------------| | | V V | |------------------|2 | | k2 <- phi(k3,k1) | | | i2 <- phi(i3,i1) | | | i2 <= N? | | |------------------| | / \ | / \ | / \ | V V | |-----------|3 |-----------|4 | | k3 <- 1 | | k2 > 0 ? | | | i3 <- i2+1| |-----------| | |-----------| / \ | | / \ | | V V |------| |----------|5 |-------------|6 | i4 <- 0 | | i5<- i2+1 | |----------| |-------------| \ / \ / V V |------------------|7 | i6 <- phi(i4,i5) | | EXIT | |------------------| SSA Applications. Dead code elimination: a variable definition is dead if and only if the variable is unused. Constant propagation: if a variable is defined to be a constant, the constant value can be substituted wherever the variable is used. Global Value numbering: 1. Build SSA form. 2. Build value graph: One node for each constant and each operator. One (numbered) edge from each operator to each of its operands. Two nodes are *congruent* if they are the same node, or contain the same constant, or contain the same operator and have congruent operands (taken in the same order). Can compute congruence by partitioning nodes into congruence classes. Start with optimistic partitioning attempt, in which we put all nodes having the same operator into the same class. Then iteratively refine classes by distinguishing nodes that have non-congruent children. [Example: See Muchnick, pp. 350-351.] SWIFT compiler: good example of complete system based on SSA. To cope with memory operations, they add explicit "threading" store variables. Example: int method (int a[], int b[]) { arr_store(a,0,10); arr_store(b,0,20); return arr_fetch(a,0); } becomes (int,Store) method (int a[], int b[], Store S0) { S1 = arr_store(a,0,10,S0); S2 = arr_store(b,0,20,S1); return (arr_fetch(a,0,S2),S2); } where S0,S1,S2 are pseudo-values representing the global store. Can now continue to use congruence testing to detect redundant computations. Can use *alias analysis* to improve understanding of dependence between memory operations. In above example, a and b might be the same array, e.g., called as method(c,c). Simplest form of alias analysis just uses types: int method (int a[], short b[]) { arr_store(a,0,10); arr_store(b,0,20); return arr_fetch(a,0); } Now know a and b cannot be aliased to the same array. A more sophisticated alias analysis (requiring dataflow analysis) tracks creation points: int method() { int a[] = new a[10]; int b[] = new b[10]; arr_store(a,0,10); arr_store(b,0,20); return arr_fetch(a,0); } Once again, a and b cannot be aliased to the same array, even though they have the same type. Can represent the results of this analysis by changing the store argument dependencies: S1 = arr_store(a,0,10,S0); S2 = arr_store(b,0,20,S0); // not S1 ! S3 = phi(S1,S2); return (arr_fetch(a,0,S1), // not S2 ! S3);