feat(asm): symbol operands ("s") — direct call/branch to a function
A `"s"` input operand feeds a function/global symbol; the template's %[name] emits the platform-mangled name, so `bl %[fn]` / `call %[fn]` branches DIRECTLY to it (PC-relative, no register load — one fewer indirection than register-indirect `blr`). Lowering: an `"s"` input lowers its RHS normally (a function name → `ptr @fn`); the rejection added last commit is removed. Emit: a symbol operand is passed with its OWN llvm type (LLVMTypeOf) and no coercion — the function value is a `ptr`, and the old coerce-to-register-int path mistyped it and failed the verifier. New asmIsSymbol helper. Verified on aarch64: examples/1656 (sx → asm → bl _cb → sx → 42); the emitted asm is a direct `bl <_cb>` (objdump-confirmed), IR constraint `...,s,...`(ptr @cb). Flipped 1656 from the rejection lock to a runnable aarch64 example. zig build test green (665 corpus, 446 unit).
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
// ASM stream — symbol operand (`"s"`): feed a function/global SYMBOL into the
|
// ASM stream — symbol operand (`"s"`): feed a function/global SYMBOL into the
|
||||||
// template so a DIRECT `bl %[fn]` (PC-relative, one fewer indirection than a
|
// template so a DIRECT `bl %[fn]` (PC-relative — one fewer indirection than a
|
||||||
// register-indirect `blr`) branches straight to it, with the platform-mangled
|
// register-indirect `blr`: no pointer load, a relative reloc, a predictable
|
||||||
// name emitted by the backend (so the template stays portable — no hardcoded
|
// branch) goes straight to it. The backend emits the platform-mangled name
|
||||||
// `_` underscore). Not yet implemented — rejected at lowering for now. The next
|
// (`_cb` on macOS, `cb` on Linux), so the template stays portable — no hardcoded
|
||||||
// commit implements it and flips this example to run (sx → asm → sx, → 42).
|
// underscore. Round trip: sx → asm → `bl _cb` → sx → 42. aarch64-macos-pinned;
|
||||||
|
// runs under the JIT here, ir-only elsewhere (the `.ir` locks `"s"`/`ptr @cb`).
|
||||||
cb :: (n: i64) -> i64 export "cb" { return n + 1; }
|
cb :: (n: i64) -> i64 export "cb" { return n + 1; }
|
||||||
|
|
||||||
tramp :: (n: i64) -> i64 {
|
tramp :: (n: i64) -> i64 {
|
||||||
|
|||||||
1
examples/expected/1656-platform-asm-symbol-operand.build
Normal file
1
examples/expected/1656-platform-asm-symbol-operand.build
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{ "target": "macos" }
|
||||||
@@ -1 +1 @@
|
|||||||
1
|
42
|
||||||
|
|||||||
28
examples/expected/1656-platform-asm-symbol-operand.ir
Normal file
28
examples/expected/1656-platform-asm-symbol-operand.ir
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
define i64 @cb(i64 %0) #0 {
|
||||||
|
entry:
|
||||||
|
%alloca = alloca i64, align 8
|
||||||
|
store i64 %0, ptr %alloca, align 8
|
||||||
|
%load = load i64, ptr %alloca, align 8
|
||||||
|
%add = add i64 %load, 1
|
||||||
|
ret i64 %add
|
||||||
|
}
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
define internal i64 @tramp(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 asm sideeffect " stp x29, x30, [sp, #-16]!\0A mov x0, ${1}\0A bl ${2}\0A mov ${0}, x0\0A ldp x29, x30, [sp], #16\0A", "=r,r,s,~{x0},~{x30},~{memory}"(i64 %load, ptr @cb)
|
||||||
|
ret i64 %asm
|
||||||
|
}
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
define i32 @main() #0 {
|
||||||
|
entry:
|
||||||
|
%call = call i64 @tramp(i64 41)
|
||||||
|
%ca.tr = trunc i64 %call to i32
|
||||||
|
ret i32 %ca.tr
|
||||||
|
}
|
||||||
@@ -1,29 +1 @@
|
|||||||
error: symbol asm operands (`"s"`) are not yet implemented
|
|
||||||
--> examples/1656-platform-asm-symbol-operand.sx:10:12
|
|
||||||
|
|
|
||||||
10 | return asm volatile {
|
|
||||||
| ^^^^^^^^^^^^^^
|
|
||||||
11 | #string ASM
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
|
||||||
12 | stp x29, x30, [sp, #-16]!
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
13 | mov x0, %[arg]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
14 | bl %[fn]
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
15 | mov %[res], x0
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
16 | ldp x29, x30, [sp], #16
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
17 | ASM,
|
|
||||||
| ^^^^
|
|
||||||
18 | [res] "=r" -> i64,
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
19 | [arg] "r" = n,
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
20 | [fn] "s" = cb, // symbol operand → direct `bl _cb`
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
21 | clobbers(.x0, .x30, .memory),
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
22 | };
|
|
||||||
| ^^^^^
|
|
||||||
|
|||||||
@@ -838,6 +838,19 @@ pub const Ops = struct {
|
|||||||
}
|
}
|
||||||
for (a.operands) |op| {
|
for (a.operands) |op| {
|
||||||
if (op.role != .input) continue;
|
if (op.role != .input) continue;
|
||||||
|
// Symbol operand (`"s"`): a function/global passed as a
|
||||||
|
// compile-time constant (its address) — pass the value with its
|
||||||
|
// OWN llvm type and NO coercion, so the template's `${N}` emits
|
||||||
|
// the platform-mangled symbol (a direct `bl _sym`). Coercing to
|
||||||
|
// a register int (the path below) mistypes it (a function value
|
||||||
|
// resolves to `ptr`) and fails the LLVM verifier.
|
||||||
|
if (asmIsSymbol(e, op)) {
|
||||||
|
const v = e.resolveRef(op.operand);
|
||||||
|
param_types[i] = c.LLVMTypeOf(v);
|
||||||
|
call_args[i] = v;
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const raw_ty = e.argIRTypeOrFail(op.operand);
|
const raw_ty = e.argIRTypeOrFail(op.operand);
|
||||||
const llvm_ty = e.toLLVMType(raw_ty);
|
const llvm_ty = e.toLLVMType(raw_ty);
|
||||||
param_types[i] = llvm_ty;
|
param_types[i] = llvm_ty;
|
||||||
@@ -1003,6 +1016,15 @@ pub const Ops = struct {
|
|||||||
return std.mem.indexOfScalar(u8, s, '*') != null;
|
return std.mem.indexOfScalar(u8, s, '*') != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if `op` is a symbol operand — constraint `"s"`. The operand is a
|
||||||
|
/// function/global passed as a compile-time constant (its address); the
|
||||||
|
/// template's `${N}` emits the platform-mangled symbol name, so a direct
|
||||||
|
/// `bl %[fn]` / `call %[fn]` branches straight to it.
|
||||||
|
fn asmIsSymbol(e: *LLVMEmitter, op: InlineAsm.AsmOperand) bool {
|
||||||
|
const s = e.ir_mod.types.getString(op.constraint);
|
||||||
|
return std.mem.eql(u8, s, "s");
|
||||||
|
}
|
||||||
|
|
||||||
/// The positional index of a named operand in the LLVM operand list
|
/// The positional index of a named operand in the LLVM operand list
|
||||||
/// (outputs first, then inputs) — the `N` in `%[name]` → `${N}`. Lowering
|
/// (outputs first, then inputs) — the `N` in `%[name]` → `${N}`. Lowering
|
||||||
/// guarantees every `%[name]` names an operand, so callers can assume a hit.
|
/// guarantees every `%[name]` names an operand, so callers can assume a hit.
|
||||||
|
|||||||
@@ -2366,17 +2366,11 @@ pub fn lowerAsmExpr(self: *Lowering, ae: *const ast.AsmExpr, span: ast.Span) Ref
|
|||||||
var operand_ref: Ref = Ref.none;
|
var operand_ref: Ref = Ref.none;
|
||||||
var out_ty: TypeId = .void;
|
var out_ty: TypeId = .void;
|
||||||
switch (op.role) {
|
switch (op.role) {
|
||||||
.input => {
|
// Inputs (incl. symbol operands `"s"` — a function/global whose
|
||||||
// Symbol operands (constraint `"s"`) — feed a function/global
|
// mangled name the template emits, e.g. a direct `bl %[fn]`). A
|
||||||
// SYMBOL whose mangled name the template emits (e.g. a direct
|
// symbol RHS (a function name) lowers to its address (`ptr @fn`);
|
||||||
// `bl %[fn]`). Not yet implemented; reject loudly rather than
|
// emit passes it with its own type so the backend prints the symbol.
|
||||||
// emit invalid IR (an LLVM-verifier crash). [Phase: symbol ops.]
|
.input => operand_ref = self.lowerExpr(op.payload),
|
||||||
if (std.mem.eql(u8, op.constraint, "s")) {
|
|
||||||
diags.addFmt(.err, span, "symbol asm operands (`\"s\"`) are not yet implemented", .{});
|
|
||||||
return self.emitPlaceholder("inline_asm");
|
|
||||||
}
|
|
||||||
operand_ref = self.lowerExpr(op.payload);
|
|
||||||
},
|
|
||||||
.out_value => out_ty = self.resolveTypeWithBindings(op.payload),
|
.out_value => out_ty = self.resolveTypeWithBindings(op.payload),
|
||||||
.out_place => {
|
.out_place => {
|
||||||
// Read-write (`+`) outputs tie an input to the output and seed
|
// Read-write (`+`) outputs tie an input to the output and seed
|
||||||
|
|||||||
Reference in New Issue
Block a user