Compiler design / 15. Linking, entries and reachability

Compilation and activation stay separate. Entry assembly selects roots and builds a reachable function union.

Choosing what runs

Compilation and activation are different verbs.

A module can compile completely, including its dormant top-level work, and still do nothing at runtime until an entry selects it. The seed can be fully formed and still sleep. Imports do not secretly run initialisers because someone mentioned a name.

Compile versus activate

Every normal root may carry dormant top-level runtime work and page fragments. That work is parsed, type-checked, lowered, borrow-validated and locally lifetime-analysed before any entry activates it.

The compiler synthesises an implicit start for a normal root. Properties that matter:

Compilation never activates start. Entry assembly may activate it once after the module already compiled.

Support roots and the project package facade have no implicit start.

In the walkthrough, @page.moth owns dormant root work and a page fragment. greeting.moth remains an imported module. Importing greet does not run page-level statements from greeting's root, because greeting is not the activated entry root.

Diagram placeholder: All modules compile before one selected root activates

Caption: exact conceptual distinction. Question: when does root work run?

Diagram placeholder: page root owns a dormant start selected by one entry

Caption: simplified root map. Question: why do imported roots stay dormant?

Page fragments and root order

Fragments split into useful kinds:

Imported modules do not run their root work merely because a consumer imported a function. Activation is explicit and entry-scoped.

The paragraph in @page.moth enters through fragment metadata and, if needed, runtime production. It does not require the backend to re-read the .moth file.

Diagram placeholder: Folded and runtime fragments preserve source indexes

Caption: exact ordering principle simplified. Question: where does the paragraph enter?

Entry assembly

The build system selects entry candidates and builds an EntryAssembly from existing facts: which root activates, which fragments apply, which metadata the builder needs.

Contrast ProjectPackageAssembly, which carries project-wide assembly privilege for the facade. Different job, shared taste for explicit data.

Builders consume compiled graphs and explicit plans. They do not rediscover source structure by walking directories again.

Reachable function union

From activated start, the linker walks call edges through:

The result is a reachable function union for the entry. Unreachable private helpers need not lower. Unsupported features in unreachable private functions need not fail target validation.

Reachability here records syntactic function and CFG reachability. It does not perform clever constant-branch tree shaking as its definition of truth. Some target checks may still inspect semantic types with the paired environment when raw reachability is not enough.

Link facts also carry capabilities and assets the entry will need.

Diagram placeholder: Reachable nodes are highlighted from start, with an unreachable helper left aside

Caption: teaching graph. Question: what reaches the backend?

Complete lifetime topology

The exact reachable union is also the point where local lifetime summaries can be instantiated against actual callers, entry/package roots, and builder-supplied lifecycle roots.

Project/link validation completes the lifetime topology before target assignment. Linking does not reopen source or mutate HIR.

No deferred compilation

Linking consumes compiled facts. It never triggers source compilation of a module that should have been in the graph already.

If a function is missing, the graph or artefact stage failed earlier. Link planning does not become a second compiler driver.

What can fail here

Target capability failures wait for the next article even when they appear adjacent in real builds.

Why Moth does it this way

Alternative: activate imported module initialisers automatically, or parse only the active root and discover callees late.

Choice: compile dormant work once, activate selected roots explicitly.

Benefit: imports remain predictable and entry planning stays data-driven.

Cost: the builder must carry explicit root and fragment metadata.

Roadmap and current status

Exit state

You leave with explicit roots, a reachable function union, and a validated project/link lifetime topology. Next: target assignment.

Compiler design / Previous / Next