Compilation narrows the program through three semantic representations and two WebAssembly artifacts:
source files
↓ lex, parse, recover
tokens + AST
↓ hoist, resolve, check, infer, lower
typed HIR
↓ discover and materialize generic instances
monomorphic HIR
↓ build CFG, make suspension explicit, optimize, insert ownership
MIR
↓ canonicalize, lower to WasmIR, allocate, encode
per-unit WebAssembly
↓ merge allocator + units, remap indices, add metadata
linked program WebAssemblyThe arrows are contracts. A stage consumes only the previous stage’s public facts, establishes the invariants needed by the next stage, and returns diagnostics when it cannot do so.
Driver entry
atoll build accepts a file, a bare directory, or a path within a manifest
project:
| Input | Compilation scope |
|---|---|
File outside an atoll.toml project |
That file, using the single-file compatibility path |
| Bare directory | Every discovered .at file as an ad-hoc project |
Path at or below atoll.toml |
The complete manifest project |
Project builds use the Salsa-backed driver in atoll-db. A one-shot CLI build
creates a fresh database, so it gains one canonical query composition but not
cross-command cache reuse. Long-lived consumers such as the language server
retain the database and update tracked file inputs.
atoll check stops after parsing and semantic analysis. atoll build
continues through specialization and WebAssembly emission. atoll run selects
an emitted unit and hands it to the runtime. These commands should agree on
front-end diagnostics because they share the same parsing and semantic
pipeline.
Stage contracts
Syntax
The syntax crate turns bytes into typed tokens and an arena-backed AST. It interns source strings, attaches byte ranges, and attempts error recovery so a single malformed construct does not erase the remainder of the file. The AST contains syntax, not resolved identities or inferred types.
Analysis
Semantic analysis combines files into one project:
- install the compiler prelude and project configuration;
- hoist declaration surfaces;
- resolve module imports and names;
- lower bodies while checking types, effects, visibility, and decorators;
- register layouts and data-query metadata;
- return typed HIR and diagnostics.
This is the last stage allowed to decide language meaning. Later stages may validate their own representation invariants but should not invent a missing type, effect, layout, or overload decision.
Specialization
Generic source functions remain useful semantic definitions, but the backend requires concrete bodies. Monomorphization walks reachable calls, derives a canonical substitution for each generic callee, reuses an existing specialization on a cache hit, and otherwise clones and substitutes the body. Calls are rewritten to concrete symbols. Instantiated nominal types receive concrete layouts at the same boundary.
MIR
MIR makes control flow and suspension explicit. It builds blocks and
terminators, computes frame state, runs validation and optimization, selects
calling conventions, and inserts explicit ownership operations such as
Clone and Drop. Its pipeline contains both per-function passes and
unit-wide passes, so “compile one function” does not imply that every
cross-function optimization can be skipped.
WebAssembly
Code generation discovers entry units and each unit’s deterministic transitive closure. Canonical MIR lowers mechanically into WasmIR, a smaller SSA form whose values already have natural WebAssembly types. WasmIR passes perform backend-local transforms, allocation, and control-flow structuring. The encoder produces a validator-clean WebAssembly module per unit.
The ordinary build writer currently writes those per-unit modules beneath
target/atoll/units/. The linker is a separate compiler API used by deployment
and runtime assembly; it can merge the allocator module and one or more units
into a program module.
Hard gates
Each transition has a stop condition:
| Gate | Failure behavior |
|---|---|
| Parse or semantic errors | No specialization or code generation |
| Specialization errors | No partial unit artifacts |
| MIR invariant or ABI errors | Diagnostic attributed to the IR layer |
| Codegen errors | Unit bytes are not written as a successful build |
| Invalid encoded module | Backend failure; validation must not be bypassed |
| Link invariant mismatch | Link error naming the input module or unsupported section |
Warnings and hints can survive a successful build. Error-severity diagnostics cannot. The CLI aggregates diagnostics by layer so users can see the source problem while compiler tests can assert which stage owned it.
Optimization modes
The command-line optimization levels select a policy rather than different languages:
| Mode | Intent |
|---|---|
-O0 / --debug |
Lowering core, debuggable output, minimal optional optimization |
-O1 |
Balanced optimization |
-O2 / --release |
Full release pipeline; current default |
All modes must preserve observable language behavior and emit valid modules. Pass-disable and trace controls are debugging tools; a fixed source, compiler revision, and effective option set must still produce deterministic output.
Artifacts and identity
An Atoll unit is a compiler work item rooted at an entry function. The
current discovery convention recognizes main and entry_*, with extra roots
available to test infrastructure. A unit owns a stable name, root symbol,
source range, and ordered static closure.
A per-unit module is not yet a deployed instance. The linker can combine units
into one or more link groups and embeds the atoll.units metadata consumed by
the runtime. The runtime then creates unit instances with their own scheduler
and arena state. See Units for the distinction.
Observability
The compiler can expose AST, HIR, MIR, WasmIR, and WebAssembly-oriented dumps. Typed trace events connect transformations across stages, while source-origin tables connect lower operations back to expressions. Provenance defines how those identities survive relocation, optimization, instruction expansion, and encoding. Debug output must remain optional: enabling it may add debug sections or trace side channels, but it must not change program semantics.
Continue with Syntax for the first stage or Diagnostics for the cross-stage failure model.