From d0ccb92ef7faaef2a5c7995b9ccb660b7b1471ff Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 19 May 2026 11:57:44 +0300 Subject: [PATCH] ffi 0.8: #foreign call sites inside struct/protocol/closure/inline-if MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 95/95 regression tests pass (+ffi-08-foreign-in-method). One trivial C helper (`ffi_method_helper`) called from each of the major sx surface constructs that can host an FFI site: 1. struct method body Counter.next 2. protocol impl method body impl Doubler for Counter 3. closure value body make_adder's `closure(...)` 4. comptime-gated branch `inline if OS == .macos { ... }` No new ABI shapes — the lowering route a `#foreign` call site takes shouldn't depend on its enclosing construct, and the test pins that lemma. A future lowering refactor that, say, breaks protocol-dispatch fast-paths for FFI-calling impl methods will fail here directly instead of being caught only by the chess Android regression. The `inline if` branches for ios/linux compile down to nothing on macOS, so only the macOS arm fires at runtime — useful smoke test that the comptime gate works around FFI sites too. --- examples/ffi-08-foreign-in-method.c | 3 + examples/ffi-08-foreign-in-method.h | 1 + examples/ffi-08-foreign-in-method.sx | 71 ++++++++++++++++++++ tests/expected/ffi-08-foreign-in-method.exit | 1 + tests/expected/ffi-08-foreign-in-method.txt | 5 ++ 5 files changed, 81 insertions(+) create mode 100644 examples/ffi-08-foreign-in-method.c create mode 100644 examples/ffi-08-foreign-in-method.h create mode 100644 examples/ffi-08-foreign-in-method.sx create mode 100644 tests/expected/ffi-08-foreign-in-method.exit create mode 100644 tests/expected/ffi-08-foreign-in-method.txt diff --git a/examples/ffi-08-foreign-in-method.c b/examples/ffi-08-foreign-in-method.c new file mode 100644 index 0000000..e4e0717 --- /dev/null +++ b/examples/ffi-08-foreign-in-method.c @@ -0,0 +1,3 @@ +#include "ffi-08-foreign-in-method.h" + +int ffi_method_helper(int x) { return x * 10; } diff --git a/examples/ffi-08-foreign-in-method.h b/examples/ffi-08-foreign-in-method.h new file mode 100644 index 0000000..e0a495e --- /dev/null +++ b/examples/ffi-08-foreign-in-method.h @@ -0,0 +1 @@ +int ffi_method_helper(int x); diff --git a/examples/ffi-08-foreign-in-method.sx b/examples/ffi-08-foreign-in-method.sx new file mode 100644 index 0000000..a0bf12f --- /dev/null +++ b/examples/ffi-08-foreign-in-method.sx @@ -0,0 +1,71 @@ +// Phase 0 baseline (PLAN-FFI.md step 0.8): `#foreign` C call sites +// embedded inside the major sx surface constructs. None of these +// touch a new ABI shape — they only verify lowering routes the call +// through identically regardless of the enclosing context: +// +// 1. struct method body (Counter.next) +// 2. protocol impl method body (impl Doubler for Counter) +// 3. closure value body (closure { ... }) +// 4. comptime-gated branch (inline if OS == ...) + +#import "modules/std.sx"; +#import "modules/compiler.sx"; + +#import c { + #include "ffi-08-foreign-in-method.h"; + #source "ffi-08-foreign-in-method.c"; +}; + +// ── 1. Struct method calling a #foreign fn ─────────────────────────── +Counter :: struct { + seed: s32 = 0; + next :: (self: *Counter) -> s32 { + v := ffi_method_helper(self.seed); + self.seed += 1; + v; + } +} + +// ── 2. Protocol impl method calling a #foreign fn ──────────────────── +Doubler :: protocol { + doubled :: (self: *Self) -> s32; +} + +impl Doubler for Counter { + doubled :: (self: *Counter) -> s32 { + ffi_method_helper(self.seed) * 2; + } +} + +// ── 3. Closure body calling a #foreign fn ──────────────────────────── +make_adder :: (bias: s32) -> Closure(s32) -> s32 { + closure((x: s32) -> s32 => ffi_method_helper(x) + bias); +} + +main :: () -> s32 { + c : Counter = .{ seed = 1 }; + + // 1. struct method + print("method next 1 = {}\n", c.next()); + print("method next 2 = {}\n", c.next()); + + // 2. protocol method (still operating on the now-bumped Counter) + print("protocol = {}\n", c.doubled()); + + // 3. closure + adder := make_adder(100); + print("closure(5) = {}\n", adder(5)); + + // 4. inline if OS branch — only one platform's call actually emits + inline if OS == .macos { + print("inline if macos = {}\n", ffi_method_helper(7)); + } + inline if OS == .ios { + print("inline if ios = {}\n", ffi_method_helper(7)); + } + inline if OS == .linux { + print("inline if linux = {}\n", ffi_method_helper(7)); + } + + 0; +} diff --git a/tests/expected/ffi-08-foreign-in-method.exit b/tests/expected/ffi-08-foreign-in-method.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/ffi-08-foreign-in-method.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/ffi-08-foreign-in-method.txt b/tests/expected/ffi-08-foreign-in-method.txt new file mode 100644 index 0000000..b864c00 --- /dev/null +++ b/tests/expected/ffi-08-foreign-in-method.txt @@ -0,0 +1,5 @@ +method next 1 = 10 +method next 2 = 20 +protocol = 60 +closure(5) = 150 +inline if macos = 70