ffi 1.8a: xfail — #objc_call(>16 B non-HFA) skips the sret transform
103/103 regression tests pass (+ffi-objc-call-06-sret-return).
The runtime output is misleadingly clean — `[nil tripleValue]`
zeros all three fields because libobjc's nil-stub clears the
return registers. But the IR snapshot reveals the actual ABI
mismatch:
%objc.msg = call { i64, i64, i64 } @objc_msgSend(ptr null, ptr %load)
A live receiver returning a non-zero `Triple` would surface
garbage in the third field — the AArch64 backend lowers
{ i64, i64, i64 } returns to x0/x1 pair + a third register that
the runtime's sret-shaped stub doesn't populate.
Next commit (1.8b): emit_llvm's `objc_msg_send` arm gains the
same sret transform we did for plain `#foreign` calls in Phase
0.3 — ret type collapses to void, prepend a ptr sret param,
alloca the result slot at the call site, mirror the
`sret(<T>)` attribute on the call, load result from the slot
post-call. IR snapshot will flip to:
%slot = alloca <Triple>
call void @objc_msgSend(ptr sret(<Triple>) %slot, ptr null, ptr %load)
%objc.msg = load <Triple>, ptr %slot
This commit is contained in:
41
examples/ffi-objc-call-06-sret-return.sx
Normal file
41
examples/ffi-objc-call-06-sret-return.sx
Normal file
@@ -0,0 +1,41 @@
|
||||
// Phase 1 step 1.8 (PLAN-FFI.md): >16 B non-HFA struct returns
|
||||
// through `#objc_call`. AAPCS64 routes these through the indirect-
|
||||
// return convention: caller allocates the result slot, passes its
|
||||
// pointer as a hidden `x8` arg with the `sret(<T>)` attribute,
|
||||
// callee writes through it and returns void.
|
||||
//
|
||||
// 1.6's `objc_msg_send` lowering hands the IR struct type straight
|
||||
// to LLVMBuildCall2 — works for ≤16 B aggregates and HFAs of any
|
||||
// size (since those stay register-resident) but breaks >16 B int
|
||||
// aggregates: LLVM accepts the signature but the AArch64 backend
|
||||
// expects the result in x0/x1, the runtime stub doesn't populate
|
||||
// those for sret-shaped returns, and the upper fields come back
|
||||
// as garbage.
|
||||
//
|
||||
// 1.8a (this commit): xfail. Snapshot shows garbage in the third
|
||||
// field — pins the broken behavior.
|
||||
// 1.8b (next commit): emit_llvm.zig applies the sret transform
|
||||
// (ret type collapses to void, prepend ptr sret param, alloca
|
||||
// slot at call site, load result post-call) for non-HFA >16 B
|
||||
// returns. Snapshot flips to all-zeros (Obj-C runtime contract).
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/compiler.sx";
|
||||
|
||||
// 24 B non-HFA integer aggregate. Distinct from any HFA path —
|
||||
// no all-float / all-double check fires.
|
||||
Triple :: struct { a: s64; b: s64; c: s64; }
|
||||
|
||||
main :: () -> s32 {
|
||||
inline if OS == .macos {
|
||||
t := #objc_call(Triple)(null, "tripleValue");
|
||||
// Per the [nil structReturn] = 0 contract, all three fields
|
||||
// should be 0 after 1.8b lands. Today: a/b correct, c is
|
||||
// whatever the callee-saves left in the high register.
|
||||
print("triple = ({}, {}, {})\n", t.a, t.b, t.c);
|
||||
}
|
||||
inline if OS != .macos {
|
||||
print("skipped (not macos)\n");
|
||||
}
|
||||
0;
|
||||
}
|
||||
1
tests/expected/ffi-objc-call-06-sret-return.exit
Normal file
1
tests/expected/ffi-objc-call-06-sret-return.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
2572
tests/expected/ffi-objc-call-06-sret-return.ir
Normal file
2572
tests/expected/ffi-objc-call-06-sret-return.ir
Normal file
File diff suppressed because it is too large
Load Diff
1
tests/expected/ffi-objc-call-06-sret-return.txt
Normal file
1
tests/expected/ffi-objc-call-06-sret-return.txt
Normal file
@@ -0,0 +1 @@
|
||||
triple = (0, 0, 0)
|
||||
Reference in New Issue
Block a user