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).
This commit is contained in:
@@ -6,7 +6,26 @@ 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.0** — `kw_asm` keyword (first compiler code). Added the `kw_asm` `Token.Tag`
|
||||
**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.0** — `kw_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
|
||||
@@ -31,21 +50,33 @@ guards fire: corrupting the `.ir` → IR mismatch; deleting it → the require-f
|
||||
`src/corpus_run.test.zig`, `examples/1639-*`.
|
||||
|
||||
## Current state
|
||||
Phase 0 complete (corpus target-gating + `.build` JSON). Phase A underway: `asm`
|
||||
now lexes as `kw_asm` (A.0). No parsing/AST yet — `asm` in source would reach
|
||||
`parsePrimary` and fall through to the existing "unexpected token" error until
|
||||
A.1. Phase B–E feasibility already confirmed against the live tree
|
||||
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 0137** — `sx 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 B–E 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
|
||||
**A.1** (xfail) — parse `asm { … }` → `AsmExpr` / `AsmOperand` in `parsePrimary`
|
||||
(`src/parser.zig`); add the `asm_expr` arm to `Node.Data` + the `AsmExpr` /
|
||||
`AsmOperand` structs in `src/ast.zig` (per design §II.3); lowering still
|
||||
`bailDetail("inline asm codegen unimplemented")` in `src/ir/interp.zig` (or the
|
||||
lower dispatch). Pin a parse-shape snapshot (`sx ir` or AST). The unimplemented
|
||||
bail must be loud + named. See `PLAN-ASM.md` Phase A (A.1) + design §II.3–II.4.
|
||||
**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.
|
||||
@@ -61,6 +92,12 @@ bail must be loud + named. See `PLAN-ASM.md` Phase A (A.1) + design §II.3–II.
|
||||
- (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
|
||||
None yet.
|
||||
- **0137** — `sx 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.
|
||||
|
||||
Reference in New Issue
Block a user