wcc/ww: per-unit prefix on _S_ strlit labels (#49)

Strlit labels were emitted as `_S_<n>` from a global counter with no
per-unit prefix (cgen.c intern_strlit + wwstage internstrlit twin).
Under separate compilation two str-bearing packages both emit `_S_0`..
-> w6l link collision.

Prefix the label with the owning package PATH (`<module>._S_<n>`,
matching mklabel's spelling). The prefix is c->cur_mod, set per-fn by
cgfn and now per-decl by let_pre_intern (save/restore so the later
emit passes, which read cur_mod for fn-ptr relocs, are unaffected).
Pure function of the module path — NOT a build-nonce — so the
self-host fixed-point holds across ww2/ww3/ww4. Both stages, symmetric.

Transparent rename on the live combined path: the label is interned
once and shared by every reference, so ref and def move in lockstep.
cs==ww byte-id holds; the w6c/wwdump combined.ww embed wcc/cgen.ww and
are regenerated.

746_strdef_inline: the strdef-inline sentinel pinned the bare
`LEAQ\t_S_` shape; update to the module-prefixed form (alpha._S_ for
the in-module def, beta._S_ for the use-site-interned cross-module
inline).

989_m3sep_run: add the #49 LINK leg. The str sub-fixture (sleaf+smid)
was keystone-only — never linked — precisely because the global
counter made both emit `_S_0`. With the prefix, compile both `-c`
separately, link (w6l) + run (sroot reads a distinguishing byte through
each string's .ptr, so a collided label would corrupt the exit), both
stages + cs==ww final exe.
This commit is contained in:
2026-06-16 01:36:14 +09:00
parent f69ef9b9da
commit f77739b1de
6 changed files with 241 additions and 37 deletions

View File

@@ -1087,7 +1087,18 @@ intern_strlit(Cg *c, const char *bytes, u64 len)
if (s->len == len && memcmp(s->bytes, bytes, len) == 0)
return s->label;
Strlit *s = amalloc(c->a, sizeof *s);
s->label = aprintf(c->a, "_S_%d", strlit_seq++);
/* #49: per-unit prefix on the strlit label. Under separate
* compilation two str-bearing packages both emit `_S_0`.. from this
* global counter → w6l link collision. Prefix with the owning
* package PATH (c->cur_mod — set per-fn by cgfn, per-decl by
* let_pre_intern for static-data strings) so the label is unique per
* compilation unit by construction. Pure function of the module path
* (NOT a build-nonce) so the self-host fixed-point holds across
* ww2/ww3/ww4. Same `<module>.` spelling as mklabel (cgen.c:1853). */
s->label = aprintf(c->a, "%s%s_S_%d",
c->cur_mod ? c->cur_mod : "",
c->cur_mod ? "." : "",
strlit_seq++);
s->bytes = bytes;
s->len = len;
s->next = strlits;
@@ -16670,7 +16681,15 @@ static void
let_pre_intern(Cg *c, Node *file)
{
if (file == NULL) return;
/* #49: strlit labels allocated here (static-data initialisers) must
* carry the OWNING decl's module prefix, not the stale last-fn
* cur_mod. Save/restore so emit_data/emit_defs/emit_lets — which read
* cur_mod for fn-ptr relocs (node_fnptr_sym) — see the same value
* they did before. let_pre_intern itself only interns, so driving
* cur_mod here has no other effect. */
const char *save_mod = c->cur_mod;
for (Node *d = file->list; d; d = d->next) {
c->cur_mod = (d->module && d->module[0]) ? d->module : NULL;
/* #22 M3: skip imported deps so the strlit table (and its _S_
* sequence) is a pure function of THIS package's own decls. A
* dep's body initializer would intern here, but its `.wwi`
@@ -16789,6 +16808,7 @@ let_pre_intern(Cg *c, Node *file)
if (r->strlen == 0) continue;
(void)intern_strlit(c, r->str, r->strlen);
}
c->cur_mod = save_mod;
}
void