feat(asm): Phase E — multi-output asm returns tuples
Replaces the N>1 "Phase E" bail with a shared asmResultType helper (lowering +
inferType) that derives the result type from the out_value operands: 0→void,
1→T, N→a named tuple (fields named via the §II.5 effective-name rule).
Key realization: toLLVMType(tuple) already produces a literal struct {T1,…,Tn} —
exactly what LLVM's multi-output inline asm returns — so emit needs NO change.
Building the op with a tuple result type makes the asm call return the struct,
which IS sx's tuple value (destructured by the normal tuple_get path).
inferType's .asm_expr arm now also delegates to asmResultType (single owner), so
`return asm`, `x := asm`, and `q, r := asm` all agree on the type.
Verified end-to-end on aarch64: split(0x1234)→(lo=52,hi=18), a udiv/msub
divmod→(3,2). IR: `call { i64, i64 } asm "divq ${4}",
"={rax},={rdx},{rax},{rdx},r,~{cc}"(…)` → extractvalue → tuple.
1640 → the x86_64 multi-output IR lock (ir-only); 1647 → a multi-output example
that runs on aarch64.
zig build test green (655 corpus, 446 unit).
This commit is contained in:
@@ -6,7 +6,25 @@ commit, one step at a time per the cadence rule (no commit may both add a test
|
|||||||
and make it pass).
|
and make it pass).
|
||||||
|
|
||||||
## Last completed step
|
## Last completed step
|
||||||
**C.1 + D** — inline asm CODEGEN (lowering builds the op + LLVM emit). **Inline
|
**E** — multi-output tuples. **Inline asm now returns tuples.** Replaced the
|
||||||
|
N>1 bail with a shared `asmResultType` helper (`src/ir/lower/expr.zig`, mixed
|
||||||
|
into `Lowering`) that derives the result type from the `out_value` operands
|
||||||
|
(0→void, 1→T, N→named tuple, named via the §II.5 effective-name rule). The key
|
||||||
|
realization: `toLLVMType(tuple)` already produces a literal struct `{T1,…,Tn}` —
|
||||||
|
exactly LLVM's multi-output asm return — so **emit needed NO change**; building
|
||||||
|
the op with a tuple result type makes the asm call return the struct, which IS
|
||||||
|
sx's tuple value (destructured by the normal `tuple_get` path). `inferType`'s
|
||||||
|
`.asm_expr` arm now also delegates to `asmResultType` (single owner), so
|
||||||
|
`return asm`, `x := asm`, and `q, r := asm` all agree on the type. Verified
|
||||||
|
end-to-end on aarch64: `split(0x1234)`→`(lo=52, hi=18)`, a udiv/msub divmod→
|
||||||
|
`(3, 2)`. IR is textbook: `call { i64, i64 } asm "divq ${4}",
|
||||||
|
"={rax},={rdx},{rax},{rdx},r,~{cc}"(…)` → extractvalue → tuple. Converted 1640 to
|
||||||
|
the x86_64 multi-output IR lock (ir-only) + added `1647-platform-asm-aarch64-multi`
|
||||||
|
(runs on aarch64). `zig build test` green (655 corpus, 446 unit). Files:
|
||||||
|
`src/ir/lower/expr.zig`, `src/ir/lower.zig`, `src/ir/expr_typer.zig`,
|
||||||
|
`examples/164{0,7}-*`.
|
||||||
|
|
||||||
|
Prior: **C.1 + D** — inline asm CODEGEN (lowering builds the op + LLVM emit). **Inline
|
||||||
assembly now runs end-to-end.** `lowerAsmExpr` (`src/ir/lower/expr.zig`) stops
|
assembly now runs end-to-end.** `lowerAsmExpr` (`src/ir/lower/expr.zig`) stops
|
||||||
bailing: it resolves each operand's effective name (§II.5 auto-naming), interns
|
bailing: it resolves each operand's effective name (§II.5 auto-naming), interns
|
||||||
template/constraints/clobbers, lowers input `Ref`s, derives the result `TypeId`
|
template/constraints/clobbers, lowers input `Ref`s, derives the result `TypeId`
|
||||||
@@ -112,14 +130,15 @@ guards fire: corrupting the `.ir` → IR mismatch; deleting it → the require-f
|
|||||||
`src/corpus_run.test.zig`, `examples/1639-*`.
|
`src/corpus_run.test.zig`, `examples/1639-*`.
|
||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
**Inline assembly works end-to-end for 0/1 value outputs.** Pipeline complete:
|
**Inline assembly works end-to-end: 0, 1, and N value outputs (tuples).** Full
|
||||||
lex (A.0) → parse (A.1) → validate (B.0/B.1 + the `%[name]` check) → IR op (C.0)
|
pipeline: lex (A.0) → parse (A.1) → validate (B.0/B.1 + `%[name]` check) → IR op
|
||||||
→ lower-builds-op + LLVM emit + JIT asm-parser init (C.1/D). Single-value-output
|
(C.0) → lower-builds-op + LLVM emit + JIT asm-parser init (C.1/D) → multi-output
|
||||||
and no-output `volatile` asm assemble and execute on the host JIT; the auto-naming
|
tuples (E). Register-class + register-pinned operands, inputs, clobbers, `#string`
|
||||||
rule (§II.5) is live (effective name = explicit `[name]` else `{reg}`). **Phase E
|
multi-instruction templates, `%[name]`/`%%` rewriting, and the §II.5 auto-naming
|
||||||
(multi-output tuples) is the remaining feature gap** — N>1 value outputs bail with
|
rule all work and execute on the host JIT. **Remaining feature gaps:** `-> @place`
|
||||||
a named "Phase E" diagnostic (1640). `-> @place` write-through outputs are still
|
write-through / read-write / indirect-memory outputs (rejected at parse — Phase 2)
|
||||||
rejected at parse (Phase 2). Global asm (Phase F) not started.
|
and global `asm { … }` + `extern` call-into-asm (Phase F). `readme.md` has no
|
||||||
|
inline-asm section yet (docs-track-changes follow-up).
|
||||||
|
|
||||||
Known orthogonal bug: **issue 0137** — `sx run` on a program with no `main`
|
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,
|
segfaults (`src/target.zig:256-273`, unguarded JIT entry lookup). Pre-existing,
|
||||||
@@ -131,21 +150,21 @@ Phase E–F feasibility already confirmed against the live tree
|
|||||||
`extern`, 60 sites; `--target` a global CLI flag).
|
`extern`, 60 sites; `--target` a global CLI flag).
|
||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
**Phase E** (multi-output tuples) — replace the N>1 "Phase E" bail in
|
Two independent directions (pick either):
|
||||||
`lowerAsmExpr`: build a tuple `TypeId` from the `out_value` types (named via the
|
- **Phase F — global asm** (smaller; the plan calls it "Small"): top-level
|
||||||
effective-name rule), set it as the op result, and in `emitInlineAsm` make the
|
`asm { … }` decl (template only — reject operands/`volatile`) → lower to
|
||||||
LLVM return type an anonymous struct `{T1,…,Tn}`, then `extractvalue i` per
|
`c.LLVMAppendModuleInlineAsm`; the call-INTO-asm direction reuses the existing
|
||||||
`out_value` → assemble the sx tuple. Lock with `divmod`→`(quot,rem)` (reuse 1640's
|
lib-less `extern` (no new surface). Parser: recognize `asm {` at decl scope →
|
||||||
shape, now running) + `cpuid`→4-tuple, arch-pinned. See `PLAN-ASM.md` Phase E +
|
an `asm_global` decl. Plus the comptime-call guard (a global-asm symbol isn't
|
||||||
design §II.6 (multi-return). Also worth adding: the x86_64-linux syscall-write
|
in the JIT host — dlsym-miss must be loud). See `PLAN-ASM.md` Phase F.
|
||||||
example (ir-only on this host via `.build { "target": "x86_64-linux" }` + `.ir`)
|
- **Phase 2 — `-> @place` outputs** (write-through, read-write `"+r" -> @place`,
|
||||||
to lock the cross-target lowering, per the plan's D verification.
|
indirect-memory `"=*m"`): currently rejected at parse. Needs place-expr
|
||||||
|
lowering for the output target + the indirect-constraint handling, plus
|
||||||
|
output-to-`const` rejection.
|
||||||
|
|
||||||
Then Phase 2 (`-> @place` write-through / read-write / indirect-memory) and Phase
|
Also worth doing soon: the **x86_64 syscall-write** ir-only example (plan's D
|
||||||
F (global asm + `extern` call into asm symbols). Result-type derivation for the
|
verification) and a **readme.md** inline-asm section (docs-track-changes). And the
|
||||||
0/1 cases now lives in BOTH `lowerAsmExpr` (the op's `Inst.ty`) and
|
orthogonal **issue 0137** (no-`main` segfault) whenever.
|
||||||
`expr_typer.zig`'s `inferType` (for `:=`/value-position typing); Phase E extends
|
|
||||||
both to the tuple case.
|
|
||||||
|
|
||||||
## Log
|
## Log
|
||||||
- (init) Plan + design doc written; ASM stream opened.
|
- (init) Plan + design doc written; ASM stream opened.
|
||||||
@@ -181,6 +200,11 @@ both to the tuple case.
|
|||||||
`inferType` arm; `LLVMInitializeNativeAsmParser` for the JIT. **Inline asm runs
|
`inferType` arm; `LLVMInitializeNativeAsmParser` for the JIT. **Inline asm runs
|
||||||
end-to-end.** N>1 bails (Phase E). Locked with 1645 (aarch64 add, runs) + 1646
|
end-to-end.** N>1 bails (Phase E). Locked with 1645 (aarch64 add, runs) + 1646
|
||||||
(`:=` binding); updated 1640/1642. `zig build test` green (654 corpus, 446 unit).
|
(`:=` binding); updated 1640/1642. `zig build test` green (654 corpus, 446 unit).
|
||||||
|
- (E) multi-output tuples — `asmResultType` helper (0→void/1→T/N→named tuple),
|
||||||
|
shared by lowering + `inferType`. `toLLVMType(tuple)` == LLVM multi-output
|
||||||
|
struct, so emit unchanged; the asm struct return IS the sx tuple. Runs on
|
||||||
|
aarch64 (1647: `split`→`(lo,hi)`); 1640 → x86 multi-output IR lock (ir-only).
|
||||||
|
`zig build test` green (655 corpus, 446 unit).
|
||||||
|
|
||||||
## Known issues
|
## Known issues
|
||||||
- **0137** — `sx run` on a program with no `main` segfaults (unguarded JIT entry
|
- **0137** — `sx run` on a program with no `main` segfaults (unguarded JIT entry
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// ASM stream — `asm { … }` parses + validates the full rich shape: named value
|
// ASM stream Phase E — x86_64 multi-output asm: `divq` produces quotient in rax
|
||||||
// outputs (`[quot] "={rax}" -> u64`), register-pinned inputs, and a
|
// and remainder in rdx, returned as a `(quot, rem)` tuple. Two `={rax}`/`={rdx}`
|
||||||
// `clobbers(.…)` clause, all accepted. This is a MULTI-output (tuple-returning)
|
// value outputs ⇒ LLVM returns a `{ i64, i64 }` struct, which IS sx's tuple
|
||||||
// asm, which is deferred to Phase E — so lowering bails LOUD + named with the
|
// representation (so `q, r := …` destructures it directly). x86-pinned via
|
||||||
// specific "Phase E" diagnostic (single-output asm already runs; see 1645).
|
// `.build`: ir-only on a non-x86 host (the `.ir` snapshot locks the struct
|
||||||
// Called from `main` so lowering reaches the asm body (lazy lowering skips
|
// return + `%[name]` rewrite); runs natively on x86_64-linux. See 1647 for a
|
||||||
// uncalled functions).
|
// multi-output example that executes on aarch64.
|
||||||
divmod :: (n: u64, d: u64) -> (quot: u64, rem: u64) {
|
divmod :: (n: u64, d: u64) -> (quot: u64, rem: u64) {
|
||||||
return asm {
|
return asm {
|
||||||
"divq %[d]",
|
"divq %[d]",
|
||||||
|
|||||||
20
examples/1647-platform-asm-aarch64-multi.sx
Normal file
20
examples/1647-platform-asm-aarch64-multi.sx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// ASM stream Phase E — multi-output asm that RUNS end-to-end on aarch64. Splits
|
||||||
|
// a value into low/high bytes via two value outputs, returned + destructured as
|
||||||
|
// a `(lo, hi)` tuple. The two outputs become an LLVM `{ i64, i64 }` struct =
|
||||||
|
// sx's tuple. aarch64-pinned via `.build`: executes on a matching host (exit
|
||||||
|
// reflects lo+hi), ir-only elsewhere.
|
||||||
|
split :: (x: u64) -> (lo: u64, hi: u64) {
|
||||||
|
return asm {
|
||||||
|
#string ASM
|
||||||
|
and %[l], %[x], #0xff
|
||||||
|
lsr %[h], %[x], #8
|
||||||
|
ASM,
|
||||||
|
[l] "=r" -> u64,
|
||||||
|
[h] "=r" -> u64,
|
||||||
|
[x] "r" = x,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
main :: () -> i64 {
|
||||||
|
lo, hi := split(0x1234);
|
||||||
|
return xx (lo + hi); // 0x34 + 0x12 = 52 + 18 = 70
|
||||||
|
}
|
||||||
1
examples/expected/1640-platform-asm-parse.build
Normal file
1
examples/expected/1640-platform-asm-parse.build
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{ "target": "x86_64-linux" }
|
||||||
@@ -1 +1 @@
|
|||||||
1
|
0
|
||||||
|
|||||||
26
examples/expected/1640-platform-asm-parse.ir
Normal file
26
examples/expected/1640-platform-asm-parse.ir
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
define internal { i64, i64 } @divmod(i64 %0, i64 %1) #0 {
|
||||||
|
entry:
|
||||||
|
%alloca = alloca i64, align 8
|
||||||
|
store i64 %0, ptr %alloca, align 8
|
||||||
|
%allocaN = alloca i64, align 8
|
||||||
|
store i64 %1, ptr %allocaN, align 8
|
||||||
|
%load = load i64, ptr %alloca, align 8
|
||||||
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
|
%asm = call { i64, i64 } asm "divq ${4}", "={rax},={rdx},{rax},{rdx},r,~{cc}"(i64 %load, i64 0, i64 %loadN)
|
||||||
|
ret { i64, i64 } %asm
|
||||||
|
}
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
define i32 @main() #0 {
|
||||||
|
entry:
|
||||||
|
%call = call { i64, i64 } @divmod(i64 17, i64 5)
|
||||||
|
%tg = extractvalue { i64, i64 } %call, 0
|
||||||
|
%alloca = alloca i64, align 8
|
||||||
|
store i64 %tg, ptr %alloca, align 8
|
||||||
|
%tgN = extractvalue { i64, i64 } %call, 1
|
||||||
|
%allocaN = alloca i64, align 8
|
||||||
|
store i64 %tgN, ptr %allocaN, align 8
|
||||||
|
ret i32 0
|
||||||
|
}
|
||||||
@@ -1,17 +1 @@
|
|||||||
error: multi-output (tuple-returning) inline assembly is not yet implemented (ASM stream Phase E)
|
|
||||||
--> examples/1640-platform-asm-parse.sx:9:12
|
|
||||||
|
|
|
||||||
9 | return asm {
|
|
||||||
| ^^^^^
|
|
||||||
10 | "divq %[d]",
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^
|
|
||||||
11 | [quot] "={rax}" -> u64,
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
12 | [rem] "={rdx}" -> u64,
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
13 | "{rax}" = n, "{rdx}" = 0, [d] "r" = d,
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
14 | clobbers(.cc),
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
15 | };
|
|
||||||
| ^^^^^
|
|
||||||
|
|||||||
1
examples/expected/1647-platform-asm-aarch64-multi.build
Normal file
1
examples/expected/1647-platform-asm-aarch64-multi.build
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{ "target": "macos" }
|
||||||
1
examples/expected/1647-platform-asm-aarch64-multi.exit
Normal file
1
examples/expected/1647-platform-asm-aarch64-multi.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
70
|
||||||
31
examples/expected/1647-platform-asm-aarch64-multi.ir
Normal file
31
examples/expected/1647-platform-asm-aarch64-multi.ir
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
define internal { i64, i64 } @split(i64 %0) #0 {
|
||||||
|
entry:
|
||||||
|
%alloca = alloca i64, align 8
|
||||||
|
store i64 %0, ptr %alloca, align 8
|
||||||
|
%load = load i64, ptr %alloca, align 8
|
||||||
|
%asm = call { i64, i64 } asm " and ${0}, ${2}, #0xff\0A lsr ${1}, ${2}, #8\0A", "=r,=r,r"(i64 %load)
|
||||||
|
%tg = extractvalue { i64, i64 } %asm, 0
|
||||||
|
%tgN = extractvalue { i64, i64 } %asm, 1
|
||||||
|
%ti = insertvalue { i64, i64 } undef, i64 %tg, 0
|
||||||
|
%tiN = insertvalue { i64, i64 } %ti, i64 %tgN, 1
|
||||||
|
ret { i64, i64 } %tiN
|
||||||
|
}
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
define i32 @main() #0 {
|
||||||
|
entry:
|
||||||
|
%call = call { i64, i64 } @split(i64 4660)
|
||||||
|
%tg = extractvalue { i64, i64 } %call, 0
|
||||||
|
%alloca = alloca i64, align 8
|
||||||
|
store i64 %tg, ptr %alloca, align 8
|
||||||
|
%tgN = extractvalue { i64, i64 } %call, 1
|
||||||
|
%allocaN = alloca i64, align 8
|
||||||
|
store i64 %tgN, ptr %allocaN, align 8
|
||||||
|
%load = load i64, ptr %alloca, align 8
|
||||||
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
|
%add = add i64 %load, %loadN
|
||||||
|
%ca.tr = trunc i64 %add to i32
|
||||||
|
ret i32 %ca.tr
|
||||||
|
}
|
||||||
1
examples/expected/1647-platform-asm-aarch64-multi.stderr
Normal file
1
examples/expected/1647-platform-asm-aarch64-multi.stderr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
examples/expected/1647-platform-asm-aarch64-multi.stdout
Normal file
1
examples/expected/1647-platform-asm-aarch64-multi.stdout
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -398,22 +398,11 @@ pub const ExprTyper = struct {
|
|||||||
}
|
}
|
||||||
break :blk self.l.inferExprType(nc.rhs);
|
break :blk self.l.inferExprType(nc.rhs);
|
||||||
},
|
},
|
||||||
// Inline asm result type from the `out_value` operands: 0 → void,
|
// Inline asm result type (0→void, 1→T, N→named tuple) — the single
|
||||||
// 1 → that operand's type. N>1 (tuple) is Phase E → `.unresolved`
|
// owner is `Lowering.asmResultType`, shared with `lowerAsmExpr` so a
|
||||||
// here (lowering bails on it anyway). Mirrors `lowerAsmExpr`, so a
|
// `return asm`, a `x := asm`, and a `q, r := asm` destructure all
|
||||||
// bare `x := asm {…-> T}` binding types correctly.
|
// agree on the type.
|
||||||
.asm_expr => |ae| blk: {
|
.asm_expr => |ae| self.l.asmResultType(&ae),
|
||||||
var n_out: usize = 0;
|
|
||||||
var first_out: ?*Node = null;
|
|
||||||
for (ae.operands) |op| {
|
|
||||||
if (op.role != .out_value) continue;
|
|
||||||
n_out += 1;
|
|
||||||
if (first_out == null) first_out = op.payload;
|
|
||||||
}
|
|
||||||
if (n_out == 0) break :blk .void;
|
|
||||||
if (n_out == 1) break :blk self.l.resolveTypeWithBindings(first_out.?);
|
|
||||||
break :blk .unresolved;
|
|
||||||
},
|
|
||||||
// Statements don't produce values (`.return_stmt` is handled above
|
// Statements don't produce values (`.return_stmt` is handled above
|
||||||
// as `.noreturn` — it diverges rather than yielding `void`).
|
// as `.noreturn` — it diverges rather than yielding `void`).
|
||||||
.assignment, .var_decl, .const_decl, .fn_decl,
|
.assignment, .var_decl, .const_decl, .fn_decl,
|
||||||
|
|||||||
@@ -1934,6 +1934,7 @@ pub const Lowering = struct {
|
|||||||
pub const resolveOptionalInner = lower_expr.resolveOptionalInner;
|
pub const resolveOptionalInner = lower_expr.resolveOptionalInner;
|
||||||
pub const lowerExpr = lower_expr.lowerExpr;
|
pub const lowerExpr = lower_expr.lowerExpr;
|
||||||
pub const lowerAsmExpr = lower_expr.lowerAsmExpr;
|
pub const lowerAsmExpr = lower_expr.lowerAsmExpr;
|
||||||
|
pub const asmResultType = lower_expr.asmResultType;
|
||||||
pub const refCapturePointee = lower_expr.refCapturePointee;
|
pub const refCapturePointee = lower_expr.refCapturePointee;
|
||||||
pub const lowerBinaryOp = lower_expr.lowerBinaryOp;
|
pub const lowerBinaryOp = lower_expr.lowerBinaryOp;
|
||||||
pub const lowerTupleOp = lower_expr.lowerTupleOp;
|
pub const lowerTupleOp = lower_expr.lowerTupleOp;
|
||||||
|
|||||||
@@ -2205,6 +2205,36 @@ fn pinnedRegister(constraint: []const u8) ?[]const u8 {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The asm expression's result type from its `out_value` operands (design
|
||||||
|
/// §II.5): 0 → `void`; 1 → that operand's type; N → a tuple `(T1,…,Tn)`, named
|
||||||
|
/// by each operand's effective name (explicit `[name]` else the `{reg}` pin;
|
||||||
|
/// `.empty` for an anonymous field). Returns `.unresolved` if any output type is
|
||||||
|
/// unresolvable (the resolver already diagnosed). Shared by `lowerAsmExpr` and
|
||||||
|
/// `ExprTyper.inferType` so a `return asm`, a `:=` binding, and a `q, r := asm`
|
||||||
|
/// destructure all agree on the type.
|
||||||
|
pub fn asmResultType(self: *Lowering, ae: *const ast.AsmExpr) TypeId {
|
||||||
|
var fields = std.ArrayList(TypeId).empty;
|
||||||
|
defer fields.deinit(self.alloc);
|
||||||
|
var names = std.ArrayList(types.StringId).empty;
|
||||||
|
defer names.deinit(self.alloc);
|
||||||
|
var has_names = false;
|
||||||
|
for (ae.operands) |op| {
|
||||||
|
if (op.role != .out_value) continue;
|
||||||
|
const fty = self.resolveTypeWithBindings(op.payload);
|
||||||
|
if (fty == .unresolved) return .unresolved;
|
||||||
|
fields.append(self.alloc, fty) catch unreachable;
|
||||||
|
const eff = op.name orelse (pinnedRegister(op.constraint) orelse "");
|
||||||
|
if (eff.len != 0) has_names = true;
|
||||||
|
names.append(self.alloc, if (eff.len == 0) types.StringId.empty else self.module.types.internString(eff)) catch unreachable;
|
||||||
|
}
|
||||||
|
if (fields.items.len == 0) return .void;
|
||||||
|
if (fields.items.len == 1) return fields.items[0];
|
||||||
|
return self.module.types.intern(.{ .tuple = .{
|
||||||
|
.fields = self.alloc.dupe(TypeId, fields.items) catch unreachable,
|
||||||
|
.names = if (has_names) self.alloc.dupe(types.StringId, names.items) catch unreachable else null,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
/// Inline assembly lowering. Phase B (partial): validate the asm shape in the
|
/// Inline assembly lowering. Phase B (partial): validate the asm shape in the
|
||||||
/// compile path with specific named diagnostics, THEN bail on the not-yet-
|
/// compile path with specific named diagnostics, THEN bail on the not-yet-
|
||||||
/// implemented codegen so the user sees the real problem first (the IR op +
|
/// implemented codegen so the user sees the real problem first (the IR op +
|
||||||
@@ -2297,26 +2327,10 @@ pub fn lowerAsmExpr(self: *Lowering, ae: *const ast.AsmExpr, span: ast.Span) Ref
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Build the IR op (C.1). D emits 0 or 1 value output; N>1 (tuple result)
|
// ── Build the IR op. Result type from the out_value operands (0→void,
|
||||||
// is Phase E — bail loudly until then. ──
|
// 1→T, N→named tuple). N outputs → LLVM returns a struct {T1,…,Tn}, which
|
||||||
var n_value_outputs: usize = 0;
|
// is exactly sx's tuple representation, so emit needs no special case. ──
|
||||||
for (ae.operands) |op| {
|
const result_ty = self.asmResultType(ae);
|
||||||
if (op.role == .out_value) n_value_outputs += 1;
|
|
||||||
}
|
|
||||||
if (n_value_outputs > 1) {
|
|
||||||
diags.addFmt(.err, span, "multi-output (tuple-returning) inline assembly is not yet implemented (ASM stream Phase E)", .{});
|
|
||||||
return self.emitPlaceholder("inline_asm");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Result type: 0 outputs → void; 1 → that operand's resolved type. (The
|
|
||||||
// resolver diagnoses an unresolvable type and returns `.unresolved`.)
|
|
||||||
var result_ty: TypeId = .void;
|
|
||||||
for (ae.operands) |op| {
|
|
||||||
if (op.role == .out_value) {
|
|
||||||
result_ty = self.resolveTypeWithBindings(op.payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result_ty == .unresolved) return self.emitPlaceholder("inline_asm");
|
if (result_ty == .unresolved) return self.emitPlaceholder("inline_asm");
|
||||||
|
|
||||||
// IR operands, in source order (= `%N` index space + LLVM operand order).
|
// IR operands, in source order (= `%N` index space + LLVM operand order).
|
||||||
|
|||||||
Reference in New Issue
Block a user