From 31ab175d5654eea077e138373ad32f7920f2bbf9 Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 19 May 2026 11:48:34 +0300 Subject: [PATCH] ffi 0.6: C-to-sx callback baseline (1-arg + ctx-ptr forms) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- examples/ffi-06-callback.sx | 59 +++++++++++++++++++++++++++++ tests/expected/ffi-06-callback.exit | 1 + tests/expected/ffi-06-callback.txt | 7 ++++ vendors/ffi_callback/ffi_callback.c | 9 +++++ vendors/ffi_callback/ffi_callback.h | 13 +++++++ 5 files changed, 89 insertions(+) create mode 100644 examples/ffi-06-callback.sx create mode 100644 tests/expected/ffi-06-callback.exit create mode 100644 tests/expected/ffi-06-callback.txt create mode 100644 vendors/ffi_callback/ffi_callback.c create mode 100644 vendors/ffi_callback/ffi_callback.h diff --git a/examples/ffi-06-callback.sx b/examples/ffi-06-callback.sx new file mode 100644 index 0000000..712fa28 --- /dev/null +++ b/examples/ffi-06-callback.sx @@ -0,0 +1,59 @@ +// Phase 0 baseline (PLAN-FFI.md step 0.6): sx function passed to C +// as a function pointer; C invokes it; sx-side observable effect. +// Mirrors the `app->onInputEvent` install pattern in +// library/modules/platform/android.sx. +// +// Two arities covered: +// 1. (s32) -> s32 — single-arg callback +// 2. (*void, s32) -> s32 — pointer + value (onInputEvent shape) +// +// Plus a side-effect via a global so we can confirm the callback +// actually fired (return value + state mutation both observable). + +#import "modules/std.sx"; + +#import c { + #source "vendors/ffi_callback/ffi_callback.c"; +}; + +ffi_apply_callback :: (cb: (s32) -> s32, value: s32) -> s32 #foreign; +ffi_apply_callback2 :: (cb: (*void, s32) -> s32, ctx: *void, v: s32) -> s32 #foreign; + +g_callback_hits : s32 = 0; +g_callback_sum : s32 = 0; + +double_it :: (x: s32) -> s32 { + g_callback_hits += 1; + g_callback_sum += x; + x * 2; +} + +add_with_ctx :: (ctx: *void, v: s32) -> s32 { + g_callback_hits += 1; + // Pass a sentinel via ctx to prove the pointer arg also survives the + // round-trip — read it back as an s32 through *s32. + p : *s32 = xx ctx; + p.* + v; +} + +main :: () -> s32 { + // ── Single-arg callback ──────────────────────────────────────── + r1 := ffi_apply_callback(double_it, 21); + print("callback returned = {}\n", r1); + print("hits after first call = {}\n", g_callback_hits); + print("sum after first call = {}\n", g_callback_sum); + + // Two more calls confirm the same fn pointer keeps working. + ffi_apply_callback(double_it, 7); + ffi_apply_callback(double_it, 11); + print("hits after three calls = {}\n", g_callback_hits); + print("sum after three calls = {}\n", g_callback_sum); + + // ── Two-arg callback with opaque ctx pointer ─────────────────── + ctx_val : s32 = 100; + r2 := ffi_apply_callback2(add_with_ctx, xx @ctx_val, 42); + print("ctx + value = {}\n", r2); + print("hits after ctx callback = {}\n", g_callback_hits); + + 0; +} diff --git a/tests/expected/ffi-06-callback.exit b/tests/expected/ffi-06-callback.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/ffi-06-callback.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/ffi-06-callback.txt b/tests/expected/ffi-06-callback.txt new file mode 100644 index 0000000..a9b8285 --- /dev/null +++ b/tests/expected/ffi-06-callback.txt @@ -0,0 +1,7 @@ +callback returned = 42 +hits after first call = 1 +sum after first call = 21 +hits after three calls = 3 +sum after three calls = 39 +ctx + value = 142 +hits after ctx callback = 4 diff --git a/vendors/ffi_callback/ffi_callback.c b/vendors/ffi_callback/ffi_callback.c new file mode 100644 index 0000000..605b43f --- /dev/null +++ b/vendors/ffi_callback/ffi_callback.c @@ -0,0 +1,9 @@ +#include "ffi_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); +} diff --git a/vendors/ffi_callback/ffi_callback.h b/vendors/ffi_callback/ffi_callback.h new file mode 100644 index 0000000..5441289 --- /dev/null +++ b/vendors/ffi_callback/ffi_callback.h @@ -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);