feat(asm): indirect-memory =*m place outputs

Implements indirect-memory (`=*m`) `-> @place` outputs — the last
substantive asm feature. Unlike a write-through `=` output (which
returns a value that is then stored), an indirect output passes the
place ADDRESS to the asm and the asm writes through it; there is no
return slot.

emitInlineAsm:
  - indirect outputs are excluded from the LLVM return type;
  - their pointer is passed as an opaque `ptr` call arg, placed FIRST
    (the arg-consuming constraint order is: output-section indirect
    pointers, then inputs, then read-write tied seeds);
  - each indirect arg gets an `elementtype(T)` call-site attribute
    (required in the opaque-pointer era), T = the pointee type;
  - the store-back loop skips indirect outputs (already written).
New asmIsIndirect helper. Lowering stops rejecting `*` (constraint kept
verbatim; `=*m` reaches the constraint string as-is). asmOperandIndex
is unchanged — indirect outputs still count as operands, so `%[name]`
${N} numbering holds.

Verified by running on aarch64: store-through-pointer (str x9, %[out]
→ 42, IR `=*m,~{x9}` with `ptr elementtype(i64)`) and a mixed case
(indirect + value output + input → `=*m,=r,r`, indirect ptr arg first,
${0}/${1}/${2} correct). 1652 flipped from the rejection lock to a
runnable aarch64 example (ir-only elsewhere). zig build test green
(661 corpus, 446 unit).
This commit is contained in:
agra
2026-06-16 07:09:17 +03:00
parent 2a43713d7f
commit cb6c032c58
6 changed files with 74 additions and 36 deletions

View File

@@ -2369,14 +2369,10 @@ pub fn lowerAsmExpr(self: *Lowering, ae: *const ast.AsmExpr, span: ast.Span) Ref
.input => operand_ref = self.lowerExpr(op.payload),
.out_value => out_ty = self.resolveTypeWithBindings(op.payload),
.out_place => {
// Indirect-memory (`*`) place outputs aren't implemented yet —
// reject loudly rather than miscompile (§II.11). Read-write (`+`)
// outputs ARE implemented (emit ties an input to the output and
// seeds it with the place's loaded value; see `emitInlineAsm`).
if (std.mem.indexOfScalar(u8, op.constraint, '*') != null) {
diags.addFmt(.err, span, "indirect-memory (`*`) asm outputs are not yet implemented", .{});
return self.emitPlaceholder("inline_asm");
}
// Read-write (`+`) outputs tie an input to the output and seed
// it with the place's loaded value; indirect-memory (`=*m`)
// outputs pass the place address as a pointer arg and the asm
// writes through it — both handled in `emitInlineAsm`.
// `@place` lowers to its address (a pointer); the asm result is
// stored through it. The stored type is the pointee.
operand_ref = self.lowerExpr(op.payload);