diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 101f544b..cd5da76a 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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 `.` 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 diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 4cbc48bf..4685d8d1 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -42162,18 +42162,26 @@ fn internstrlit(c: *cgen, bytes: str) str = { }; s = s.slnext; }; - // New label "_S_". - let buf: [32]u8; - buf[0] = 95u8; buf[1] = 83u8; buf[2] = 95u8; // "_S_" + // New label "._S_" (bare "_S_" 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. diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index f622ca2a..f6a1d2ef 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -952,18 +952,26 @@ fn internstrlit(c: *cgen, bytes: str) str = { }; s = s.slnext; }; - // New label "_S_". - let buf: [32]u8; - buf[0] = 95u8; buf[1] = 83u8; buf[2] = 95u8; // "_S_" + // New label "._S_" (bare "_S_" 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; @@ -1466,8 +1474,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 @@ -1640,6 +1655,7 @@ export fn letpreintern(c: *cgen, file: *node) void = { }; d = d.next; }; + c.curmod = savedmod; }; // emitletdataw — DATAW directive per top-level `let` global. diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index fe8e38d6..07b2d839 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -42162,18 +42162,26 @@ fn internstrlit(c: *cgen, bytes: str) str = { }; s = s.slnext; }; - // New label "_S_". - let buf: [32]u8; - buf[0] = 95u8; buf[1] = 83u8; buf[2] = 95u8; // "_S_" + // New label "._S_" (bare "_S_" 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. diff --git a/test/wcc/746_strdef_inline.c b/test/wcc/746_strdef_inline.c index ecb2f79b..b044d77a 100644 --- a/test/wcc/746_strdef_inline.c +++ b/test/wcc/746_strdef_inline.c @@ -34,11 +34,11 @@ * Pin: 2 rows. Row 1 (bare ident, single module) sentinel-flips the * cgenexpr.ww:555 deflookup-strlit-inline branch on wwstage — revert * the branch and row 1 fails (`MOVQ\talpha.MSG(SB)` appears instead - * of `LEAQ\t_S_`). Row 2 (mod-qualified, two modules) sentinel-flips - * the cgenexpr.ww:1730 cgdot deflookup-strlit-inline branch — revert - * and row 2 fails the same way (`MOVQ\talpha.MSG(SB)` appears in + * of `LEAQ\t._S_`). Row 2 (mod-qualified, two modules) sentinel- + * flips the cgenexpr.ww:1730 cgdot deflookup-strlit-inline branch — + * revert and row 2 fails the same way (`MOVQ\talpha.MSG(SB)` appears in * beta.b). Asserts the strlit-inline pair lands inside the right - * TEXT sym (LEAQ\t_S_ + MOVQ\t$,) with the bad_movq + * TEXT sym (LEAQ\t._S_ + MOVQ\t$,) with the bad_movq * anti-check on each stage plus cs-vs-ws byte-id per row. */ #include @@ -77,7 +77,9 @@ static const struct row rows[] = { "def MSG: str = \"strdef_inline_pin_aaaaaaaaaaaaaaa_43chr\";\n" "export fn afn() str = { return MSG; };\n" "export fn main() i32 = { return 0; };\n", - "TEXT alpha.afn", "LEAQ\t_S_", "MOVQ\t$39,", "alpha.MSG(SB)" }, + /* #49: strlit labels carry the interning module's path prefix + * (here alpha — MSG's def interns in alpha's compile). */ + "TEXT alpha.afn", "LEAQ\talpha._S_", "MOVQ\t$39,", "alpha.MSG(SB)" }, { "mod_qualified_strdef", "package alpha;\n" "def MSG: str = \"strdef_inline_pin_aaaaaaaaaaaaaaa_43chr\";\n" @@ -85,7 +87,9 @@ static const struct row rows[] = { "import alpha;\n" "export fn bfn() str = { return alpha.MSG; };\n" "export fn main() i32 = { return 0; };\n", - "TEXT beta.bfn", "LEAQ\t_S_", "MOVQ\t$39,", "alpha.MSG(SB)" }, + /* #49: the inlined sdef interns at the USE site (beta.bfn), so the + * strlit label carries beta's path prefix, not alpha's. */ + "TEXT beta.bfn", "LEAQ\tbeta._S_", "MOVQ\t$39,", "alpha.MSG(SB)" }, }; static int diff --git a/test/wcc/989_m3sep_run.c b/test/wcc/989_m3sep_run.c index 49365251..50a76f11 100644 --- a/test/wcc/989_m3sep_run.c +++ b/test/wcc/989_m3sep_run.c @@ -28,8 +28,10 @@ * w6c_ww on the identical sep-unit), sep-path determinism (compile twice -> * identical), and the value-global guard (an imported `export let` must NOT * re-emit DATA — that would duplicate the dep's own definition -> link - * collision). A keystone-only str sub-fixture (sleaf -> smid, never linked) - * makes the strlit-intern guard non-vacuous — see its note below. End-to-end: + * collision). A str sub-fixture (sleaf -> smid -> sroot) makes the strlit- + * intern guard non-vacuous AND, post-#49 (per-unit `_S_` prefix), LINKS the + * two str-bearing packages without a label collision — see its note below. + * End-to-end: * compile each package's .o under `-c`, link with w6l, run -> exit code is the * real cross-boundary computation; the SAME with the wwstage tools (w6c_ww/ * w6a_ww/w6l_ww) -> cs==ww at the final-exe level + behavioral identity. @@ -161,10 +163,11 @@ static const char *root_src = " return r;\n" "};\n"; -/* Str-literal sub-fixture (sleaf -> smid), KEYSTONE-ONLY (per-package - * bodies-vs-.wwi cmp; never linked, so it sidesteps the global-counter `_S_` - * label collision that blocks cross-unit linking of str-bearing packages — - * filed for M3-tail). Its job is to make the strlit-intern guard (let_pre_ +/* Str-literal sub-fixture (sleaf -> smid). Two roles: (1) the per-package + * bodies-vs-.wwi keystone cmp, and (2) post-#49 the cross-unit LINK leg below + * (the per-unit `_S_` prefix removed the global-counter label collision that + * used to block linking str-bearing packages). Its job is to make the strlit- + * intern guard (let_pre_ * intern / letpreintern) non-vacuous: sleaf carries a top-level str `export * let` whose initializer interns a strlit in sleaf's OWN compile but is * stripped from sleaf's `.wwi`. With the guard live, a dependent must NOT re- @@ -175,12 +178,35 @@ static const char *root_src = static const char *sleaf_src = "package sleaf;\n" "export let banner: str = \"sleaf-banner\";\n" - "export fn tag(a: i32) i32 = { return a + 1; };\n"; + "export fn tag(a: i32) i32 = { return a + 1; };\n" + "export fn getbanner() str = { return banner; };\n"; static const char *smid_src = "package smid;\n" "import sleaf;\n" "export let label: str = \"smid-label\";\n" - "export fn use(a: i32) i32 = { return sleaf.tag(a); };\n"; + "export fn use(a: i32) i32 = { return sleaf.tag(a); };\n" + "export fn getlabel() str = { return label; };\n"; + +/* #49 LINK leg: a root over the str sub-fixture. Two str-bearing packages + * (sleaf, smid) compiled `-c` separately now LINK without an `_S_` label + * collision — the M3-core str sub-fixture was keystone-only (never linked) + * precisely because the global `_S_` counter made both emit `_S_0`. With + * the per-unit prefix (sleaf._S_0 / smid._S_0) the link is clean. The exit + * reads a DISTINGUISHING byte THROUGH each string's `.ptr` (so a collided + * `_S_0` resolving to the wrong package's bytes would corrupt the result): + * getbanner()[0] = 's'(115) of "sleaf-banner" + * getlabel()[4] = '-'(45) of "smid-label" + * => 115 + 45 = 160. */ +#define EXPECT_SLINK 160 +static const char *sroot_src = + "package main;\n" + "import sleaf;\n" + "import smid;\n" + "fn main() i32 = {\n" + " let a: str = sleaf.getbanner();\n" + " let b: str = smid.getlabel();\n" + " return (a[0]: i32) + (b[4]: i32);\n" + "};\n"; /* A package's sep-unit + bodies-unit composition. `name` is the dotted package * path; deps are spliced ahead under //ww:module , then the target's @@ -211,10 +237,12 @@ main(void) snprintf(p, sizeof p, "%s/root.ww", td); if (write_file(p, root_src)) { fail++; goto out; } snprintf(p, sizeof p, "%s/sleaf.ww", td); if (write_file(p, sleaf_src)) { fail++; goto out; } snprintf(p, sizeof p, "%s/smid.ww", td); if (write_file(p, smid_src)) { fail++; goto out; } + snprintf(p, sizeof p, "%s/sroot.ww", td); if (write_file(p, sroot_src)) { fail++; goto out; } char leafww[1024], midww[1024], rootww[1024]; char leafwwi[1024], midwwi[1024]; char sleafww[1024], smidww[1024], sleafwwi[1024]; + char smidwwi[1024], srootww[1024]; snprintf(leafww, sizeof leafww, "%s/leaf.ww", td); snprintf(midww, sizeof midww, "%s/mid.ww", td); snprintf(rootww, sizeof rootww, "%s/root.ww", td); @@ -223,6 +251,8 @@ main(void) snprintf(sleafww, sizeof sleafww, "%s/sleaf.ww", td); snprintf(smidww, sizeof smidww, "%s/smid.ww", td); snprintf(sleafwwi, sizeof sleafwwi, "%s/sleaf.wwi", td); + snprintf(smidwwi, sizeof smidwwi, "%s/smid.wwi", td); + snprintf(srootww, sizeof srootww, "%s/sroot.ww", td); /* ---- produce dep .wwi COLD, in dependency order -------------------- */ /* leaf has no deps: produce directly. */ @@ -248,6 +278,20 @@ main(void) "timeout 180 %s/w6c -I %s -o /dev/null %s >/dev/null 2>&1", bin, sleafwwi, sleafww); if (runwait(cmd) != 0) { fprintf(stderr, "m3sep FAIL: sleaf.wwi produce\n"); fail++; goto out; } + /* smid deps sleaf: produce smid.wwi (smid primary, sleaf.wwi scope) for + * the #49 link leg's sroot scope. */ + { + char unit[1024]; snprintf(unit, sizeof unit, "%s/smid.prod.ww", td); + FILE *u = fopen(unit, "wb"); + if (!u) { fail++; goto out; } + if (append_section(u, "//ww:module sleaf", sleafwwi) || + append_section(u, "//ww:module-reset", smidww)) { fclose(u); fail++; goto out; } + fclose(u); + snprintf(cmd, sizeof cmd, + "timeout 180 %s/w6c -I %s -o /dev/null %s >/dev/null 2>&1", + bin, smidwwi, unit); + if (runwait(cmd) != 0) { fprintf(stderr, "m3sep FAIL: smid.wwi produce\n"); fail++; goto out; } + } /* ---- per-package .s gate: keystone + cs==ww + determinism ---------- */ /* compose mid's bodies-unit and sep-unit, then root's. */ @@ -411,6 +455,93 @@ main(void) } } + /* ---- #49 LINK leg: two str-bearing packages link without _S_ clash --- */ + /* sleaf and smid each carry a top-level str global -> each emits a strlit + * label. Under the OLD global `_S_` counter both would emit `_S_0`, so + * linking them was impossible (the M3-core str sub-fixture is keystone- + * ONLY for exactly this reason). With the #49 per-unit prefix the labels + * are sleaf._S_0 / smid._S_0 -> the link is clean. sroot reads a + * distinguishing byte THROUGH each string's `.ptr`, so a collided label + * resolving to the wrong package's bytes would corrupt the exit code. */ + { + char smidsep[1024], srootsep[1024]; + snprintf(smidsep, sizeof smidsep, "%s/smid.sep.ww", td); + snprintf(srootsep, sizeof srootsep, "%s/sroot.sep.ww", td); + /* sroot imports sleaf + smid: scope = both .wwi, then sroot's body. + * (smid.sep.ww was already composed by the keystone loop above.) */ + { + FILE *u = fopen(srootsep, "wb"); + if (!u) { fail++; goto out; } + if (append_section(u, "//ww:module sleaf", sleafwwi) || + append_section(u, "//ww:module smid", smidwwi) || + append_section(u, "//ww:module-reset", srootww)) { fclose(u); fail++; goto out; } + fclose(u); + } + + struct { const char *tool_c, *tool_a, *tool_l; const char *tag; } tc[] = { + { "w6c", "w6a", "w6l", "cs" }, + { "w6c_ww", "w6a_ww", "w6l_ww", "ww" }, + }; + char sexe[2][1024]; + for (int t = 0; t < 2; t++) { + char ls[1024], lo[1024], ms[1024], mo[1024], rs[1024], ro[1024]; + snprintf(ls, sizeof ls, "%s/sleaf.%s.s", td, tc[t].tag); + snprintf(lo, sizeof lo, "%s/sleaf.%s.o", td, tc[t].tag); + snprintf(ms, sizeof ms, "%s/smid.%s.s", td, tc[t].tag); + snprintf(mo, sizeof mo, "%s/smid.%s.o", td, tc[t].tag); + snprintf(rs, sizeof rs, "%s/sroot.%s.s", td, tc[t].tag); + snprintf(ro, sizeof ro, "%s/sroot.%s.o", td, tc[t].tag); + snprintf(sexe[t], sizeof sexe[t], "%s/sprog.%s", td, tc[t].tag); + + int ok = 1; + snprintf(cmd, sizeof cmd, "timeout 180 %s/%s -c -o %s %s >/dev/null 2>&1 && timeout 180 %s/%s -o %s %s >/dev/null 2>&1", + bin, tc[t].tool_c, ls, sleafww, bin, tc[t].tool_a, lo, ls); + if (runwait(cmd) != 0) ok = 0; + snprintf(cmd, sizeof cmd, "timeout 180 %s/%s -c -o %s %s >/dev/null 2>&1 && timeout 180 %s/%s -o %s %s >/dev/null 2>&1", + bin, tc[t].tool_c, ms, smidsep, bin, tc[t].tool_a, mo, ms); + if (runwait(cmd) != 0) ok = 0; + snprintf(cmd, sizeof cmd, "timeout 180 %s/%s -c -o %s %s >/dev/null 2>&1 && timeout 180 %s/%s -o %s %s >/dev/null 2>&1", + bin, tc[t].tool_c, rs, srootsep, bin, tc[t].tool_a, ro, rs); + if (runwait(cmd) != 0) ok = 0; + /* #49 structural proof (cstage .s once): each str package's strlit + * label is MODULE-PREFIXED, never a shared bare `_S_0`. */ + if (t == 0) { + char *sl = NULL, *sm = NULL; size_t nl = 0, nm = 0; + if (slurp(ls, &sl, &nl) == 0 && slurp(ms, &sm, &nm) == 0) { + if (strstr(sl, "sleaf._S_") == NULL || strstr(sm, "smid._S_") == NULL) { + fprintf(stderr, "m3sep FAIL: #49 strlit label not module-prefixed\n"); + fail++; + } + if (strstr(sl, "DATA _S_0(SB)") != NULL || strstr(sm, "DATA _S_0(SB)") != NULL) { + fprintf(stderr, "m3sep FAIL: #49 bare _S_0 survives (cross-unit link collision)\n"); + fail++; + } + } + free(sl); free(sm); + } + /* link sroot + smid + sleaf + runtime: clean iff labels are unique. */ + snprintf(cmd, sizeof cmd, "timeout 180 %s/%s -o %s %s %s %s %s/../lib/libwwrt.a >/dev/null 2>&1", + bin, tc[t].tool_l, sexe[t], ro, mo, lo, bin); + if (runwait(cmd) != 0) ok = 0; + if (!ok) { + fprintf(stderr, "m3sep FAIL: %s str-pkg sep build/link failed (#49)\n", tc[t].tag); + fail++; + sexe[t][0] = '\0'; + continue; + } + int rc = runwait(sexe[t]); + if (rc != EXPECT_SLINK) { + fprintf(stderr, "m3sep FAIL: %s str-link program exit=%d, expected %d " + "(#49 _S_ collision corrupts the linked strings)\n", tc[t].tag, rc, EXPECT_SLINK); + fail++; + } + } + if (sexe[0][0] && sexe[1][0] && files_eq(sexe[0], sexe[1]) != 0) { + fprintf(stderr, "m3sep FAIL: cs str-exe != ww str-exe (rule 10, #49)\n"); + fail++; + } + } + out: snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); @@ -419,7 +550,8 @@ out: return 1; } printf("m3sep: synth leaf->mid->root — per-pkg bodies==.wwi (-c) + cs==ww " - ".s/exe + determinism + value-global guard + behavioral (exit %d)\n", + ".s/exe + determinism + value-global guard + behavioral (exit %d) + " + "#49 str-pkg sep-LINK (sleaf+smid, exit 160)\n", EXPECT_EXIT); return 0; }