diff --git a/examples/101-ffi-medium-struct.sx b/examples/101-ffi-medium-struct.sx new file mode 100644 index 0000000..00ccce2 --- /dev/null +++ b/examples/101-ffi-medium-struct.sx @@ -0,0 +1,33 @@ +// 16-byte integer-only struct passed by value through `#foreign`. +// +// emit_llvm.zig's `abiCoerceParamType` routes 9..16-byte non-HFA +// structs through `[2 x i64]` for register-pair passing on AAPCS64 / +// SysV AMD64. The call-site side loads the sx struct as `{ i64, i64 }`, +// so the verifier needs a memory-bitcast bridge in both directions +// (`abi.struct2arr` on the way in, `abi.arr2struct` on the way back). +// +// Different ABI path from 8-byte register-pair structs (coerce to +// i64), HFAs at the same total size (Vec4f stays a struct), and +// >16-byte aggregates (passed via pointer + byval). +// +// This file was originally filed as issue-0036 (LLVM verifier +// failure repro). Promoted to a focused feature example once the +// emit_llvm.zig coerceArg branches landed. + +#import "modules/std.sx"; + +#import c { + #source "vendors/ffi_medium_struct/ffi_medium_struct.c"; +}; + +Pair64 :: struct { a: s64; b: s64; } + +ffi_pair64_swap :: (p: Pair64) -> Pair64 #foreign; + +main :: () -> s32 { + p : Pair64 = .{ a = 1, b = 2 }; + q := ffi_pair64_swap(p); + print("swap = ({}, {})\n", q.a, q.b); + print("ok = {}\n", q.a == 2 and q.b == 1); + 0; +} diff --git a/examples/issue-0036.sx b/examples/issue-0036.sx deleted file mode 100644 index ff73159..0000000 --- a/examples/issue-0036.sx +++ /dev/null @@ -1,38 +0,0 @@ -// 16-byte integer-only struct by value through `#foreign` trips -// the LLVM verifier: -// -// Call parameter type does not match function signature! -// %loadN = load { i64, i64 }, ptr %allocaM, align 8 -// [2 x i64] %callN = call [2 x i64] @issue0036_swap({ i64, i64 } %load...) -// -// The foreign-decl side lowers `Pair64` to `[2 x i64]` (ABI-coercion -// for the small-struct register-pair return on AAPCS64 / SysV AMD64), -// while the call site loads the sx struct as `{ i64, i64 }`. The two -// representations need to agree. -// -// Float-only aggregates of the same total size (e.g. Vec4f, 4×f32 = -// 16 B) work today because they're routed through the HFA path which -// keeps the struct representation. See `examples/ffi-02-small-struct.sx` -// for the working cases. -// -// Expected once fixed: print "ok = true" with exit 0. Today this -// example fails at codegen with a non-zero exit; the snapshot captures -// that failure so the test suite stays honest. - -#import "modules/std.sx"; - -#import c { - #source "vendors/issue_0036/issue_0036.c"; -}; - -Pair64 :: struct { a: s64; b: s64; } - -issue0036_swap :: (p: Pair64) -> Pair64 #foreign; - -main :: () -> s32 { - p : Pair64 = .{ a = 1, b = 2 }; - q := issue0036_swap(p); - print("swap = ({}, {})\n", q.a, q.b); - print("ok = {}\n", q.a == 2 and q.b == 1); - 0; -} diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index b6111b2..774e3b7 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -2607,6 +2607,21 @@ pub const LLVMEmitter = struct { _ = c.LLVMBuildStore(self.builder, val, tmp); return c.LLVMBuildLoad2(self.builder, param_ty, tmp, "abi.ret.coerce"); } + // Struct → Array (C ABI coercion for 9..16-byte structs — paired with + // abiCoerceParamType's `[2 x i64]` slot for that size class). Same + // memory-bitcast pattern as the integer case; the array type carries + // 16 bytes of storage so we alloca with param_ty to guarantee size. + if (val_kind == c.LLVMStructTypeKind and param_kind == c.LLVMArrayTypeKind) { + const tmp = c.LLVMBuildAlloca(self.builder, param_ty, "abi.struct2arr"); + _ = c.LLVMBuildStore(self.builder, val, tmp); + return c.LLVMBuildLoad2(self.builder, param_ty, tmp, "abi.coerce.arr"); + } + // Array → Struct (return-side counterpart for 9..16-byte structs) + if (val_kind == c.LLVMArrayTypeKind and param_kind == c.LLVMStructTypeKind) { + const tmp = c.LLVMBuildAlloca(self.builder, val_ty, "abi.arr2struct"); + _ = c.LLVMBuildStore(self.builder, val, tmp); + return c.LLVMBuildLoad2(self.builder, param_ty, tmp, "abi.ret.coerce.arr"); + } // Array → Ptr (array decay: alloca + GEP to first element) if (val_kind == c.LLVMArrayTypeKind and param_kind == c.LLVMPointerTypeKind) { const tmp = c.LLVMBuildAlloca(self.builder, val_ty, "ca.arr"); diff --git a/tests/expected/101-ffi-medium-struct.exit b/tests/expected/101-ffi-medium-struct.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/101-ffi-medium-struct.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/101-ffi-medium-struct.txt b/tests/expected/101-ffi-medium-struct.txt new file mode 100644 index 0000000..14f4edb --- /dev/null +++ b/tests/expected/101-ffi-medium-struct.txt @@ -0,0 +1,2 @@ +swap = (2, 1) +ok = true diff --git a/tests/expected/issue-0036.exit b/tests/expected/issue-0036.exit deleted file mode 100644 index d00491f..0000000 --- a/tests/expected/issue-0036.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/tests/expected/issue-0036.txt b/tests/expected/issue-0036.txt deleted file mode 100644 index 87e147d..0000000 --- a/tests/expected/issue-0036.txt +++ /dev/null @@ -1,3 +0,0 @@ -LLVM verification failed: Call parameter type does not match function signature! - %load = load { i64, i64 }, ptr %alloca5, align 8 - [2 x i64] %call = call [2 x i64] @issue0036_swap({ i64, i64 } %load) diff --git a/vendors/ffi_medium_struct/ffi_medium_struct.c b/vendors/ffi_medium_struct/ffi_medium_struct.c new file mode 100644 index 0000000..1aef039 --- /dev/null +++ b/vendors/ffi_medium_struct/ffi_medium_struct.c @@ -0,0 +1,13 @@ +// Companion to examples/101-ffi-medium-struct.sx — a single +// roundtrip through a 16-byte integer-only struct. Pinned in a +// dedicated example because integer aggregates in this size class +// route through emit_llvm.zig's `[2 x i64]` ABI coercion slot, a +// different path from the small (≤8 B) integer struct, the 16-byte +// HFA, and the >16 B byval-pointer cases. + +typedef struct { long long a; long long b; } Pair64; + +Pair64 ffi_pair64_swap(Pair64 p) { + Pair64 r = { p.b, p.a }; + return r; +} diff --git a/vendors/issue_0036/issue_0036.c b/vendors/issue_0036/issue_0036.c deleted file mode 100644 index 3013dcd..0000000 --- a/vendors/issue_0036/issue_0036.c +++ /dev/null @@ -1,9 +0,0 @@ -// Repro for issue-0036: 16-byte integer-only struct by-value through -// #foreign. See examples/issue-0036.sx for details. - -typedef struct { long long a; long long b; } Pair64; - -Pair64 issue0036_swap(Pair64 p) { - Pair64 r = { p.b, p.a }; - return r; -}