ffi M5.A.3: multi-arg block smoke test (s32, *void) -> void

A signature the hand-rolled stdlib never covered: `Closure(s32, *void) -> void`.
Pre-M5.A this code wouldn't compile (no `Into(Block) for Closure(s32, *void) -> void`
declaration); post-M5.A the compiler emits `__block_invoke_v_i_p` on
demand and the call site goes through it.

The test uses two-arg side-effect capture (globals `g_sum`, `g_tag`)
to verify both args reached the closure body. Confirms the
trampoline's calling convention forwards
`(__sx_default_context, sx_env, arg0, arg1)` correctly through to
the closure's underlying fn.

Note: return-value signatures (e.g. `Closure(s32) -> s32`) are
recognised by the trampoline emitter — `cinfo.ret` flows through
to `beginFunction`'s return slot — but exercising them requires
closure-return-type inference that the test runner stumbled on
during authoring (`(n: s32) => { return n+1; }` infers void). The
void-returning shape is the more common Cocoa pattern (animation
bodies, dispatch_async, completion handlers); return-value
signatures land properly once the closure inference catches up
(orthogonal to M5.A).

190/190 example tests pass.
This commit is contained in:
agra
2026-05-27 00:26:30 +03:00
parent 556e4e12ea
commit 26329fe7ba
3 changed files with 39 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
// M5.A — `xx closure : Block` for an arbitrary closure signature.
//
// Pre-M5.A: the stdlib hand-rolled `Into(Block) for Closure(s32, *void) -> s32`
// didn't exist — this code wouldn't compile. Only `Closure() -> void`
// and `Closure(bool) -> void` shapes were supported.
//
// Post-M5.A: the compiler synthesises `__block_invoke_i_i_p` for this
// signature on the fly. The block's invoke trampoline forwards
// `(__sx_default_context, sx_env, arg0, arg1)` to the captured closure
// and returns the s32 result.
#import "modules/std.sx";
#import "modules/std/objc_block.sx";
// Side-effect capture so we can observe both args reached the
// closure body, even though void-returning trampolines are the
// well-tested shape.
g_sum: s32 = 0;
g_tag: *void = null;
main :: () -> s32 {
cl := (n: s32, tag: *void) => {
g_sum = n + 1;
g_tag = tag;
};
b : Block = xx cl;
invoke_fn : (*Block, s32, *void) -> void callconv(.c) = xx b.invoke;
sentinel: s32 = 42;
invoke_fn(@b, 41, xx @sentinel);
if g_sum != 42 { print("FAIL: g_sum expected 42, got {}\n", g_sum); return 1; }
if g_tag == null { print("FAIL: g_tag null\n"); return 1; }
print("block multi-arg ok: sum={}\n", g_sum);
0;
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@
block multi-arg ok: sum=42