ffi: move test-companion .c/.h next to their .sx (drop vendors/ namespace)

vendors/ is a third-party namespace (stb_image, kb_text_shape, etc.);
test fixtures don't belong there. The .c/.h companion files for the
Phase-0 FFI baselines now sit alongside the .sx that drives them in
examples/, with matching basenames:

  examples/ffi-01-primitives.{sx,c,h}    <- was vendors/ffi_primitives/
  examples/ffi-02-small-struct.{sx,c,h}  <- was vendors/ffi_structs/
  examples/ffi-03-large-struct.{sx,c,h}  <- was vendors/ffi_large_struct/
  examples/ffi-04-fp-struct.{sx,c,h}     <- was vendors/ffi_fp_struct/
  examples/ffi-05-string-args.{sx,c,h}   <- was vendors/ffi_strings/
  examples/ffi-06-callback.{sx,c,h}      <- was vendors/ffi_callback/
  examples/101-ffi-medium-struct.{sx,c}  <- was vendors/ffi_medium_struct/

`#source` / `#include` paths in the .sx files become bare filenames
(no prefix) since imports.zig's base_dir resolution finds them
relative to the importing .sx file's directory.

`library/vendors/sx_ffi_resolve_test/` stays put — that one's the
whole point: regression coverage for the stdlib-search branch of
the resolution chain, so it must live where ONLY that branch can
find it.

94/94 regression tests pass.
This commit is contained in:
agra
2026-05-19 11:54:36 +03:00
parent c08a749043
commit 3855f2351e
20 changed files with 14 additions and 14 deletions

View File

@@ -0,0 +1,13 @@
// Companion to examples/101-ffi-medium-struct.sx — a single
// roundtrip through a 16-byte integer-only struct. Pinned in a
// dedicated example because integer aggregates in this size class
// route through emit_llvm.zig's `[2 x i64]` ABI coercion slot, a
// different path from the small (≤8 B) integer struct, the 16-byte
// HFA, and the >16 B byval-pointer cases.
typedef struct { long long a; long long b; } Pair64;
Pair64 ffi_pair64_swap(Pair64 p) {
Pair64 r = { p.b, p.a };
return r;
}

View File

@@ -17,7 +17,7 @@
#import "modules/std.sx";
#import c {
#source "vendors/ffi_medium_struct/ffi_medium_struct.c";
#source "101-ffi-medium-struct.c";
};
Pair64 :: struct { a: s64; b: s64; }

View File

@@ -0,0 +1,16 @@
#include "ffi-01-primitives.h"
int ffi_id_int (int v) { return v; }
unsigned int ffi_id_uint (unsigned int v) { return v; }
short ffi_id_short (short v) { return v; }
unsigned short ffi_id_ushort(unsigned short v) { return v; }
long long ffi_id_s64 (long long v) { return v; }
unsigned long long ffi_id_u64 (unsigned long long v) { return v; }
signed char ffi_id_schar (signed char v) { return v; }
unsigned char ffi_id_uchar (unsigned char v) { return v; }
float ffi_id_f32 (float v) { return v; }
double ffi_id_f64 (double v) { return v; }
void * ffi_id_ptr (void * v) { return v; }
int ffi_add_int (int a, int b) { return a + b; }
double ffi_add_double(double a, double b) { return a + b; }

View File

@@ -0,0 +1,20 @@
// FFI baseline test helpers — one trivial roundtrip per primitive C
// type so the sx-side test can verify both the parameter ABI and the
// return-value ABI per type. Locking these in BEFORE the Phase 1
// `#objc_call` / `#jni_call` work so any future lowering change that
// silently regresses primitive marshalling shows up here.
int ffi_id_int (int v);
unsigned int ffi_id_uint (unsigned int v);
short ffi_id_short (short v);
unsigned short ffi_id_ushort(unsigned short v);
long long ffi_id_s64 (long long v);
unsigned long long ffi_id_u64 (unsigned long long v);
signed char ffi_id_schar (signed char v);
unsigned char ffi_id_uchar (unsigned char v);
float ffi_id_f32 (float v);
double ffi_id_f64 (double v);
void * ffi_id_ptr (void * v);
int ffi_add_int (int a, int b);
double ffi_add_double(double a, double b);

View File

@@ -10,8 +10,8 @@
#import "modules/std.sx";
#import c {
#include "vendors/ffi_primitives/ffi_primitives.h";
#source "vendors/ffi_primitives/ffi_primitives.c";
#include "ffi-01-primitives.h";
#source "ffi-01-primitives.c";
};
main :: () -> s32 {

View File

@@ -0,0 +1,57 @@
#include "ffi-02-small-struct.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;
}
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

@@ -0,0 +1,29 @@
// 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 { 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);
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);

View File

@@ -18,7 +18,7 @@
// 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";
#source "ffi-02-small-struct.c";
};
Vec2 :: struct { x: f32; y: f32; }

View File

@@ -0,0 +1,30 @@
#include "ffi-03-large-struct.h"
Big24 ffi_big24_make(long long a, long long b, long long c) {
Big24 r = { a, b, c };
return r;
}
Big24 ffi_big24_rotate(Big24 v) {
Big24 r = { v.c, v.a, v.b };
return r;
}
long long ffi_big24_sum(Big24 v) {
return v.a + v.b + v.c;
}
Big48 ffi_big48_make(long long a, long long b, long long c,
long long d, long long e, long long f) {
Big48 r = { a, b, c, d, e, f };
return r;
}
Big48 ffi_big48_reverse(Big48 v) {
Big48 r = { v.f, v.e, v.d, v.c, v.b, v.a };
return r;
}
long long ffi_big48_sum(Big48 v) {
return v.a + v.b + v.c + v.d + v.e + v.f;
}

View File

@@ -0,0 +1,22 @@
// FFI large-struct (>16 B) by-value roundtrips. These route through
// the byval-pointer ABI path (caller copies onto its stack, hands the
// callee a pointer; on AAPCS64 a separate `x8` indirect-return
// register; on SysV AMD64 a hidden first arg). Distinct from the
// register-pair / [2 x i64] / HFA paths the small-struct baseline
// covers — locking those in here keeps the byval path honest.
//
// Big24 — 24 B, three s64
// Big48 — 48 B, six s64
typedef struct { long long a; long long b; long long c; } Big24;
typedef struct { long long a; long long b; long long c;
long long d; long long e; long long f; } Big48;
Big24 ffi_big24_make (long long a, long long b, long long c);
Big24 ffi_big24_rotate(Big24 v);
long long ffi_big24_sum (Big24 v);
Big48 ffi_big48_make (long long a, long long b, long long c,
long long d, long long e, long long f);
Big48 ffi_big48_reverse(Big48 v);
long long ffi_big48_sum (Big48 v);

View File

@@ -16,7 +16,7 @@
#import "modules/std.sx";
#import c {
#source "vendors/ffi_large_struct/ffi_large_struct.c";
#source "ffi-03-large-struct.c";
};
Big24 :: struct { a: s64; b: s64; c: s64; }

View File

@@ -0,0 +1,29 @@
#include "ffi-04-fp-struct.h"
FQuad ffi_fquad_make(float a, float b, float c, float d) {
FQuad r = { a, b, c, d };
return r;
}
FQuad ffi_fquad_reverse(FQuad v) {
FQuad r = { v.d, v.c, v.b, v.a };
return r;
}
float ffi_fquad_sum(FQuad v) {
return v.a + v.b + v.c + v.d;
}
DQuad ffi_dquad_make(double a, double b, double c, double d) {
DQuad r = { a, b, c, d };
return r;
}
DQuad ffi_dquad_reverse(DQuad v) {
DQuad r = { v.d, v.c, v.b, v.a };
return r;
}
double ffi_dquad_sum(DQuad v) {
return v.a + v.b + v.c + v.d;
}

View File

@@ -0,0 +1,20 @@
// Focused FP-aggregate (HFA) FFI baselines. Distinct from the int-aggregate
// register-coercion paths because all-float / all-double structs of ≤4 fields
// stay as struct values in LLVM and are passed/returned via the float
// register file (AAPCS64 v0..v3; SysV AMD64 xmm0..xmm7). This was the
// `UIEdgeInsets`-as-f32-vs-f64 landmine — pinned here so a future ABI rule
// change that wrecks the FP path fails this test directly.
//
// FQuad — 16 B, four float (small HFA; same slot as Vec4f)
// DQuad — 32 B, four double (UIEdgeInsets-shape HFA)
typedef struct { float a; float b; float c; float d; } FQuad;
typedef struct { double a; double b; double c; double d; } DQuad;
FQuad ffi_fquad_make (float a, float b, float c, float d);
FQuad ffi_fquad_reverse(FQuad v);
float ffi_fquad_sum (FQuad v);
DQuad ffi_dquad_make (double a, double b, double c, double d);
DQuad ffi_dquad_reverse(DQuad v);
double ffi_dquad_sum (DQuad v);

View File

@@ -16,7 +16,7 @@
#import "modules/std.sx";
#import c {
#source "vendors/ffi_fp_struct/ffi_fp_struct.c";
#source "ffi-04-fp-struct.c";
};
FQuad :: struct { a: f32; b: f32; c: f32; d: f32; }

View File

@@ -0,0 +1,25 @@
#include "ffi-05-string-args.h"
int ffi_strlen(const char *s) {
int n = 0;
while (s[n] != 0) n++;
return n;
}
int ffi_first_byte(const char *s) {
return (int)(unsigned char)s[0];
}
int ffi_sum_bytes(const unsigned char *buf, int len) {
int total = 0;
for (int i = 0; i < len; i++) total += buf[i];
return total;
}
void ffi_write_byte(unsigned char *buf, int idx, unsigned char val) {
buf[idx] = val;
}
const char* ffi_static_greeting(void) {
return "hello from C";
}

View File

@@ -0,0 +1,14 @@
// String / byte-pointer FFI baselines. Covers the three shapes
// callers actually use at the sx ↔ C boundary:
// - null-terminated `[:0]u8` (C-style string)
// - raw byte pointer `[*]u8` + length (slice-style)
// - sx `string` decayed to `ptr` (the slice-decay branch
// in coerceArg pulls .ptr)
#include <stddef.h>
int ffi_strlen (const char *s);
int ffi_first_byte (const char *s);
int ffi_sum_bytes (const unsigned char *buf, int len);
void ffi_write_byte (unsigned char *buf, int idx, unsigned char val);
const char* ffi_static_greeting(void);

View File

@@ -11,7 +11,7 @@
#import "modules/std.sx";
#import c {
#source "vendors/ffi_strings/ffi_strings.c";
#source "ffi-05-string-args.c";
};
ffi_strlen :: (s: [:0]u8) -> s32 #foreign;

View File

@@ -0,0 +1,9 @@
#include "ffi-06-callback.h"
int ffi_apply_callback(int (*cb)(int), int value) {
return cb(value);
}
int ffi_apply_callback2(int (*cb)(void *ctx, int v), void *ctx, int v) {
return cb(ctx, v);
}

View File

@@ -0,0 +1,13 @@
// C-to-sx callback FFI baseline. C takes a function pointer + a value,
// invokes the callback with the value, and returns whatever the callback
// returned. Mirrors the `app->onInputEvent` pattern in
// library/modules/platform/android.sx where sx installs a handler that
// native_app_glue invokes from its input-event loop.
int ffi_apply_callback(int (*cb)(int), int value);
// Two-arg variant — the actual chess-on-Android shape:
// the callback receives a pointer + a value (mirrors
// onInputEvent(app, event) where both are opaque pointers from
// the C caller's point of view).
int ffi_apply_callback2(int (*cb)(void *ctx, int v), void *ctx, int v);

View File

@@ -13,7 +13,7 @@
#import "modules/std.sx";
#import c {
#source "vendors/ffi_callback/ffi_callback.c";
#source "ffi-06-callback.c";
};
ffi_apply_callback :: (cb: (s32) -> s32, value: s32) -> s32 #foreign;