From 36e929101bc88f161f57c4e67974287c3a4f79a4 Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 19 May 2026 11:22:56 +0300 Subject: [PATCH] issue-0036: 16-byte integer-only struct by value trips LLVM verifier Surfaced while writing the ffi-02-small-struct.sx baseline. The sx #foreign decl lowers `{ s64, s64 }` (and other 16-byte integer-only shapes like `{ s32, s32, s32, s32 }`) to `[2 x i64]` for the small- struct register-pair ABI on AAPCS64 / SysV AMD64, but the call site loads the struct as `{ i64, i64 }`. The two types must agree for the LLVM verifier to accept the call: Call parameter type does not match function signature! %load = load { i64, i64 }, ptr %alloca, align 8 [2 x i64] %call = call [2 x i64] @issue0036_swap({ i64, i64 } %load) Float-only 16-byte aggregates (e.g. Vec4f) work because they route through the HFA path which keeps the struct representation. See examples/ffi-02-small-struct.sx for the working cases. Phase 1's #foreign lowering rework is the natural place to unify these representations; check there before fixing inline. --- examples/issue-0036.sx | 38 +++++++++++++++++++++++++++++++++ tests/expected/issue-0036.exit | 1 + tests/expected/issue-0036.txt | 3 +++ vendors/issue_0036/issue_0036.c | 9 ++++++++ 4 files changed, 51 insertions(+) create mode 100644 examples/issue-0036.sx create mode 100644 tests/expected/issue-0036.exit create mode 100644 tests/expected/issue-0036.txt create mode 100644 vendors/issue_0036/issue_0036.c diff --git a/examples/issue-0036.sx b/examples/issue-0036.sx new file mode 100644 index 0000000..ff73159 --- /dev/null +++ b/examples/issue-0036.sx @@ -0,0 +1,38 @@ +// 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/tests/expected/issue-0036.exit b/tests/expected/issue-0036.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/tests/expected/issue-0036.exit @@ -0,0 +1 @@ +1 diff --git a/tests/expected/issue-0036.txt b/tests/expected/issue-0036.txt new file mode 100644 index 0000000..87e147d --- /dev/null +++ b/tests/expected/issue-0036.txt @@ -0,0 +1,3 @@ +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/issue_0036/issue_0036.c b/vendors/issue_0036/issue_0036.c new file mode 100644 index 0000000..3013dcd --- /dev/null +++ b/vendors/issue_0036/issue_0036.c @@ -0,0 +1,9 @@ +// 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; +}