selfhost+test: graduate bare-leaf fnparamslookup same-module-first (#4d)
Class A silent miscompile, latent until two modules export the same fn leaf name with diverging tagged-vs-scalar param shapes. Wwstage's fnparamslookup (selfhost/cmd/wcc/cgen.ww) walked c.fnrets head-first by fname and returned the FIRST match's params. cgcall's N_IDENT branch (cgenexpr.ww:2875) handed it the bare leaf; pushargsrev's istaggedtype(c, pt) then fired against the wrong-module foo's param-type. A foo(7) call against a same-leaf (i32 | void) param re-laid the i32 arg into a 2-word tagged slot (MOVQ $7 push + MOVQ $0 tag push + 2 POPs into DI/SI) instead of the caller-intended single push (MOVQ $7 push + POPQ DI). Cstage carries no sister bug: cmd/wcc/check.c N_CALL routes cexpr(c, n->lhs) through scope_lookup_prefer for an N_IDENT callee, then cmd/w6c/cgen.c reads params from the typed n->lhs->type's TY_FN sig — module-aware via typed AST, sidestepping any bare-leaf table. cs vs ws diverged on every same-leaf fn collision but no in-tree corpus declares two same-leaf fns with diverging tagged-vs- scalar param shapes (same surfacing pattern as #4a enumlookup post-strings, #4b structlookup, #4c def): 995 stays green. Seventh leaf of the trio graduation (after #27 aliaslookup, #28 fnparams *mod*-variant, #31 fnret *mod*-variant, #4a enum, #4b struct, #4c def). fnparamslookupmod (the N_DOT consumer at cgenexpr.ww:2876) already exists post-#28; this commit graduates only the BARE-LEAF entry point with a same-module-first walk mirroring aliaslookup's two-pass shape (cgen.ww:75). Three bare- leaf callsites consume the graduated lookup uniformly: cgcall N_IDENT branch at cgenexpr.ww:2875 (load-bearing for the tagged- widening shape), cglocalsize scratch reservation at cgendecl.ww:420 (fires only on tagged-param + struct-payload arg), and callee_variadic_param at cgenutil.ww:66 (fires only on variadic callee). The latter two also accept N_DOT callees and feed the bare leaf — pre-graduation those head-picked, post-graduation they prefer same-module. NOT separately re-routed to fnparamslookupmod in this commit: the only in-tree N_DOT cross- module fn collisions (strings.next vs utf8.next; bytes.hasprefix vs strings.hasprefix and equivalents) all have invariant param shape across the colliding overloads, so widening/scratch/variadic behavior is invariant either way for sites 2 and 3 on the present corpus. A future stdlib port introducing a tagged-vs-scalar or variadic-vs-non-variadic same-leaf N_DOT collision shape will need the *mod re-routing — file at that surfacing. 732_fnparams_bare_leaf_shadow pins the fix with 1 row: alpha defines fn foo(x: i32) i32 and fn alphacaller() i32 = { return foo(7); }, beta defines fn foo(x: (i32|void)) i32 declared LAST in source so beta.foo prepends to the head of c.fnrets. alphacaller's bare foo(7) must compile against alpha.foo's i32 param (single PUSHQ/POPQ DI shape) even with beta.foo at the head of c.fnrets. Asserts the matching POPQ DI inside the right TEXT sym + bad_imm POPQ SI anti-check on each stage plus cs-vs-ws byte-id per row.
This commit is contained in:
@@ -1596,11 +1596,24 @@ fn fnretlookupmod(c: *cgen, name: str, mod: str) *node = {
|
||||
};
|
||||
|
||||
// fnparamslookup — head of the declared param-list for a fn, or nil
|
||||
// if the name isn't a registered fn. Used by cgcall / pushargsrev to
|
||||
// detect implicit widening from a concrete variant into a tagged-union
|
||||
// parameter slot.
|
||||
// if the name isn't a registered fn. Same-module-first walk before the
|
||||
// head-walk fallback. Trio-leaf graduation (#4d) mirroring aliaslookup
|
||||
// (#27), fnret/fnparamslookupmod (#28/#31), enum/struct/deflookup
|
||||
// (#4a/#4b/#4c): without the prefer pass a bare-leaf `foo(x)` call in
|
||||
// module M (callee N_IDENT) silently picks another module's same-leaf
|
||||
// `foo` from the head of c.fnrets, then pushargsrev's widening
|
||||
// detection fires (or doesn't) against the wrong param-type — `foo(7)`
|
||||
// against a same-leaf `(i32 | void)` param re-layouts 7 into a 2-word
|
||||
// tagged slot vs the same-module `i32` param's single push.
|
||||
fn fnparamslookup(c: *cgen, name: str) *node = {
|
||||
let f: *fnret = c.fnrets;
|
||||
for (f != nil) {
|
||||
if (streq(f.fname, name)) {
|
||||
if (streq(f.fmod, c.curmod)) { return f.params; };
|
||||
};
|
||||
f = f.frnext;
|
||||
};
|
||||
f = c.fnrets;
|
||||
for (f != nil) {
|
||||
if (streq(f.fname, name)) { return f.params; };
|
||||
f = f.frnext;
|
||||
|
||||
Reference in New Issue
Block a user