From 84b3fc8866af75c38a6ef2c2ab2a7ae1b6af7d75 Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 19 May 2026 11:21:16 +0300 Subject: [PATCH] ffi 0.2: small struct baseline (Vec2, Vec4f) by-value through #foreign MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- examples/ffi-02-small-struct.sx | 50 +++++++++++++++++++++++++ tests/expected/ffi-02-small-struct.exit | 1 + tests/expected/ffi-02-small-struct.txt | 6 +++ vendors/ffi_structs/ffi_structs.c | 29 ++++++++++++++ vendors/ffi_structs/ffi_structs.h | 18 +++++++++ 5 files changed, 104 insertions(+) create mode 100644 examples/ffi-02-small-struct.sx create mode 100644 tests/expected/ffi-02-small-struct.exit create mode 100644 tests/expected/ffi-02-small-struct.txt create mode 100644 vendors/ffi_structs/ffi_structs.c create mode 100644 vendors/ffi_structs/ffi_structs.h diff --git a/examples/ffi-02-small-struct.sx b/examples/ffi-02-small-struct.sx new file mode 100644 index 0000000..5687551 --- /dev/null +++ b/examples/ffi-02-small-struct.sx @@ -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; +} diff --git a/tests/expected/ffi-02-small-struct.exit b/tests/expected/ffi-02-small-struct.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/ffi-02-small-struct.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/ffi-02-small-struct.txt b/tests/expected/ffi-02-small-struct.txt new file mode 100644 index 0000000..a29959e --- /dev/null +++ b/tests/expected/ffi-02-small-struct.txt @@ -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 diff --git a/vendors/ffi_structs/ffi_structs.c b/vendors/ffi_structs/ffi_structs.c new file mode 100644 index 0000000..73728a2 --- /dev/null +++ b/vendors/ffi_structs/ffi_structs.c @@ -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; +} diff --git a/vendors/ffi_structs/ffi_structs.h b/vendors/ffi_structs/ffi_structs.h new file mode 100644 index 0000000..35b7077 --- /dev/null +++ b/vendors/ffi_structs/ffi_structs.h @@ -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);