# 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 (elaborate_workspace → MirWorkspace; ailang-check::lower_to_mir)
            ├─ emit LLVM IR (.ll)  (ailang-codegen::lower_workspace consumes &MirWorkspace)
            └─ 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)
```

MIR ([`ailang-mir`](../../crates/ailang-mir/src/lib.rs)) is the single
typed artefact crossing the check→codegen boundary: `lower_to_mir`
re-enters check's canonical synth on the post-mono AST and attaches
every fact codegen needs (node `ty`, resolved `Callee`, `Mode` +
`consume`, `StrRep`), so codegen consumes `&MirWorkspace` and
re-derives nothing — no type synthesis, no callee-ladder walk, no
uniqueness inference. The invariant and its failure mode are
[check→codegen boundary](../contracts/0018-check-codegen-boundary.md).

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 —
it flattens nested constructor patterns and alpha-renames any binder
whose name shadows an enclosing binding to a fresh `<name>$<n>` (so the
uniqueness side-table's `(def_name, binder_name)` key is injective per
fn), and 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 `<hint>$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     <module.ail.json>          — loads, validates, typechecks
ail manifest  <module.ail.json>          — table: name :: type !effects [hash]
ail describe  <module> <name>            — detail of a definition (form-A body)
ail render    <module.ail.json>          — JSON-AST  → form-A text  (exact inverse of `parse`)
ail parse     <module.ail>              — form-A text → canonical JSON-AST
ail prose     <module.ail.json>          — JSON-AST  → form-B (lossy human prose, no parser)
ail merge-prose <m.ail.json> <m.prose.txt>
                                         — compose the LLM-mediator prompt for the prose round-trip
ail deps      <module.ail.json>          — list cross-module references
ail diff      <a.ail.json> <b.ail.json>  — content-addressed def-level diff
ail workspace <entry.ail.json>           — 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   <module> [--emit=staticlib]  — writes .ll (staticlib: a main-free kernel's IR, no @main)
ail build     <module> [--emit=staticlib]  — full pipeline → binary (staticlib: lib<entry>.a + libailang_rt.a)
ail run       <module>                   — 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).
