From 608ff34d55206a6572fd7ed2fcd64be6921e0274 Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 19 May 2026 11:59:18 +0300 Subject: [PATCH] =?UTF-8?q?ffi=200.9:=20foreign-result=20chains=20?= =?UTF-8?q?=E2=80=94=20handle=20threaded=20through=20struct=20+=20List?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 96/96 regression tests pass (+ffi-09-foreign-result-chain). Opaque C-handle pattern that mirrors how real sx code threads MTLBuffer*, AAssetManager*, file pointers, etc. through composite sx values. C side has a trivial heap-int handle (`ffi_chain_make` returning `void*`, `ffi_chain_bump` / `_peek` / `_dispose`). The sx side exercises: 1. Chained calls — make -> bump -> bump -> peek; one handle threaded through four FFI sites in sequence. 2. Struct field — `Counter { handle: *void; label: string; }` hosts the handle; methods/accesses go through `.handle` to feed back into C. 3. List(*void) — push N handles, iterate, peek each, iterate again to bump each, iterate again to read back. Catches any aliasing / lifetime breakage when handles round-trip through the slice backing of List. --- examples/ffi-09-foreign-result-chain.c | 22 ++++++ examples/ffi-09-foreign-result-chain.h | 10 +++ examples/ffi-09-foreign-result-chain.sx | 79 +++++++++++++++++++ .../expected/ffi-09-foreign-result-chain.exit | 1 + .../expected/ffi-09-foreign-result-chain.txt | 13 +++ 5 files changed, 125 insertions(+) create mode 100644 examples/ffi-09-foreign-result-chain.c create mode 100644 examples/ffi-09-foreign-result-chain.h create mode 100644 examples/ffi-09-foreign-result-chain.sx create mode 100644 tests/expected/ffi-09-foreign-result-chain.exit create mode 100644 tests/expected/ffi-09-foreign-result-chain.txt diff --git a/examples/ffi-09-foreign-result-chain.c b/examples/ffi-09-foreign-result-chain.c new file mode 100644 index 0000000..5a0c62a --- /dev/null +++ b/examples/ffi-09-foreign-result-chain.c @@ -0,0 +1,22 @@ +#include "ffi-09-foreign-result-chain.h" +#include + +void *ffi_chain_make(int seed) { + int *p = (int *)malloc(sizeof(int)); + if (p) *p = seed; + return p; +} + +int ffi_chain_bump(void *h, int delta) { + int *p = (int *)h; + *p += delta; + return *p; +} + +int ffi_chain_peek(void *h) { + return *(int *)h; +} + +void ffi_chain_dispose(void *h) { + free(h); +} diff --git a/examples/ffi-09-foreign-result-chain.h b/examples/ffi-09-foreign-result-chain.h new file mode 100644 index 0000000..9bf283a --- /dev/null +++ b/examples/ffi-09-foreign-result-chain.h @@ -0,0 +1,10 @@ +// Trivial opaque-handle pattern — `make` produces a heap-allocated +// counter, `bump` returns the new value, `peek` reads without +// mutating, `dispose` frees. Mirrors the shape of real C handles +// (MTLBuffer*, AAssetManager*, file pointers, etc.) without pulling +// in any platform deps. + +void *ffi_chain_make (int seed); +int ffi_chain_bump (void *h, int delta); +int ffi_chain_peek (void *h); +void ffi_chain_dispose (void *h); diff --git a/examples/ffi-09-foreign-result-chain.sx b/examples/ffi-09-foreign-result-chain.sx new file mode 100644 index 0000000..f3c5c26 --- /dev/null +++ b/examples/ffi-09-foreign-result-chain.sx @@ -0,0 +1,79 @@ +// Phase 0 baseline (PLAN-FFI.md step 0.9): FFI result chains. The +// shapes that real sx code uses for opaque C handles (MTLBuffer*, +// AAssetManager*, file pointers, ...) — passing a C-returned +// pointer into another C call, stashing it in a struct field, +// pushing into a `List(*void)`, and iterating that list to feed each +// handle back through C. +// +// No new ABI shape — pointer-in, pointer-out. The lemma locked in: +// handle-shaped flows survive sx's struct-field assignment, List +// storage, and iteration-then-call cycles. + +#import "modules/std.sx"; + +#import c { + #include "ffi-09-foreign-result-chain.h"; + #source "ffi-09-foreign-result-chain.c"; +}; + +// Struct field hosts an FFI-returned handle. +Counter :: struct { + handle: *void = null; + label: string; +} + +main :: () -> s32 { + // ── 1. Chain: make → bump → peek ─────────────────────────────── + a := ffi_chain_make(100); + print("peek after make = {}\n", ffi_chain_peek(a)); + print("bump(+5) = {}\n", ffi_chain_bump(a, 5)); + print("bump(+3) = {}\n", ffi_chain_bump(a, 3)); + print("peek after bumps = {}\n", ffi_chain_peek(a)); + + // ── 2. Stash handle in a struct field, use through `.handle` ── + c : Counter = .{ handle = ffi_chain_make(50), label = "ctr-a" }; + print("ctr label = {}\n", c.label); + print("ctr peek = {}\n", ffi_chain_peek(c.handle)); + ffi_chain_bump(c.handle, 7); + print("ctr after bump = {}\n", ffi_chain_peek(c.handle)); + + // ── 3. Push handles into a List, iterate, feed back to C ────── + handles : List(*void) = .{}; + i : s32 = 0; + while i < 3 { + h := ffi_chain_make(i * 10); + handles.append(h); + i += 1; + } + + j : s64 = 0; + while j < handles.len { + h := handles.items[j]; + v := ffi_chain_peek(h); + print("list[{}] peek = {}\n", j, v); + j += 1; + } + + // Iterate again, bump each, observe the cumulative effect. + j = 0; + while j < handles.len { + ffi_chain_bump(handles.items[j], 1); + j += 1; + } + j = 0; + while j < handles.len { + print("list[{}] after bump= {}\n", j, ffi_chain_peek(handles.items[j])); + j += 1; + } + + // ── Cleanup ───────────────────────────────────────────────────── + ffi_chain_dispose(a); + ffi_chain_dispose(c.handle); + j = 0; + while j < handles.len { + ffi_chain_dispose(handles.items[j]); + j += 1; + } + + 0; +} diff --git a/tests/expected/ffi-09-foreign-result-chain.exit b/tests/expected/ffi-09-foreign-result-chain.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/ffi-09-foreign-result-chain.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/ffi-09-foreign-result-chain.txt b/tests/expected/ffi-09-foreign-result-chain.txt new file mode 100644 index 0000000..91bf857 --- /dev/null +++ b/tests/expected/ffi-09-foreign-result-chain.txt @@ -0,0 +1,13 @@ +peek after make = 100 +bump(+5) = 105 +bump(+3) = 108 +peek after bumps = 108 +ctr label = ctr-a +ctr peek = 50 +ctr after bump = 57 +list[0] peek = 0 +list[1] peek = 10 +list[2] peek = 20 +list[0] after bump= 1 +list[1] after bump= 11 +list[2] after bump= 21