Working state — a note taken while the work happens, not a specification. The system as it is meant to be is in Architecture.

The compiler, after the spine — the opportunity map

Written 2026-07-27 for planning the compiler pass that begins when the finalization arc (archived at plans/archive/finalization-arc.md; its remaining opens are P-0 of plans/archive/one-binary.md) lands. What follows is the opportunity map: where the brainstorm starts, deliberately unruled.

The settled half of this document — the artifact chain from source to cart, what each format is, what is portable and what is target-final, and where pressing happens — is now docs, where target design belongs: docs/architecture/pipeline.md, with the byte-level detail in docs/spec/artifact-formats.md. Read that first; the rest of this file assumes it.


The opportunity map (unruled; the brainstorm starts here)

Known defects first (bisected, oracle-equipped, waiting)

  1. The 255-slot module limit — RULED 2026-08-02: extended operand encoding (extension word), phase P1 of plans/archive/one-binary.md, which supersedes this item and absorbs the mcode-level opportunities below (the ruled arc: binary-as-floor, forge-orchestrated fusion, Pit lowering, the mcode linker). Still the first stone because it is a correctness cliff, not a perf knob — and now also because inlining is capped behind it.
  2. hpack huffman_encode miscompile — context-sensitive (same source correct in one realization, wrong in another), oracle huffman_encode("t") == 4f, streamline suspected, the differential fuzzer is blind to the class. The fuzzer needs a realization-context dimension.
  3. Record-literal provenance["record", …] bypasses position stamping, so record construction sites are invisible to the origin map. Small, model-fit.

mcode-level opportunities

  • The IR is verbose JSON and streamline is ~77% of compile time (measured July baseline; guards ~11% of final mcode; ~15.5 instrs/line). The representation itself is the first question: a binary or interned-array IR between passes could cut parse and copy costs without changing semantics. mcode-the-artifact stays JSON (portable, debuggable); mcode-the-in-memory-IR need not be the same thing.
  • Slot allocation as a real register allocator. Today slots are close to single-assignment temporaries; the 255 fix opens the door to actual lifetime-based allocation — fewer frame slots, fewer moves, smaller frames on 32-bit targets where frames are memory.
  • Guard elimination with the type facts we already carry. facts per function is durable and travels; streamline’s inference exists; the question is coverage — the measured 11% guard share is the budget. The July-13 register-lane experiments left three findings worth re-deriving in the pool world (their diffs target deleted code; the archived branches are gone — start fresh): self-TCO numeric phis staying raw, exact array results staying raw, and one negative result — fusing native nullable-decode guards was tried and rejected for decode overhead. A fourth, “consume exact types after inline splices,” survives as a portable patch in the 2026-08-01 cleanup archive, awaiting a gate. That gate is now python3 perf/ir_census.py gate plus make benchmake budget’s compiler half was retired 2026-08-06 and make budget is the shop budget only.
  • Shape-aware record ops. Shapes are now first-class stone rows the instruction set addresses directly. Literal records with known shapes can compile to slot stores with no hashing at all; the shaped→hashed degradation on key-add is already the semantic.
  • Site-ID-keyed PGO everywhere. Inline decisions, branch layout, guard ordering — all can consume profiles that survive recompilation, because the identity is the mcode site, not the output position.

The linker sees N units together with their fragments — a whole-program view the per-unit compiler never had, at bytes level, in Pit, cheap to iterate on:

  • Cross-unit stone dedup is landed; cross-unit SHAPE dedup is not (each unit’s shapes stay contiguous today). Identical shapes across units are one row’s worth of win each, and shape identity is already a canonical hash.
  • Function dedup by stable hash. FUNCTION rows carry a stable function hash; byte-identical function bodies across units (generated code, small accessors) can collapse to one row with two ENTRIES — the linker already half-does this for shared children.
  • Same-image call finalization = the inlining seam. When the linker turns an import edge into a direct index, it knows both bodies. Cross-unit inlining at link time — with the origin map keeping blame correct via inline_chain (already in the origin spec) — is the classic LTO win, and here the “LTO” input is structured IR with facts, not opaque objects.
  • PGO-driven layout. Placement groups and the link plan exist for exactly this: hot functions contiguous, cold sections separate, per-bank duplication on ROM targets chosen by profile rather than by hand. The origin map normalizes profiles back to mcode identity, so one desktop profiling session can drive a console cart’s layout.
  • Claims-as-liveness stripping. The manifest knows every unit’s claims and the closure’s aggregate; engine_lite’s diet (D3) is the first customer, but the same mechanism is a general dead-code story: unclaimed = absent, no separate stripping pass, and the executable manifest proves what was kept and why.
  • Link plans as the granularity lever. One pool per executable (ship), one per module (dev, hot-reload-friendly), or profile-derived groupings — the pool boundary is a performance decision the link plan owns, and nothing else changes.

The questions worth a whiteboard

  • What does the in-memory IR look like if streamline is rebuilt against it? (The 77% is mostly traversal and copying, not analysis.)
  • How much of guard/type inference moves from per-unit streamline to link-time, where the whole graph is visible?
  • Does the VM want any new opcodes once pools are universal (e.g., shape-indexed record construction, direct cross-image calls), and what does each cost in the dispatch loop? ABI bumps are one reseed now — the historical cost argument is gone.
  • What is the minimal PGO counter set that pays for itself on the Playdate-class targets, given counters are per-actor and the profile artifact is portable?
  • Where does native (the deferred AOT lane) re-enter — and does the fragment/linker split give it a cleaner seam than qbe had? (Streamlined mcode was already the IL; fragments may be the better handoff.)

What the spine guarantees this work

Stable ground, specifically: the unit/fragment/pool/manifest formats frozen and gated; one constant-ownership story (no intern table to reason around); actors executing from mapped stone (so measurements measure the compiler, not the loader); the freshness gate (an edited compiler NEVER serves stale output — the historical plague, now falsifiable); the origin map + debugger (see what any line became); test all honest; and every arm one --set away. The compiler pass starts with oracles, gates, and blame — which is the whole reason it waited.

Source: plans/compiler-vision.md