explanationView source

Compiler pipeline#

What happens to a source file, in order, and what each step may decide.

bytes
  -> lossless lexemes and trivia          Alpha.Surface.Source
  -> concrete syntax tree (CST)           Alpha.Surface.Source.parseCst
  -> surface module                       Alpha.Surface.Module.parseAlphaModule
  -> core terms                           Alpha.Surface.Elaborate
  -> CHECKED terms                        Alpha.Core.Trusted.Kernel
  -> checked interface (cached)           Alpha.Surface.Interface
  -> sealed program                       Alpha.Surface.Module.sealCheckedSharedModuleProgram
  -> erased runtime term                  Alpha.Core.Erasure
  -> either: reduced erased term          Alpha.Compiler.ErasedReduction (evaluator lane)
             -> runtime graph + evaluator Alpha.Compiler.RuntimeGraph
     or:     typed control flow -> code   Alpha.IR.Semantic + Alpha.Compiler.ControlFlowNative (direct lane)
  -> native object                        Alpha.Compiler.NativeObject
  -> ELF                                  Alpha.Compiler.Link + Alpha.Target.X86_64 (or Alpha.Target.AArch64)

Step by step#

Lexing is lossless. Every byte of the file belongs to exactly one lexeme, including whitespace and comments, so the file can be rebuilt from the lexemes. A line break is its own lexeme because declaration ownership is decided per line.

Parsing builds a CST over those lexemes. Malformed structure becomes an error node rather than an exception, which is why the formatter can refuse a file precisely ("unterminated list at line 143") instead of failing.

Module parsing reads the line-oriented declaration grammar: module, import, family blocks and def. Import order is significant — it fixes the closure order and therefore the artifact's bytes — so no tool sorts imports.

Elaboration turns surface terms into core terms. It resolves names through the module grammar's visibility rules and inserts nothing the source did not say. It may not decide well-typedness.

Checking is the kernel: inference, definitional equality, quantity accounting, effect rows, totality and positivity. A definition that checks yields a CheckedTerm, which is the only evidence the rest of the pipeline accepts.

Interfaces are what the cache stores: a header, a lazily decoded body, a semantic digest that excludes the source digest, and an integrity digest. Two files that differ only in comments produce the same semantic digest, which is what keeps a comment edit from re-checking the whole cone.

Sealing turns the checked modules into one program: a single main, family uniqueness, and the program term.

Erasure removes everything that is compile-time only. The measured effect is large: 5735 definitions encoding 1.64 MB of core produce an erased program of 536 runtime nodes.

Emission takes one of the two lanes described in docs/compiler/ARCHITECTURE.md. The direct lane additionally verifies its own work: it interprets the lowered control flow and compares the result with the defunctionalized residual before emitting anything.

That self-check has one honest limit, and the certificate records it. A program that READS a file has no compile-time answer to compare against — the interpreter has no file — so for those programs the value comparison is skipped and only the structural verification remains: the x86 verifier decodes and re-encodes every instruction it emitted.

Linking places objects, resolves symbols, applies relocations and rebases source maps. It refuses rather than guessing.

What each step may report#

Every step reports through the same record: a stable ALPHA-* code, an exact span, a message, and optional detail (expected, actual, related spans, fixes). docs/language/DIAGNOSTICS.md lists the codes; the JSON shape is frozen by tests/golden/protocol/diagnostic-v1.json.

Where the pipeline is measured#

alpha profile times a cold check by phase from inside the engine. On the 134-module application closure the split is parse 3158 ms, check 1834 ms, interface 423 ms — parsing dominates a cold check, which is the opposite of the usual assumption and worth re-measuring before optimising the checker again.