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

@@ -42162,18 +42162,26 @@ fn internstrlit(c: *cgen, bytes: str) str = {
};
s = s.slnext;
};
// New label "_S_<seq>".
let buf: [32]u8;
buf[0] = 95u8; buf[1] = 83u8; buf[2] = 95u8; // "_S_"
// New label "<module>._S_<seq>" (bare "_S_<seq>" when curmod empty).
// #49: per-unit prefix so two str-bearing packages don't both emit
// `_S_0`.. and collide at w6l link. Pure function of the module path
// (matching mklabel's spelling), so the self-host fixed-point holds.
let buf: [128]u8;
let i: i32 = 0;
let mname: str = c.curmod;
let j: i32 = 0;
for (j < mname.len) { buf[i] = mname[j]; i += 1; j += 1; };
if (mname.len > 0) { buf[i] = '.'; i += 1; };
buf[i] = 95u8; i += 1; buf[i] = 83u8; i += 1; buf[i] = 95u8; i += 1; // "_S_"
let ns: str = strconv.i64tos(c.strlitseq: i64, strconv.base.DEC);
let n: i32 = ns.len;
let dk: i32 = 0;
for (dk < n) { buf[3 + dk] = ns.ptr[dk]; dk += 1; };
for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; };
c.strlitseq += 1;
let total: i32 = 3 + n;
let total: i32 = i + n;
let p: []u8 = alloc([], (total: u64) + 1u64)!;
let i: i32 = 0;
for (i < total) { p[i] = buf[i]; i += 1; };
let k: i32 = 0;
for (k < total) { p[k] = buf[k]; k += 1; };
p[total] = 0u8;
let lab: str;
lab.ptr = p.ptr;
@@ -42676,8 +42684,15 @@ fn preinternstrarray(c: *cgen, au: *tinfo, r: *node) void = {
// DATAW lets) section order and break byte-identity.
export fn letpreintern(c: *cgen, file: *node) void = {
if (file == nil) { return; };
// #49: strlit labels allocated here (static-data initialisers) take
// the OWNING decl's module prefix, not the stale last-fn curmod.
// Save/restore so the later emit passes — which read curmod for
// fn-ptr relocs — see the same value they did before; letpreintern
// itself only interns, so driving curmod here has no other effect.
let savedmod: str = c.curmod;
let d: *node = file.list;
for (d != nil) {
c.curmod = d.nmod;
// #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` (init
@@ -42850,6 +42865,7 @@ export fn letpreintern(c: *cgen, file: *node) void = {
};
d = d.next;
};
c.curmod = savedmod;
};
// emitletdataw — DATAW directive per top-level `let` global.