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

@@ -500,12 +500,27 @@ static Ffi *ffi_map;
typedef struct Sdef Sdef;
struct Sdef {
const char *name;
const char *mod; /* raw `// MODULE:` directive on the decl,
* or NULL. Mirrors cgfn's c->cur_mod which
* stores the same raw form. */
const char *bytes;
u64 len;
Sdef *next;
};
static Sdef *sdefs;
/* Same-module-first match for Sdef walks. Mirrors wwstage deflookuprhs's
* first pass: returns 1 iff s belongs to the fn we're emitting. Caller
* still re-walks for the any-module fallback. */
static int
sdef_mod_match(Cg *c, Sdef *s)
{
const char *a = s->mod, *b = c->cur_mod;
if (a == b) return 1;
if (a == NULL || b == NULL) return 0;
return strcmp(a, b) == 0;
}
/* Interned string literals — emitted as DATA directives after all
* function bodies, so the linker lays them out alongside .text. */
typedef struct Strlit Strlit;
@@ -1742,14 +1757,33 @@ cgexpr(Cg *c, Node *n, Local *locals)
mafn(c, n->str, c->cur_mod), areg(D_AX));
break;
}
for (Sdef *s = sdefs; s; s = s->next) {
if (strcmp(s->name, n->str) != 0) continue;
const char *lab = intern_strlit(c, s->bytes,
s->len);
ins2(c, A_LEAQ, asym(lab), areg(D_AX));
ins2(c, A_MOVQ, aimm((long long)s->len),
areg(D_BX));
goto ident_done;
{
/* Same-module-first walk over Sdef. Without
* the prefer pass two modules with same-leaf
* `def MSG: str = "..."` silently fold the
* wrong strlit into the caller's bare-ident
* load (sister callsite of cgdot's str-def
* field fold + wwstage deflookuprhs #4c). */
Sdef *s;
for (s = sdefs; s; s = s->next) {
if (strcmp(s->name, n->str) != 0)
continue;
if (sdef_mod_match(c, s)) break;
}
if (s == NULL) {
for (s = sdefs; s; s = s->next)
if (strcmp(s->name, n->str) == 0)
break;
}
if (s != NULL) {
const char *lab = intern_strlit(c,
s->bytes, s->len);
ins2(c, A_LEAQ, asym(lab), areg(D_AX));
ins2(c, A_MOVQ,
aimm((long long)s->len),
areg(D_BX));
goto ident_done;
}
}
if (let_islet(n->str)
&& (let_isstr(n->type) || let_isslice(n->type))) {
@@ -5404,19 +5438,47 @@ cgexpr(Cg *c, Node *n, Local *locals)
* N_IDENT branch above. Without this we'd
* load BP+8 (return-address slot) as the
* "len". */
for (Sdef *s = sdefs; s; s = s->next) {
if (strcmp(s->name, n->lhs->str) != 0)
continue;
if (ptrfld) {
const char *lab = intern_strlit(c,
s->bytes, s->len);
ins2(c, A_LEAQ, asym(lab), areg(D_AX));
} else {
ins2(c, A_MOVQ,
aimm((long long)s->len),
areg(D_AX));
{
/* Same-module-first walk: two
* same-leaf `def MSG: str = ...`
* across modules would otherwise
* fold the wrong strlit's length /
* label into `MSG.len` / `MSG.ptr`
* (sister of wwstage deflookuprhs
* #4c). */
Sdef *s;
for (s = sdefs; s; s = s->next) {
if (strcmp(s->name,
n->lhs->str) != 0)
continue;
if (sdef_mod_match(c, s))
break;
}
if (s == NULL) {
for (s = sdefs; s;
s = s->next)
if (strcmp(s->name,
n->lhs->str)
== 0)
break;
}
if (s != NULL) {
if (ptrfld) {
const char *lab =
intern_strlit(c,
s->bytes,
s->len);
ins2(c, A_LEAQ,
asym(lab),
areg(D_AX));
} else {
ins2(c, A_MOVQ,
aimm((long long)
s->len),
areg(D_AX));
}
goto dot_done;
}
goto dot_done;
}
/* Top-level str/slice `let` — load
* the field through &name(SB). Same
@@ -7521,7 +7583,7 @@ emit_defs(Cg *c, FILE *out, Node *file)
static void
sdef_collect(Cg *c, Node *file)
{
(void)c;
(void)file;
sdefs = NULL;
for (Node *d = file->list; d; d = d->next) {
if (d->kind != N_DEF || d->rhs == NULL) continue;
@@ -7530,6 +7592,7 @@ sdef_collect(Cg *c, Node *file)
if (r == NULL || r->kind != N_STRLIT) continue;
Sdef *s = amalloc(c->a, sizeof *s);
s->name = d->str;
s->mod = (d->module && d->module[0]) ? d->module : NULL;
s->bytes = r->str;
s->len = r->strlen;
s->next = sdefs;