Commit Graph

11 Commits

Author SHA1 Message Date
agra
3855f2351e 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.
2026-05-19 11:54:36 +03:00
agra
31ab175d56 ffi 0.6: C-to-sx callback baseline (1-arg + ctx-ptr forms)
93/93 regression tests pass (+ffi-06-callback).

Mirrors the `app->onInputEvent` install pattern from
library/modules/platform/android.sx:

  1. (s32) -> s32              — single primitive arg/return
  2. (*void, s32) -> s32       — opaque ctx pointer + value
                                  (the onInputEvent shape)

Side effects via two file-level globals so the test observes both
the return value AND state mutation across multiple calls:
- g_callback_hits = N proves the callback fired N times.
- g_callback_sum  = sum of args proves each individual call landed
  with the correct value.

The ctx-pointer variant casts `*void` back to `*s32` inside the
callback and reads through it (`p.*`), proving the pointer survives
the round-trip with no aliasing weirdness.
2026-05-19 11:48:34 +03:00
agra
31715bd251 ffi 0.5: string + byte-pointer baseline through #foreign
92/92 regression tests pass (+ffi-05-string-args).

Covers the four shapes that actually appear at the sx ↔ C boundary
today:

  1. [:0]u8 string literal -> const char*  (ffi_strlen, ffi_first_byte)
  2. sx `string` value via .ptr            (slice-decay branch in
                                            coerceArg pulls the pointer)
  3. [*]u8 raw buffer + length             (ffi_sum_bytes, mutated via
                                            ffi_write_byte and read back)
  4. C-returned const char*                (round-trips back as [*]u8)

The mutate-via-C path catches any pointer-aliasing regression — sx
allocates the fixed array `bytes : [4]u8`, passes `.ptr` to C which
writes index 1, and the sx side reads `bytes[1]` to confirm the
mutation took effect through the same memory.
2026-05-19 11:46:47 +03:00
agra
736382d39c ffi 0.4: focused FP-aggregate (HFA) baseline — FQuad + DQuad
91/91 regression tests pass (+ffi-04-fp-struct).

Single-file regression net for the all-float / all-double aggregate
ABI path:

  FQuad — 16 B, 4×f32   (same slot as ffi-02's Vec4f)
  DQuad — 32 B, 4×f64   (UIEdgeInsets-shape — the f32-vs-f64 landmine)

Already nominally covered by ffi-02's Vec4f, but pinning it as a
focused single-file test means a future ABI rule change that breaks
the HFA path fails *this* test directly without a noisy drag-in from
the multi-shape baseline.

DQuad at 32 B straddles the AAPCS64 HFA limit (≤4 floats of same
type, total ≤64 B); it stays as a struct value passed through
v0..v3 rather than going indirect. The snapshot confirms the values
arrive intact.
2026-05-19 11:44:43 +03:00
agra
2463eea1d4 ffi 0.3: large struct baseline (Big24, Big48) through sret return path
90/90 regression tests pass (+ffi-03-large-struct).

vendors/ffi_large_struct/{.h,.c} defines:
  Big24 — 24 B, three s64    (byval params + sret return)
  Big48 — 48 B, six s64      (same path, larger)

`make / rotate-or-reverse / sum` helpers per shape. The sx-side
example imports via `#source` only and declares matching structs +
hand-written #foreign decls.

Snapshot pins today's >16-byte aggregate ABI now that the
emit_llvm.zig sret-return transform is in place (previous commit).
That gives us a regression net for all four C-ABI aggregate slots
in one place:

  ≤8 B int       — i64 coercion          (ffi-01 vec-likes)
  9..16 B int    — [2 x i64] coercion    (ffi-02 Pair64/Quad32, 101)
  16 B HFA       — struct, no coercion    (ffi-02 Vec2/Vec4f)
  >16 B          — byval params + sret    (this commit)
2026-05-19 11:41:06 +03:00
agra
edd8689fb2 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.
2026-05-19 11:32:36 +03:00
agra
7d2c2fb062 emit_llvm: bridge struct<->array ABI for 9..16-byte foreign structs
Resolves issue-0036 (LLVM verifier failure on 16-byte integer-only
struct by value through #foreign). The mismatch:

  Call parameter type does not match function signature!
    %load = load { i64, i64 }, ptr %alloca, align 8
  [2 x i64]  %call = call [2 x i64] @fn({ i64, i64 } %load)

`abiCoerceParamType` had already chosen `[2 x i64]` for 9..16-byte
non-HFA structs (the AAPCS64 / SysV AMD64 register-pair ABI slot for
that size class) on the foreign-decl side, but `coerceArg` only knew
how to bridge struct<->integer (the ≤8 B case) — not struct<->array.
LLVM's verifier rejects type-mismatched call args, so the call site
never landed.

Added the symmetric branches in coerceArg:
  - Struct -> Array : alloca <array>; store <struct>; load <array>
  - Array -> Struct : alloca <array>; store <array>;  load <struct>

Both use the LLVM opaque-pointer memory-bitcast pattern already in
place for the integer case. They're paired with the existing
i64 <-> small-struct bridge so all four (≤8 B int, 9..16 B int,
16 B HFA, >16 B byval) ABI slots round-trip cleanly through
emit_llvm now.

File mechanics: promotes the issue-0036 repro to a focused feature
example per CLAUDE.md's issue-resolution workflow:

  examples/issue-0036.sx              -> examples/101-ffi-medium-struct.sx
  tests/expected/issue-0036.{txt,exit} -> tests/expected/101-ffi-medium-struct.{txt,exit}
  vendors/issue_0036/issue_0036.c     -> vendors/ffi_medium_struct/ffi_medium_struct.c

Snapshot updated to the passing output. 89/89 regression tests pass;
chess Android build still clean.
2026-05-19 11:31:04 +03:00
agra
36e929101b issue-0036: 16-byte integer-only struct by value trips LLVM verifier
Surfaced while writing the ffi-02-small-struct.sx baseline. The sx
#foreign decl lowers `{ s64, s64 }` (and other 16-byte integer-only
shapes like `{ s32, s32, s32, s32 }`) to `[2 x i64]` for the small-
struct register-pair ABI on AAPCS64 / SysV AMD64, but the call site
loads the struct as `{ i64, i64 }`. The two types must agree for the
LLVM verifier to accept the call:

  Call parameter type does not match function signature!
    %load = load { i64, i64 }, ptr %alloca, align 8
  [2 x i64]  %call = call [2 x i64] @issue0036_swap({ i64, i64 } %load)

Float-only 16-byte aggregates (e.g. Vec4f) work because they route
through the HFA path which keeps the struct representation. See
examples/ffi-02-small-struct.sx for the working cases.

Phase 1's #foreign lowering rework is the natural place to unify
these representations; check there before fixing inline.
2026-05-19 11:22:56 +03:00
agra
84b3fc8866 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.
2026-05-19 11:21:16 +03:00
agra
bb80b7ca87 ffi 0.1: primitives baseline (#import c, one roundtrip per type)
87/87 regression tests pass (was 86; +ffi-01-primitives).

vendors/ffi_primitives/{.h,.c} exposes a trivial identity roundtrip
per primitive C type — int/uint/short/ushort/long long/unsigned long
long/signed char/unsigned char/float/double/void* — plus two-arg
add helpers (int + double) for multi-arg ABI exercise. The sx-side
example imports the .h via `#import c { #include / #source }` and
prints each result; the snapshot in tests/expected pins today's
parameter + return ABI so Phase 1's #objc_call / #jni_call lowering
work can't silently regress primitive marshalling.

Two findings logged in current/CHECKPOINT-FFI.md's Known issues
section (current behavior, not new bugs): (1) c_import.zig maps
`signed char` -> `u8` not `s8`, and (2) sx integer-literal parser
rejects values >= 2^63 as overflow even when the receiver is u64.
Both worked around in this test without blocking the baseline.
2026-05-19 11:15:13 +03:00
agra
d3e574eae5 import c 2026-02-22 17:24:04 +02:00