G27
Self-checkedTier 1 11 min readcd.local-optimisation

Local optimisation

Optimising inside a basic block: common subexpression elimination, copy propagation, constant folding, algebraic simplification and strength reduction, dead-code elimination, renaming and reordering, local value numbering as a systematic method, and peephole optimisation.

Why it exists

The code produced by a straightforward translation is wasteful: the same expression is computed twice, a variable is copied around, a constant is added to zero, a temporary is never used. Optimisation rewrites the code so that it runs faster or is smaller without changing what it computes. The easiest place to start is the basic block (basic blocks): control cannot jump into the middle of it or out of it, so the effect of each instruction on the values is easy to follow. Optimisation inside one block is local optimisation; across blocks it needs data flow analysis (data flow framework).

Prerequisites

You need basic blocks (basic blocks and control flow graphs) and three-address code.

Precise definitions

Safety. A transformation is safe (a correct optimisation) if the optimised program computes the same result as the original for all inputs. In a block this means: the variables that are live on exit (used later, in other blocks) get the same values, and the observable effects (printing, memory stores to memory visible elsewhere) are the same.

Local transformations on a basic block.

TransformationIdeaExample
Common subexpression eliminationan expression already computed, whose operands have not changed since, is not computed againa = x + y ... b = x + y becomes b = a
Copy propagationafter x = y, use y in place of x as long as neither changesx = y; z = x + 1 becomes x = y; z = y + 1
Constant foldingan operation on constants is done at compile timet = 4 * 3 becomes t = 12
Constant propagationafter x = 5, replace uses of x by 5x = 5; y = x + 1 becomes x = 5; y = 6
Algebraic simplificationuse identities: x + 0 = x, x * 1 = x, x * 0 = 0, x - x = 0z = x + 0 becomes z = x
Strength reductionreplace an expensive operation by a cheaper onex * 2 becomes x + x (or a shift); x ** 2 becomes x * x
Dead-code eliminationdelete an assignment to a variable that is not used afterwards (a dead variable)t = a + b with t never used is removed
Renaming temporariesgive every temporary a unique name (a normal-form block), so that transformations are saferename the second t1 to t2
Reorderinginterchange two adjacent independent instructions (no common variables written and read)helps scheduling and register use

Local value numbering (LVN). A systematic algorithm that does the first six of these at once. Give every distinct value a number. Keep a table from variables to the number of the value they hold and from expressions (op,v1,v2)(\text{op}, v_1, v_2) to a value number. For each instruction x=y op zx = y \text{ op } z in order:

  1. Look up (or create) the value numbers vyv_y, vzv_z of the operands. If both are constants, fold; if an identity applies (y + 0, y * 1, ...), the result has the value number of the other operand.
  2. Otherwise look up (op,vy,vz)(\text{op}, v_y, v_z) (for a commutative operator sort the two numbers). If it is in the table, the value has been computed already by some variable: replace the instruction by a copy from that variable (common subexpression elimination). If not, give it a new number and keep the instruction (writing each operand by the variable that currently holds the value: copy propagation).
  3. Make xx hold that value number (and remove xx from the old value it held).

After the pass, delete assignments to variables that are not live on exit and are not used later in the block (dead-code elimination, by a backward scan). The key point is step 3: an assignment kills the old value of the variable, so an expression that mentioned the old value is not a common subexpression any more.

Peephole optimisation. A small window (a peephole) slides over the generated code and replaces patterns by shorter or faster ones: redundant loads and stores (ST R0, a followed by LD a, R0: drop the load), unreachable code (after an unconditional jump), flow-of-control optimisation (a jump to a jump goto L1 ... L1: goto L2 becomes goto L2; a conditional jump over a jump is inverted), algebraic simplification and strength reduction, and use of machine idioms (increment instructions, special addressing modes). It is applied to target code, repeatedly, because one replacement can enable another.

Limits. Local optimisation sees one block only. It cannot know that a value computed in an earlier block is still valid, or that a variable is dead because of what happens in a later block (the live-on-exit set must be given). That is the job of data flow analysis (liveness, available expressions).

How it works

The block a = b + c; b = a - d; c = b + c; d = a - d. The instruction 4 computes a - d again, and neither a nor d has changed since instruction 2: its value number is the same as that of b, so it becomes the copy d = b. The instruction 3, b + c, looks like instruction 1 but is not a common subexpression, because b was reassigned in instruction 2 (so it has a new value number). The animation shows the numbers v1, v2, ... of the values and the action taken for each instruction.

Local value numbering of a block with a repeated subexpression: start

Computed by a script, not typed
instructionvalue numberactionafter optimisation
1
2
3
4

Step 0

Each distinct value gets a number. The instruction d = a - d computes the same value as b = a - d (neither a nor d was changed in between), so it can become a copy; c = b + c does not repeat a = b + c, because b changed in between.

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

A block with constants, identities and copies. t1 = 4 makes a * t1 into a * 4; x = t2 + 0 is the same value as t2; y = x and z = y * 1 are again that value; w = a * 4 repeats t2 = a * 4. Everything after the second instruction becomes a copy of t2, and t1, which nobody uses, is dead.

Value numbering with constants, identities and copies: start

Computed by a script, not typed
instructionvalue numberactionafter optimisation
1
2
3
4
5
6

Step 0

The same method also propagates constants and copies and applies algebraic identities: t1 = 4 makes a * t1 into a * 4; adding 0 and multiplying by 1 give the other operand; a * 4 appears twice.

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

Worked examples

EX 1Which transformation?

Easy

(a) t = 2 * 8 becomes t = 16: constant folding. (b) y = x * 1: algebraic simplification, y = x. (c) u = a + b; v = a + b: common subexpression elimination, v = u. (d) x = y; z = x + w: copy propagation, z = y + w. (e) t = a * 2: strength reduction, t = a + a. (f) t = a + b where t is never used again: dead-code elimination. (g) goto L1 ... L1: goto L2: a peephole flow-of-control optimisation.

EX 2Copy propagation and dead code

Easy

Block: t1 = a; t2 = t1 + b; c = t2, with only c live on exit. Value numbering: t1 gets the value of a; t1 + b is therefore a + b; c gets the same value as t2. Result: t2 = a + b; c = t2 (t1 = a is dead and removed; t2 stays because c = t2 reads it; a further pass would merge the two into c = a + b). The code went from 3 to 2 instructions (the optimiser was run on this block and agrees).

EX 3A redefinition kills a common subexpression

Medium

Block: x = a + b; a = 5; y = a + b, with x and y live. The second a + b is not the same value as the first: a has changed from its old value to 5. LVN gives x = a + b; y = 5 + b (constant propagation into the second instruction, no sharing). Compare with x = a + b; y = a + b where nothing is reassigned in between: there the second becomes y = x. A frequent mistake in answers is to eliminate across the redefinition.

EX 4Everything folds away

Medium

Block: x = 3; y = x * 4; z = y + x, only z live. Constant propagation and folding: x is 3, y = 12, z = 12 + 3 = 15. The optimised block is the single instruction z = 15 (checked by running the optimiser: the assignments to x and y are dead). Another: x = y * 2; z = x + 0; w = z - z with x and w live gives x = y + y; w = 0 (the identities x + 0 = x and z - z = 0, then z is dead).

EX 5Count the instructions after optimisation

GATE level

a = b + c; d = c + b (live: a, d). Addition is commutative, so c + b and b + c have the same value number: d = a. The block keeps 2 instructions but the second is a cheap copy. t1 = a + b; c = t1 + 1; t2 = a + b; d = t2 + 1 (live: c, d) becomes t1 = a + b; c = t1 + 1; d = c: after the second a + b is found equal to t1, t2 is t1, and t2 + 1 is the same value as c, so d = c. (Both results were obtained with the optimiser.) A question usually fixes the live variables: the number of instructions left depends on them.

Cheat box

Cheat box

  • Local = inside one basic block. Safe = same values for the live-out variables.
  • Transformations: CSE, copy and constant propagation, constant folding, algebraic simplification, strength reduction, dead code, renaming temporaries, reordering independent instructions.
  • Local value numbering: value numbers for variables and expressions; an assignment to x kills the old value of x; commutative operands sorted.
  • An expression is a common subexpression only if its operands have not been reassigned in between.
  • Peephole: redundant load/store, unreachable code, jump to jump, algebraic and strength reduction, machine idioms.
  • Dead-code removal needs the live-on-exit set.

Traps and GATE patterns

Connections

The units of work are the basic blocks; the data structure that shares common subexpressions is the DAG of a block. What is live on exit comes from liveness analysis; availability of expressions across blocks is available expressions and global CSE. The instructions optimised here are three-address code.

PYQs

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

GATE 2021 · Set 2 · MCQQ3 · page 10 of the book
Consider the following ANSI C code segment:
z = x + 3 + y->f1 + y→f2;
  for (i = 0; i< 200; i = i + 2)
{
    if (z >i) {
      p = p + x + 3:
      q = q + y→f2;
    } else {
      p = p + y→f2:
      q = q + x + 3:
    }
}
Assume that the variable y points to a struct (allocated
on the heap) containing two fields f1 and f2, and the
local variables x, y, z, p, q, and i are allotted registers.
Common
sub-expression
elimination
(CSE)
optimization is applied on the code. The number of
addition and dereference operations (of the form y->f1
or y->f2) in the optimized code, respectively, are:
  • (a)303 and 102
  • (b)303 and 2
  • (c)403 and 102
  • (d)203 and 2
GATE 2014 · MCQQ17 · page 282 of the book
Which one of the following is FALSE?
  • (a)A basic block is a sequence of instructions where control enters the sequence at the beginning and exits at the end.
  • (b)Available expression analysis can be used for common subexpression elimination.
  • (c)Live variable analysis can be used for dead code elimination.
  • (d)x=4*5⇒x=20 is an example of common subexpression elimination
GATE 2014 · NATQ22 · page 283 of the book
The minimum number of arithmetic operations
required to evaluate the polynomial P(x) = x5 + 4x3 +
6x + 5 for a given value of x, using only one
temporary variable is ____ .
GATE 2006 · MCQ · 1 markQ28 · page 285 of the book
Consider the following C code segment.

 for (i = 0; < i< n; i++)

{

    for (j=0; j< n; j++)

    {

        if (i%2)

        {

            x += (4*j + 5*i);

            y += (7 + 4*j);

        }

    }

}

Which one is false?
  • (a)The code contains loop-in variant computation
  • (b)There is scope of common sub-expression elimination in this code
  • (c)There is scope strength reduction in this code
  • (d)There is scope of dead code elimination in this code

Self-check

Question 1

In a = x + y; b = x + y; x = 1; c = x + y, which of the three sums can be replaced by an earlier result?

Show answer

Only the second: b = a. After x = 1 the sum is a different value, so the third must be recomputed (with constant propagation c = 1 + y).

Question 2

What does constant folding do to t = 3 * 4 + 5?

Show answer

It evaluates the constants at compile time: t = 17.

Question 3

When is it correct to delete the assignment t = a + b inside a block?

Show answer

When t is not live on exit and is not used by a later instruction of the block (or it is overwritten before any use).

Question 4

Give an example of peephole optimisation.

Show answer

Removing a load that immediately follows a store of the same register to the same location, or replacing a jump to a jump by a direct jump.

Revision card

One-screen revision

  • Local optimisation within a basic block: CSE, copy/constant propagation, folding, algebraic simplification, strength reduction, dead code.
  • LVN: value numbers, commutative sort, an assignment kills the old value; delete dead assignments using the live-out set.
  • No elimination across a redefinition; b - c is not commutative.
  • Peephole: small windows on target code.

References

  • ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 8 Code Generation (8.5 optimization of basic blocks; 8.7 peephole optimization) and Chapter 9 (9.1 the principal sources of optimization)
  • ·Cooper and Torczon, Engineering a Compiler, Chapter 8 Introduction to Optimization (local value numbering)

Verification

The optimiser on this page is a local value numbering pass with constant folding, algebraic identities, copy propagation and a final backward removal of dead assignments (scripts/cd_icg.py). scripts/enum-checks/cd_icg.py runs the optimised block and the original block on 8 random inputs for each of 4000 random blocks and compares every variable that is live on exit: no difference. The six blocks and their optimised forms quoted on the page are asserted in the same script. The list of transformations follows the textbook; strength reduction is shown as x * 2 becoming x + x, which is valid for integers. 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