Review: SSA Construction
We introduced control flow graphs (CFG) and statis single assignemnt form
(SSA form) last time, which is already widely used in modern compiler
optimization. We also introduced some basic ideas of constructing SSA from a
normal CFG. That is, inserting PHI instructions according to dominance
frontiers (DF).
Consider the following CFG, point out all pairs of nodes
u, v such that
u dominates
v.
r -->
A
A -->
B,
C
B -->
A
Consider the following CFG, point out all pairs of nodes
u, v such that
u dominates
v (and strictly dominates
v, respectively).
r -->
A
A -->
B,
C
B -->
D
C -->
D,
E
D -->
A,
E
Consider the following CFG, point out all pairs of nodes
u, v such that
u strictly dominates
v.
r -->
A,
E
A -->
B,
G
B -->
C,
D
C -->
E,
F
D -->
F,
G
E -->
H
F -->
H,
I
G -->
I
H -->
J
I -->
J
Consider the following CFG, compute
DF(A),
DF(B),
DF(C) and
DF(D).
r -->
A
A -->
B,
C
B -->
D
C -->
D,
E
D -->
A,
E
Consider the following CFG, compute
DF(A) and
DF(B)
r -->
A,
E
A -->
B,
G
B -->
C,
D
C -->
E,
F
D -->
F,
G
E -->
H
F -->
H,
I
G -->
I
H -->
J
I -->
J
Intuitively,
DF(n) is the boundary of those nodes dominated by
n.
Inserting PHI commands
Last time, we mentioned that we can compute the location of inserting PHI
commands by computing iterated dominance frontier, theoretically. In
practice,
DF(n) are pre-computed for every node
n and insertion
locations are computed using a working list
W and flags
F (to avoid
multiple insertions). The following is the algorithm description.
- For every variable v in the original program
- (1) Let F be the empty set
- (2) Let W be the empty set
- (3) For every basic block B in the CFG
- (3.1) Check whether B contains a definition of v
- (3.2) If yes, let W be the union of W and {B}.
- (4) While W is nonempty, execute the following operations
- (4.1) Let X be an element of W
- (4.2) Remove X from W
- (4.3) For every Y in DF(X) but not in F
- (4.3.1) Add a PHI-command for v at the entry of Y
- (4.3.2) Add Y to F
- (4.3.3) If Y itself does not contain a definition of v, add Y to W
In short, the worklist of nodes
W is used to record definition points that
the algorithm has not yet processed, i.e., it has not yet inserted
PHI-commands at their dominance frontiers. Because a PHI-command is itself a
definition of
v, it may require further PHI-functions to be inserted. This
is the cause of node insertions into the worklist
W during iterations of
the inner loop in the algorithm above. Effectively, we compute the iterated
dominance frontier on the fly. The set
F is used to avoid repeated
insertion of PHI-comamnds on a single block. Dominance frontiers of distinct
nodes may intersect, but once a PHI-command for a particular variable has
been inserted at a node, there is no need to insert another.
For inserting PHI commands, the only leftover problem is how to compute
dominance frontiers. Naively, we can traverse CFG from the root and compute
DF(n) for every node
n according to its definition. We will introduce
a more efficient algorithm for it later today.
Remark 1. For each variable use in a PHI-function, it is conventional to
treat them as if the use actually occurs on the corresponding incoming edge
or at the end of the corresponding predecessor node. If we follow this
convention, then we can claim that SSA forms always have the following nice
property: the single definition that reaches each use dominates that use.
Remark 2. Although we write consecutive PHI-commands sequentially, but their
behavior is actually parallel.
Renaming variables
Intuitively, every use of a variable
v should be renamed together with the
closest definition of
v before it. Moreover, this definition should
dominate the use, or else a PHI-command will be inserted. This critical
observation inspires us to complete variable renaming efficiently with the
help of dominance relation.
Dominance trees
The main idea is, the dominance relation of a CFG has a tree-like structure.
Consider three node
u,
v and
w and suppose that both
u and
v
strictly dominates
w. Then either
u dominates
v or
v dominates
u
according to the definition of dominance relation.
The
immediate dominator or ``idom'' of a node
n is the unique node that
strictly dominates
n but does not strictly dominate any other node that
strictly dominates
n. All nodes in a CFG but the entry node have immediate
dominators. A dominance tree is a tree where the children of each node are
those nodes it immediately dominates. For example,
CFG:
A -->
B,
C
B -->
D
C -->
D
D -->
E
Dom tree:
A -->
B,
C,
D
D -->
E
Compute the dominance tree of the following CFG.
A -->
B
B -->
C,
D
C -->
B
D -->
E
Renaming variables using dominance tree
We can complete variable renaming via a depth first search on a dominance
tree.
- When entering a basic block n in DFS preorder traversal of the dominance
tree
- (1) For each instruction i in n (in their order)
- (1.1) For each v used in i, when i is not a PHI-command
- (1.1.1) UpdateReachingDef(v, i)
- (1.1.2) Replace this use of v in i by v.RD
- (1.2) For the variable v defined by i
- (1.2.1) UpdateReachingDef(v, i)
- (1.2.2) Create fresh variable v'
- (1.2.3) Replace this definition of v by v' in i
- (1.2.4) Let v'.PD be v.RD
- (1.2.5) Let v.RD be v'
- (2) For each PHI-command i in n's successors
- (2.1) For each v used in i
- (2.1.1) UpdateReachingDef(v, i)
- (2.1.2) Replace this use of v in i by v.RD
In this algorithm, for new created variables
v',
v'.PD represents a
linked-list-like structure indicating the previous available definition (PD)
of
v''s original variable. For an original program variable
v,
v.RD is
the reaching definition (RD) of
v that can be used on the current
location. It will be updated by the following algorithm (the algorithm of
UpdateReachingDef(v,i)):
- (1) Let r be v.RD
- (2) While the location of r's definition does not dominates i
- (2.1) Let r be r.PD
- (3) Let v.RD be r
Computing dominance frontiers by dominance tree
Using dominance tree, we can compute dominance frontiers of different nodes
in an more efficient approach.
- (1) For every node n in the CFG
- (1.1) Let DF(n) be the empty set
- (2) For every edge from X to Y in the CFG
- (2.1) Let n be X
- (2.2) While n does not strictly dominates Y
- (2.2.1) Add Y to DF(n)
- (2.2.2) Let n be n's immediate dominator
Desctructing SSA form
When freshly constructed, an SSA code is
conventional (we will explain
that later) and its destruction is straightforward: one simply has to
rename all PHI-related variables (source and destination operands of the
same Phi-function) into a unique representative variable. Then, each
PHI-function should have syntactically identical names for all its operands,
and thus can be removed to coalesce the related live-ranges.
Here, we say that
x and
y are PHI-related to one another if they are
referenced by the same PHI-command, i.e., if
x and
y are either
parameters or defined by the PHI-command. We refer to a set of PHI-related
variables as a PHI-web, i.e. a PHI-web is an equivalence class of the
reflexive transitive closure of the PHI-related relation. The PHI-webs
discovery algorithm is straightforward and efficient based on the
union-find pattern:
- (1) For each variable v
- (1.1) Let phiweb(v) be the singleton set containing v
- (2) For each PHI-command a0 = PHI(a1, a2, ..., an)
- (2.1) For i in 1, 2, 3, ..., n
- (2.1.1) Union phiweb(a0) and phiweb(ai)
We call an SSA code conventional, if all variables of a PHI-web have
non-overlapping live-ranges.