# Pipeline and CLI ## Pipeline ``` .ail.json ─┐ ├─ load + validate schema ├─ resolve names + assign hashes ├─ desugar (AST → AST) ├─ typecheck (HM, effect rows; mode-strict per the memory model) ├─ lift_letrecs (post-typecheck AST → AST) ├─ lower to MIR (SSA-like, named SSA values) ├─ emit LLVM IR (.ll) └─ clang -O2 *.ll -o binary --alloc=rc → emits inc/dec (@ailang_rc_inc / _dec; canonical, default) --alloc=bump → links bump-floor (@bump_malloc; raw-alloc bench-floor) ``` Two allocator backends share the same MIR. `--alloc=rc` is the canonical backend committed to in the [memory model](../contracts/0008-memory-model.md) and the CLI default; the typechecker enforces `(own)` / `(borrow)` modes, codegen emits `ailang_rc_inc` / `_dec` calls at the points dictated by linearity, and `Term::Clone` / `Term::ReuseAs` materialise into actual rc-bumps and in-place rewrites respectively. `--alloc=bump` selects the raw-alloc bench-floor (`runtime/bump.c`, no free, leak-only) and is used by `bench/run.sh` to measure RC overhead against the structurally cheapest allocator — it is not a production target. The **desugar** pass ([`ailang-core::desugar::desugar_module`](../../crates/ailang-core/src/desugar.rs)) runs before typecheck and codegen in every entry point of `ailang-check` and `ailang-codegen`. It is a pure AST → AST rewriter — currently only flattens nested constructor patterns, but is the chosen home for any future surface-smoothing rewrites that should not bloat the core AST or the backends. **Critical invariant:** `CheckedModule.symbols` in the `check` entry point continues to hash from the *original* on-disk module, not the desugared one, so `ail diff` and `ail manifest` report identities that match the canonical JSON the user is editing. The **lift_letrecs** pass (`ailang-check::lift_letrecs`) runs **after** typecheck and **before** codegen, but only on the `build` / `run` paths — the `check` subcommand stops at typecheck and never sees a lifted module. It eliminates every `Term::LetRec` that the desugar pass left in place (the case where at least one capture is `Term::Let`-bound, so its type is only knowable after inference). The output is a module with synthetic `$lr_N` top-level fns appended, ready for codegen. Synthetic FnDefs added by this pass do **not** appear in `CheckedModule.symbols` — same invariant as the desugar-pass lifts. ## CLI ``` ail check — loads, validates, typechecks ail manifest — table: name :: type !effects [hash] ail describe — detail of a definition (form-A body) ail render — JSON-AST → form-A text (exact inverse of `parse`) ail parse — form-A text → canonical JSON-AST ail prose — JSON-AST → form-B (lossy human prose, no parser) ail merge-prose — compose the LLM-mediator prompt for the prose round-trip ail deps — list cross-module references ail diff — content-addressed def-level diff ail workspace — list all modules transitively reachable from entry (`--json` for machine output; `manifest --workspace` and `diff --workspace` extend single-module subcommands to workspaces) ail builtins — list built-in fns and effect ops ail emit-ir [--emit=staticlib] — writes .ll (staticlib: a main-free kernel's IR, no @main) ail build [--emit=staticlib] — full pipeline → binary (staticlib: lib.a + libailang_rt.a) ail run — build + execute (tempdir), passthrough exit code ``` The text projections the CLI moves between are documented in [authoring surface](../contracts/0001-authoring-surface.md) (Form-A, round-trippable) and [prose projection](0006-prose-projection.md) (Form-B, lossy, no parser); `ail build --emit=staticlib` produces the layout fixed in [embedding ABI](../contracts/0003-embedding-abi.md) plus [frozen value layout](../contracts/0006-frozen-value-layout.md).