series2 notebooks · 15 plates · about 46 minutes of reading
How code becomes a program
I’m writing the compiler for Miri, my own programming language, so this is the corner of computing I spend my evenings in. These two notebooks are for anyone who has used a language for years and never looked inside one.
The first follows a program from source text to machine code. One stage per plate. The second asks what happens to all the memory that program allocates, and compares the very different answers languages have settled on.
start with № 05: The architecture of an ahead-of-time compiler →
The reading path
-
The architecture of an ahead-of-time compiler
An ahead-of-time compiler translates a whole program into machine code before it runs. It works as a pipeline: a lexer splits the text into tokens, a parser builds a syntax tree, a checker resolves names and types, the code is lowered to SSA form and optimised, then registers are allocated, machine code is emitted and a linker produces the binary. Before you press runWordsGrammarMeaningOne name, one valueThe optimiserSixteen registersBytes, symbols, a binary -
How languages manage memory
Programming languages free memory in a few ways. In C you call free yourself. Tracing garbage collectors (Java, Go, JavaScript) find what is still reachable and free the rest. Reference counting (Swift, Python) frees an object when its last reference goes. Rust proves ownership at compile time, and Perceus (Koka, Lean) makes counting precise and reuses memory in place. You free it yourselfThe janitor walks the heapEveryone keeps a tallyOne owner, many borrowersCounting at compile timeCounting precisely, reusing in placeWhich one, for what
Questions people ask
- What does a compiler do?
It translates source code into machine code before the program runs: lexing, parsing, building an intermediate representation, optimising it, then generating instructions for the target processor. The compiler notebook runs each stage in the page, so you can watch one line of code change shape.
- What is the difference between garbage collection and reference counting?
A tracing garbage collector periodically finds everything still reachable and frees the rest. Reference counting keeps a count on each object and frees it the moment the count reaches zero. Rust settles most of it at compile time, by checking who owns each value.
- Is ahead-of-time compilation faster than an interpreter?
For most programs, yes, because the translation happens once, before the program starts. A just-in-time compiler closes much of the gap by compiling hot code while the program runs, and pays for it in start-up time and memory.