Middle-end

What a Compiler Is Allowed to Do

Observable behavior, the as-if rule, and undefined behavior as a licence to assume rather than a promise to crash. Semantics decide what is legal, not cleverness.

Optimization Legality
▶ lab

An optimization is valid only if it preserves the language's defined observable behavior. Every pass, every flag and every argument about undefined behavior in this domain is a consequence of that one sentence.

Q · What makes a transformation an optimization rather than a bug?
Observable Behavior
▶ lab

The list the whole domain depends on: input and output, volatile accesses, whether the program terminates, and the order the language sequences those in. Elapsed time, memory used, chosen registers and instruction counts are not on it — which is exactly why the compiler may change them.

Q · Which effects of my program is the compiler obliged to preserve, and which is it free to change?
The As-If Rule
▶ lab

The compiler may transform the program however it likes, provided the observable behavior of the result follows the rules of the language's abstract machine. It is not a loophole — it is the clause that makes any optimization at all legal.

Q · Where does a compiler actually get permission to rewrite my program?
Undefined Behavior
▶ lab

Undefined behavior is not a run-time error and not a promise of a crash. It is a licence for the compiler to assume the program never does it — which turns a source-level mistake into a premise the optimizer reasons from.

Q · What does "undefined behavior" actually license a compiler to do?
How Undefined Behavior Becomes Faster Code
▶ lab

The canonical case, worked properly: a null check placed after a dereference is deleted, because dereferencing already implied the pointer was non-null. Not a compiler being malicious — ordinary branch simplification applied to a fact the language supplied.

Q · How exactly does an undefined construct in my source turn into a missing check in my binary?
Semantics Decide, Not Cleverness
▶ lab

Can integer overflow occur? Can two references alias? Can a function have hidden side effects? The answers are properties of the language, and they decide what its compiler may do — which is why the same transformation is routine in C, forbidden in Java and unnecessary in Rust.

Q · Why does the same optimization happen in one language and not another, for identical-looking source?
Optimization Levels
▶ lab

What `-O0` through `-O3`, `-Os` and `-Oz` actually select, why a higher number is not automatically faster, and why the only way to choose between two of them for your program is to measure both.

Q · What do the optimization levels really change, and how do I pick one?
Pass Pipelines
▶ lab

The middle-end is a sequence: IR in, pass, IR out, repeat. Passes come in three kinds — analyses that compute facts, transformations that rewrite, and cleanups that make the next pass's job possible — and the pipeline is how a compiler is actually organised.

Q · How is an optimizer actually structured, and what is a "pass"?
Phase Ordering
▶ lab

The same passes in a different order produce different code, and no order is best for every program. Constant propagation, branch simplification and dead-code elimination are the canonical cascade — and running the pipeline to a fixed point is what a compiler does instead of solving the problem.

Q · Does the order of optimization passes matter, and if so, what is the right order?