Files
sx/current/CHECKPOINT-ASM.md
agra f8e029d719 feat(asm): Phase A.1 — parse asm { … } into AsmExpr; loud lowering bail
`asm volatile? { "tmpl", [name]? "constraint" (-> Type | = expr), …,
clobbers(.…) }` now parses into a flat-operand AsmExpr/AsmOperand (ast.zig +
parser.zig parseAsmExpr, dispatched from parsePrimary on .kw_asm). `volatile`
and `clobbers` are recognized contextually (not reserved). `-> @place`
write-through is rejected with a clear "Phase 2" parse error.

Codegen is not implemented yet (IR op + LLVM emit are Phases C–E), so lowering
bails LOUD + named via an explicit .asm_expr arm in lower/expr.zig (not the
generic unknown_expr else) — emitPlaceholder makes hasErrors() abort the build
on the message.

The new asm_expr tag forced (and got) arms in three exhaustive Node.Data
switches: sema.zig analyzeNode + findNodeAtOffset, semantic_diagnostics.zig
checkBindingNames — each recurses into template + operand payloads.

Design: adopted the operand auto-naming rule (design §II.5) — name auto-derived
from a {reg} pin, explicit [name] only when it differs or for register-class
operands, echo form rejected. Typing-stage rule; parser stores name: ?[]const u8.

Locked with examples/1640-platform-asm-parse.sx (multi-output divmod: named
operands, register pins, clobbers — parses then bails, called from main).

Also files issue 0137 (pre-existing, orthogonal: `sx run` with no `main`
segfaults via an unguarded JIT entry lookup in target.zig — not an asm bug).

zig build test green (648 corpus, 445 unit).
2026-06-15 20:21:25 +03:00

6.6 KiB
Raw Blame History

sx Inline Assembly — Checkpoint (ASM stream)

Companion to current/PLAN-ASM.md; design in docs/inline-asm-design.md. Update after every commit, one step at a time per the cadence rule (no commit may both add a test and make it pass).

Last completed step

A.1 — parse asm { … } + loud lowering bail (folded A.1+A.2 into one honest lock commit, since the loud bail IS current correct behavior — cadence option (a)). Added AsmExpr/AsmOperand to src/ast.zig + the asm_expr Node.Data arm; parseAsmExpr in src/parser.zig (parsePrimary .kw_asm dispatch) — parses the template, flat operand list ([name]? "constraint" -> Type value output / = expr input), and clobbers(.…); volatile/clobbers recognized contextually via isContextualWord. The new asm_expr tag forced (and got) arms in three exhaustive Node.Data switches: src/sema.zig analyzeNode + findNodeAtOffset, src/ir/semantic_diagnostics.zig checkBindingNames (all recurse into template + operand payloads). Lowering bails LOUD + named in src/ir/lower/expr.zig ("inline assembly codegen is not yet implemented…") via an explicit .asm_expr arm (not the generic unknown_expr else) returning emitPlaceholder. -> @place write-through is rejected with a clear "Phase 2" parse error. Locked with examples/1640-platform-asm-parse.sx (multi-output divmod, named operands, register pins, clobbers — parses then bails; called from main). zig build test green (648 corpus, 0 failed; 445 unit). Files: src/ast.zig, src/parser.zig, src/sema.zig, src/ir/semantic_diagnostics.zig, src/ir/lower/expr.zig, examples/1640-*.

Prior: A.0kw_asm keyword (first compiler code). Added the kw_asm Token.Tag variant + .{ "asm", .kw_asm } keyword-map entry in src/token.zig; volatile / clobbers deliberately stay OUT of the global table (contextual). New exhaustive Tag switch in src/lsp/server.zig classifyToken flagged the missing arm (the intended coverage tripwire) — added .kw_asm to the keyword group. Lock test in new src/lexer.test.zig (asmkw_asm, volatile/clobbersidentifier), wired into the src/root.zig barrel as lexer_tests. zig build test green (648 corpus, 0 failed; 445 unit, 0 failed — +1). Files: src/token.zig, src/lexer.test.zig, src/root.zig, src/lsp/server.zig.

Prior: 0.2 — CLAUDE.md docs for <name>.build; Phase 0 COMPLETE. 0.1 — corpus runner ir-only branch for cross-target examples. Replaced 0.0's loud placeholder bail: when cfg.target doesn't match the host (ir_only), sweepRoot skips run/build/exec and verifies via sx ir --target only — asserting .exit (ir cmd) + .ir (normalized stdout) + .stderr, never .stdout (write skipped in update mode, assertion skipped in verify mode). An .ir snapshot is required in ir-only mode — its absence is a loud failure ("needs an .ir snapshot for ir-only mode"). Locked with examples/1639-platform-target-cross.sx (asm-free main :: () -> i64 { return 0; }), .build { "target": "x86_64-linux" }, + checked-in .ir. Verified both guards fire: corrupting the .ir → IR mismatch; deleting it → the require-failure. zig build test green (647 corpus, 0 failed; 444 unit). Files: src/corpus_run.test.zig, examples/1639-*.

Current state

Phase A underway: asm { … } lexes (A.0) and parses into AsmExpr (A.1); lowering bails LOUD + named (no IR op / emit yet). Result-type derivation, the operand auto-naming rule, and the validation checklist are Phase B (not yet implemented — any asm reaching lowering errors out). The adopted operand auto-naming rule (design §II.5, decided this session): name auto-derived from a {reg} pin; explicit [name] only when it differs or for register-class (=r) operands; echo form [eax] "={eax}" rejected. Parser stores name: ?[]const u8; the rule is a Phase-B (typing) concern, so the parser needs no change for it.

Known orthogonal bug: issue 0137sx run on a program with no main segfaults (src/target.zig:256-273, unguarded JIT entry lookup). Pre-existing, asm-independent; does NOT block the ASM stream (every example has a main).

Phase BE feasibility already confirmed against the live tree (LLVMGetInlineAsm / LLVMBuildCall2 / LLVMAppendModuleInlineAsm in LLVM@19 Core.h; ERR-stream extractvalue→tuple in emit_llvm.zig:726-927; lib-less extern, 60 sites; --target a global CLI flag).

Next step

B.0/B.1 (Phase B — sema/typing) — derive the asm result type from the out_value operands (0→void + require volatile; 1→T; N→tuple, named via the §II.5 auto-naming rule), in the expression typer (src/ir/expr_typer.zig / inferExprType). Implement the validation checklist (no-output⇒volatile; layout; comptime-string template; coerce comptime int→i64/float→f64) + the auto-naming / echo-rejection diagnostics. On failure return the .unresolved sentinel, never a silent default. Pin error-message examples. See PLAN-ASM.md Phase B + design §II.5. (Lowering keeps bailing until Phase C adds the IR op.)

Log

  • (init) Plan + design doc written; ASM stream opened.
  • (0.0) Corpus runner target-gating: <name>.build JSON config (replaces .aot marker), --target threading, hostMatchesTarget execute-gate, loud cross-target placeholder bail. Migrated 1226/1227 .aot.build; locked with 1638 fixture + unit tests. zig build test green.
  • (0.1) ir-only branch: cross-target examples verify via sx ir --target only (exit+ir+stderr, no stdout; .ir required). Locked with 1639 fixture; verified corrupt-.ir → mismatch and missing-.ir → loud failure. zig build test green.
  • (0.2) docs: CLAUDE.md documents <name>.build JSON sidecar (aot + target + ir-only gating), replacing stale .aot marker prose. Phase 0 COMPLETE.
  • (A.0) kw_asm keyword in token.zig (+ map entry); LSP classifyToken switch coverage; lock test in new lexer.test.zig (wired via root.zig). volatile / clobbers stay contextual identifiers. zig build test green (445 unit, +1).
  • (A.1) parse asm { … }AsmExpr + loud lowering bail; asm_expr arms in 3 exhaustive Node.Data switches; -> @place rejected (Phase 2). Adopted operand auto-naming rule (design §II.5). Locked with 1640 fixture. Filed orthogonal issue 0137 (no-main JIT segfault). zig build test green (648 corpus, 445 unit).

Known issues

  • 0137sx run on a program with no main segfaults (unguarded JIT entry lookup, src/target.zig:256-273). Pre-existing, asm-independent. Filed issues/0137-jit-run-no-main-segfault.md. Does not block A.1.