Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Language and compiler pipelines

Meaning is not the pipeline

A compiler is clearest when language meaning is not identified with parser trees, passes, bytecode, or the machine loop. Correctness is one commuting law: running the compiled program agrees with the program's denotation.

A compiler is easiest to reason about when the language meaning is not identified with parser trees, compiler passes, bytecode, or a virtual-machine loop.

This chapter uses a simplified concurrent language shaped like Tolang, keeping the focus on the semantic architecture.

Four distinct layers

Keep these layers separate:

  1. Source syntax
  2. Normalized language
  3. Executable representation
  4. Machine execution

A typical pipeline is:

The convenience operation is composition:

The composed helper is not the semantic definition of each stage.

Syntax is not yet meaning

Parser output mirrors grammar choices:

  • punctuation
  • sugar
  • precedence
  • source spans
  • generated parser representation

These facts matter to diagnostics and tooling, but they need not define the core language.

Normalization removes accidental syntactic variation and introduces a smaller meaning-bearing form. For example, several surface binding forms may normalize to one core restriction operation.

Many source formsone normalized constructor

This creates one place to state later compiler and runtime laws.

Denotational and compiled meaning

Let:

assign meanings in semantic domain D.

Compilation produces executable values:

and execution observes them:

The central correctness obligation is a commuting diagram:

For nondeterministic or concurrent systems, equality may be observational equivalence, a set of outcomes, trace equivalence, or bisimilarity rather than ordinary value equality. The semantic model must choose.

Concurrent process meaning

The base reflective calculus includes ideas such as:

Tolang extends the base calculus compositionally. One such extension adds the surface form

This binding creates a fresh channel and scopes its name over ; process-calculus literature calls the operation restriction. The reflective core has no primitive restriction operator. See Meredith and Radestock’s A Reflective Higher-order Calculus.

The operational machine may implement these with a tuple space, queues, matching indexes, continuation records, and concurrent tasks. Those mechanisms are not the language definition.

The semantic layer identifies observable process behavior and structural laws. Candidate laws for the core parallel composition include:

Laws for this fresh-binding extension are separate. Conventional candidate schemas include alpha-renaming, commutation of distinct bindings, and scope extrusion:

Here means that is fresh for , is capture-avoiding renaming, and is the set of free channel names in . These schemas become laws only when the chosen observational equivalence validates them; they must not be assumed from the surface syntax or inferred from one bytecode layout.

Normalization as a meaning-preserving map

If source programs s₁ and s₂ differ only by accepted sugar, normalization should make their common meaning explicit:

Other useful obligations include:

  • alpha-renaming preserves normalized meaning
  • lexical scope resolves to the intended binder
  • source positions survive where diagnostics require them
  • compile-time constants erase without runtime behavior
  • malformed syntax accumulates diagnostics according to the stage contract

Some projects require canonical normalized values. Others require only semantic equivalence. State which one.

Stage capabilities

No god compiler object

Do not make one giant compiler object the only stage interface.

A normalizer may need capabilities such as:

  • Read and extend binding scope
  • Resolve source names
  • Accumulate diagnostics
  • Construct normalized terms

A code generator may need:

  • Allocate executable references
  • Emit instructions
  • Resolve normalized variables
  • Finalize executable structure

Express stage entries as extensions over these capabilities. Concrete environments store maps, arenas, counters, and diagnostics, but they do not own the stage meaning.

#[ext(name = NormalizeEntryExt)]
pub impl<This> This
where
    This: BindingScopeAlg + DiagnosticAlg + NormalizeRecAlg,
{
    fn normalize(&mut self, syntax: &Syntax) -> Result<Core, BuildError> {
        // initialize the semantic stage and invoke recursive normalization
    }
}

The precise capability names vary by compiler. The important shape is explicit dependency and a stable stage map.

Parser and runtime can remain operational

Not every subsystem must be forced into the same encoding.

A parser may be dominated by generated grammar machinery. A virtual machine may be dominated by efficient mutation and scheduling. They can remain operationally shaped while implementing well-defined boundaries:

StageContract
ParserProduces Syntax
NormalizerProduces meaning-bearing Core
Code generatorPreserves meaning in the executable form
RuntimeProduces observations consistent with the language model

Denotational Design does not remove machines. It prevents machines from silently becoming language semantics.

Compiler laws and tests

Strong evidence comes from several directions:

  • normalization laws over equivalent source forms
  • round trips for parsing and pretty printing where intended
  • direct semantic evaluation compared with compiled execution
  • bytecode equivalence when canonical output is promised
  • runtime conformance scenarios
  • differential tests across interpreters

Byte equality is not semantic equality

Do not confuse byte-for-byte equality with semantic equality. Byte equality is a useful stronger property only when canonical compilation is part of the specification.

How this prepares the reader

When reading a meaning-first language implementation, look for:

  • Semantic syntax or capability traits
  • Derived normalization operations
  • Explicit stage contracts
  • First-order core programs
  • Laws for substitution, scope, and composition
  • Thin environments that interpret stage capabilities
  • Operational parser, compiler, and VM machinery at the edges

This lens bridges general Denotational Design and Tolang, showing how semantic boundaries guide language design, compilation, and runtime interpretation.