ffi 0.2: fold Pair64 + Quad32 back into small-struct baseline

Now that emit_llvm.zig bridges the struct<->[2 x i64] ABI mismatch
(previous commit), the 9..16-byte integer-only shapes round-trip
cleanly. Extended `examples/ffi-02-small-struct.sx` to cover all
four aggregate ABI slots in one place:

  Vec2   — 8 B,  two f32    (register pair, float)
  Vec4f  — 16 B, four f32   (HFA — homogeneous float aggregate)
  Pair64 — 16 B, two s64    (9..16 B int — [2 x i64] coercion slot)
  Quad32 — 16 B, four s32   (same slot as Pair64)

Vendor helpers (`vendors/ffi_structs/{ffi_structs.h,ffi_structs.c}`)
grow `ffi_pair64_*` + `ffi_quad32_*` companions. Snapshot updated
to capture the full output. 89/89 regression tests pass.

`examples/101-ffi-medium-struct.sx` keeps a minimal focused repro
of the Pair64 case so the issue's emergence-and-fix history stays
greppable.
This commit is contained in:
agra
2026-05-19 11:32:36 +03:00
parent 7d2c2fb062
commit edd8689fb2
4 changed files with 111 additions and 41 deletions

View File

@@ -1,27 +1,30 @@
// Phase 0 baseline (PLAN-FFI.md step 0.2): small structs (≤16 bytes)
// passed by value into a C `#foreign` fn and returned by value. Two
// shapes that exercise different aggregate ABI paths today:
// Vec2 — 8 bytes, two f32 (float register pair on AAPCS64)
// Vec4f — 16 bytes, four f32 (HFA — homogeneous float aggregate)
// passed by value into a C `#foreign` fn and returned by value. Four
// shapes that exercise distinct aggregate ABI paths:
// Vec2 — 8 B, two f32 (register pair, float)
// Vec4f — 16 B, four f32 (HFA — homogeneous float aggregate)
// Pair64 — 16 B, two s64 (9..16 B int — [2 x i64] coercion slot)
// Quad32 — 16 B, four s32 (9..16 B int — same slot as Pair64)
//
// 16-byte integer-only structs (e.g. `{ s64, s64 }`, `{ s32, s32, s32, s32 }`)
// are *not* covered here: sx's `#foreign` decl currently lowers them
// as `[2 x i64]` while the call site uses the struct type, tripping
// the LLVM verifier. Repro pinned in `examples/issue-0036.sx`; once
// that bug closes, fold those shapes back into this baseline.
// Pair64 / Quad32 were originally excluded (LLVM verifier rejected the
// struct<->[2 x i64] type mismatch — see git history for issue-0036);
// folded back in once emit_llvm.zig's coerceArg learned to bridge
// struct <-> array via the abi.struct2arr / abi.arr2struct branches.
#import "modules/std.sx";
// `#source` only — c_import would rewrite struct-typed params/returns
// in the .h to *void (its "struct/opaque pointer → *void" default),
// which would link but pass through the wrong ABI. The sx declarations
// below match the C signatures exactly.
// in the .h to *void (its struct/opaque pointer default), losing the
// by-value ABI. The hand-written #foreign decls below keep sx's
// struct types end-to-end.
#import c {
#source "vendors/ffi_structs/ffi_structs.c";
};
Vec2 :: struct { x: f32; y: f32; }
Vec4f :: struct { x: f32; y: f32; z: f32; w: f32; }
Vec2 :: struct { x: f32; y: f32; }
Vec4f :: struct { x: f32; y: f32; z: f32; w: f32; }
Pair64 :: struct { a: s64; b: s64; }
Quad32 :: struct { a: s32; b: s32; c: s32; d: s32; }
ffi_vec2_make :: (x: f32, y: f32) -> Vec2 #foreign;
ffi_vec2_swap :: (v: Vec2) -> Vec2 #foreign;
@@ -31,20 +34,42 @@ ffi_vec4f_make :: (x: f32, y: f32, z: f32, w: f32) -> Vec4f #foreign;
ffi_vec4f_reverse :: (v: Vec4f) -> Vec4f #foreign;
ffi_vec4f_sum :: (v: Vec4f) -> f32 #foreign;
ffi_pair64_make :: (a: s64, b: s64) -> Pair64 #foreign;
ffi_pair64_swap :: (p: Pair64) -> Pair64 #foreign;
ffi_pair64_sum :: (p: Pair64) -> s64 #foreign;
ffi_quad32_make :: (a: s32, b: s32, c: s32, d: s32) -> Quad32 #foreign;
ffi_quad32_reverse :: (q: Quad32) -> Quad32 #foreign;
ffi_quad32_sum :: (q: Quad32) -> s32 #foreign;
main :: () -> s32 {
// ── Vec2 (8 bytes, float pair) ─────────────────────────────────
v := ffi_vec2_make(1.5, 2.5);
print("vec2 make = ({}, {})\n", v.x, v.y);
print("vec2 make = ({}, {})\n", v.x, v.y);
w := ffi_vec2_swap(v);
print("vec2 swap = ({}, {})\n", w.x, w.y);
print("vec2 sum = {}\n", ffi_vec2_sum(v));
print("vec2 swap = ({}, {})\n", w.x, w.y);
print("vec2 sum = {}\n", ffi_vec2_sum(v));
// ── Vec4f (16 bytes, HFA) ──────────────────────────────────────
f := ffi_vec4f_make(1.0, 2.0, 3.0, 4.0);
print("vec4f make = ({}, {}, {}, {})\n", f.x, f.y, f.z, f.w);
print("vec4f make = ({}, {}, {}, {})\n", f.x, f.y, f.z, f.w);
g := ffi_vec4f_reverse(f);
print("vec4f rev = ({}, {}, {}, {})\n", g.x, g.y, g.z, g.w);
print("vec4f sum = {}\n", ffi_vec4f_sum(f));
print("vec4f rev = ({}, {}, {}, {})\n", g.x, g.y, g.z, g.w);
print("vec4f sum = {}\n", ffi_vec4f_sum(f));
// ── Pair64 (16 bytes, 2×s64 — [2 x i64] coercion path) ─────────
p := ffi_pair64_make(100, 200);
print("pair64 make = ({}, {})\n", p.a, p.b);
pp := ffi_pair64_swap(p);
print("pair64 swap = ({}, {})\n", pp.a, pp.b);
print("pair64 sum = {}\n", ffi_pair64_sum(p));
// ── Quad32 (16 bytes, 4×s32 — same coercion path as Pair64) ────
q := ffi_quad32_make(10, 20, 30, 40);
print("quad32 make = ({}, {}, {}, {})\n", q.a, q.b, q.c, q.d);
r := ffi_quad32_reverse(q);
print("quad32 rev = ({}, {}, {}, {})\n", r.a, r.b, r.c, r.d);
print("quad32 sum = {}\n", ffi_quad32_sum(q));
0;
}

View File

@@ -1,6 +1,12 @@
vec2 make = (1.500000, 2.500000)
vec2 swap = (2.500000, 1.500000)
vec2 sum = 4.000000
vec4f make = (1.000000, 2.000000, 3.000000, 4.000000)
vec4f rev = (4.000000, 3.000000, 2.000000, 1.000000)
vec4f sum = 10.000000
vec2 make = (1.500000, 2.500000)
vec2 swap = (2.500000, 1.500000)
vec2 sum = 4.000000
vec4f make = (1.000000, 2.000000, 3.000000, 4.000000)
vec4f rev = (4.000000, 3.000000, 2.000000, 1.000000)
vec4f sum = 10.000000
pair64 make = (100, 200)
pair64 swap = (200, 100)
pair64 sum = 300
quad32 make = (10, 20, 30, 40)
quad32 rev = (40, 30, 20, 10)
quad32 sum = 100

View File

@@ -27,3 +27,31 @@ Vec4f ffi_vec4f_reverse(Vec4f v) {
float ffi_vec4f_sum(Vec4f v) {
return v.x + v.y + v.z + v.w;
}
Pair64 ffi_pair64_make(long long a, long long b) {
Pair64 r = { a, b };
return r;
}
Pair64 ffi_pair64_swap(Pair64 p) {
Pair64 r = { p.b, p.a };
return r;
}
long long ffi_pair64_sum(Pair64 p) {
return p.a + p.b;
}
Quad32 ffi_quad32_make(int a, int b, int c, int d) {
Quad32 r = { a, b, c, d };
return r;
}
Quad32 ffi_quad32_reverse(Quad32 q) {
Quad32 r = { q.d, q.c, q.b, q.a };
return r;
}
int ffi_quad32_sum(Quad32 q) {
return q.a + q.b + q.c + q.d;
}

View File

@@ -1,18 +1,29 @@
// FFI struct-marshalling baselines. Two shapes covered today:
// Vec2 — 8 bytes (two f32) — register pair, float path
// Vec4f — 16 bytes (four f32) — homogeneous float aggregate (HFA)
// Declared here so the .c has a header to include; the sx side
// imports via `#source` only and re-declares the structs natively
// (c_import currently rewrites struct-typed params/returns to *void,
// which loses the by-value ABI).
// FFI struct-marshalling baselines covering four aggregate ABI slots:
// Vec2 — 8 B, two f32 — register-pair (float) path
// Vec4f — 16 B, four f32 — HFA (homogeneous float aggregate)
// Pair64 — 16 B, two s64 — 9..16 B int ABI ([2 x i64] coercion)
// Quad32 — 16 B, four s32 — 9..16 B int ABI ([2 x i64] coercion)
// Declared here so the .c has a header to include; sx side imports
// via `#source` only and re-declares the structs natively (c_import
// rewrites struct-typed params/returns to *void).
typedef struct { float x; float y; } Vec2;
typedef struct { float x; float y; float z; float w; } Vec4f;
typedef struct { float x; float y; } Vec2;
typedef struct { float x; float y; float z; float w; } Vec4f;
typedef struct { long long a; long long b; } Pair64;
typedef struct { int a; int b; int c; int d; } Quad32;
Vec2 ffi_vec2_make (float x, float y);
Vec2 ffi_vec2_swap (Vec2 v);
float ffi_vec2_sum (Vec2 v);
Vec2 ffi_vec2_make (float x, float y);
Vec2 ffi_vec2_swap (Vec2 v);
float ffi_vec2_sum (Vec2 v);
Vec4f ffi_vec4f_make (float x, float y, float z, float w);
Vec4f ffi_vec4f_reverse(Vec4f v);
float ffi_vec4f_sum (Vec4f v);
Vec4f ffi_vec4f_make (float x, float y, float z, float w);
Vec4f ffi_vec4f_reverse(Vec4f v);
float ffi_vec4f_sum (Vec4f v);
Pair64 ffi_pair64_make (long long a, long long b);
Pair64 ffi_pair64_swap (Pair64 p);
long long ffi_pair64_sum (Pair64 p);
Quad32 ffi_quad32_make (int a, int b, int c, int d);
Quad32 ffi_quad32_reverse(Quad32 q);
int ffi_quad32_sum (Quad32 q);