The aggregate claim, checked against a control instead of asserted
The same three shapes written in C and as first-class IR aggregates,
compiled by the same clang. { i8, i64 } agrees. { i8, float } does not: C
packs both halves into rax, the IR form answers in al and xmm0. And a
24-byte struct does not agree at all -- C spills through an sret pointer,
the IR form returns it in rax, rdx and rcx, and rcx is a register SysV never
uses for a return value.
That resolves the ret-big anomaly the first pass noted and moved past, and
it makes the finding stronger than it was written: the internal convention
is not the C ABI, not only undocumented in the emitted IR.
spike_call0 deleted with it -- declared, never bound, and the two unused
probes were removed for the same reason.
This commit is contained in:
parent
e403e74b90
commit
6bd353bb8c
27
DISCUSS.md
27
DISCUSS.md
@ -787,17 +787,28 @@ output rather than read off a table:
|
||||
|
||||
- `%slice` argument → `rdi`:`rsi`, two registers, and the next argument shifts along.
|
||||
- `{ i8, i64 }` return → tag in `al`, value in `rdx`.
|
||||
- `{ i8, float }` return → tag in `al`, value in **`xmm0`**. C's psABI would classify that single eightbyte as
|
||||
INTEGER and pack both into `rax`. **LLVM's lowering of a first-class aggregate in IR is per-field, and it is not
|
||||
the C ABI.**
|
||||
- `{ i8, float }` return → tag in `al`, value in **`xmm0`**.
|
||||
- `%vec` return (six words) → a hidden `sret` pointer in `rdi`, the real arguments shifted along behind it, and the
|
||||
pointer returned in `rax`. **That pointer does not appear in the `define` line at all.**
|
||||
|
||||
So **the internal calling convention is not specified anywhere. It is whatever LLVM's backend does with a first-class
|
||||
struct, discoverable only by disassembling.** That is the sharpest obstacle in this report, and it is sharper than
|
||||
raylib for three reasons: the reference is an implementation rather than a document; the failure mode is a garbage
|
||||
field rather than a link error; and it is not stable by contract across LLVM versions, which is exactly the coupling
|
||||
`plan.org` chose text IR to avoid.
|
||||
The last two are not the C ABI, and that was checked against a control rather than asserted — the same three shapes
|
||||
written in C, compiled by the same clang at `-O2`, beside the same shapes written as first-class IR aggregates:
|
||||
|
||||
| shape | from C | from IR |
|
||||
|---|---|---|
|
||||
| `{ i8, i64 }` | `al` + `rdx` | `al` + `rdx` — agree |
|
||||
| `{ i8, float }` | **packed into `rax`** (`movd`/`shl`/`or`) | `al` + `xmm0` |
|
||||
| `{ i64, i64, i64 }` | **`sret` pointer in `rdi`** | **`rax` + `rdx` + `rcx`** |
|
||||
|
||||
The 24-byte case is the striking one: C spills to memory through a hidden pointer, and the IR form returns it in three
|
||||
registers, one of which — `rcx` — the SysV ABI never uses for a return value at all. Somewhere past that, LLVM does
|
||||
switch to `sret`, which is what `%vec` gets.
|
||||
|
||||
So **the internal calling convention is not the C ABI and is not specified anywhere. It is whatever LLVM's backend
|
||||
does with a first-class struct, discoverable only by disassembling.** That is the sharpest obstacle in this report,
|
||||
and it is sharper than raylib for three reasons: the reference is an implementation rather than a document; the
|
||||
failure mode is a garbage field rather than a link error; and it is not stable by contract across LLVM versions, which
|
||||
is exactly the coupling `plan.org` chose text IR to avoid.
|
||||
|
||||
It also forces the decision that determines everything else:
|
||||
|
||||
|
||||
@ -45,13 +45,9 @@ value spike_jit_protect(value vp, value vlen) {
|
||||
/* Every Flan function's emitted signature is its parameters followed by the
|
||||
* transfer channel (emit.ml, [signature]), so the trampolines below all pass a
|
||||
* trailing pointer. Nothing in the spike transfers, so it is NULL. */
|
||||
typedef int64_t (*fn0)(void *);
|
||||
typedef int64_t (*fn1)(int64_t, void *);
|
||||
typedef int64_t (*fn2)(int64_t, int64_t, void *);
|
||||
|
||||
value spike_call0(value vp) {
|
||||
return caml_copy_int64(((fn0)Nativeint_val(vp))(NULL));
|
||||
}
|
||||
value spike_call1(value vp, value a) {
|
||||
return caml_copy_int64(((fn1)Nativeint_val(vp))(Int64_val(a), NULL));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user