%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
n0 --> n1;
n1 --> n2;
n1 --> n3;
n2 --> n4; n3 --> n4;
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; n0 --> n1; n1 --> n2; n1 --> n3; n2 --> n4; n3 --> n4;
We are going to define assorted graph properties, that can be calculated on cfgs.
We first define a binary relation on cfg nodes, called dominance. a node d dominates a node i (d dom i) if every possible execution path in the cfg that goes from the entry to i goes through d.
We next define immediate dominators a idom b, a != b and there is no c != a and c != b where a dom c and c dom b.
A strict dominator a sdom b if a dom b and a != b
A control flow graph
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
n0 --> n1;
n1 --> n2;
n1 --> n3;
n2 --> n4; n3 --> n4;
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; n0 --> n1; n1 --> n2; n1 --> n3; n2 --> n4; n3 --> n4;
The dominator tree
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
n0 --> n1;
n1 --> n2;n1 --> n3 n1 --> n4;
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; n0 --> n1; n1 --> n2;n1 --> n3 n1 --> n4;
A control flow graph
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
n1 --> n2;
n2 --> n3;
n2 --> n4; n2--> n6;
n3 --> n5
n4 --> n5 n5 --> n2
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; n1 --> n2; n2 --> n3; n2 --> n4; n2--> n6; n3 --> n5 n4 --> n5 n5 --> n2
Dominator tree
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
n1--> n2
n2 --> n3
n2 --> n4
n2--> n5 n2 --> n6
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; n1--> n2 n2 --> n3 n2 --> n4 n2--> n5 n2 --> n6
\[ \begin{gathered} \operatorname{Dom}\left(n_o\right)=\left\{n_o\right\} \\ \operatorname{Dom}(n)=\{n\} \cup\left(\bigcap_{p \in \operatorname{preds}(n)} \operatorname{Dom}(p)\right) \end{gathered} \]
To find the dominators of a node, first put the node itself in the dominators set. Then, take all the common (i.e. intersection) dominators of its predecessors and put them in the set.
What order do we want to process the nodes?
assume nodes start at 0,
compute_dominators(CFG cfg) {
cfg[0].dominators = {0}
for (bb in cfg except 0) {
b.dominators = {all nodes in cfg}
}
do {
change = false;
for (bb in cfg except 0) {
temp = {all nodes in cfg}
for (pred in bb.predecessors) {
temp = intersect(temp, pred.dominators)
}
temp = union(temp, {bb})
if (temp != bb.dominators) {
change = true
bb.dominators = temp
}
}
} while (change);
}
number the vertices starting at 0, vertices are 0,1,2, number_of_vertices -1 so we could use a bit-vector for the set, and we should process vertices in reverse post order
Cooper, Harvey, Kennedy Algorithm
if we have the dominator tree, finding immediate dominators is easy, its the parent of the node Finding dominators is also easy, its all the parents on the path from the entry to the node
suppose we have a node in the cfg with two parents, like n4, if we takes paths backward in the dominator tree the first common ancestor is n1, (the dominator)
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
n0 --> n5;
n0 --> n1;
n5 --> n7;
n5 --> n6;
n1 --> n2 ;
n1 --> n3;
n7 --> n8;
n6 --> n4;
n2 --> n4;
n4 --> n8 ; n3 --> n8;
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; n0 --> n5; n0 --> n1; n5 --> n7; n5 --> n6; n1 --> n2 ; n1 --> n3; n7 --> n8; n6 --> n4; n2 --> n4; n4 --> n8 ; n3 --> n8;
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
n0--> n5
n0 --> n1
n5--> n7
n5--> n6
n1--> n2 n1 --> n3
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; n0--> n5 n0 --> n1 n5--> n7 n5--> n6 n1--> n2 n1 --> n3
need n4 and n8
both are dominated by n0
for each node in the dom tree we have the depth, how far from the root, so if a and b have the same parent, that is the dominator, otherwise move the node with the higher depth up one
a fast way to determine which node is lower keep the nodes in post order, nodes at the top of the cfg have higher numbers
intersect(b1, b2, idoms,postorder_map) {
while (b1 != b2) {
if (postorder_map[b1] < postorder_map[b2]) {
b1 = idoms[b1];
} else {
b2 = idoms[b2];
}
}
return b1;
void compute_dominators(CFG cfg) {
// Some initialization steps and e.g. get postorder.
// Map its basic block to its postorder traversal.
foreach (p ; postorder) {
postorder_map[p] = counter;
++counter;
}
bool change;
do {
change = false;
foreach_reverse i in postorder) {
bb = cffg block i
new_idom = bb.preds[0]; // Arbitrarily choose the first predecessor
for pred in preds (bb)) {
if (cfg.idoms[pred] != CFG.UNDEFINED_IDOM) {
new_idom = intersect(new_idom, pred, cfg.idoms, postorder_map);
}
}
if (cfg.idoms[i] != new_idom) {
cfg.idoms[i] = new_idom;
change = true;
}
}
} while (change);
}
A node A has a dominance frontier which are set of nodes b where A does not dominate b but A dominates a pred of b. Lets see n5 dominance frontier
Finally we have a post dominates b if all paths from b to the exit go through a. for instance n4 post dominates n6.
graph TD;
entry --> loop
loop --> if
if --> then
if --> else
then --> endif
else --> endif
endif --> loop loop --> exit
graph TD; entry --> loop loop --> if if --> then if --> else then --> endif else --> endif endif --> loop loop --> exit
How about an example that has a cycle and no header
graph TD;
entry --> if;
if --> loop1
if --> loop2
loop2 --> loop1 loop1 --> loop2
graph TD; entry --> if; if --> loop1 if --> loop2 loop2 --> loop1 loop1 --> loop2
This loop has two entry points.
A back-edge is an edge A->B, where B dominates A
other edges are forward edges
Natural loops:
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
entry --> H1
H1 --> A
A --> H2
H2 --> B
B --> H2
B --> H1 H1 --> exit
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; entry --> H1 H1 --> A A --> H2 H2 --> B B --> H2 B --> H1 H1 --> exit
for B-> H2, loop is H2,
for B-> H1, loop is H1, A, H2, B
in a reducible cfg every back edge has a natural loop.
A reducible CFG is one with edges that can be partitioned into two disjoint sets: forward edges, and back edges, such that:
Forward edges form a directed acyclic graph with all nodes reachable from the entry node.
For all back edges (A, B), node B dominates node A.
Structured programming languages are often designed such that all CFGs they produce are reducible, and common structured programming statements such as IF, FOR, WHILE, BREAK, and CONTINUE produce reducible graphs. To produce irreducible graphs, statements such as GOTO are needed. Irreducible graphs may also be produced by some compiler optimizations.
Let G be a CFG. Suppose n is a node in G with a self-loop, that is, an edge from n to itself.
Transformation T1: on node n is removal of this self-loop.
Let n1 and n2 be nodes in G such that n2 has the unique direct ancestor n1, and n2 is not the initial node.
transformation T2: on node pair (n1,n2) is merging nodes n1 and n2 into one node,
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
a0[" "] --> n
a1[" "] --> n
n --> n
n --> b[" "] n --> b1[" "]
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; a0[" "] --> n a1[" "] --> n n --> n n --> b[" "] n --> b1[" "]
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
a0[" "] --> n1
a1[" "] --> n1
n1 --> n2
n2--> n1
n2 --> b[" "] n2 --> b1[" "]
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; a0[" "] --> n1 a1[" "] --> n1 n1 --> n2 n2--> n1 n2 --> b[" "] n2 --> b1[" "]
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
a0[" "] --> n
a1[" "] --> n
n --> b[" "] n --> b1[" "]
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; a0[" "] --> n a1[" "] --> n n --> b[" "] n --> b1[" "]
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
a0[" "] --> n1["n1_n2"]
a1[" "] --> n1
n1 --> b[" "] n1 --> b1[" "]
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; a0[" "] --> n1["n1_n2"] a1[" "] --> n1 n1 --> b[" "] n1 --> b1[" "]
int n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
entry --> switch;
switch --> case0-7
switch --> case1
switch --> case2
case0-7 --> case2
case2--> case1
case1 --> dowhile
dowhile --> case0-7 dowhile --> exit
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; entry --> switch; switch --> case0-7 switch --> case1 switch --> case2 case0-7 --> case2 case2--> case1 case1 --> dowhile dowhile --> case0-7 dowhile --> exit
not reducible
loop: if (cond) goto past_loop
s1
call bar()
goto loop
pastloop:
function bar()
b1
if () return
b2
loop: if (cond) goto past_loop
s1
b1
if () go to next
b2
next:
goto loop
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD;
loop--> s1
loop---> past_loop
s1--> b1
b1 -->inline_if
inline_if --> b2
b2 --> next_goto
inline_if --> loop next_goto --> loop
%%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; loop--> s1 loop---> past_loop s1--> b1 b1 -->inline_if inline_if --> b2 b2 --> next_goto inline_if --> loop next_goto --> loop
Now we have two back edges so two loops