ffi 0.2: small struct baseline (Vec2, Vec4f) by-value through #foreign

88/88 regression tests pass (+ffi-02-small-struct).

vendors/ffi_structs/ defines:
  Vec2  — 8 B, two f32 — register-pair (float) ABI
  Vec4f — 16 B, four f32 — homogeneous float aggregate (HFA) on AAPCS64

Both pass cleanly today: the sx-side struct declarations match the C
ABI for these float-only shapes, and the call-site / foreign-decl
type representations agree.

`#source` only (no `#include`) — c_import's type mapping rewrites
struct-typed params/returns to *void, which would link but pass
through the wrong ABI silently. The hand-written #foreign decls keep
sx's struct types end to end.

16-byte integer-only shapes (`{s64, s64}`, `{s32, s32, s32, s32}`)
discovered to trip the LLVM verifier (`[2 x i64]` vs `{ i64, i64 }`
mismatch between foreign decl and call site). Excluded from this
baseline; filed separately in the next commit as issue-0036.
This commit is contained in:
agra
2026-05-19 11:21:16 +03:00
parent bb80b7ca87
commit 84b3fc8866
5 changed files with 104 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
// 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)
//
// 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.
#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.
#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; }
ffi_vec2_make :: (x: f32, y: f32) -> Vec2 #foreign;
ffi_vec2_swap :: (v: Vec2) -> Vec2 #foreign;
ffi_vec2_sum :: (v: Vec2) -> f32 #foreign;
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;
main :: () -> s32 {
// ── Vec2 (8 bytes, float pair) ─────────────────────────────────
v := ffi_vec2_make(1.5, 2.5);
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));
// ── 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);
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));
0;
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,6 @@
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

29
vendors/ffi_structs/ffi_structs.c vendored Normal file
View File

@@ -0,0 +1,29 @@
#include "ffi_structs.h"
Vec2 ffi_vec2_make(float x, float y) {
Vec2 r = { x, y };
return r;
}
Vec2 ffi_vec2_swap(Vec2 v) {
Vec2 r = { v.y, v.x };
return r;
}
float ffi_vec2_sum(Vec2 v) {
return v.x + v.y;
}
Vec4f ffi_vec4f_make(float x, float y, float z, float w) {
Vec4f r = { x, y, z, w };
return r;
}
Vec4f ffi_vec4f_reverse(Vec4f v) {
Vec4f r = { v.w, v.z, v.y, v.x };
return r;
}
float ffi_vec4f_sum(Vec4f v) {
return v.x + v.y + v.z + v.w;
}

18
vendors/ffi_structs/ffi_structs.h vendored Normal file
View File

@@ -0,0 +1,18 @@
// 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).
typedef struct { float x; float y; } Vec2;
typedef struct { float x; float y; float z; float w; } Vec4f;
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);