/* * 989_depmain_sep — #99 E3 flip-blocker: an IMPORTED dir-package's non- * exported `fn main` must mangle on its import path under `ww build --sep`, * not emit a bare `TEXT main` that collides with the root unit's entry. * * The bare-`main` carve-out (cgen mod_collect + cgfn label, both stages) * keys on `leaf=="main" && imported==0`. Under COMBINED that predicate is * exact — only the root's main is imported==0. Under SEP each package is its * own w6c unit, composed with a path-carrying `//ww:module-reset ` * (#57) that mangles decls but leaves imported==0 — so a dep unit's `fn * main` was imported==0 too, the carve-out fired, and it emitted a bare * `TEXT main` → `w6l: duplicate symbol main` against the root's real main. * * The fix adds a NON-ROOT gate `&& !sep_isdep` to the carve-out. sep_isdep * is set from `wwiout != NULL`: the producer passes -I (the .wwi output) to * DEP units ONLY — the root/link-entry unit's .wwi is stripped (#69) — so * `wwiout==NULL` is the exact "this is the root/link-entry unit" oracle. A * dep's main now mangles on its path; only the root's stays bare. * * Fixtures (test/wcc/data/depmain99/, srcd-relative dir-package resolution): * main.ww package main, import aa.bb — root; calls bb.run() → 7. * aa/bb/bb.ww package bb, `fn main`→7 + `export fn run` calls it — the * DOTTED-path imported dep whose main must mangle aa.bb.main. * main_cc.ww package main, import cc — root; calls cc.run() → 9. * cc/cc.ww package cc, `fn main`→9 + `export fn run` — the SINGLE- * component imported dep whose main must mangle cc.main. * * Asserts (both driver stages, COLD per-stage WW_PKGCACHE): * 1. SEP build+link+run exit == the dep's main's return — pre-fix the * bare-main collision makes w6l reject. This is the non-vacuity teeth * (verified: revert `&& !sep_isdep` → `w6l: duplicate symbol main`). * 2. MANGLED — the dep's .s shows `TEXT .main`; exactly ONE bare * `TEXT main,` across the whole sep build (the root's __root.s). * 3. cs==ww (rule 10) — per-package .s/.wwi byte-identical across the * cstage `ww` and wwstage `ww_ww` sep-drivers. * 4. COMBINED unaffected — `ww build` (no --sep) still links+runs to the * same exit on both stages (the fix must not change combined behavior). * * Light wwstage-driver test (CLAUDE.md rule 14): every intermediate is * `-o`-redirected to /tmp, so phase-1 parallel-safe. Models 989_coloimport_sep * 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 -2; } 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; } /* 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, "depmain 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, "depmain FAIL: %s — cs!=ww for %s (rule 10)\n", label, nm); bad++; } } closedir(d); if (seen == 0) { fprintf(stderr, "depmain FAIL: %s — no .s/.wwi in %s\n", label, csdir); bad++; } return bad; } /* count_bare_main — number of bare `TEXT main,` labels across every .s in * `dir`. Exactly 1 is correct (the root entry); the bug produces 2 (the * dep's mis-bared main collides). */ static int count_bare_main(const char *dir) { DIR *d = opendir(dir); if (!d) return -1; int n = 0; struct dirent *ent; while ((ent = readdir(d)) != NULL) { const char *nm = ent->d_name; size_t nl = strlen(nm); if (!(nl > 2 && strcmp(nm + nl - 2, ".s") == 0)) continue; char p[2048]; snprintf(p, sizeof p, "%s/%s", dir, nm); char *b = NULL; size_t bn = 0; if (slurp(p, &b, &bn) < 0) continue; /* a bare label is `TEXT main,` at column 0 of any line. */ for (char *s = b; (s = strstr(s, "TEXT main,")) != NULL; s++) if (s == b || s[-1] == '\n') n++; free(b); } closedir(d); return n; } /* file_has — 0 if `needle` occurs in `path`, 1 if absent, -1 on read err. */ static int file_has(const char *path, const char *needle) { char *b = NULL; size_t bn = 0; if (slurp(path, &b, &bn) < 0) return -1; int found = (strstr(b, needle) != NULL); free(b); return found ? 0 : 1; } struct tcase { const char *label; const char *entry; /* root entry built under --sep */ int exit; /* the dep main's return, == the program exit */ const char *deps; /* the dep's .s basename in .sepwork */ const char *mangled; /* the mangled TEXT label the dep must emit */ }; static struct tcase cases[] = { /* dotted-path import: dep main mangles on the full path. */ { "aabb", "test/wcc/data/depmain99/main.ww", 7, "aa.bb.s", "TEXT aa.bb.main," }, /* single-component import: dep main mangles on the leaf package. */ { "cc", "test/wcc/data/depmain99/main_cc.ww", 9, "cc.s", "TEXT cc.main," }, { NULL, NULL, 0, NULL, NULL }, }; int main(void) { const char *bin = absbin(); if (!bin) return 1; char td[64], cmd[8192]; int fail = 0; snprintf(td, sizeof td, "/tmp/wwdepmain_%d", getpid()); snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); mkdir(td, 0755); for (int i = 0; cases[i].label; i++) { struct tcase *t = &cases[i]; struct { const char *drv, *tag; int rc; } stg[] = { { "ww", "cs", -1 }, { "ww_ww", "ww", -1 } }; /* 1. SEP build+link+run exit == the dep main's return, both stages. */ for (int s = 0; s < 2; s++) { char prog[1024]; snprintf(prog, sizeof prog, "%s/%s.%s.bin", td, t->label, stg[s].tag); snprintf(cmd, sizeof cmd, "WW_PKGCACHE='%s/cache.%s.%s' %s/%s build --sep -o %s %s " ">/dev/null 2>&1 && %s", td, t->label, stg[s].tag, bin, stg[s].drv, prog, t->entry, prog); stg[s].rc = runwait(cmd); if (stg[s].rc != t->exit) { fprintf(stderr, "depmain FAIL: %s sep %s exit=%d (expected %d)\n", t->label, stg[s].tag, stg[s].rc, t->exit); fail++; } } char csdir[1024], wwdir[1024]; snprintf(csdir, sizeof csdir, "%s/%s.cs.bin.sepwork", td, t->label); snprintf(wwdir, sizeof wwdir, "%s/%s.ww.bin.sepwork", td, t->label); /* 2. MANGLED: the dep's .s emits `TEXT .main`; exactly one bare * `TEXT main,` across the whole sep build (the root's __root.s). */ char deps[2048]; snprintf(deps, sizeof deps, "%s/%s", csdir, t->deps); if (file_has(deps, t->mangled) != 0) { fprintf(stderr, "depmain FAIL: %s — %s lacks `%s` (dep main not " "mangled)\n", t->label, deps, t->mangled); fail++; } int nbare = count_bare_main(csdir); if (nbare != 1) { fprintf(stderr, "depmain FAIL: %s — %d bare `TEXT main,` in %s " "(expected 1, the root entry)\n", t->label, nbare, csdir); fail++; } /* 3. cs==ww (rule 10): per-package .s/.wwi byte-identical. */ fail += cmp_sepwork(csdir, wwdir, t->label); /* 4. COMBINED unaffected: `ww build` (no --sep) still runs to exit. */ for (int s = 0; s < 2; s++) { char prog[1024]; snprintf(prog, sizeof prog, "%s/%s.%s.cmb", td, t->label, stg[s].tag); snprintf(cmd, sizeof cmd, "%s/%s build -o %s %s >/dev/null 2>&1 && %s", bin, stg[s].drv, prog, t->entry, prog); int rc = runwait(cmd); if (rc != t->exit) { fprintf(stderr, "depmain FAIL: %s combined %s exit=%d " "(expected %d)\n", t->label, stg[s].tag, rc, t->exit); fail++; } } } snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); if (fail) { fprintf(stderr, "depmain: %d check(s) failed\n", fail); return 1; } printf("depmain: imported dir-package `fn main` mangles on its import path " "under `ww build --sep` (only the root entry stays bare) on both driver " "stages + cs==ww per-pkg .s/.wwi + combined unaffected\n"); return 0; }