selfhost+cstage+test: graduate deflookup/deflookuprhs same-module-first (#4c)

Class A silent miscompile, latent until two modules export the same
str-typed def leaf name and the .ptr/.len field-fold path consumes
the wrong-module strlit address/length. Wwstage's deflookuprhs
(selfhost/cmd/wcc/cgen.ww) walked c.defs head-first by dname; cgdot's
.ptr/.len field-fold handed it the bare leaf from N_IDENT.str,
silently inlining the wrong-module strlit. Cstage carries the same
shape at cmd/w6c/cgen.c (Sdef walk #3 N_DOT field-fold): Sdef keyed
by name only, head-pick on every cross-module collision. No in-tree
corpus declares two same-leaf str defs, so 995_self_rebuild stayed
green (same surfacing pattern as #4a enumlookup post-strings and
#4b structlookup).

Sixth leaf of the trio leaf-name lookup graduation (after #27
aliaslookup, #28 fnparams, #31 fnret, #4a enum, #4b struct). Same
bundle precedent as #4a (which bundled wwstage enumlookup +
enumlookupmod + cstage scope_lookup_prefer sister fix under one
structural concern): four sister changes ship together.

  - defent +dmod field; collectdefs captures d.module.
  - wwstage deflookup two-pass walk — cosmetic (bool return is
    invariant under head-pick vs same-module-first), kept for
    structural symmetry with deflookuprhs.
  - wwstage deflookuprhs two-pass walk — load-bearing for the
    .ptr/.len field fold.
  - cstage Sdef +mod field; sdef_collect captures d->module raw
    (matches cgfn's raw cur_mod convention); new sdef_mod_match
    helper handles NULL-safe strcmp; cstage Sdef walk #3 N_DOT
    field-fold graduation (sister of wwstage deflookuprhs).

Two additional cstage Sdef walks (N_IDENT bare load + N_DOT mod-
qualified fallback) are DEFERRED. Both consume wwstage's
cgenexpr.ww:553 path which is independently broken (str-def bare/
qualified reference emits MOVQ symname(SB) where strlit-inline is
required); sentinel rows for those walks fail cs-vs-ws byte-id
regardless of the cstage prefer-pass behavior. Per rule 7 the
prefer-pass cannot ship without sentinels. Filed: task #11 (cstage
walk #2 also needs n->lhs->str as hint source rather than cur_mod,
matching #4a/#28/#31's *mod variant pattern) + task #12 (wwstage
str-def symbol-load fix that unblocks both deferrals).

735_def_modshadow pins the fix with 1 row: bare-leaf .len of MSG
in module alpha must fold against alpha's own def MSG (strlit
length 41) even with beta's same-leaf 27-char def MSG at the head
of c.defs / sdefs. Asserts the matching immediate inside the right
TEXT sym + bad_imm anti-check on both stages plus byte-id between
stages.
This commit is contained in:
2026-05-18 14:42:20 +09:00
parent f8d2f92316
commit 4bd4ed925a
6 changed files with 400 additions and 39 deletions

View File

@@ -1635,6 +1635,7 @@ fn fnparamslookupmod(c: *cgen, name: str, mod: str) *node = {
type defent = struct {
dname: str,
dmod: str, // originating module (`// MODULE: foo`), or empty
drhs: *node,
dnext: *defent,
};
@@ -1644,8 +1645,9 @@ fn collectdefs(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
if (d.kind == nkind.N_DEF) {
let e: *defent = amalloc(c.a, 32u64): *defent;
let e: *defent = amalloc(c.a, 64u64): *defent;
e.dname = d.str;
e.dmod = d.module;
e.drhs = d.rhs;
e.dnext = c.defs;
c.defs = e;
@@ -1654,24 +1656,43 @@ fn collectdefs(c: *cgen, file: *node) void = {
};
};
// Same-module-first walk, then any. Trio-leaf graduation mirroring
// aliaslookup (#27) and enum/structlookup (#4a/#4b): bool answer is
// invariant either way, but the structural shape mirrors deflookuprhs
// where the entry's drhs IS module-sensitive.
fn deflookup(c: *cgen, name: str) bool = {
let e: *defent = c.defs;
for (e != nil) {
let dn: str = e.dname;
if (streq(dn, name)) { return true; };
if (streq(e.dname, name)) {
if (streq(e.dmod, c.curmod)) { return true; };
};
e = e.dnext;
};
e = c.defs;
for (e != nil) {
if (streq(e.dname, name)) { return true; };
e = e.dnext;
};
return false;
};
// Returns the rhs init node for a top-level `def`, or nil if `name`
// doesn't name a def. Used by cgdot to inline `.ptr`/`.len` on
// doesn't name a def. Same-module-first walk: without the prefer pass
// `MSG.ptr`/`MSG.len` in module M can collapse onto another module's
// same-leaf `def MSG: str = ...` sitting at the head of c.defs and
// inline the wrong strlit. Used by cgdot to inline `.ptr`/`.len` on
// `def NAME: str = "..."` — those aren't laid out in memory.
fn deflookuprhs(c: *cgen, name: str) *node = {
let e: *defent = c.defs;
for (e != nil) {
let dn: str = e.dname;
if (streq(dn, name)) { return e.drhs; };
if (streq(e.dname, name)) {
if (streq(e.dmod, c.curmod)) { return e.drhs; };
};
e = e.dnext;
};
e = c.defs;
for (e != nil) {
if (streq(e.dname, name)) { return e.drhs; };
e = e.dnext;
};
return nil;