G27
Self-checkedTier 2 17 min readcd.code-generation-registers

Code generation and register allocation (outside syllabus, used in the journey)

The last phase of a compiler for a register machine: how many registers an expression needs (Sethi-Ullman numbering) and the code that uses exactly that many, live variables and the interference graph, assigning registers by graph colouring, spilling when there are too few registers, and a code generator for a basic block with register and address descriptors.

Why it exists

The last phase of a compiler turns the intermediate code into instructions for a real machine. A real machine has few registers, which are much faster than memory, and arithmetic works on registers. So the compiler must decide which values live in which register at each moment, and what to do when the program has more live values than registers. The topic is outside the official GATE syllabus, but it has appeared in questions: how many registers does an expression need, how many registers does this program need without spilling, which optimisation uses live variable analysis. It also completes the story of the compiler: the data flow analysis of the earlier pages finds the live variables that decide the registers.

Prerequisites

You need the three-address code and the basic block of basic blocks and control flow graphs, and the idea of registers and load/store instructions from addressing modes.

Precise definitions

The machine. Registers R1,R2,…R_1, R_2, \ldots; instructions LD R, x (memory to register), ST x, R (register to memory), and arithmetic OP Rd, a, b where aa and bb are registers or immediate constants and the result may be put in the register of an operand. Only loads and stores access memory. (Where a machine allows a memory operand in an arithmetic instruction, fewer registers are needed; a question states which.)

Code generation has three jobs: instruction selection (which instructions), register allocation (which values are in registers at each point) and register assignment (which register holds which value).

How many registers does an expression need? The Sethi-Ullman number. For an expression tree, label every node:

  • a leaf that is a variable: 1 (it must be loaded into a register); a leaf that is a constant used as an operand: 0 (an immediate operand);
  • an operation node with labels ll and rr of its children: max⁡(l,r)\max(l, r) if l≠rl \ne r, and l+1l + 1 if l=rl = r.

The label of the root is the minimum number of registers with which the expression can be evaluated without storing intermediate results in memory, and the algorithm that achieves it is: evaluate the child with the larger label first (its register is then free except for its result), then the other child, using one more register. If the labels are equal, either order needs l+1l + 1. The code uses exactly the registers R1,…,RlabelR_1, \ldots, R_{label}.

Live variables. A variable is live at a point if its current value may still be used later (before it is redefined). It is found by a backward pass over the code (the liveness analysis): for each instruction, live-in == (live-out −- defined) ∪\cup used. Two values that are live at the same time cannot share a register.

Registers needed by a program. For straight-line code the minimum number of registers without spilling is the largest number of values that must be in registers at the same time: for every instruction max⁡(∣live-in∣,∣live-out∪{result}∣)\max(|\text{live-in}|, |\text{live-out} \cup \{\text{result}\}|) (the result may reuse the register of an operand that dies there). A variable that is never used still needs a register at the moment it is computed.

Interference graph and colouring. The interference graph has one node per variable and an edge between two variables that are live at the same time (more exactly: a variable defined at an instruction is joined to every other variable live just after it, except the source of a plain copy). Register assignment is graph colouring: colours are registers, and adjacent nodes get different colours; the minimum number of colours is the chromatic number, which equals the largest set of mutually interfering variables for the programs of this page. Finding the chromatic number of a general graph is NP-hard; compilers use heuristics (Chaitin's algorithm: remove a node with fewer than kk neighbours, colour the rest, put it back).

Spilling. If the graph cannot be coloured with the kk available registers, some values are spilled: stored in memory (a ST) and loaded again (LD) when needed. The compiler chooses values with a distant next use or a low use count. The minimum number of spills for a given number of registers and a given program can be found by search; it is 0 when the registers are at least the maximum number of live values.

Code for a basic block. The simple generator keeps two descriptors: the register descriptor (which variables each register holds) and the address descriptor (where the current value of each variable is: memory, a register, or both). For each statement x=y op zx = y\ op\ z:

  1. get a register for each operand (use the register that already holds it; otherwise load it into a free register; if none is free, spill a register: store its variable if its only copy is in the register and it is still needed, preferring registers whose values are dead or also in memory, and then the value whose next use is farthest);
  2. choose the register for the result: the register of yy (or zz) if that operand is not live afterwards (so the result can overwrite it), else a free register;
  3. emit OP, then update the descriptors: the register now holds only xx, and xx is nowhere else;
  4. at the end of the block store every live variable whose only copy is in a register.

How it works

Sethi-Ullman. Five expressions; the label (and the minimum over all evaluation orders, found by search) and the size of the generated code.

Sethi-Ullman labels: the number of registers an expression needs: start

Computed by a script, not typed
expressionlabel (registers needed)minimum over all evaluation ordersinstructionsof which
(a - 1) * (((b + c) / 3) + d)
(a + b) * (c + d)
((a + b) + c) + d
a + (b + (c + d))
((a + b) * (c - d)) / ((e + f) * (g - h))

Step 0

Machine: only LD and ST touch memory; arithmetic takes registers or immediate operands and may put its result in the register of an operand; no spills. The code is generated by evaluating the side with the larger label first and run on a virtual machine.

0 / 5
computed by script: programs run under three storage policies, code generated for a virtual machine and executed, liveness and exact searches for registers and spills. python3 scripts/gen-cd-t2-traces.py

Live variables of a small straight-line program, instruction by instruction, and the number of values that need registers at each:

Live variables of a straight-line program, instruction by instruction: start

Computed by a script, not typed
instructionlive beforelive afterregisters needed here
x = 2
y = 3
z = x + y
w = z * x
v = w + y
u = v - z
return u + w

Step 0

At most 3 values are live at once, so 3 registers are enough and necessary without spilling; the interference graph of the variables needs 3 colours. Computed by a backward pass.

0 / 7
computed by script: programs run under three storage policies, code generated for a virtual machine and executed, liveness and exact searches for registers and spills. python3 scripts/gen-cd-t2-traces.py

Interference graph and a colouring with the minimum number of colours:

The interference graph and a colouring with the minimum number of registers: start

Computed by a script, not typed
variableinterferes withregister
u
v
w
x
y
z

Step 0

Two variables are joined if one is defined while the other is live. Graph colouring with 3 colours (an exact search): variables of the same colour share a register. 8 edges.

0 / 6
computed by script: programs run under three storage policies, code generated for a virtual machine and executed, liveness and exact searches for registers and spills. python3 scripts/gen-cd-t2-traces.py

Spilling. The minimum number of spills for the same program with fewer registers than the peak:

How many spills does the program need with R registers?: start

Computed by a script, not typed
registersminimum number of spills
1 register
2 registers
3 registers
4 registers

Step 0

Exhaustive dynamic programme over every choice of which value to evict (loads are free, a store of a live value that is not yet in memory costs one). With 3 registers (the maximum number of live values) nothing needs to be spilled.

0 / 4
computed by script: programs run under three storage policies, code generated for a virtual machine and executed, liveness and exact searches for registers and spills. python3 scripts/gen-cd-t2-traces.py

A block compiled with 3 registers and with 2. With three registers nothing is spilled; with two the generator must store a value and load it back. Descriptors after each statement.

Code for a basic block with 3 registers (w and x are live at the end): start

Computed by a script, not typed
statementcode generatedregister descriptoraddress descriptor
t = a + b
u = t * c
v = a - u
w = v + t
x = w * u
end of block: store the live variables

Step 0

10 instructions: 3 loads, 2 stores. The code was executed on 200 random inputs and gives the same w and x as the block. Variables a, b, c start in memory; the block's live-out variables are w and x.

0 / 6
computed by script: programs run under three storage policies, code generated for a virtual machine and executed, liveness and exact searches for registers and spills. python3 scripts/gen-cd-t2-traces.py

Code for a basic block with 2 registers (w and x are live at the end): start

Computed by a script, not typed
statementcode generatedregister descriptoraddress descriptor
t = a + b
u = t * c
v = a - u
w = v + t
x = w * u
end of block: store the live variables

Step 0

15 instructions: 6 loads, 4 stores. The code was executed on 200 random inputs and gives the same w and x as the block. Variables a, b, c start in memory; the block's live-out variables are w and x.

0 / 6
computed by script: programs run under three storage policies, code generated for a virtual machine and executed, liveness and exact searches for registers and spills. python3 scripts/gen-cd-t2-traces.py

Worked examples

EX 1Sethi-Ullman: labels and code

Easy

The program labels five expression trees and prints the code for the first one, (a−1)∗(((b+c)/3)+d)(a - 1) * (((b + c) / 3) + d), on the machine described above (a constant is an immediate operand). The larger side is evaluated first.

#include <stdio.h>
 
enum kind { VAR, CONST, OP };
struct node { enum kind k; const char *name; int imm; char op; int l, r; };
 
static struct node t[64];
static int nn;
 
static int var(const char *s) { t[nn] = (struct node){VAR, s, 0, 0, -1, -1}; return nn++; }
static int cst(int v) { t[nn] = (struct node){CONST, 0, v, 0, -1, -1}; return nn++; }
static int op(char o, int l, int r) { t[nn] = (struct node){OP, 0, 0, o, l, r}; return nn++; }
 
static int label(int i) {
    if (t[i].k == CONST) return 0;
    if (t[i].k == VAR) return 1;
    int l = label(t[i].l), r = label(t[i].r);
    return l == r ? l + 1 : (l > r ? l : r);
}
 
struct opd { int imm; int v; };
 
static struct opd gen(int i, int k) {
    if (t[i].k == CONST) return (struct opd){1, t[i].imm};
    if (t[i].k == VAR) {
        printf("LD R%d, %s\n", k, t[i].name);
        return (struct opd){0, k};
    }
    struct opd a, b;
    if (label(t[i].l) >= label(t[i].r)) {
        a = gen(t[i].l, k);
        b = gen(t[i].r, k + 1);
    } else {
        b = gen(t[i].r, k);
        a = gen(t[i].l, k + 1);
    }
    const char *name = t[i].op == '+' ? "ADD" : t[i].op == '-' ? "SUB" : t[i].op == '*' ? "MUL" : "DIV";
    printf("%s R%d, ", name, k);
    printf(a.imm ? "#%d, " : "R%d, ", a.v);
    printf(b.imm ? "#%d\n" : "R%d\n", b.v);
    return (struct opd){0, k};
}
 
int main(void) {
    int roots[5];
    /* (a - 1) * (((b + c) / 3) + d) */
    roots[0] = op('*', op('-', var("a"), cst(1)), op('+', op('/', op('+', var("b"), var("c")), cst(3)), var("d")));
    /* (a + b) * (c + d) */
    roots[1] = op('*', op('+', var("a"), var("b")), op('+', var("c"), var("d")));
    /* ((a + b) + c) + d */
    roots[2] = op('+', op('+', op('+', var("a"), var("b")), var("c")), var("d"));
    /* a + (b + (c + d)) */
    roots[3] = op('+', var("a"), op('+', var("b"), op('+', var("c"), var("d"))));
    /* ((a + b) * (c - d)) / ((e + f) * (g - h)) */
    roots[4] = op('/', op('*', op('+', var("a"), var("b")), op('-', var("c"), var("d"))), op('*', op('+', var("e"), var("f")), op('-', var("g"), var("h"))));
    printf("Sethi-Ullman labels:");
    for (int i = 0; i < 5; i++) printf(" %d", label(roots[i]));
    printf("\n");
    gen(roots[0], 1);
    return 0;
}
Sethi-Ullman labels: 2 3 2 2 4
LD R1, b
LD R2, c
ADD R1, R1, R2
DIV R1, R1, #3
LD R2, d
ADD R1, R1, R2
LD R2, a
SUB R2, R2, #1
MUL R1, R2, R1

Reading it. The labels are 2, 3, 2, 2 and 4. For the first tree the right side, ((b+c)/3)+d((b + c)/3) + d, has label 2 and the left side, a−1a - 1, has label 1 (the constant needs no register): the right side goes first into R1R_1, using R2R_2 for its operands, and then a−1a - 1 is computed in R2R_2 and the product in R1R_1. So two registers are enough, never a third. (a+b)∗(c+d)(a + b) * (c + d) has two equal sides of label 2, so it needs 2+1=32 + 1 = 3. A chain such as ((a+b)+c)+d((a + b) + c) + d needs only 2, because each step reuses the register of the partial result; the balanced tree of eight leaves needs 4.

EX 2How many registers does a program need?

Medium

The program computes seven statements, with no spilling allowed. The C program does the backward liveness pass and prints, for each statement, the variables live before and after it.

#include <stdio.h>
 
/* variables u v w x y z are bits 0..5 */
#define U 0
#define V 1
#define W 2
#define X 3
#define Y 4
#define Z 5
 
struct stmt { const char *text; int dst, a, b; };      /* dst, a, b: variable index, or -1 for none / a constant */
 
static int popcount(int m) { int c = 0; while (m) { c += m & 1; m >>= 1; } return c; }
 
static void show(int m) {
    printf("{");
    int first = 1;
    for (int i = 0; i < 6; i++)
        if (m >> i & 1) { printf("%s%c", first ? "" : ",", "uvwxyz"[i]); first = 0; }
    printf("}");
}
 
int main(void) {
    struct stmt p[7] = {
        {"x = 2", X, -1, -1}, {"y = 3", Y, -1, -1}, {"z = x + y", Z, X, Y}, {"w = z * x", W, Z, X},
        {"v = w + y", V, W, Y}, {"u = v - z", U, V, Z}, {"return u + w", -1, U, W}};
    int before[7], after[7], live = 0, peak = 0;
    for (int i = 6; i >= 0; i--) {
        after[i] = live;
        if (p[i].dst >= 0) live &= ~(1 << p[i].dst);
        if (p[i].a >= 0) live |= 1 << p[i].a;
        if (p[i].b >= 0) live |= 1 << p[i].b;
        before[i] = live;
    }
    for (int i = 0; i < 7; i++) {
        int need_after = after[i] | (p[i].dst >= 0 ? 1 << p[i].dst : 0);
        int need = popcount(before[i]) > popcount(need_after) ? popcount(before[i]) : popcount(need_after);
        if (need > peak) peak = need;
        printf("%-13s live before ", p[i].text);
        show(before[i]);
        printf(", after ");
        show(after[i]);
        printf("; registers needed here: %d\n", need);
    }
    printf("maximum number of live values: %d\n", peak);
    return 0;
}
x = 2         live before {}, after {x}; registers needed here: 1
y = 3         live before {x}, after {x,y}; registers needed here: 2
z = x + y     live before {x,y}, after {x,y,z}; registers needed here: 3
w = z * x     live before {x,y,z}, after {w,y,z}; registers needed here: 3
v = w + y     live before {w,y,z}, after {v,w,z}; registers needed here: 3
u = v - z     live before {v,w,z}, after {u,w}; registers needed here: 3
return u + w  live before {u,w}, after {}; registers needed here: 2
maximum number of live values: 3

Reading it. At z = x + y the values xx and yy are live before and xx, yy, zz after: three values must be in registers, and the same at the next three statements (for instance at w = z * x the values xx, yy, zz are live before and ww, yy, zz after; xx dies there and its register is reused for ww). Never more than three: three registers are enough and two are not. The interference graph of the table needs three colours, and with only two registers the minimum number of spills found by search is 3. A question "what is the minimum number of registers needed to execute this program without spilling" is answered by this maximum, counting each variable that is live at the same time; a variable that is assigned again gets a new live range, but a question phrased in terms of the names uses the colouring of the interference graph of the names. For this program both give 3.

EX 3Spills: two registers instead of three

Medium

For the same program: with 3 registers there is no spill; with 2 the best possible schedule needs 3 stores (the table). With 1 register no program can run an instruction that has two operands in registers. In the block of the table (five statements computing t,u,v,w,xt, u, v, w, x from a,b,ca, b, c, with ww and xx live at the end) the generator with three registers emits 10 instructions (3 loads of the inputs, 5 operations and the 2 final stores of ww and xx) and no spill. With two registers it emits 15: it stores tt before reusing its register and loads it back for w=v+tw = v + t, stores uu and reloads it for x=w∗ux = w * u, and must also reload aa for v=a−uv = a - u: the spill code is the 2 extra stores and the 3 extra loads. Choosing which value to evict matters: the generator prefers a register whose value is dead or also in memory (free to drop), and then the value whose next use is farthest.

EX 4Which technique uses live variable analysis?

GATE level

Register assignment to variables uses it: two variables can share a register only if they are not live at the same time, which is what the live variable analysis computes. Constant folding needs no liveness (it evaluates constant expressions). Strength reduction replaces expensive operations by cheaper ones (no liveness). Run-time function call management concerns activation records. Dead code elimination also uses it: an assignment to a variable that is not live afterwards can be removed. A question about "the minimum number of registers" for an expression tree (no common subexpressions, no spilling, load/store machine) expects the Sethi-Ullman number: for (a−1)∗(((b+c)/3)+d)(a - 1) * (((b + c) / 3) + d) it is 2; for an expression tree on a machine whose arithmetic instructions may have one memory operand, fewer registers are needed (a leaf operand then needs none).

EX 5A common mistake: counting variables instead of live values

GATE level

A program with six variables does not need six registers: it needs as many as the largest number of values live at once. In the program above, six variables are used but never more than three are live. Conversely, a program can need more registers than its expressions suggest, because values that are live across a statement occupy registers while the statement computes: an addition with a long-lived variable on the side needs one extra register. And in an expression tree the order matters: evaluating a big subtree first costs fewer registers than evaluating it last, because the small subtree's register is not held while the big one is computed.

Cheat box

Cheat box

  • Load/store machine: leaf variable 1, constant (immediate) 0, node: max⁡(l,r)\max(l, r) if different, l+1l + 1 if equal. Evaluate the larger side first. Result: minimum registers, no spills.
  • Straight-line program: minimum registers == maximum number of live values (result may reuse a dying operand's register).
  • Interference graph: variables live together are joined; registers = colours; NP-hard in general, heuristics in practice.
  • Too few registers: spill (store, reload later). No spill if registers ≥\ge max live.
  • Block code generator: register and address descriptors; reuse the register of an operand that is dead; store live values at the end of the block.
  • Register assignment uses live variable analysis.

Traps and GATE patterns

Connections

The live variables come from the liveness analysis of the data flow pages; the code generator works on a basic block of three-address code, which a DAG or local optimisation has already tidied. The instructions it emits are those of the machine described in addressing modes.

PYQs

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

GATE 2025 · MCQQ5 · page 280 of the book
Which ONE of the following techniques used in
compiler code optimization uses live variable
analysis?
  • (a)Run-time function call management
  • (b)Register assignment to variables
  • (c)Strength reduction
  • (d)Constant folding
GATE 2017 · NAT · 2 marksQ11 · page 281 of the book
Consider the expression ( a-1)* (((b + c) / 3) + d) . Let
X be the minimum number of registers required by an
optimal code generation (without any register spill)
algorithm for a load/store architecture in which (i)
only loads and store instructions can have memory
operands and (ii) arithmetic instructions can have
only register or immediate operands. The value of X
is _________.
GATE 2013 · MCQ · 2 marksQ23 · page 283 of the book
The following code segment is executed on a
processor which allows only register operands in its
instructions. Each instruction can have almost two
source operands and one destination operand.
Assume that all variables are dead after this code
segment.
c = a + b;
d = c *a;
e = c + a;
x = c *c;
 if (x > a){
 y = a*a;
 }
 else {
d = d * d;
e = e*e;
 }

Suppose the instruction set architecture of the
processor has only two registers. The only allowed
compiler optimization is code motion, which
moves statements from one place to another while
preserving correctness. What is the minimum
number of spills to memory in the compiled code?
  • (a)0
  • (b)1
  • (c)2
  • (d)3 COMPILER DESIGN
GATE 2011 · MCQ · 2 marksQ25 · page 284 of the book
Consider evaluating the following expression tree on
a machine with load-store architecture in which
memory can be accessed only through load and store
instructions. The variables a, b, c, d and e initially
stored in memory. The binary operators used in this
expression tree can be evaluate by the machine only
when the operands are in registers. The instructions
produce results only in a register. If no intermediate
results can be stored in memory, what is the minimum
number of registers needed to evaluate this
expression?
(b)

COMPILER DESIGN
  • (a)2
  • (b)9
  • (c)5
  • (d)3
GATE 2010 · MCQ · 2 marksQ26 · page 285 of the book
The program below uses six temporary variables a,
b, c, d, e, f.

a = 1

b = 10

c = 20

d = a+b

e = c+d

f = c+e

b = c+e

e = b+f

d = 5+e

return d+f

Assuming that all operations take their operands
from registers, what is the minimum number of
registers needed to execute this program without
spilling?
  • (a)2
  • (b)3
  • (c)4
  • (d)6

Self-check

Question 1

Sethi-Ullman number of (a+b)∗(c−d)(a + b) * (c - d)?

Show answer

a+ba + b has label 2 and c−dc - d has label 2; equal labels give 2+1=32 + 1 = 3.

Question 2

Of (a+b)+(c∗3)(a + b) + (c * 3): label?

Show answer

a+ba + b: 2. c∗3c * 3: max⁡(1,0)=1\max(1, 0) = 1. The root: max⁡(2,1)=2\max(2, 1) = 2.

Question 3

A straight-line program has at most 4 values live at any point. Minimum registers without spilling?

Show answer

4 (counting the result of an instruction that does not reuse a dying operand's register, the maximum of live-in and live-out).

Question 4

Which analysis decides whether two variables can share a register?

Show answer

Live variable analysis: variables that are never live at the same time can share a register.

Question 5

Why does the generator prefer to evict a register whose value is dead?

Show answer

A dead value need not be stored, so no spill code (no store, no later load) is needed.

Revision card

One-screen revision

  • Sethi-Ullman: variable 1, constant 0, node max⁡\max or l+1l + 1; larger side first; gives the minimum number of registers.
  • Registers for a program == max live values; colour the interference graph; spill when short.
  • Block generator: register and address descriptors; reuse a dead operand's register; store live values at the end.
  • Register assignment and dead code elimination use live variable analysis.

References

  • ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 8 Code Generation (a simple code generator, register allocation and assignment, optimal code generation for expressions, the Sethi-Ullman algorithm)
  • ·Appel, Modern Compiler Implementation, Chapter 11 Register Allocation (liveness, interference graphs, colouring, spilling)
  • ·Cooper, Torczon, Engineering a Compiler, Chapter 13 Register Allocation

Verification

Everything is computed and executed. The Sethi-Ullman number of an expression is compared by scripts/enum-checks/cd_t2_check.py with the minimum over EVERY evaluation order (a dynamic programme over the sets of evaluated nodes) and with a separate recursive formula on 1500 random trees, and the generated code is run on a small virtual machine and checked against a direct evaluation; liveness is compared with a forward scan on 800 random straight-line programs and colourings are checked to be proper (the chromatic number equals the maximum number of live values for programs in which every variable is assigned once); the minimum number of spills for R registers is compared with a micro-step Dijkstra search (load, store, drop and execute as separate moves) on 360 (program, register count) pairs; the block code generator is executed on 1200 random blocks with 2, 3 and 4 registers and leaves the right values of the live variables in memory. The two C programs on the page are run by npm run verify:c and the check compares their output with the Python implementation (the same labels and the same instruction listing). Machine model: only loads and stores touch memory; arithmetic takes register or immediate operands and may put its result in a register of an operand; the code generator for blocks is the textbook heuristic, not optimal, and its spill choices are one of several reasonable ones. The page does not reproduce any exam figure. The topic is outside the official syllabus (the concept map marks it as used in the journey). Textbook chapter titles are from memory. The independent second pass has not been run, so the page stays draft. 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