Programs spend almost all of their time in loops, so a computation that lands inside a loop is
the most expensive real estate in the whole procedure — run it a million times and you pay a million
times over. Yet loops are full of work that does not actually depend on the loop. Inside
for (i = 0; i < n; i++) a[i] = x * y + i;, the product x * y is recomputed
on every iteration even though x and y never change. Loop-invariant
code motion (LICM, also called hoisting) is the optimisation that spots such a
computation and lifts it out of the loop, so it runs once instead of
The pay-off is enormous, but the reasoning is delicate: move the wrong thing, or move it to the wrong place, and you change what the program computes — or make it crash on an input where it used to run fine. LICM is therefore a small masterclass in the compiler's central discipline: an optimisation is only worth doing if it is provably safe on every path. Getting the safety conditions exactly right is what this page is about.
Before you can move code out of a loop, the compiler must identify the loop in the raw control-flow
graph — there is no for keyword left by this stage. The precise, structural definition
rests on dominators. A node
A single-entry header is what makes hoisting well-defined: there is one place, just before the header, where hoisted code can live and be sure to run exactly once per entry to the loop. That place does not exist yet, so we create it — the preheader.
A preheader is a fresh basic block inserted on the edge(s) entering the header from outside the loop; every such entry edge is redirected through it, while the loop's back edge still targets the header directly. It is the one block that dominates the header and runs once before the loop begins — the natural home for anything we lift out.
Reveal the diagram: the raw loop (header t = x * y migrates
up into it. Inside the loop, the body now just reads t.
A statement in the loop computes a loop-invariant value if — no matter which iteration you are on — its operands always have the same value. Formally, an operand is invariant if it is:
| An operand is loop-invariant if… |
|---|
| it is a literal constant, or |
| all of its reaching definitions lie outside the loop, or |
| it has exactly one reaching definition, and that definition is itself a statement already marked loop-invariant. |
That last clause is recursive, so you compute the invariant set by iteration to a fixed
point: repeatedly sweep the loop marking any statement whose operands are all now invariant,
until a sweep marks nothing new. It leans directly on
The engine below takes a loop body as a list of three-address statements, plus the set of variables defined outside the loop, and marks the invariant statements by fixed-point iteration — exactly the recursive rule above.
t = x * y is invariant (both operands come from outside), and then
s = t + n becomes invariant on the next sweep because its only in-loop operand,
t, was just marked — the fixed point in action. But a = i * 4 and
i = i + 1 stay put: i is redefined every iteration, so they vary.
Here is the deep point: a statement being loop-invariant does not by itself make it safe to
hoist. Move it to the preheader and it will now execute unconditionally, once, before the
loop — even if, in the original program, some iterations (or all of them) would have skipped it.
For the transformation to preserve meaning, the statement
The first condition is the one people forget, and it is the one that protects correctness of
outcome. If if within the loop, or after a
possible early exit, then some run of the loop might never have executed
Hoisting has a famous sibling that also exploits loop structure. An induction variable
is one that changes by a fixed amount each iteration — the loop counter
It is not invariant code —
Because a loop header can have several predecessors from outside the loop (think two
if arms that both fall into the same while). If you hoisted into one of them,
the code would not run when control entered through the other — and if you hoisted into all of them you
would duplicate it and risk running it when the loop is never entered from a third path. The preheader
solves this cleanly: it is a single block on the combined entry, dominating the header,
guaranteed to run exactly once each time the loop is entered and never otherwise. Creating it is cheap,
and it gives every subsequent loop optimisation (LICM, strength reduction, invariant guards) a canonical
place to deposit setup code.
The classic bug: you find t = a / b loop-invariant (both a and b
defined outside) and hoist it — but in the original loop that statement sat inside
if (b != 0) { … }, so it only ran when b was non-zero. Hoisted to the
preheader it runs unconditionally, and on an input where the loop body's guard would have kept
b == 0 away from the division, your "optimised" program now divides by zero and crashes.
The guardrail is the dominance condition: hoist