// 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; }