G27
Self-checkedTier 1 10 min readcd.intermediate-code

Intermediate code generation

Why compilers use an intermediate representation, the usual forms (syntax trees, DAGs, postfix notation, three-address code), translating expressions to three-address code with temporaries by a syntax-directed definition, array address calculation and the role of types and declarations.

Why it exists

A compiler could translate a source program straight into machine code, but it is much better to go through an intermediate representation (IR) between the front end and the back end. The IR is independent of the source language and of the target machine. That makes it possible to build the front end for a language once and attach back ends for several machines (m+nm + n pieces instead of m×nm \times n, see phases of a compiler), and it gives the optimiser a simple, uniform form on which to work.

Prerequisites

You need syntax-directed translation, in particular schemes that build code while parsing (translation schemes).

Precise definitions

Kinds of intermediate representation.

FormWhat it isTypical use
Syntax treethe parse tree with the grammar's chain nodes removed: operators as interior nodes, operands as leavestype checking, later phases
DAG (directed acyclic graph)a syntax tree in which identical subexpressions are represented by one shared nodedetecting common subexpressions (DAG of a block)
Postfix notationa linearised form: operands first, then the operator (a b c * +)stack machines, simple translation
Three-address codea sequence of instructions each with at most three addresses (two operands and a result): x = y op zmost compilers' IR (three-address code)
Control-flow graphbasic blocks connected by jumpsoptimisation (basic blocks)
Static single assignment (SSA)three-address code in which every variable is assigned exactly oncemodern optimisers

Three-address code. Every instruction has at most one operator on the right side, so a compound expression is broken up and the compiler invents temporaries to hold the intermediate values. For a=b∗−c+b∗−ca = b * -c + b * -c the code is

t1 = minus c
t2 = b * t1
t3 = minus c
t4 = b * t3
t5 = t2 + t4
a = t5

The name "three-address" refers to the instruction x = y op z, with addresses for xx, yy and zz. An address is a name of the source program (the compiler uses the symbol-table entry), a constant, or a compiler-generated temporary.

Translation of expressions by a syntax-directed definition. Each expression node EE has an attribute E.addrE.addr (where the value is held) and, in the non-incremental version, E.codeE.code (the code that computes it):

ProductionRules
S→id=ES \to \text{id} = E`S.code = E.code
E→E1+E2E \to E_1 + E_2E.addr = new Temp(); `E.code = E1.code
E→−E1E \to - E_1E.addr = new Temp(); `E.code = E1.code
E→(E1)E \to ( E_1 )E.addr = E1.addr; E.code = E1.code
E→idE \to \text{id}E.addr = id.addr; E.code = ''

Here || concatenates code and gen makes one instruction. The definition is S-attributed. In practice the code is not stored in attributes: the translator emits the instructions one by one as the parser reduces (the incremental translation, a postfix translation scheme), so the instructions come out in a bottom-up order and operands are always computed before they are used.

Temporaries. Every operator node creates a fresh temporary. A compiler may reuse a temporary after its last use (a stack-like discipline), but the textbook examples give each temporary a new name. In the textbook translation the assignment S→id=ES \to \text{id} = E ends with a copy id = E.addr, so a=b+ca = b + c takes two instructions (t1 = b + c; a = t1); optimising translators emit the final operator directly into a.

Arrays. An element address is computed from the base address and the index: for a one-dimensional array with element width ww, the element A[i] is at base+i×w\text{base} + i \times w (adjusted for the lower bound); for a two-dimensional array stored row by row with n2n_2 columns, A[i1][i2] is at base+(i1×n2+i2)×w\text{base} + (i_1 \times n_2 + i_2) \times w (the address formulas). The intermediate code has indexed copies: x = y[i] and x[i] = y (here i is an offset in bytes in the textbook; in this page's examples the index is the element number).

Types and declarations. The translator keeps a type for every name and expression, computed by the semantic rules (type expressions: basic types, arrays, records, pointers, functions). From the types it computes the width (size in bytes) of each name and assigns offsets in the activation record or the static area, which the symbol table stores; the addresses in the generated code are relative to these. Type conversions (inttofloat) are inserted when the types of operands differ.

How it works

The animation generates the code for a=b∗−c+b∗−ca = b * -c + b * -c one instruction at a time, in the order a bottom-up translator emits them (the rows are the instructions, the right-hand column says which production caused each). Note that the same product b * -c is computed twice.

Three-address code for a = b * -c + b * -c: start

Computed by a script, not typed
labelthree-address instruction
1
2
3
4
5
6

Step 0

Translating an expression bottom-up: every operator gets a new temporary for its result. The tree has the subexpression b * -c twice, and the code computes it twice.

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

A DAG for the same expression has one node for -c and one for b * -c, and the sum has both its children pointing to the same node. Built from the code above instruction by instruction: when t3 = minus c asks for the node (minus, c0) it already exists, so t3 is just attached to it, and likewise t4. The code regenerated from the DAG computes each subexpression once.

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 1Translate an expression

Easy

For x=a+b∗cx = a + b * c the textbook translation is t1 = b * c, t2 = a + t1, x = t2: 3 instructions and 2 temporaries (because of the final copy). The multiplication comes first because it is the deeper node of the tree. An optimising translator that emits the last operator straight into x would use 2 instructions and 1 temporary: always check which convention a question follows (the textbook one has the extra copy).

EX 2Count temporaries

Easy

For a=b∗−c+b∗−ca = b * -c + b * -c the translation has the operators minus, *, minus, *, +: five operator nodes, so five temporaries t1 to t5 and 6 instructions including the final copy (the animation). With the DAG the two minus c and the two b * t1 collapse: t1 = minus c, t2 = b * t1, t3 = t2 + t2, a = t3: 3 temporaries, 4 instructions (a block optimiser that writes the sum straight into a, as in the animation of the DAG, needs only 3 instructions). A tree translator never shares: it costs one temporary per operator node.

EX 3Array element address

Medium

Declare int A[10][20] (row major, 4-byte elements, lower bounds 0). The address of A[i][j] is base+(i×20+j)×4\text{base} + (i \times 20 + j) \times 4, which in three-address code is t1 = i * 20, t2 = t1 + j, t3 = t2 * 4, t4 = A[t3] if the offset is a byte offset, followed by x = t4. That is 3 arithmetic instructions plus the indexed load. With lower bounds l1,l2l_1, l_2 the index terms become (i−l1)(i - l_1) and (j−l2)(j - l_2).

EX 4Choose the representation

Medium

The same code can be stored with explicit temporaries (quadruples), with positions as names (triples) or with a pointer list (indirect triples): see the next page. The choice matters for optimisation: instructions can be moved freely only in the forms where they do not refer to each other by position.

EX 5Which statements are true?

GATE level

(a) "Three-address code is machine independent": true. (b) "Each three-address instruction has at most one operator on the right": true. (c) "A DAG always has fewer nodes than the syntax tree of the same expression": false, it has fewer or equal: without repeated subexpressions the two are the same size. (d) "The syntax tree and the parse tree have the same number of nodes": false: the syntax tree omits the chain productions and punctuation. (e) "Postfix notation needs parentheses": false, the order of the operators encodes the grouping. (f) "Temporaries are variables of the source program": false, they are created by the compiler.

Cheat box

Cheat box

  • IR = machine- and language-independent form between front end and back end (m+nm + n not m×nm \times n).
  • Forms: syntax tree, DAG, postfix, three-address code, CFG, SSA.
  • Three-address code: at most one operator per instruction; a new temporary per operator node (tree translation).
  • Textbook S→id=ES \to \text{id} = E ends with a copy id = E.addr.
  • DAG shares equal subexpressions (fewer or equal nodes than the tree).
  • Array address: base + index × width; 2-D row major: base + (i × columns + j) × width.
  • Types give widths and offsets; conversions are inserted by the translator.

Traps and GATE patterns

Connections

The code is produced by translation schemes during parsing. Its concrete storage formats are on three-address code, quadruples and triples, the translation of conditions and loops on control-flow translation and backpatching, and the sharing of subexpressions on DAG representation of basic blocks. The code is then cut into basic blocks for optimisation. Array offsets use the formulas of multidimensional array addressing.

PYQs

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

GATE 2021 · MCQQ10 · page 281 of the book
In the context of compilers, which of the following
is/are NOT an intermediate representation of the
source program?
  • (a)Three address code
  • (b)Control Flow Graph (CFG)
  • (c)Abstract Syntax Tree (AST)
  • (d)Symbol table
GATE 2016 · NATQ12 · page 281 of the book
Consider the following code segment.

x = u - t;

y = x * v;

x = y + w;

y = t - z;

y = x * y;

The minimum number of  variables required to
convert the above code segment to static single
assignment form is ________.
GATE 2015 · MCQQ13 · page 281 of the book
In the context of abstract-syntax-tree (AST) and
control-flow-graph (CFG), which one of the
following is TRUE?

COMPILER DESIGN
  • (a)In both AST and CFG, let node N2 be the successor of node N1. In the input program, the code corresponding to N2 is present after the code corresponding in N1.
  • (b)For any input program, neither AST nor CFG will contain a cycle
  • (c)The maximum number of successors of a node in an AST and a CFG depends on the input program
  • (d)Each node is AST and CFG corresponds to at most one statement in the input program.
GATE 2014 · MCQQ16 · page 282 of the book
One of the purposes of using intermediate code in
compilers is to:-
  • (a)make parsing and semantic analysis simpler.
  • (b)improve error recovery and error reporting.
  • (c)increases the chances of reusing the machine- independent code optimizer in other compilers.
  • (d)improve the register allocation
GATE 2008 · MCQ · 1 markQ29 · page 285 of the book
Some code optimizations are carried out on the
intermediate code because
  • (a)They enhance the portability of the compiler to other target processors
  • (b)Program analysis is more accurate on intermediate code than on machine code
  • (c)The information from dataflow analysis cannot otherwise be used for optimization
  • (d)The information from the front end cannot otherwise be used for optimization COMPILER DESIGN

Self-check

Question 1

How many instructions and temporaries does the textbook translation of x = (a + b) * (a + b) use?

Show answer

t1 = a + b, t2 = a + b, t3 = t1 * t2, x = t3: 4 instructions and 3 temporaries (a DAG would compute a + b once: t1 = a + b, t2 = t1 * t1, x = t2).

Question 2

Name three intermediate representations.

Show answer

Syntax tree, DAG, postfix notation, three-address code (also CFG and SSA).

Question 3

Why does an intermediate representation help for mm languages and nn machines?

Show answer

It needs mm front ends and nn back ends, m+nm + n components instead of m×nm \times n compilers.

Question 4

What is the offset of A[3] from the base of int A[10] with 4-byte elements?

Show answer

3×4=123 \times 4 = 12 bytes (lower bound 0).

Revision card

One-screen revision

  • IR: machine independent; syntax tree, DAG, postfix, three-address code.
  • Three-address: one operator per instruction, temporaries per operator node, final copy id = E.addr.
  • DAG shares subexpressions; array address = base + index × width.
  • Types supply widths and offsets.

References

  • ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 6 Intermediate-Code Generation (6.1 variants of syntax trees, 6.2 three-address code, 6.3 types and declarations, 6.4 translation of expressions)
  • ·Cooper and Torczon, Engineering a Compiler, Chapter 5 Intermediate Representations

Verification

The three-address code on this page comes from a real translator (scripts/cd_icg.py) that follows the textbook's translation of expressions; scripts/enum-checks/cd_icg.py runs the generated code and the source program side by side on 1500 random programs and compares all variables, arrays and the printed output (no mismatch), and recounts the nodes of every DAG by symbolic expansion. The statement that an assignment x = E copies from E.addr (one more instruction than the minimum) is the textbook convention; optimising translators emit the operator directly into x. 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; No other defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never