feat(asm): Phase C.0 — add inline_asm IR op (lock, no behavior change)
Adds the `inline_asm: InlineAsm` opcode to the IR Op union (inst.zig): interned template + operand list (role/name/constraint/operand) + interned clobber names + has_side_effects; the result rides on Inst.ty (void / scalar / tuple). The new variant forces coverage in the exhaustive Op switches: - interp.zig: loud bailDetail — inline asm is never comptime-evaluable. - print.zig: an IR-dump arm. - emit_llvm.zig: a @panic TRIPWIRE — emit lands in Phase D, and until then lowerAsmExpr still bails, so no inline_asm op is ever created. Reaching emit would mean lowering switched over before emit was ready; crash loudly rather than miscompile. No behavior change: lowering still bails, the op is constructed only in the new `inline_asm op shape` unit test (inst.test.zig). zig build test green (652 corpus, 446 unit).
This commit is contained in:
@@ -6,7 +6,21 @@ commit, one step at a time per the cadence rule (no commit may both add a test
|
||||
and make it pass).
|
||||
|
||||
## Last completed step
|
||||
**B.1** — operand-name validation (design §II.5 auto-naming rule). Extended
|
||||
**C.0** — IR op `inline_asm` (lock; no behavior change). Added `inline_asm:
|
||||
InlineAsm` to the IR `Op` union + the `InlineAsm` struct (`template: StringId`,
|
||||
`operands: []const AsmOperand` {role/name/constraint/operand}, `clobbers:
|
||||
[]const StringId`, `has_side_effects`) in `src/ir/inst.zig` — all strings
|
||||
interned, operands in source order, result on `Inst.ty`. The new variant forced
|
||||
(and got) arms in two exhaustive `Op` switches: `src/ir/interp.zig` (loud
|
||||
`bailDetail` — inline asm is never comptime-evaluable) and `src/ir/print.zig`
|
||||
(IR dump). `src/ir/emit_llvm.zig` gets a `@panic` **tripwire** — emit lands in
|
||||
Phase D, and until then `lowerAsmExpr` still bails so no `inline_asm` op is ever
|
||||
created (reaching emit would be a lowering-switched-over-too-early bug). Unit
|
||||
test `inline_asm op shape` in `src/ir/inst.test.zig`. `zig build test` green
|
||||
(652 corpus, 446 unit). Files: `src/ir/inst.zig`, `src/ir/interp.zig`,
|
||||
`src/ir/print.zig`, `src/ir/emit_llvm.zig`, `src/ir/inst.test.zig`.
|
||||
|
||||
Prior: **B.1** — operand-name validation (design §II.5 auto-naming rule). Extended
|
||||
`lowerAsmExpr` with a `pinnedRegister(constraint)` helper (`"={eax}"`→`eax`,
|
||||
`"+{rax}"`→`rax`, `"=r"`→null) and two checks: (1) **reject the echo form**
|
||||
`[eax] "={eax}"` — a label identical to its own pinned register is redundant
|
||||
@@ -93,19 +107,21 @@ Phase B–E feasibility already confirmed against the live tree
|
||||
`extern`, 60 sites; `--target` a global CLI flag).
|
||||
|
||||
## Next step
|
||||
**C.0** (Phase C — IR op) — add `inline_asm: InlineAsm` to `Op` (`src/ir/inst.zig`,
|
||||
next to `objc_msg_send`) + the `AsmOperand` IR struct (role/name/constraint/operand
|
||||
as `StringId`/`Ref`), and an interp `bailDetail` arm (`src/ir/interp.zig`) — inline
|
||||
asm can never be comptime-evaluated. Unit-test the IR shape. This is a `lock`
|
||||
commit (no behavior change yet — `lowerAsmExpr` keeps bailing). Then C.1 wires
|
||||
`lowerAsmExpr` to actually intern strings + lower input `Ref`s + build the op and
|
||||
compute the result `TypeId`, at which point **result-type derivation becomes
|
||||
observable** and the auto-naming/tuple typing (`expr_typer.zig`, deferred from B)
|
||||
can be tested end-to-end. See `PLAN-ASM.md` Phase C + design §II.6.
|
||||
|
||||
Remaining deferred validation (do alongside C, once template scanning is worth
|
||||
it): `%[name]` references in the template that name no operand. Needs effective-
|
||||
name resolution (explicit `[name]` ∪ auto-derived register names).
|
||||
**C.1 + D together** (must land as one green step) — wire `lowerAsmExpr` to BUILD
|
||||
the `inline_asm` op (intern template + constraints + clobber names; resolve each
|
||||
operand's effective name via the §II.5 auto-naming rule; lower input `Ref`s;
|
||||
compute the result `TypeId` from the `out_value` operands — 0→void, 1→T, N→tuple,
|
||||
named) AND implement `emitInlineAsm` in `src/ir/emit_llvm.zig` (replacing the
|
||||
`@panic` tripwire) — the port of Zig's `airAssembly`: assemble the LLVM constraint
|
||||
string (outputs `=`/`+`, inputs, `clobbers`→`~{name}`), rewrite `%[name]`→`${N}` /
|
||||
`%%` / `%=`, `LLVMGetInlineAsm` + `LLVMBuildCall2`, AT&T dialect. They land
|
||||
together because the moment lowering stops bailing, emit is reached — a half-step
|
||||
would hit the tripwire. First target: the single-value-output syscall on
|
||||
`x86_64-linux` (ir-only via a `.build` `{ "target": "x86_64-linux" }` + `.ir`
|
||||
snapshot, since the host is aarch64). Result-type derivation for `expr_typer.zig`
|
||||
(`inferType` `.asm_expr` arm) also lands here — now observable. Then E (multi-
|
||||
return tuples) + remaining validation (`%[name]` references a real operand). See
|
||||
`PLAN-ASM.md` Phases C–E + design §II.6.
|
||||
|
||||
## Log
|
||||
- (init) Plan + design doc written; ASM stream opened.
|
||||
@@ -132,6 +148,9 @@ name resolution (explicit `[name]` ∪ auto-derived register names).
|
||||
- (B.1) operand-name validation: `pinnedRegister` helper + reject echo form
|
||||
(`[eax] "={eax}"`) and duplicate names. Locked with 1643 + 1644. `zig build
|
||||
test` green (652 corpus, 445 unit).
|
||||
- (C.0) IR op `inline_asm: InlineAsm` + interp `bailDetail` + print arm + emit
|
||||
`@panic` tripwire (Phase D). No behavior change (lowering still bails). Unit
|
||||
test `inline_asm op shape`. `zig build test` green (652 corpus, 446 unit).
|
||||
|
||||
## Known issues
|
||||
- **0137** — `sx run` on a program with no `main` segfaults (unguarded JIT entry
|
||||
|
||||
Reference in New Issue
Block a user