selfhost+test: route callee_variadic_param N_DOT via fnparamslookupmod (#16)

Latent silent miscompile surfaced by worker-strcontains3 attempting
strings.contains tagged-variadic graduation: wwstage cgcall's
callee_variadic_param helper (cgenutil.ww:60-70) consumed the N_DOT
callee's leaf via callee.str but routed bare fnparamslookup — bypassed
the module hint at callee.lhs.str. When two modules export same-leaf
fns with differing variadic shapes (e.g. strings.contains(str|rune)...
+ bytes.contains scalar (u8|[]u8)), the bare walk returned the wrong
fn's params for arg-prep while the CALL targeted the correct
module-qualified symbol — ABI mismatch.

Direct sister of #34 (049ebc1) which graduated fnret's N_DOT arm
through fnretlookupmod. #4d's commit body (862715d) explicitly
deferred callee_variadic_param's *mod re-routing pending "future
stdlib port introducing a tagged-vs-scalar or variadic-vs-non-variadic
same-leaf N_DOT collision shape." This is that surfacing.

cgenutil.ww: split callee_variadic_param on callee.kind. N_IDENT stays
on bare fnparamslookup (same-module-first post-#4d). N_DOT routes
through fnparamslookupmod(c, callee.str, callee.lhs.str), pattern-
identical to cgcall's N_DOT branch at cgenexpr.ww:2922-2935.

Cstage cmd/w6c/cgen.c:4279-4302 reads callee params via typed AST
(n->lhs->type + cu->params) — module-aware natively, no sister
change needed (mirrors #4d/#28/#31/#34 cstage no-sister notes).

752_modparam_callee: table-driven 3 rows x 2 stages = 6 fixtures.
cross_module_same_leaf_variadic_vs_scalar (the wedge),
same_module_same_leaf (no-regress), bare_leaf_no_collision (control).

#17 filed for the wider convenience-wrapper audit (enumerate all
wwstage cgen* helpers that take *node and do bare-leaf lookups; sweep
for N_DOT-arm omissions). This commit is narrow to callee_variadic_param.

make test 126/126; ww2==ww3==ww4 byte-id holds via 995_self_rebuild.
This commit is contained in:
2026-05-19 04:56:56 +09:00
parent 77e62e5ed2
commit d9b0c90fbc
5 changed files with 364 additions and 18 deletions

View File

@@ -57,15 +57,33 @@ fn findvariadicparam(ps: *node, nfixed_out: *i32) *node = {
// callee_variadic_param — convenience wrapper: looks up the callee
// by name and finds its variadic param + nfixed. Returns nil if the
// callee isn't registered or has no variadic param.
//
// N_DOT routes through fnparamslookupmod with the module hint
// (callee.lhs.str) — bare fnparamslookup walks same-module-first
// (#4d) which is wrong for a cross-module N_DOT call into a module
// whose same-leaf fn has divergent variadic-vs-non-variadic shape.
// #4d explicitly deferred this re-routing; surfaced by #16 when
// strings.contains gained a variadic shape and a caller's
// bytes.contains call site picked strings.contains' variadic
// params for arg-prep while emitting CALL bytes.contains.
fn callee_variadic_param(c: *cgen, callee: *node, nfixed_out: *i32) *node = {
*nfixed_out = 0;
if (callee == nil) { return nil; };
let cnm: str;
cnm.ptr = nil; cnm.len = 0;
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
if (cnm.len == 0) { return nil; };
let ps: *node = fnparamslookup(c, cnm);
let ps: *node = nil;
if (callee.kind == nkind.N_IDENT) {
if (callee.str.len == 0) { return nil; };
ps = fnparamslookup(c, callee.str);
} else { if (callee.kind == nkind.N_DOT) {
if (callee.str.len == 0) { return nil; };
let cmod: str;
cmod.ptr = nil; cmod.len = 0;
if (callee.lhs != nil) {
if (callee.lhs.kind == nkind.N_IDENT) {
cmod = callee.lhs.str;
};
};
ps = fnparamslookupmod(c, callee.str, cmod);
}; };
return findvariadicparam(ps, nfixed_out);
};