From 9e76a83f69b89558c09d38ccf9991c02fc2eea4a Mon Sep 17 00:00:00 2001 From: agra Date: Thu, 28 May 2026 12:24:49 +0300 Subject: [PATCH] =?UTF-8?q?ffi=20block-string-arg=20ABI=20mismatch=20?= =?UTF-8?q?=E2=80=94=20expected-failing=20lock-in?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generic `Into(Block) for Closure(string) -> void` (step 5.2) emits a trampoline whose `callconv(.c)` param type collapses through `abiCoerceParamType`'s `string → ptr` heuristic — the libc "char *" convention. The caller side (typed fn-pointer cast + indirect call through `b.invoke`) keeps the full `{ptr, i64}` slice. Result on AArch64: caller passes 16 bytes in x0+x1, trampoline reads 8 bytes from x0 only, the slice len is lost or mis-tracked, and the trampoline's `memcpy` from the half-formed string segfaults. `examples/188-block-string-arg.sx` pins the post-fix behaviour ("got: "). Today's run segfaults inside the trampoline's first read. The next commit splits `abiCoerceParamType` into a foreign-only path (extern decls keep the libc collapse) and a preserve-slice path (sx-internal `callconv(.c)`). --- examples/188-block-string-arg.sx | 28 ++++++++++++++++++++++++ tests/expected/188-block-string-arg.exit | 1 + tests/expected/188-block-string-arg.txt | 1 + 3 files changed, 30 insertions(+) create mode 100644 examples/188-block-string-arg.sx create mode 100644 tests/expected/188-block-string-arg.exit create mode 100644 tests/expected/188-block-string-arg.txt diff --git a/examples/188-block-string-arg.sx b/examples/188-block-string-arg.sx new file mode 100644 index 0000000..1fe8a8e --- /dev/null +++ b/examples/188-block-string-arg.sx @@ -0,0 +1,28 @@ +// Generic `Into(Block)` impl with a `string`-typed arg in the +// closure signature. The block trampoline declares the param with +// callconv(.c); without the abi-collapse fix, sx `string` got +// silently collapsed to `ptr` (the libc `char *` heuristic) and +// the caller's 16-byte `{ptr, len}` value mismatched the +// trampoline's 8-byte `ptr` slot. Result: segfault inside the +// trampoline's first read. +// +// The fix lives in `abiCoerceParamTypeEx`: the `string`/`slice` → +// `ptr` collapse only applies to `is_extern` foreign decls (libc +// interop). sx-internal `callconv(.c)` keeps the full slice +// shape, which lands as `[2 x i64]` at the LLVM signature site +// and matches the caller's two-register pass on AArch64. + +#import "modules/std.sx"; +#import "modules/std/objc_block.sx"; + +g_s: string = ""; + +main :: () -> s32 { + cl := (s: string) => { g_s = s; }; + b : Block = xx cl; + invoke_fn : (*Block, string) -> void callconv(.c) = xx b.invoke; + invoke_fn(@b, "hello"); + if g_s.len == 0 { print("FAIL: empty\n"); return 1; } + print("got: <{}>\n", g_s); + 0; +} diff --git a/tests/expected/188-block-string-arg.exit b/tests/expected/188-block-string-arg.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/188-block-string-arg.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/188-block-string-arg.txt b/tests/expected/188-block-string-arg.txt new file mode 100644 index 0000000..30da966 --- /dev/null +++ b/tests/expected/188-block-string-arg.txt @@ -0,0 +1 @@ +got: