feat(asm): portable symbol refs — auto-inject :c operand modifier
A `%[name]` that references a symbol ("s") operand without an explicit
modifier now lowers to `${N:c}` (LLVM 'bare constant — no punctuation')
instead of `${N}`. This makes `bl %[fn]` / `call %[fn]` portable across
targets with no per-arch knowledge: x86 would otherwise render `$cb`
(an invalid call target, requiring a hand-written `:P`); aarch64 is
unaffected. Verified `:c` is equivalent to `:P` for x86-64 calls (both
emit R_X86_64_PLT32), and correct for branch targets, RIP-relative
addressing, and `$`-prefixed absolute immediates.
renderAsmTemplate injects `:c` only for symbol operands lacking an
explicit modifier (asmNamedIsSymbol helper); an explicit `%[name:X]`
still wins (escape hatch). x86 example 1659 drops its `:P` for the same
plain `%[fn]` as aarch64 1656. Snapshots regen to `${N:c}`. zig build
test green (668 corpus, 446 unit).
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
// ASM stream — symbol operand (`"s"`) on x86_64 (cross-arch sibling of the
|
||||
// aarch64 1656). A DIRECT `call` to an `export`ed sx function by symbol. x86
|
||||
// direct calls need the `P` operand modifier (`%[fn:P]` → `${N:P}`) — the GCC
|
||||
// `%P0` call-target idiom — whereas aarch64 `bl %[fn]` needs none. The backend
|
||||
// emits the platform-mangled name (`call cb` on Linux). x86-pinned; ir-only
|
||||
// here, runs on x86_64-linux. Round trip: sx → asm → call cb → sx → 42.
|
||||
// aarch64 1656). A DIRECT `call` to an `export`ed sx function by symbol, written
|
||||
// with the SAME portable `%[fn]` as the aarch64 example — the compiler injects
|
||||
// the `:c` operand modifier for symbol operands, so the symbol prints bare on
|
||||
// every target (x86 would otherwise render `$cb`, a bad call target). The
|
||||
// backend emits the platform-mangled name (`call cb` on Linux). x86-pinned;
|
||||
// ir-only here, runs on x86_64-linux. Round trip: sx → asm → call cb → sx → 42.
|
||||
cb :: (n: i64) -> i64 export "cb" { return n + 1; }
|
||||
|
||||
tramp :: (n: i64) -> i64 {
|
||||
return asm volatile {
|
||||
"call %[fn:P]",
|
||||
"call %[fn]",
|
||||
[ret] "={rax}" -> i64,
|
||||
"{rdi}" = n, // arg in rdi (SysV)
|
||||
[fn] "s" = cb, // symbol operand → direct `call cb`
|
||||
|
||||
@@ -15,7 +15,7 @@ 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)
|
||||
%asm = call i64 asm sideeffect " stp x29, x30, [sp, #-16]!\0A mov x0, ${1}\0A bl ${2:c}\0A mov ${0}, x0\0A ldp x29, x30, [sp], #16\0A", "=r,r,s,~{x0},~{x30},~{memory}"(i64 %load, ptr @cb)
|
||||
ret i64 %asm
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ 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 "call ${2:P}", "={rax},{rdi},s,~{rcx},~{rdx},~{rsi},~{r8},~{r9},~{r10},~{r11},~{memory}"(i64 %load, ptr @cb)
|
||||
%asm = call i64 asm sideeffect "call ${2:c}", "={rax},{rdi},s,~{rcx},~{rdx},~{rsi},~{r8},~{r9},~{r10},~{r11},~{memory}"(i64 %load, ptr @cb)
|
||||
ret i64 %asm
|
||||
}
|
||||
|
||||
|
||||
@@ -1042,6 +1042,17 @@ pub const Ops = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// True if the operand named `name` (effective name) is a symbol operand.
|
||||
/// Drives the auto-`:c` injection in `renderAsmTemplate` so `%[fn]` is
|
||||
/// portable across targets.
|
||||
fn asmNamedIsSymbol(self: Ops, a: InlineAsm, name: []const u8) bool {
|
||||
const e = self.e;
|
||||
for (a.operands) |op| {
|
||||
if (op.name != .empty and std.mem.eql(u8, e.ir_mod.types.getString(op.name), name) and asmIsSymbol(e, op)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Rewrite the asm template into LLVM form. State machine over the bytes:
|
||||
/// `$`→`$$`, `%%`→`%`, `%=`→`${:uid}`, `%[name]`→`${N}`, `%[name:mod]`→
|
||||
/// `${N:mod}`. Port of `FuncGen.zig`'s template rewriter.
|
||||
@@ -1085,6 +1096,14 @@ pub const Ops = struct {
|
||||
if (modifier) |m| {
|
||||
out.append(alloc, ':') catch unreachable;
|
||||
out.appendSlice(alloc, m) catch unreachable;
|
||||
} else if (self.asmNamedIsSymbol(a, name)) {
|
||||
// A symbol operand referenced without an explicit
|
||||
// modifier: inject `:c` (bare constant — no punctuation)
|
||||
// so a direct `bl`/`call %[fn]` emits the plain symbol on
|
||||
// EVERY target. Without it x86 prints `$sym` (a bad call
|
||||
// target); aarch64 is unaffected. Keeps the template
|
||||
// portable — the user never writes a per-arch `:P`/`:c`.
|
||||
out.appendSlice(alloc, ":c") catch unreachable;
|
||||
}
|
||||
out.append(alloc, '}') catch unreachable;
|
||||
i = close + 1;
|
||||
|
||||
Reference in New Issue
Block a user