Around all of it

Compilation at Scale

Compilation units, modules, interface files and the dependency analysis that keeps a rebuild proportional to the change rather than to the codebase.

Compilation Units
▶ lab

The unit the compiler processes at once decides everything about build cost. In C and C++ that unit is a translation unit — one source file plus every header it transitively includes — which is why editing one line of a header can rebuild half the project.

Q · What does the compiler actually process in one invocation, and why does one header change rebuild so much?
Separate Compilation
▶ lab

Compile each unit independently, link the results together. It buys parallelism and incremental rebuilds, and pays with an optimizer that cannot see past the boundary — which is precisely the gap LTO exists to fill.

Q · Why do we compile files separately and link afterwards instead of compiling the whole program at once?
Modules as Units of Separate Compilation

A language-level module gives namespacing, explicit dependencies, encapsulation and separate compilation without textual inclusion — a dependent reads a compiled interface rather than re-parsing your source.

Q · What does a language-level module system give me that headers and includes do not?
Interface Files

A compiler can consume a dependency's exported signatures without reparsing its implementation. `.hi`, `.mli`, `.d.ts`, C++ BMIs and Go export data are all the same idea, and it is what makes incremental compilation work at scale.

Q · How does a compiler type-check my code against a library it never parses?
Incremental Compilation

Recompile what the change actually affected, not what it touched. The modern form is not file timestamps but a memoized graph of queries, where a change invalidates exactly the results that depended on it.

Q · How does a compiler avoid redoing work when only a little changed?
The Build Dependency Graph

A build is a directed acyclic graph of artifacts. A change to a node may or may not require rebuilding its dependents, and which one it is depends on *what* changed — a body or a signature.

Q · When I change one file, how does the build decide what else has to be rebuilt?
Where the Compiler Ends and the Build System Begins

The compiler turns one set of sources into one artifact. The build system decides which of those invocations must run at all. Getting that division wrong — most often by trusting timestamps — produces both missed and spurious rebuilds.

Q · What is the build system's job, and what is the compiler's?
Hermetic Compilation
▶ lab

A build is hermetic when its result depends only on its declared inputs — not on which `cc` happens to be first in `PATH`, not on a header that exists on one laptop, not on anything fetched from the network while it runs.

Q · Why does this build work on my machine and fail in CI, when the commit is identical?
Compile Time versus Runtime
▶ lab

Every optimization is a purchase: build seconds now for execution seconds later. The exchange rate is set by how often the program runs against how often it is built — and there are whole classes of program where the purchase buys nothing at all.

Q · Is it worth turning on more optimization, and how would I know?