Dominance Properties
1. If x is the i'th argument of a phifunction in CFG block n, then
the definition of x dominates the i'th predecessor of n.
2. If x is used in a nonphi 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 phinodes:
If node x defines a, then any node x in DF(x) requires a phifunction for a.
Since such a phifunction is itself a definition of a, must (in general) iterate
until there are no more phifunctions 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 phinodes 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 noncongruent children.
[Example: See Muchnick, pp. 350351.]
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 pseudovalues 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);