C Mastery / How Optimizers Work: O0–O3, Inlining, DCE
Part 6 — Debugging and Optimization

How Optimizers Work: O0–O3, Inlining, DCE

This chapter explains what a compiler optimizer does, the -O0 through -O3 levels, and two fundamental passes: inlining and dead-code elimination.

Why This Matters

You cannot reason about performance or "release-only" bugs without knowing what the optimizer is doing to your code. This is the foundation for the performance and UB-optimization chapters.

Prerequisites

Core Concept

An optimizer transforms the compiler's intermediate representation to make the program faster or smaller, while preserving observable behavior. It runs a series of passes — each a specific transformation.

LevelMeaning
-O0no optimization
-O1basic optimizations, quick compile
-O2aggressive, standard for release
-O3-O2 + loop/vectorization, may increase size
-Osoptimize for size
-Ofast-O3 + non-standard floating-point relaxations

Fundamental Passes

Inlining

Replaces a function call with the function body, eliminating call overhead and enabling further optimization. The compiler uses heuristics (function size, call frequency) to decide.

Dead-code elimination (DCE)

Removes code whose results are never used. This includes unreachable code and stores to variables that are never read.

Constant folding and propagation

Computes constant expressions at compile time and replaces variables with their known constant values.

Common subexpression elimination

Reuses a computation already performed instead of recomputing it.

Examples

A function the optimizer can simplify

int f(int x)
{
    int y = x * 2;
    int z = y + 1;
    if (0) { return z; }   /* dead branch */
    return x * 2 + 1;      /* constant-foldable to same as z */
}

At -O2, this collapses to a single expression.

How It Works

The compiler builds an IR, applies passes repeatedly (often until a fixed point), and emits optimized machine code. Each pass must preserve the abstract machine's observable behavior — assuming no UB.

Variations

LTO runs optimization across translation units, enabling cross-module inlining (c.opt.3).

Profile-guided optimization (PGO)

PGO uses run-time profiles to guide inlining and branch layout (c.opt.3).

Common Mistakes

Undefined Behavior

surprising ways (c.opt.2).

Portability

differ between GCC, Clang, and MSVC.

Under the Hood

GCC uses GIMPLE/RTL; Clang uses LLVM IR. Passes operate on SSA form, which makes dataflow analysis (DCE, constant propagation) efficient.

Practical Usage

Exercises

1. Compile a small function at -O0 and -O2 and compare the assembly. 2. Identify inlining and DCE in the generated code. 3. Experiment with -Os vs. -O3 on size and speed.

Deep Challenge

Write a function where -O2 produces dramatically different assembly than -O0 (e.g., a loop that gets vectorized or a switch that becomes a jump table), and explain each transformation you observe.

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.opt.levels06
c.opt.inlining06
c.opt.dce05