ffi block-string-arg ABI fix: split foreign-C-API collapse from callconv(.c)
`abiCoerceParamType` had a libc-friendly heuristic: sx `string` /
`[]T` slice → `ptr` (drop the len, just pass the start pointer).
The heuristic is right for `#foreign` decls that mirror libc
signatures (`puts(const char *)`, `strlen(const char *)`); it's
wrong for sx-internal `callconv(.c)` (e.g. block trampolines) where
both sides see and exchange the full slice.
Split via a new `abiCoerceParamTypeEx(ir_ty, llvm_ty,
is_foreign_c_api)`. The old single-arg form forwards with
`is_foreign_c_api = true` so every call site that already collapses
keeps doing so. The function-decl emit at lines 1442 / 1454 now
passes `func.is_extern` — sx-internal `callconv(.c)` declarations
take the false path and preserve the slice as `{ptr, i64}` →
`[2 x i64]` via the general struct-coerce branch (true C ABI for
a 16-byte aggregate: passed in x0+x1 on AArch64).
`examples/188-block-string-arg.sx` flips green ("got: <hello>");
suite stays at 222/222. Foreign-decl call sites
(objc msg_send / JNI / direct extern calls) keep the libc
collapse — they pass `is_foreign_c_api = true` via the legacy
`abiCoerceParamType` shim.
This commit is contained in:
@@ -1439,7 +1439,7 @@ pub const LLVMEmitter = struct {
|
||||
const uses_sret = needs_c_abi and !is_main and self.needsByval(func.ret, raw_ret_ty);
|
||||
const ret_ty = if (is_main) self.cached_i32
|
||||
else if (uses_sret) self.cached_void
|
||||
else if (needs_c_abi) self.abiCoerceParamType(func.ret, raw_ret_ty)
|
||||
else if (needs_c_abi) self.abiCoerceParamTypeEx(func.ret, raw_ret_ty, func.is_extern)
|
||||
else raw_ret_ty;
|
||||
|
||||
// Build parameter types — apply C ABI coercion for foreign/callconv(.c) functions.
|
||||
@@ -1451,7 +1451,7 @@ pub const LLVMEmitter = struct {
|
||||
if (uses_sret) param_types[0] = self.cached_ptr;
|
||||
for (func.params, 0..) |param, j| {
|
||||
const llvm_ty = self.toLLVMType(param.ty);
|
||||
param_types[j + sret_offset] = if (needs_c_abi) self.abiCoerceParamType(param.ty, llvm_ty) else llvm_ty;
|
||||
param_types[j + sret_offset] = if (needs_c_abi) self.abiCoerceParamTypeEx(param.ty, llvm_ty, func.is_extern) else llvm_ty;
|
||||
}
|
||||
|
||||
const is_var_arg: c_int = if (func.is_variadic) 1 else 0;
|
||||
@@ -4241,11 +4241,28 @@ pub const LLVMEmitter = struct {
|
||||
// - HFA (homogeneous float aggregate) → leave as-is (LLVM handles it)
|
||||
|
||||
fn abiCoerceParamType(self: *LLVMEmitter, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef) c.LLVMTypeRef {
|
||||
// String/slice → raw pointer (universal across all targets for foreign calls)
|
||||
if (ir_ty == .string) return self.cached_ptr;
|
||||
if (!ir_ty.isBuiltin()) {
|
||||
const info = self.ir_mod.types.get(ir_ty);
|
||||
if (info == .slice) return self.cached_ptr;
|
||||
return self.abiCoerceParamTypeEx(ir_ty, llvm_ty, true);
|
||||
}
|
||||
|
||||
/// Same as `abiCoerceParamType` but with an explicit
|
||||
/// `is_foreign_c_api` knob. When true, sx `string` / `[]T` slices
|
||||
/// collapse to `ptr` — the libc convention where the user writes
|
||||
/// `string` to mean `char *` and the length is dropped. When
|
||||
/// false (sx-internal `callconv(.c)` like block trampolines), the
|
||||
/// full slice shape is preserved and goes through the general
|
||||
/// struct-coerce path (16-byte slice → `[2 x i64]`, lands in two
|
||||
/// registers on AArch64 — the true C ABI for a 16-byte
|
||||
/// aggregate). Without the split, sx-to-sx calls through a
|
||||
/// `(*Block, string) -> void callconv(.c)` fn-pointer mismatched
|
||||
/// the caller's `{ptr, i64}` value against the trampoline's
|
||||
/// collapsed `ptr` param.
|
||||
fn abiCoerceParamTypeEx(self: *LLVMEmitter, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef, is_foreign_c_api: bool) c.LLVMTypeRef {
|
||||
if (is_foreign_c_api) {
|
||||
if (ir_ty == .string) return self.cached_ptr;
|
||||
if (!ir_ty.isBuiltin()) {
|
||||
const info = self.ir_mod.types.get(ir_ty);
|
||||
if (info == .slice) return self.cached_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
// WASM32: usize/isize are pointer-sized (i32 on wasm32).
|
||||
|
||||
Reference in New Issue
Block a user