fix(0128): foreign cstring returns + conflicting same-symbol bindings

Two genuine defects behind the 0128 filing (whose original repros were
both poisoned by binding getenv, which std already declares -> *u8):

1. Re-declaring a C symbol was silent first-wins: every call through
   the later declaration was typed by the older signature. Foreign
   registration now dedupes — equal signatures share one FuncId,
   conflicting ones are diagnosed.

2. Foreign -> string / -> ?string returns read garbage: C returns one
   char*, but the LLVM signature declared the fat {ptr,i64} (len =
   register garbage), and ?string was mis-declared SRET (the hidden
   out-pointer landed in the callee's first arg register). cstrRetKind
   now classifies such returns, declares them as plain ptr (never
   sret), and the call site synthesizes {ptr, strlen} via a
   branch-guarded strlen (NULL -> {null,0} / optional null), wrapping
   {string, i1} for ?string.

?[:0]u8 itself resolves fine (it is ?string); the spelling works in
return, param, local, and alias positions.

Regression: examples/1221 (plain + optional non-null + NULL paths) and
examples/1172 (conflict diagnostic); both FAIL pre-fix. The extern
dedupe collapses duplicate libc decls, so affected .ir snapshots were
regenerated. zig build test 426/426; run_examples 602/602;
distribution suite 21/21.
This commit is contained in:
agra
2026-06-12 14:13:01 +03:00
parent a8fbded567
commit d88bdd7242
50 changed files with 24903 additions and 28907 deletions

View File

@@ -804,7 +804,10 @@ pub const Ops = struct {
};
const callee_needs_c_abi = callee_func.is_extern or callee_func.call_conv == .c;
const callee_raw_ret = self.e.toLLVMType(callee_func.ret);
const callee_uses_sret = callee_needs_c_abi and self.e.needsByval(callee_func.ret, callee_raw_ret);
// Foreign string/?string returns receive one `char *` — never sret
// (must mirror declareFunction's signature classification).
const cstr_ret = self.e.cstrRetKind(callee_func);
const callee_uses_sret = callee_needs_c_abi and cstr_ret == .none and self.e.needsByval(callee_func.ret, callee_raw_ret);
// When the callee uses sret, prepend an alloca for the result.
// Index alignment: actual_args[0] = sret_slot; actual_args[i+1] = sx arg i.
@@ -859,6 +862,10 @@ pub const Ops = struct {
c.LLVMAddCallSiteAttribute(result, param1_idx, sret_attr);
// Load the actual struct value the callee wrote into the slot.
result = c.LLVMBuildLoad2(self.e.builder, callee_raw_ret, sret_slot, "sret.load");
} else if (!call_is_void_like and cstr_ret != .none) {
// The C side returned `char *`; build the fat sx string (and the
// optional wrapper) from it.
result = self.e.cstrReturnToSx(result, cstr_ret == .optional);
} else if (!call_is_void_like and callee_func.is_extern) {
// Coerce ABI return value (e.g. i64 / [2 x i64]) back to IR struct type if needed
const expected_ty = self.e.toLLVMType(instruction.ty);