/* * 989_m3sep_run — M3 `.wwi` separate-compilation CONSUMER gate (task #22 arc). * * M3 = w6c `-c` (separate-compile / primary-only codegen) reads each imported * package's `.wwi` prototypes (M2's producer output) as import scope INSTEAD of * re-checking dep bodies. Pure addition: combined.ww stays the LIVE path, `-c` * is off on every existing invocation. This gate certifies the consumer. * * Fixture: a leaf -> mid -> root chain carrying ALL FOUR cross-boundary fact- * classes — fn SIGNATURE (leaf.add), struct/type LAYOUT (leaf.Point, used as a * by-value param + returned), `def` const VALUE (leaf.SCALE, const-folded into * mid), and `export let` value-global (leaf.origin_tag, read across the * boundary). mid imports leaf; root imports both. * * THE LOAD-BEARING CLAIM (rob §4.1): compiling package P in sep mode (deps as * `.wwi`) produces the SAME codegen for P's own symbols as compiling P with * deps as full BODIES — proven by holding the `-c` codegen FILTER constant and * varying ONLY the dep-source form: * Pbodies.s = w6c -c (deps as //ww:module body sections + P) * Psep.s = w6c -c (deps as //ww:module .wwi sections + P) * GATE: cmp Pbodies.s Psep.s → byte-identical (per-package, sharp blame). * Both emit ONLY P's symbols (the `-c` filter), so the cmp proves the `.wwi` * conveys exactly the type/layout/const facts the dep bodies did, for P's code. * * Plus, per package: the keystone on BOTH stages (cstage bodies==sep and * wwstage bodies==sep — so a wwstage-only guard regression can't hide behind * the `.wwi`-sourced sep unit), cs==ww at the `.s` level (rule 10 — w6c vs * 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 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. * * COLD: a fresh getpid-keyed /tmp dir, `.wwi` materialized from scratch every * run (no warm cache — a stale `.wwi` is a #110-class freshness hazard). No * source-tree writes -> phase-1 parallel-safe. 9xx is full; shares the 989 * prefix per the 989_lib_byteid / 989_m2wwi precedent (the short name keys the * binary). Models 989_m2wwi_run.c conventions. */ #include #include #include #include #include #include static int runwait(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); return 1; } static const char * absbin(void) { const char *b = getenv("BIN"); if (!b) b = "out/bin"; if (b[0] == '/') return b; static char buf[2048]; char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return NULL; snprintf(buf, sizeof buf, "%s/%s", cwd, b); return buf; } static int slurp(const char *path, char **outbuf, size_t *outlen) { FILE *f = fopen(path, "rb"); if (!f) return -1; fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET); if (n < 0) { fclose(f); return -1; } char *b = malloc((size_t)n + 1); if (!b) { fclose(f); return -1; } if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; } b[n] = '\0'; fclose(f); *outbuf = b; *outlen = (size_t)n; return 0; } static int files_eq(const char *a, const char *b) { char *ba = NULL, *bb = NULL; size_t na = 0, nb = 0; if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) { free(ba); free(bb); return -1; } int eq = (na == nb && memcmp(ba, bb, na) == 0); free(ba); free(bb); return eq ? 0 : 1; } static int write_file(const char *path, const char *body) { FILE *f = fopen(path, "wb"); if (!f) return -1; fputs(body, f); fclose(f); return 0; } /* concat src files (verbatim bodies, each under a //ww:module directive or the * //ww:module-reset root boundary) into one sep-unit. `pre` is the directive * line (without trailing newline) to emit before `src`'s bytes; chained calls * build the full unit. Returns 0 on success. */ static int append_section(FILE *out, const char *directive, const char *srcpath) { char *b = NULL; size_t n = 0; if (slurp(srcpath, &b, &n) < 0) return -1; fprintf(out, "%s\n", directive); fwrite(b, 1, n, out); fputc('\n', out); free(b); return 0; } /* The fixture. leaf is a leaf package (no imports); mid imports leaf; root * imports both. All FOUR fact-classes cross the leaf boundary. root's exit code * is the real cross-boundary computation: * combine(p) = add(p.x,p.y) + SCALE + origin_tag = add(3,4)+7+42 = 7+7+42 = 56 * main = combine(mkpoint(3,4)) + add(1,2) = 56 + 3 = 59 */ #define EXPECT_EXIT 59 static const char *leaf_src = "package leaf;\n" "export type Point = struct { x: i32, y: i32 };\n" "export def SCALE: i32 = 7;\n" "export let origin_tag: i32 = 42;\n" "export fn add(a: i32, b: i32) i32 = { return a + b; };\n" "export fn mkpoint(x: i32, y: i32) Point = { return Point { x = x, y = y }; };\n" "fn leafpriv(z: i32) i32 = { return z * SCALE; };\n"; static const char *mid_src = "package mid;\n" "import leaf;\n" "export fn combine(p: leaf.Point) i32 = {\n" " return leaf.add(p.x, p.y) + leaf.SCALE + leaf.origin_tag;\n" "};\n"; static const char *root_src = "package main;\n" "import leaf;\n" "import mid;\n" "fn main() i32 = {\n" " let p = leaf.mkpoint(3, 4);\n" " let r = mid.combine(p);\n" " r = r + leaf.add(1, 2);\n" " return r;\n" "};\n"; /* 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- * intern it, so smid's own strlit stays `_S_0` in both the bodies and `.wwi` * units -> keystone byte-id. Drop the guard and sleaf.banner interns ahead of * smid.label in the bodies-unit, shifting it to `_S_1` -> keystone goes RED. * The i32-only leaf->mid->root fixture has no strlit and is blind to this. */ 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 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 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"; /* #53 LINK leg: two packages each EXPORT the SAME-named non-fn leaf (`let v`, * `def K`). Pre-#53 exported non-fn decls skipped path-qualification and * emitted a BARE symbol, so linking cea+ceb clashed (w6l: duplicate symbol * v/K) — the rest of the suite never caught it because no two in-tree packages * export the same non-fn leaf. With §7-A every exported decl path-qualifies * (cea.v/ceb.v, cea.K/ceb.K) so the link is clean and each getter reads its * OWN module's data. A collided leaf resolving to the wrong package's data * would corrupt the exit: * cea.val() = 40 + 3 = 43 * ceb.val() = 90 + 7 = 97 * => 43 + 97 = 140. */ #define EXPECT_CXLINK 140 static const char *cea_src = "package cea;\n" "export let v: i64 = 40;\n" "export def K: i64 = 3;\n" "export fn val() i64 = { return v + K; };\n"; static const char *ceb_src = "package ceb;\n" "export let v: i64 = 90;\n" "export def K: i64 = 7;\n" "export fn val() i64 = { return v + K; };\n"; static const char *cxroot_src = "package main;\n" "import cea;\n" "import ceb;\n" "fn main() i32 = { return (cea.val(): i32) + (ceb.val(): i32); };\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 * own source under //ww:module-reset. */ struct pkg { const char *name; /* dotted path / //ww:module directive */ const char *src; /* the package's own source */ const char *file; /* on-disk .ww basename */ const char *wwi; /* produced .wwi basename (NULL if not produced) */ }; int main(void) { const char *bin = absbin(); if (!bin) return 1; char td[64], cmd[8192], p[1024]; int fail = 0; snprintf(td, sizeof td, "/tmp/wwm3_%d", getpid()); snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); mkdir(td, 0755); /* materialize the fixture cold */ snprintf(p, sizeof p, "%s/leaf.ww", td); if (write_file(p, leaf_src)) { fail++; goto out; } snprintf(p, sizeof p, "%s/mid.ww", td); if (write_file(p, mid_src)) { fail++; goto out; } 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); snprintf(leafwwi, sizeof leafwwi, "%s/leaf.wwi", td); snprintf(midwwi, sizeof midwwi, "%s/mid.wwi", td); 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. */ snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c -I %s -o /dev/null %s >/dev/null 2>&1", bin, leafwwi, leafww); if (runwait(cmd) != 0) { fprintf(stderr, "m3sep FAIL: leaf.wwi produce\n"); fail++; goto out; } /* mid deps leaf: produce against leaf.wwi (mid primary). */ { char unit[1024]; snprintf(unit, sizeof unit, "%s/mid.prod.ww", td); FILE *u = fopen(unit, "wb"); if (!u) { fail++; goto out; } if (append_section(u, "//ww:module leaf", leafwwi) || append_section(u, "//ww:module-reset", midww)) { 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, midwwi, unit); if (runwait(cmd) != 0) { fprintf(stderr, "m3sep FAIL: mid.wwi produce\n"); fail++; goto out; } } /* sleaf has no deps: produce directly (str sub-fixture). */ snprintf(cmd, sizeof cmd, "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. */ /* [8] (not [6]) so a 6-entry initializer leaves a NULL terminator in the * trailing slots — the k+=2 loop stops at the first NULL. */ struct { const char *label; const char *combine_bodies[8]; const char *combine_sep[8]; } units[] = { { "mid", /* bodies: leaf body, then mid */ { "//ww:module leaf", leafww, "//ww:module-reset", midww, NULL, NULL }, /* sep: leaf.wwi, then mid */ { "//ww:module leaf", leafwwi, "//ww:module-reset", midww, NULL, NULL } }, { "root", { "//ww:module leaf", leafww, "//ww:module mid", midww, "//ww:module-reset", rootww }, { "//ww:module leaf", leafwwi, "//ww:module mid", midwwi, "//ww:module-reset", rootww } }, /* str sub-fixture: keystone-only (this loop never links), so the * cross-unit `_S_` collision is moot here. Exercises the strlit- * intern guard — neutralize it and sleaf.banner interns ahead of * smid.label in the bodies-unit, desyncing `_S_` -> keystone RED. */ { "smid", { "//ww:module sleaf", sleafww, "//ww:module-reset", smidww, NULL, NULL }, { "//ww:module sleaf", sleafwwi, "//ww:module-reset", smidww, NULL, NULL } }, }; for (int i = 0; i < (int)(sizeof units / sizeof units[0]); i++) { char bodies[1024], sep[1024]; snprintf(bodies, sizeof bodies, "%s/%s.bodies.ww", td, units[i].label); snprintf(sep, sizeof sep, "%s/%s.sep.ww", td, units[i].label); FILE *bf = fopen(bodies, "wb"), *sf = fopen(sep, "wb"); if (!bf || !sf) { if (bf) fclose(bf); if (sf) fclose(sf); fail++; goto out; } int berr = 0, serr = 0; for (int k = 0; units[i].combine_bodies[k]; k += 2) berr |= append_section(bf, units[i].combine_bodies[k], units[i].combine_bodies[k+1]); for (int k = 0; units[i].combine_sep[k]; k += 2) serr |= append_section(sf, units[i].combine_sep[k], units[i].combine_sep[k+1]); fclose(bf); fclose(sf); if (berr || serr) { fail++; goto out; } char csb[1024], css[1024], wws[1024], css2[1024], wwb[1024]; snprintf(csb, sizeof csb, "%s/%s.bodies.cs.s", td, units[i].label); snprintf(css, sizeof css, "%s/%s.sep.cs.s", td, units[i].label); snprintf(wws, sizeof wws, "%s/%s.sep.ww.s", td, units[i].label); snprintf(css2, sizeof css2, "%s/%s.sep.cs2.s", td, units[i].label); snprintf(wwb, sizeof wwb, "%s/%s.bodies.ww.s", td, units[i].label); snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c -c -o %s %s >/dev/null 2>&1", bin, csb, bodies); if (runwait(cmd) != 0) { fprintf(stderr, "m3sep FAIL: %s bodies -c\n", units[i].label); fail++; continue; } snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c -c -o %s %s >/dev/null 2>&1", bin, css, sep); if (runwait(cmd) != 0) { fprintf(stderr, "m3sep FAIL: %s sep -c (cstage)\n", units[i].label); fail++; continue; } snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c_ww -c -o %s %s >/dev/null 2>&1", bin, wws, sep); if (runwait(cmd) != 0) { fprintf(stderr, "m3sep FAIL: %s sep -c (wwstage)\n", units[i].label); fail++; continue; } snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c -c -o %s %s >/dev/null 2>&1", bin, css2, sep); if (runwait(cmd) != 0) { fprintf(stderr, "m3sep FAIL: %s sep -c (determinism re-run)\n", units[i].label); fail++; continue; } snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c_ww -c -o %s %s >/dev/null 2>&1", bin, wwb, bodies); if (runwait(cmd) != 0) { fprintf(stderr, "m3sep FAIL: %s bodies -c (wwstage)\n", units[i].label); fail++; continue; } /* KEYSTONE: deps-as-bodies == deps-as-.wwi (holding -c). */ if (files_eq(csb, css) != 0) { fprintf(stderr, "m3sep FAIL: %s — bodies.s != sep.s (the .wwi does " "not convey the dep facts P's codegen needs)\n", units[i].label); fail++; } /* wwstage keystone: the cstage cmp above compiles BODIES with the C * stage, so a wwstage-only guard regression (e.g. letpreintern) would * not shift the .wwi-sourced sep unit and would slip. Compile the * bodies unit with w6c_ww too and hold the same bodies==sep invariant * on the wwstage side. */ if (files_eq(wwb, wws) != 0) { fprintf(stderr, "m3sep FAIL: %s — ww bodies.s != ww sep.s " "(wwstage keystone)\n", units[i].label); fail++; } /* rule 10: cstage sep.s == wwstage sep.s. */ if (files_eq(css, wws) != 0) { fprintf(stderr, "m3sep FAIL: %s — cs sep.s != ww sep.s (rule 10)\n", units[i].label); fail++; } /* determinism: same input twice -> identical. */ if (files_eq(css, css2) != 0) { fprintf(stderr, "m3sep FAIL: %s — sep codegen non-deterministic\n", units[i].label); fail++; } /* value-global guard: an imported `export let` must NOT re-emit. The * `mid`/`root` sep-units import leaf.origin_tag; its DATAW must appear * ONLY in leaf's own compile, never in a dependent's. */ { char *s = NULL; size_t n = 0; if (slurp(css, &s, &n) == 0) { /* the DEFINITION is `DATAW origin_tag(SB),...`; a READ is * `LEAQ origin_tag(SB),...` (legitimately present — the * dependent loads the imported global). Gate on the DATA[W] * definition only. */ if (strstr(s, "DATAW origin_tag(SB)") != NULL || strstr(s, "DATA origin_tag(SB)") != NULL) { fprintf(stderr, "m3sep FAIL: %s — imported value-global " "origin_tag re-emitted (link collision)\n", units[i].label); fail++; } free(s); } } } /* ---- behavioral + final-exe cs==ww --------------------------------- */ /* Build each package's .o under -c with BOTH toolchains, link, run. * leaf is its own primary (no deps); mid/root use their sep-units. */ { char leafsep_cs[1024], midsep[1024], rootsep[1024]; snprintf(midsep, sizeof midsep, "%s/mid.sep.ww", td); snprintf(rootsep, sizeof rootsep, "%s/root.sep.ww", td); (void)leafsep_cs; 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 exe[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/leaf.%s.s", td, tc[t].tag); snprintf(lo, sizeof lo, "%s/leaf.%s.o", td, tc[t].tag); snprintf(ms, sizeof ms, "%s/mid.%s.s", td, tc[t].tag); snprintf(mo, sizeof mo, "%s/mid.%s.o", td, tc[t].tag); snprintf(rs, sizeof rs, "%s/root.%s.s", td, tc[t].tag); snprintf(ro, sizeof ro, "%s/root.%s.o", td, tc[t].tag); snprintf(exe[t], sizeof exe[t], "%s/prog.%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, leafww, 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, midsep, 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, rootsep, bin, tc[t].tool_a, ro, rs); if (runwait(cmd) != 0) ok = 0; 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, exe[t], ro, mo, lo, bin); if (runwait(cmd) != 0) ok = 0; if (!ok) { fprintf(stderr, "m3sep FAIL: %s toolchain sep build/link failed\n", tc[t].tag); fail++; exe[t][0] = '\0'; continue; } int rc = runwait(exe[t]); if (rc != EXPECT_EXIT) { fprintf(stderr, "m3sep FAIL: %s sep program exit=%d, expected %d " "(cross-boundary behavior wrong)\n", tc[t].tag, rc, EXPECT_EXIT); fail++; } } /* cs==ww at the final-exe level (rule 10 end-to-end). Symbol order * is identical because both toolchains link the SAME object set in * the SAME order; only the compiler differs, and it is byte-id at * the .s level (proven above), so the linked images match too. */ if (exe[0][0] && exe[1][0] && files_eq(exe[0], exe[1]) != 0) { fprintf(stderr, "m3sep FAIL: cs exe != ww exe (rule 10, final image)\n"); fail++; } } /* ---- #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++; } } /* ---- #53 LINK leg: same-leaf exported non-fn decls path-qualify ----- */ /* cea and ceb each `export let v` + `export def K` (see cea_src note). * Pre-#53 both emitted BARE v/K -> w6l duplicate-symbol clash; with §7-A * path-qualification (cea.v/ceb.v, cea.K/ceb.K) the link is clean and each * getter reads its OWN module's data. */ { char ceaww[1024], cebww[1024], cxrootww[1024]; char ceawwi[1024], cebwwi[1024], cxrootsep[1024]; snprintf(ceaww, sizeof ceaww, "%s/cea.ww", td); snprintf(cebww, sizeof cebww, "%s/ceb.ww", td); snprintf(cxrootww, sizeof cxrootww, "%s/cxroot.ww", td); snprintf(ceawwi, sizeof ceawwi, "%s/cea.wwi", td); snprintf(cebwwi, sizeof cebwwi, "%s/ceb.wwi", td); snprintf(cxrootsep, sizeof cxrootsep, "%s/cxroot.sep.ww", td); if (write_file(ceaww, cea_src) || write_file(cebww, ceb_src) || write_file(cxrootww, cxroot_src)) { fail++; goto out; } /* cea, ceb have no deps: produce their .wwi directly. */ snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c -I %s -o /dev/null %s >/dev/null 2>&1", bin, ceawwi, ceaww); if (runwait(cmd) != 0) { fprintf(stderr, "m3sep FAIL: cea.wwi produce (#53)\n"); fail++; goto out; } snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c -I %s -o /dev/null %s >/dev/null 2>&1", bin, cebwwi, cebww); if (runwait(cmd) != 0) { fprintf(stderr, "m3sep FAIL: ceb.wwi produce (#53)\n"); fail++; goto out; } /* cxroot imports cea + ceb: scope = both .wwi, then cxroot's body. */ { FILE *u = fopen(cxrootsep, "wb"); if (!u) { fail++; goto out; } if (append_section(u, "//ww:module cea", ceawwi) || append_section(u, "//ww:module ceb", cebwwi) || append_section(u, "//ww:module-reset", cxrootww)) { 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 cxexe[2][1024]; for (int t = 0; t < 2; t++) { char as[1024], ao[1024], bs[1024], bo[1024], rs[1024], ro[1024]; snprintf(as, sizeof as, "%s/cea.%s.s", td, tc[t].tag); snprintf(ao, sizeof ao, "%s/cea.%s.o", td, tc[t].tag); snprintf(bs, sizeof bs, "%s/ceb.%s.s", td, tc[t].tag); snprintf(bo, sizeof bo, "%s/ceb.%s.o", td, tc[t].tag); snprintf(rs, sizeof rs, "%s/cxroot.%s.s", td, tc[t].tag); snprintf(ro, sizeof ro, "%s/cxroot.%s.o", td, tc[t].tag); snprintf(cxexe[t], sizeof cxexe[t], "%s/cxprog.%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, as, ceaww, bin, tc[t].tool_a, ao, as); 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, bs, cebww, bin, tc[t].tool_a, bo, bs); 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, cxrootsep, bin, tc[t].tool_a, ro, rs); if (runwait(cmd) != 0) ok = 0; /* #53 structural proof (cstage .s once): each package's exported * non-fn leaf is MODULE-PREFIXED, never a shared bare v/K. */ if (t == 0) { char *sa = NULL, *sb = NULL; size_t na = 0, nb = 0; if (slurp(as, &sa, &na) == 0 && slurp(bs, &sb, &nb) == 0) { if (strstr(sa, "cea.v(SB)") == NULL || strstr(sa, "cea.K(SB)") == NULL || strstr(sb, "ceb.v(SB)") == NULL || strstr(sb, "ceb.K(SB)") == NULL) { fprintf(stderr, "m3sep FAIL: #53 exported non-fn leaf not module-prefixed\n"); fail++; } if (strstr(sa, "DATAW v(SB)") != NULL || strstr(sa, "DATA K(SB)") != NULL || strstr(sb, "DATAW v(SB)") != NULL || strstr(sb, "DATA K(SB)") != NULL) { fprintf(stderr, "m3sep FAIL: #53 bare exported leaf survives (cross-unit link collision)\n"); fail++; } } free(sa); free(sb); } /* link cxroot + cea + ceb + runtime: clean iff leaves 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, cxexe[t], ro, ao, bo, bin); if (runwait(cmd) != 0) ok = 0; if (!ok) { fprintf(stderr, "m3sep FAIL: %s export-leaf sep build/link failed (#53)\n", tc[t].tag); fail++; cxexe[t][0] = '\0'; continue; } int rc = runwait(cxexe[t]); if (rc != EXPECT_CXLINK) { fprintf(stderr, "m3sep FAIL: %s export-leaf link program exit=%d, expected %d " "(#53 same-leaf export collision corrupts the linked data)\n", tc[t].tag, rc, EXPECT_CXLINK); fail++; } } if (cxexe[0][0] && cxexe[1][0] && files_eq(cxexe[0], cxexe[1]) != 0) { fprintf(stderr, "m3sep FAIL: cs export-leaf exe != ww exe (rule 10, #53)\n"); fail++; } } out: snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); if (fail) { fprintf(stderr, "m3sep: %d check(s) failed\n", fail); return 1; } printf("m3sep: synth leaf->mid->root — per-pkg bodies==.wwi (-c) + cs==ww " ".s/exe + determinism + value-global guard + behavioral (exit %d) + " "#49 str-pkg sep-LINK (sleaf+smid, exit 160) + " "#53 export-leaf sep-LINK (cea+ceb same-leaf v/K, exit 140)\n", EXPECT_EXIT); return 0; }