Skip to content
Beskid Platform specification

Beskid

Jump to a Beskid service

Beskid

Jump to a Beskid service

Stage ordering and lowering - Design model

Platform spec article

Stage ordering and lowering - Design model

Spec standingStandard

Owner
Piotr Mikstacki
Submitter
Piotr Mikstacki
flowchart LR
  resolve_input[resolve_input]
  assemble[program.assemble]
  parse[parse]
  macro[macro.expand]
  modload[mod.load collect generate]
  semantic[semantic rules]
  comp[composition.resolve]
  modar[mod.analyze rewrite]
  hir[HIR and ModuleIndex]
  lower[lower_program]
  artifact[CodegenArtifact]
  resolve_input --> assemble --> parse --> macro --> modload --> semantic --> comp --> modar --> hir --> lower --> artifact

The compile model has a strict handoff chain:

resolve_input -> program.assemble -> parse (entry + indexed units) -> macro.expand -> mod collect/generate (when enabled) -> semantic rules gate -> composition.resolve (app host DI; see Native dependency injection) -> analyzer/rewriter passes -> HIR normalization -> resolution (with ModuleIndex) -> typing -> lower_program -> CodegenArtifact.

Backends (JIT and AOT) consume this artifact and must not reorder semantic phases.

Language macros expand in macro.expand, immediately after parse and before mod.load. The phase:

  1. Resolves name! invocations to in-scope macro definitions.
  2. Substitutes fragment arguments for $param in macro bodies (typed AST only).
  3. Repeats until no invocations remain or maxMacroExpansionDepth (default 32) is exceeded.

When mod.generate re-parses merged syntax, hosts must run macro.expand again before the next mod.collect / semantic gate (same syntax generation). Emit syntax.generation when either macros or mods mutate the program snapshot.

Compiler mods (see Compiler Mods) run as explicit host-driven phases after macro.expand and before lowering.

  1. Phase vocabulary — Register stable string ids in compiler/crates/beskid_pipeline/src/phases.rs and update PIPELINE.md in the same change.
  2. Observer parity — CLI and codegen entrypoints must bracket mod phases with PhaseStart/PhaseEnd (or documented WorkUnit ids).
  3. Bounded generationGenerator is incremental by default and runs with a bounded replay loop (maxGeneratorRounds). On each replay, host merges typed AST contributions and re-parses affected units.
  4. Analyzer/rewrite orderingAnalyzer runs on merged host+generated code; Rewriter applies typed replacements and is surfaced as fix-oriented behavior.
  5. No lowering on torn trees — lowering runs only on a consistent post-merge snapshot.

Placement relative to beskid_codegen services

Section titled “Placement relative to beskid_codegen services”

The reference lowering path orders parse, semantic diagnostics, lower, and codegen. Mod phases are inserted after parse: collect/generate, then semantic, then analyze/rewrite, then lowering.

Normative mod phase id strings (beskid_pipeline)

Section titled “Normative mod phase id strings (beskid_pipeline)”

These literals must match Compiler Mods / Mod projects and pipeline phase ids.

Full build (FULL_BUILD_PHASE_ORDER) — After parse, insert macro.expand, then mod.load, mod.collect, mod.generate (plus syntax.generation on replay), then semantic phases, then mod.analyze, optional mod.rewrite, then lower/codegen/link.

JIT run (JIT_RUN_PHASE_ORDER) — No JIT mod execution path is normative for compiler mods; mod execution is AOT-hosted only.

Auxiliary idsworkspace.graph_changed and semantic.snapshot are emitted by workspace refresh and semantic pipeline orchestration respectively; they must use the same string literals when represented as phases (or as documented WorkUnit id prefixes if only work units are used—pick one representation per id and document it in PIPELINE.md).

lower.ready — Emitted immediately before the existing lower phase entrypoint runs on a merged program; hosts must emit it even when no mods ran (no-op instant) so observers can assert ordering uniformly.