Middle-end

Scalar Optimization

Folding, elimination, propagation, inlining and devirtualization — each with the precondition that makes it legal and the budget that makes it wise.

Constant Folding
▶ lab

Evaluate at build time what would otherwise be evaluated at run time — but only when the operands are literals, the operation cannot fault, and the compiler computes exactly the value the machine would have computed.

Q · If the compiler can already see that an expression is `2 * 3`, why would it ever emit a multiply?
Dead Code Elimination
▶ lab

Delete an instruction only when it has no side effect AND no user. Both halves are required, and removing an effectful instruction because its value happens to be unused is a miscompilation rather than an optimization.

Q · The compiler deleted code I wrote. What did it have to prove first, and when does it get that wrong?
Common Subexpression Elimination
▶ lab

Compute `a * b` once and reuse it — but only when the earlier computation dominates the later one, so the value is guaranteed available on every path that reaches the reuse. Over registers this is easy; over memory it needs alias analysis, which is why the two are different problems.

Q · The same expression appears twice. Why does the compiler sometimes reuse the first result and sometimes not?
Copy Propagation
▶ lab

If `a` is a copy of `b`, use `b` directly and let the copy die. In SSA this is legal by construction; outside SSA it needs a reaching-definitions analysis, and that difference is one of the clearest arguments for SSA there is.

Q · Why does the compiler bother emitting `a = b` at all, and what lets it get rid of the copy afterwards?
Strength Reduction and Algebraic Identities
▶ lab

Replace an operation with a cheaper one that computes the identical value. The real content is not that shifts beat multiplies on some 1990s CPU — it is that `x + 0` is unconditionally `x` for integers and is not valid for IEEE-754 floats, which is why `-ffast-math` exists and why it changes what a program means.

Q · Should I write `x << 1` instead of `x * 2`, and why does the compiler refuse to simplify some of my floating-point arithmetic?
Inlining
▶ lab

Replace a call with the callee's body. The direct saving — a call and a return — is the least interesting part; the value is that every other optimization can now see across a boundary it could not cross. The cost is code size, compile time and instruction-cache pressure, and it is a budget rather than a rule.

Q · When does inlining a function actually make the program faster, and when does it make it slower?
Devirtualization
▶ lab

Turn an indirect call through a dispatch table into a direct call to a known function — and then, because the target is known, inline it. The whole value is in that second step; a direct call on its own is barely cheaper than an indirect one.

Q · My hot loop calls a virtual method. Can the compiler turn that into a direct call, and what does it have to know first?
Partial Evaluation and Specialization
▶ lab

When some inputs are known and others are not, a program can be specialized with respect to the known ones — producing a smaller, faster program that takes only the remaining inputs. It is the idea behind constant folding, template instantiation, JIT specialization and monomorphization, and it explains why they behave alike.

Q · Half of my function's inputs are fixed at startup. Can the compiler produce a version of it that only takes the rest?