# sx Inline Assembly — Implementation Plan (ASM stream) **Design source of truth:** [docs/inline-asm-design.md](../docs/inline-asm-design.md). This plan turns that doc's §II.7 stage-map + §II.8 phasing into ordered, commit-sized, testable steps. Read the design doc first — this file is the *how/when*, not the *what/why*. **Surface (decided):** `asm volatile { "template", "=r" -> T, "r" = expr, clobbers(.cc, .memory) }` — brace block; `->` output / `=` input; `clobbers(.…)` dot-name list; N `-> Type` outputs return a tuple; templates are pure AT&T (via LLVM). **Feasibility (confirmed):** sx links LLVM@19; `src/llvm_api.zig` `@cImport`s `llvm-c/Core.h`, so `llvm_api.c.*` already exposes `LLVMGetInlineAsm` (9-arg), `LLVMInlineAsmDialectATT`, `LLVMBuildCall2`, `LLVMAppendModuleInlineAsm`. No shim. **Relationship to other streams:** - Phases A–E (the inline-asm *expression*) are independent of EXTERN-EXPORT. - Phase F (global asm) consumes `extern`/`export` to import/expose asm symbols — do it **after** `PLAN-EXTERN-EXPORT.md` Phase 2. ## Cadence (IMPASSIBLE) No commit may both add a test AND make it pass. Each feature step is either a behavior-locking PASSING test, or an xfail test the *next* commit turns green. Arch-pinned tests live in `examples/16xx-platform-asm-*` (must declare `target=`). Never regenerate snapshots while red. ## Phase A — keyword + AST + parser (parses; no codegen) | Step | Commit | What | Files | |---|---|---|---| | A.0 | lock | add `kw_asm` keyword + map entry; unit lex test `asm → kw_asm` | `src/token.zig`, `src/lexer.zig` + `.test.zig` | | A.1 | xfail | parse `asm { … }` → `AsmExpr`/`AsmOperand` in `parsePrimary`; pin an AST/`sx ir` parse snapshot; lowering still `bailDetail("inline asm codegen unimplemented")` | `src/ast.zig` (:85 union arm, :721 structs), `src/parser.zig` (parsePrimary), `src/ir/interp.zig` | | A.2 | green | parse-shape snapshot lands green; the unimplemented bail is loud + named | — | ## Phase B — sema / typing | Step | Commit | What | Files | |---|---|---|---| | B.0 | xfail | result-type rule (0→`void` / 1→`T` / N→named-or-positional tuple) + checklist (no-output⇒`volatile`, layout, comptime-string template) — pin error messages | `src/ir/expr_typer.zig` | | B.1 | green | typing + diagnostics implemented; `.unresolved` sentinel on failure (no silent default) | `src/ir/expr_typer.zig`, `src/ir/semantic_diagnostics.zig` | ## Phase C — IR op + lowering | Step | Commit | What | Files | |---|---|---|---| | C.0 | lock | add `inline_asm: InlineAsm` to `Op` + `AsmOperand` (role/name/constraint/operand) + interp `bailDetail` arm; unit tests for the IR shape | `src/ir/inst.zig` (:80), `src/ir/interp.zig` | | C.1 | xfail→green | `lowerAsmExpr` in `lowerExpr` dispatch — interns template/constraints/clobber-names, lowers input `Ref`s, sets result `TypeId` | `src/ir/lower/expr.zig` | ## Phase D — LLVM emit (single value-output; the core) | Step | Commit | What | Files | |---|---|---|---| | D.0 | xfail | `examples/16xx-platform-asm-syscall-write.sx` + `…-register-read.sx` + `…-no-output-volatile.sx` + `…-missing-volatile.sx` (expected compile error) — all red | examples + `expected/` markers | | D.1 | green | `emitInlineAsm`: **port `FuncGen.airAssembly`** — constraint-string assembler (outputs `=`/`+`, inputs, `clobbers(.name)`→`~{name}`), `%[name]`→`${N}` / `%%` / `%=` template rewriter, `LLVMGetInlineAsm`+`LLVMBuildCall2`, `sideeffect=volatile`, AT&T dialect | `src/ir/emit_llvm.zig` (emitInst dispatch + handler) | | D.2 | green | lock the template-rewrite + constraint string via an `expected/*.ir` snapshot on `…-template-subst.sx` | examples | **Phase D verification:** `zig build test`; the syscall example runs on `x86_64-linux`; IR snapshot matches the design doc's worked `sys_write` lowering. ## Phase E — multi-return tuples + `clobbers(.…)` | Step | Commit | What | Files | |---|---|---|---| | E.0 | xfail | `…-asm-multi-return.sx` (`divmod`→`(quot,rem)`, `cpuid`→4-tuple) red | examples | | E.1 | green | N `out_value` → LLVM struct return + `extractvalue i` → sx tuple (named when operands named); `clobbers(.name)` dot-name lowering finalized | `src/ir/emit_llvm.zig`, `src/ir/lower/expr.zig` | ## Phase F — global asm (needs EXTERN-EXPORT Phase 2) | Step | Commit | What | Files | |---|---|---|---| | F.0 | xfail | top-level `asm { … }` decl parsed (reject operands/`volatile`); `…-asm-global.sx` (defines a symbol, imported via `extern`) red | `src/parser.zig`, `src/ast.zig` | | F.1 | green | lower `asm_global` → `c.LLVMAppendModuleInlineAsm`; comptime-call guard (dlsym-miss is loud); blocks concatenate in source order | `src/ir/lower/decl.zig`, `src/ir/emit_llvm.zig`, `src/ir/interp.zig` | ## Phase G — later (own steps when scheduled) `-> @place` write-through + read-write (`"+r" -> @place`) + indirect-memory (`"=*m"`) outputs · `%=` unique-id · output-to-const rejection · Intel-dialect opt-in · naked functions (`callconv(.naked)`, coordinate with EXTERN-EXPORT). ## Open decisions (design doc §II.10) Dialect (AT&T-only v1, recommended) · `volatile` contextual-keyword (recommended) · brace separator comma (recommended) · `clobbers(.name)` dot-name sugar now → checked per-arch `Clobber` enum later (Phase 4 of the design doc). ## End-to-end verification (per phase) `zig build && zig build test`; for arch-pinned examples confirm they run on a matching host or assert on `sx ir`/`.s` snapshots. After intentional output changes only: `zig build test -Dupdate-goldens`, then review the diff.