Middle-end

Control Flow

Turning statements into a graph you can reason about: basic blocks, edges, natural loops, dominators and the dominance frontier that SSA construction needs.

The Control-Flow Graph
▶ lab

Once the statements are instructions, the `if` is gone. What remains is a directed graph: blocks of straight-line code as nodes, the ways control can pass between them as edges. Every question about "when does this run" becomes a question about paths.

Q · What is a control-flow graph, and what can I ask of it that I could not ask of the source?
Basic Blocks
▶ lab

A maximal run of instructions with one way in and one way out. If the first instruction executes, all of them do — and that single guarantee is what makes the block, rather than the instruction, the unit every analysis is written against.

Q · What exactly makes a group of instructions a basic block, and why is that the unit compilers analyse?
Building the CFG
▶ lab

An `if` becomes two blocks and a join. A `while` becomes three blocks and an edge that points backwards. Then there is the edge nobody expects: the one from a two-way branch straight into a merge, which has no safe place to put anything — and which AtlasLang reports rather than guesses at.

Q · How does a compiler turn nested `if` and `while` statements into blocks and edges, and what goes wrong?
Natural Loops
▶ lab

The `while` was destroyed by lowering, so the optimizer has to find the loop again in the graph. A back edge `n -> h` where `h` dominates `n` is a loop; the body is `h` plus everything that reaches `n` without going through `h`. That is a definition, not a heuristic.

Q · How does a compiler find the loops in a graph after the `while` and `for` have been lowered away?
Dominators
▶ lab

A dominates B if every path from the entry to B goes through A — so if B runs, A has already run. Loops make the graph cyclic, so this cannot be computed in one traversal: the algorithm iterates until a full pass changes nothing, and the second pass is not optional.

Q · What does it mean for one block to dominate another, and why does computing it require iteration?
The Dominator Tree
▶ lab

Every block has exactly one immediate dominator, so the relation is a tree — and it is a different tree from the CFG, drawn on the same nodes. Draw it separately, because the edges mean something the CFG edges do not, and half the confusion about dominance comes from overlaying them.

Q · What does the dominator tree look like, how is it different from the CFG, and what walks it?
The Dominance Frontier
▶ lab

The frontier of A is the set of blocks where A stops being guaranteed — the first blocks reachable from A that A does not dominate. That is exactly the set of places where a definition in A might not be the one that arrives, which is exactly where a phi node goes.

Q · Where does a definition stop being guaranteed, and why is that the same set as "where the phi nodes go"?