wwstage: make the cgdot alias-peel struct-break module-aware (#223)

The wwstage cgdot #191 alias-peel loop broke on a name-keyed any-module
structlookup, so a receiver whose alias name collides with a struct of the
same name in ANOTHER module resolved to the foreign struct and fell through to
an undefined `name(SB)` global instead of the field load. The eFinal FLIP
renames io's `vstream` -> `stream`, which collides with memio's `stream`
struct, so io.read/io.write/io.close's `match (s.reader)` emitted
`MOVQ reader(SB), AX` (reader is also a type-alias) -> cs != ww (cstage chases
the nominal TY_NAMED.under pointer chain, module-correct). Gate-blind: on
master both stages emit the same wrong store so byte-id stays green; the FLIP
corpus is the first to put the io-alias/memio-struct collision in one build.

Fix (wwstage-only align-down; cstage is the authority and is untouched): make
the peel's struct-break MODULE-AWARE — break only on a same-module struct (a
genuine struct-value receiver); a same-module alias keeps peeling to its
underlying (io.stream -> *vtable -> the pointer field-load arm); a foreign leaf
keeps the prior any-module heuristic. New structsamemod / aliassamemod mirror
the same-module-first pass already in structlookup / aliaslookup. This is not a
naive alias-first reorder (which would reintroduce the mirror collision: a
same-module struct plus a foreign same-leaf alias). Peel-only — the direct-
struct arm's broader cross-module same-leaf-STRUCT name-keying is filed as #224.

#208-family (name-keyed resolution dropping to a wrong global) but in cgen, not
the checker; #213 is distinct (cosmetic local-struct-match divergence).

test/wcc/784_xmod_alias_struct_collide_run: collision (cross-module alias-vs-
struct, same leaf), symmetric (guards the same-module-struct break against a
naive reorder), and a no-collision control — branched callee. The discriminating
net is cs.s == ww.s (the path is gate-blind and cstage is correct, so byte-id
flips when wwstage is fixed); confirmed by source-revert. The FLIP's combined.ww
is now cs.s == ww.s byte-identical.
This commit is contained in:
2026-05-30 02:00:07 +09:00
parent 1175711021
commit 80184a3acf
7 changed files with 528 additions and 9 deletions

View File

@@ -1381,6 +1381,23 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
return nil;
};
// #223: same-module-ONLY struct lookup. structlookup's any-module
// fallback returns a foreign same-leaf struct; the cgdot alias-peel
// needs to break ONLY on a struct that THIS module defines (a genuine
// struct-value receiver), not on a foreign struct that merely shares a
// leaf with a same-module alias (io.stream alias vs memio.stream
// struct). Returns the struct only when it lives in c.curmod.
fn structsamemod(c: *cgen, name: str) *structinfo = {
let s: *structinfo = c.structs;
for (s != nil) {
if (streq(s.sname, name)) {
if (streq(s.smod, c.curmod)) { return s; };
};
s = s.sinext;
};
return nil;
};
// primsize — size in bytes of a primitive type name (or 0 if not
// recognised as a primitive — the caller falls back to other paths).
// fldnumidx — parse a tuple field name like "0" / "1" / "12" into an