/* * 989_c6soak_run — M3-tail commit-6 dual-path soak gate, phase-1 leg * (#46 c6, rob-c6-spec.md Tier-A/B + §3 collision). * * The STANDING regression-gate for M4: separate-compilation must be * behaviorally equivalent to the combined (embed-everything) build on * real, multi-package code. The oracle is OBSERVABLE OUTPUT, not binary * identity — a sep-linked binary and a combined-linked binary differ in * symbol order/layout (rob-c6-spec §2 ★), so we run both and compare * exit codes, never `cmp` the executables. * * Targets (real lib chains, cheap → phase-1): * - utf8 : encoding.utf8 (DOTTED-path #57) + strings → decode + count * runes ("héllo" = 5). Exercises the multi-component import * the combined build resolves by leaf and sep must resolve * by the full dotted path. * - collide: strings.contains AND bytes.contains — the SAME leaf `contains` * exported by two packages (rob-c6-spec §3, the #48/#49 class * on real code). sep path-qualifies them to DISTINCT symbols; * the program prints both correct results; the §3 non-vacuity * check below greps the per-pkg .s to prove the two labels are * genuinely distinct (disambiguation is real, not luck). * * Asserts per target (all COLD — fresh per-stage WW_PKGCACHE, scratch * wiped each run): * 1. DUAL-PATH OUTPUT EQUIVALENCE (the teeth): combined-build run-exit == * sep-build run-exit == expected, for BOTH driver stages. This is the * M4 behavioral proof: sep emits a program that behaves identically to * the combined one. * 2. cs==ww (rule 10): the cstage `ww` and wwstage `ww_ww` sep-drivers emit * byte-identical per-package .s and .wwi (the .wwi enters the compare * set — rob-c6-spec). Both also link to a binary that runs to `expect`. * 3. §3 NON-VACUITY (collide only): the per-package strings.s defines * `TEXT strings.contains` and bytes.s defines `TEXT bytes.contains` — * two DISTINCT symbols. The w6l dup-gate stays silent (no collision); * both resolve; the program returns 7. * * The heavy Tier-C capstone (real tool w6c/wwdump self-compile output * equivalence) is FOLDED into the 990-997 bootstrap oracle (994_w6c_ww), * not bolted on here (rob-c6-spec §2 / amendment-B). * * Light wwstage-driver test (CLAUDE.md rule 14): all intermediates are * `-o`-redirected to /tmp, so it is phase-1 parallel-safe. Models * 989_sepbuild_run.c / 989_sepdotpath_run.c conventions; 989 prefix per * the sep-gate precedent. */ #include #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; } /* 0 if `needle` occurs in the file at `path`, 1 if absent, -1 on read err. */ static int file_contains(const char *path, const char *needle) { char *b = NULL; size_t n = 0; if (slurp(path, &b, &n) < 0) return -1; int found = (strstr(b, needle) != NULL); free(b); return found ? 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; } /* cs==ww over a sep-build's per-package output: every .s/.wwi the cstage * driver produced in `csdir` must be byte-identical to the wwstage * driver's same-named file in `wwdir`. Returns the count of mismatches. */ static int cmp_sepwork(const char *csdir, const char *wwdir, const char *label) { DIR *d = opendir(csdir); if (!d) { fprintf(stderr, "c6soak FAIL: %s — no cs sepwork %s\n", label, csdir); return 1; } int bad = 0, seen = 0; struct dirent *ent; while ((ent = readdir(d)) != NULL) { const char *nm = ent->d_name; size_t nl = strlen(nm); int is_s = (nl > 2 && strcmp(nm + nl - 2, ".s") == 0); int is_wwi = (nl > 4 && strcmp(nm + nl - 4, ".wwi") == 0); if (!is_s && !is_wwi) continue; seen++; char a[2048], b[2048]; snprintf(a, sizeof a, "%s/%s", csdir, nm); snprintf(b, sizeof b, "%s/%s", wwdir, nm); if (files_eq(a, b) != 0) { fprintf(stderr, "c6soak FAIL: %s — cs!=ww for %s (rule 10)\n", label, nm); bad++; } } closedir(d); /* An existing-but-empty sepwork would pass the cs==ww loop vacuously * (no per-pkg .s/.wwi to compare); the utf8 root has no other cs==ww * anchor, so demand at least one compared file. */ if (seen == 0) { fprintf(stderr, "c6soak FAIL: %s — no .s/.wwi in %s\n", label, csdir); bad++; } return bad; } struct root { const char *label; const char *src; int expect; int collide; /* run the §3 distinct-label non-vacuity check */ }; static struct root roots[] = { { "utf8", "package main;\n" "import encoding.utf8;\n" "import strings;\n" "fn main() i32 = {\n" " let d = utf8.decode(strings.toutf8(\"h\303\251llo\"));\n" " let n: i32 = 0;\n" " for (true) {\n" " match (utf8.next(&d)) {\n" " case rune => n += 1;\n" " case => break;\n" " };\n" " };\n" " return n;\n" "};\n", 5, 0 }, { "collide", "package main;\n" "import strings;\n" "import bytes;\n" "fn main() i32 = {\n" " let s = strings.contains(\"hello\", \"ell\");\n" " let b = bytes.contains(strings.toutf8(\"hello\"), strings.toutf8(\"ell\"));\n" " if (s && b) { return 7; };\n" " return 1;\n" "};\n", 7, 1 }, { NULL, NULL, 0, 0 }, }; int main(void) { const char *bin = absbin(); if (!bin) return 1; char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; /* The real include set the sep producer needs to resolve the lib + * frontend package graph (verified live; the bare default path grabs * the in-tree *test.combined.ww fixtures and collides on `main`). */ char inc[4096]; snprintf(inc, sizeof inc, "-I %s/lib -I %s/lib/ww -I %s/selfhost/cmd/wcc", cwd, cwd, cwd); char td[64], cmd[8192]; int fail = 0; snprintf(td, sizeof td, "/tmp/wwc6soak_%d", getpid()); snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); mkdir(td, 0755); for (int i = 0; roots[i].label; i++) { struct root *r = &roots[i]; char rootww[1024]; snprintf(rootww, sizeof rootww, "%s/%s.ww", td, r->label); if (write_file(rootww, r->src)) { fail++; continue; } /* 1. combined build (cstage driver) → run. */ char comb[1024]; snprintf(comb, sizeof comb, "%s/%s.comb", td, r->label); snprintf(cmd, sizeof cmd, "timeout 240 %s/ww build %s -o %s %s >/dev/null 2>&1", bin, inc, comb, rootww); if (runwait(cmd) != 0) { fprintf(stderr, "c6soak FAIL: %s combined build\n", r->label); fail++; continue; } int comb_rc = runwait(comb); /* sep build both stages, fresh per-stage WW_PKGCACHE so every * package compiles COLD (a warm shared cache would skip the * per-pkg .s/.wwi this gate inspects — mirrors 989_pkgcache). */ struct { const char *drv, *tag; char prog[1024]; int rc; } stg[] = { { "ww", "cs", {0}, -1 }, { "ww_ww", "ww", {0}, -1 } }; int built = 1; for (int s = 0; s < 2; s++) { /* `.bin` infix keeps the stage-`ww` output distinct from the * `