G27
Self-checkedTier 1 10 min readcd.dag-basic-block

DAG representation of basic blocks

The directed acyclic graph of a basic block: leaf nodes for initial values, interior nodes for operations, identifiers attached as labels, the construction algorithm, how it finds common subexpressions and dead code, regenerating code from the DAG, and counting nodes and edges.

Why it exists

A basic block is a list of instructions, but what it computes is a set of values that depend on each other, not a sequence. Drawing the block as a directed acyclic graph (DAG) shows this directly. Two instructions that compute the same expression from the same operands share one node, so common subexpressions are found automatically; values that are never used are exposed as nodes with no live identifier; and the order of the instructions can be rearranged in any order that respects the edges.

Prerequisites

You need basic blocks (basic blocks and control flow graphs).

Precise definitions

DAG of a basic block. A directed acyclic graph with these nodes:

  • Leaf nodes, one for each variable or constant that is used before it is assigned in the block, labelled with the initial value of the variable, written with subscript 0 (b0, c0) or with the constant.
  • Interior nodes, labelled by an operator, with edges to the nodes of their operands (in order, left and right). Every interior node has at least one identifier attached to it as a label (the variables assigned that value) — or none, if the value is only used as an intermediate result.

Each node also keeps the list of identifiers attached to it: the variables that hold that value at the end of the block.

Construction algorithm. Start with no nodes. For each instruction in order:

  1. x = y op z: find the node for the current value of y (if there is none, create a leaf labelled y0), likewise for z. If a node labelled op with exactly these two children already exists, use it; otherwise create it. Attach x to this node and remove x from any other node it was attached to before.
  2. x = op y: the same with one child.
  3. x = y (copy): find the node for y and attach x to it; no new node.

Because attaching x removes it from its old node, the DAG always records the latest value of each variable. An assignment to a variable does not change nodes that used its old value (those nodes keep their children), which is why a redefinition correctly prevents sharing.

What the DAG tells.

  • Common subexpressions: step 1 finds the existing node, no new node is created: a repeated expression with unchanged operands is one node.
  • Nodes and edges: the number of nodes is (number of leaves) + (number of interior nodes); every interior node has as many edges as operands, so for binary operators the number of edges is twice the number of interior nodes. An interior node with two equal children has two parallel edges.
  • Dead code: an interior node with no live identifier and no parent computes nothing used on exit and can be removed together with the nodes only it needs.
  • Output: the DAG is evaluated in a topological order (children before parents): each interior node is computed once into one of its live identifiers (or a temporary if it has none but is used by another node), and the other live identifiers are copied from it. Different topological orders give different code, and a good order reduces registers and loads.

Limits of the simple DAG. For an array element x = a[i] or a[j] = y the DAG must consider that a store a[j] may change what a[i] means (if i and j can be equal): the stores kill the nodes of the earlier loads from the same array; similarly an assignment through a pointer or a procedure call can change anything it may reach. The textbook handles these with extra rules (nodes for array operations, ordering constraints); the algorithm above is for scalar variables.

How it works

The block a = b + c; b = a - d; c = b + c; d = a - d (all four variables live on exit). Watch the table of nodes: instructions 1 and 2 create interior nodes, instruction 3 creates a new interior node although it looks like instruction 1 (the b in it is the value of node 5, not b0), and instruction 4 creates no new node: a - d0 already exists as node 5, so d is simply attached to it, next to b. The final DAG has 6 nodes (three leaves b0, c0, d0 and three interior nodes) and 6 edges, and the code regenerated from it is a = b + c; b = a - d; c = b + c; d = b.

DAG of a basic block: start

Computed by a script, not typed
nodeoperator or initial valuechildrenidentifiers attached
1
2
3
4
5
6

Step 0

The DAG of the block a = b + c; b = a - d; c = b + c; d = a - d. A leaf stands for the value a variable has on entry to the block (b0, c0, d0).

0 / 5
computed by script: the algorithm is executed, one step at a time. python3 scripts/gen-cd-icg-traces.py

When only z is live after x = a + b; y = a + b; z = x * y: the second instruction finds the node of a + b again, and z = x * y becomes a node whose two children are the same node (two edges to it). Neither x nor y is live, so the sum is held in a temporary and z is computed as t1 * t1: the block shrinks from 3 to 2 instructions.

DAG of a block when only z is live on exit: start

Computed by a script, not typed
nodeoperator or initial valuechildrenidentifiers attached
1
2
3
4

Step 0

In x = a + b; y = a + b; z = x * y the second instruction repeats the first.

0 / 4
computed by script: the algorithm is executed, one step at a time. python3 scripts/gen-cd-icg-traces.py

And the expression from the beginning of the intermediate-code pages. The two occurrences of -c and of b * -c become one node each.

The DAG of a = b * -c + b * -c: start

Computed by a script, not typed
nodeoperator or initial valuechildrenidentifiers attached
1
2
3
4
5

Step 0

A DAG shares equal subtrees. The nodes are created for the instructions of the tree-based code one by one; when an instruction asks for a node that exists already, nothing new is created.

0 / 7
computed by script: the algorithm is executed, one step at a time. python3 scripts/gen-cd-icg-traces.py

Worked examples

EX 1Count nodes and edges

Easy

For a = b + c; b = a - d; c = b + c; d = a - d: leaves b0, c0, d0 (3 nodes); interior nodes + (for b0 + c0, labelled a), - (labelled b, d), + (for the new b + c0, labelled c) (3 nodes). 6 nodes; each interior node has two children: 6 edges. Only the leaf c0 has two parents (the first and the last + node); every other node has at most one (the last + node, which is labelled c, has none).

EX 2Which instruction creates no node?

Easy

In the same block the fourth instruction d = a - d computes the same value as the second (a - d0), so no node is created: d is attached to the node that already has b. This is how common subexpressions are detected. The instruction c = b + c does not find the node of the first instruction because its left operand is now the value held by b (node 5), not b0.

EX 3A bigger expression

Medium

For ((a+a⋅(b−c))+(b−c)⋅d)((a + a \cdot (b - c)) + (b - c) \cdot d) translated into t1 = b - c; t2 = a * t1; t3 = a + t2; t4 = b - c; t5 = t4 * d; t6 = t3 + t5 the DAG has: leaves a0, b0, c0, d0 (4); interior - (for b - c, shared by t1 and t4), * (a0 * (b - c)), + (a0 + ...), * ((b - c) * d0), + (the final sum) (5). Total 9 nodes and 5×2=105 \times 2 = 10 edges. The syntax tree of the same expression has 13 nodes (7 leaves and 6 operators): the DAG saves the repeated leaves and the second b - c. (Confirmed by the construction algorithm and by the independent count of distinct symbolic expressions.)

EX 4Dead code and regenerating code

Medium

For x = a + b; y = a + b; z = x * y with only z live the DAG has 4 nodes (a0, b0, the sum, the product) and 4 edges (two to each of the sum's children, and two from the product to the sum). The sum has labels x and y but neither is live, so it is computed into a temporary: t1 = a + b; z = t1 * t1. If x were also live on exit we would get x = a + b; z = x * x; y = x. The code depends on which identifiers are live.

EX 5True or false

GATE level

(a) "The DAG of a block can have fewer nodes than there are instructions": true (common subexpressions, copies). (b) "A leaf of the DAG represents the initial value of a variable or a constant": true. (c) "The DAG of a block with no repeated subexpressions has as many interior nodes as there are operator instructions": true (copies add no node). (d) "Reordering the instructions of a block always preserves the DAG": false: only reorderings that respect the dependences keep the same values. (e) "After x = y the node of y gains the label x": true. (f) "Two interior nodes can have the same operator and children": false; the algorithm would have used the existing node.

Cheat box

Cheat box

  • Leaves: initial values (b0) and constants. Interior nodes: operators. Identifiers attached to nodes (the latest value of each variable).
  • For x = y op z: find or create nodes for y, z; find or create the op node with those children; move label x to it. Copy x = y: just attach x.
  • A repeated expression with unchanged operands creates no new node.
  • Nodes = leaves + interior nodes; edges = sum of operands of the interior nodes (parallel edges count).
  • Dead code: interior node with no live label and no parent.
  • Code from the DAG: topological order, compute into a live label, copy to the other live labels.
  • Arrays, pointers and calls need extra care: stores kill earlier loads.

Traps and GATE patterns

Connections

The DAG is built per basic block of the intermediate code, and it implements the common subexpression and dead-code parts of local optimisation, where the same results are obtained by value numbering. Which variables are live on exit is a liveness question.

PYQs

1 previous year question from your book. I re-solved each one myself (1 by running or simulating, 0 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2014 · MCQQ21 · page 283 of the book
Consider the basic block given below.

a= b + c

c =a + d

d =b + c

e =d - b
a =e + b

The minim um number of nodes and edges present
in the DAG representation of the above basic block
respectively are
  • (a)6 and 6
  • (b)8 and 10
  • (c)9 and 12
  • (d)4 and 4

Self-check

Question 1

How many nodes does the DAG of t1 = a + b; t2 = a + b; t3 = t1 * t2 have?

Show answer

4: leaves a0 and b0, one node for a + b (labelled t1 and t2), and one for the product.

Question 2

In the construction algorithm, what happens to the label x when x is assigned again?

Show answer

It is removed from its old node and attached to the node of the new value.

Question 3

Why is c = b + c not recognised as a repeat of a = b + c when b was reassigned in between?

Show answer

Its left operand is the new value of b (a different node), not the leaf b0, so the (operator, left, right) combination is new.

Question 4

Which interior nodes can be dropped when generating code?

Show answer

Nodes with no live identifier attached and no parent (their value is never used).

Revision card

One-screen revision

  • Leaves = initial values and constants; interior = operators; identifiers attached (latest value).
  • New node only if (operator, children) is new; copies add only a label.
  • Nodes = leaves + interior; edges = 2 per binary interior node.
  • Dead = no live label and no parent; code in topological order.

References

  • ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 8 Code Generation (8.5.1 the DAG representation of basic blocks, 8.5.2 finding local common subexpressions, 8.5.3 dead code elimination, 8.5.4 the use of algebraic identities, 8.5.5 representation of array references, 8.5.6 pointer assignments and procedure calls, 8.5.7 reassembling basic blocks from DAGs)

Verification

The DAGs are built by the textbook algorithm (scripts/cd_icg.py). scripts/enum-checks/cd_icg.py recomputes the number of interior nodes of the DAG of each of 4000 random blocks by a method that does not use the DAG algorithm (symbolic expansion of every value in terms of the initial values, counting distinct expressions), and executes the code regenerated from the DAG against the original block on random inputs (the live-out variables agree). The node counts quoted on the page (6, 4 and 9 nodes) were asserted there. Edges are counted with multiplicity (an interior node whose two children are the same node has two edges); answer keys may count such a doubled edge once. Arrays, pointers and procedure calls are mentioned in outline only: the DAG of the page covers scalar assignments. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand or by a small script (token counts, FIRST and FOLLOW sets, LL(1) tables and moves, LR item sets, state counts and parses, operator-precedence values, attribute evaluation and printing schemes, activation-record counts and access links, parameter-passing results, three-address code, jumping and backpatched code, leaders and blocks, value numbering and DAGs, gen/kill and data flow sets, Sethi-Ullman labels and live ranges); long C programs were not re-read line by line (they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose); tables and animations are machine output and were read, not re-run; One sentence about parent counts in the DAG of the worked example said every other node has exactly one parent; the final node has none. Status raised to self-checked; verified needs a textbook or human check.

last verified: never