docs(asm): symbol refs are portable — explain the auto-:c mechanism
Updates the symbol-operand guide: x86 now uses the same plain %[fn] as aarch64, and a 'How the portability works' note explains the mechanism (compiler auto-injects LLVM's :c modifier for "s" operands, equivalent to GCC :P/%P0 for x86 calls, no-op on aarch64, overridable). Drops the stale per-arch :P guidance; checkpoint updated.
This commit is contained in:
@@ -372,9 +372,16 @@ Orthogonal: **issue 0137** (no-`main` segfault).
|
|||||||
arches: 1657 read-write (`"incq ${0}","=r,0"`), 1658 indirect (`"movq $$42,
|
arches: 1657 read-write (`"incq ${0}","=r,0"`), 1658 indirect (`"movq $$42,
|
||||||
${0}","=*m"`(ptr elementtype)), 1659 symbol (`"call ${2:P}"`, direct call). x86
|
${0}","=*m"`(ptr elementtype)), 1659 symbol (`"call ${2:P}"`, direct call). x86
|
||||||
templates validated by cross-emitting an object (integrated assembler accepts;
|
templates validated by cross-emitting an object (integrated assembler accepts;
|
||||||
objdump confirms 1659's direct `call` reloc). **Note:** x86 direct calls need
|
objdump confirms 1659's direct `call` reloc). Pure additive locks. `zig build
|
||||||
the `P` operand modifier (`%[fn:P]`); aarch64 `bl %[fn]` needs none. Pure
|
test` green (668 corpus, 446 unit).
|
||||||
additive locks. `zig build test` green (668 corpus, 446 unit).
|
- (symbol portability) made `%[fn]` portable across arches — `renderAsmTemplate`
|
||||||
|
auto-injects LLVM's `:c` modifier (`${N}`→`${N:c}`) for symbol (`"s"`) operands
|
||||||
|
lacking an explicit modifier (`asmNamedIsSymbol` helper). Without it x86 renders
|
||||||
|
`$cb` (a bad `call` target needing a hand-written `:P`); aarch64 unaffected.
|
||||||
|
Verified `:c` ≡ `:P` for x86-64 calls (both → `R_X86_64_PLT32`). Explicit
|
||||||
|
`%[fn:X]` still wins (escape hatch). 1659 dropped its `:P` → same plain `%[fn]`
|
||||||
|
as aarch64 1656; both IRs regen to `${N:c}`. `zig build test` green (668 corpus,
|
||||||
|
446 unit).
|
||||||
|
|
||||||
## Known issues
|
## Known issues
|
||||||
- **0138** — RESOLVED. `@const` (address-of a `::` comptime constant) yielded a
|
- **0138** — RESOLVED. `@const` (address-of a `::` comptime constant) yielded a
|
||||||
|
|||||||
@@ -79,20 +79,11 @@ ASM,
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Two reasons to prefer this over passing a function *pointer* in a plain
|
The same `%[fn]` works on **x86_64** — just the branch mnemonic differs:
|
||||||
`"r"` register and using an indirect `blr`/`call *`:
|
|
||||||
|
|
||||||
- **One fewer indirection** — a direct PC-relative branch, no pointer
|
|
||||||
load into a register, and a predictable (non-indirect) branch.
|
|
||||||
- **Portable** — the backend emits the correctly-mangled name, so you
|
|
||||||
don't hardcode the macOS leading underscore.
|
|
||||||
|
|
||||||
On **x86_64**, a direct `call` to a symbol operand needs the `P`
|
|
||||||
("call-target") operand modifier — `%[name:P]` (the GCC `%P0` idiom):
|
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
return asm volatile {
|
return asm volatile {
|
||||||
"call %[fn:P]", // x86_64 — note the :P modifier
|
"call %[fn]", // x86_64 — same portable %[fn]
|
||||||
[ret] "={rax}" -> i64,
|
[ret] "={rax}" -> i64,
|
||||||
"{rdi}" = n,
|
"{rdi}" = n,
|
||||||
[fn] "s" = cb,
|
[fn] "s" = cb,
|
||||||
@@ -100,7 +91,24 @@ return asm volatile {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
aarch64 `bl %[fn]` needs no modifier.
|
Two reasons to prefer this over passing a function *pointer* in a plain
|
||||||
|
`"r"` register and using an indirect `blr`/`call *`:
|
||||||
|
|
||||||
|
- **One fewer indirection** — a direct PC-relative branch, no pointer
|
||||||
|
load into a register, and a predictable (non-indirect) branch.
|
||||||
|
- **Portable** — `%[fn]` is the same on every target; the backend emits
|
||||||
|
the correctly-mangled name, so you never hardcode the macOS leading
|
||||||
|
underscore *or* a per-arch operand modifier.
|
||||||
|
|
||||||
|
**How the portability works.** A bare `%[fn]` would render differently
|
||||||
|
per target — on x86 the symbol prints as `$cb` (an immediate `$`-prefix
|
||||||
|
that `call` rejects), while aarch64 prints it bare. So for a symbol (`"s"`)
|
||||||
|
operand the compiler **auto-injects LLVM's `:c` operand modifier** (`%[fn]`
|
||||||
|
→ `${N:c}`, "print the constant with no punctuation"). `:c` prints the
|
||||||
|
plain symbol on every target — equivalent to the GCC `:P`/`%P0` call-target
|
||||||
|
idiom on x86 (both emit the same `R_X86_64_PLT32` relocation) and a no-op
|
||||||
|
on aarch64. You can still override it with an explicit `%[fn:X]` if you
|
||||||
|
ever need a different rendering, but for a call/branch you never should.
|
||||||
|
|
||||||
The callee needs a stable, externally-linked symbol — i.e. `export`
|
The callee needs a stable, externally-linked symbol — i.e. `export`
|
||||||
(which also gives it the C ABI). A plain or `callconv(.c)`-only function
|
(which also gives it the C ABI). A plain or `callconv(.c)`-only function
|
||||||
|
|||||||
Reference in New Issue
Block a user