/* * 989_m1usehint_run — M1 (#40): the cgen mangle-hint must be MODULE-scoped, * not unit-global. When two directory-packages in the same compilation unit * export the same leaf fn (a.math and b.math both `export fn pick`), and two * referencing modules each `import .math` aliasing `math`, every * `math.pick()` call must mangle to the package its OWN module imported — * not to whichever `use` the file-global hint collected first. * * Pre-#40 the hint (use_hint / usehint) was file-global first-match: both * modules' `math.pick()` resolved to the same package, so one call landed * the wrong body and returned the wrong value — IDENTICALLY on both stages * (a byte-id-green #263-class miscompile). Gate-visible: each module's call * has a distinct expected value, so a mis-route changes the runtime exit. * * row | proves * -----------------+-------------------------------------------------- * leaf_collision | two same-leaf exported fns; each module's qualified * | call mangles to its OWN import (111 / 222) -> 0 * collision_diff | same, with mismatched signatures — the checker's * | curmod-preferenced resolve agrees with the hint * | (pick(int) vs pick()) -> 0 * * Plus a byte-id gate: cstage.s == wwstage.s on the leaf_collision unit * (rule-10). */ #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; } struct file { const char *path; const char *content; }; struct row { const char *label; struct file files[6]; /* {NULL,NULL}-terminated package files */ const char *root; /* root main.ww content */ int want_exit; }; static const struct row rows[] = { { "leaf_collision", { { "a/math/math.ww", "package math;\n" "export fn pick() int = { return 111; };\n" }, { "b/math/math.ww", "package math;\n" "export fn pick() int = { return 222; };\n" }, { "one/one.ww", "package one;\n" "import a.math;\n" "export fn getone() int = { return math.pick(); };\n" }, { "two/two.ww", "package two;\n" "import b.math;\n" "export fn gettwo() int = { return math.pick(); };\n" }, { NULL, NULL } }, "package main;\n" "import one;\n" "import two;\n" "export fn main() int = {\n" " if (one.getone() != 111) { return 1; };\n" " if (two.gettwo() != 222) { return 2; };\n" " return 0;\n" "};\n", 0 }, { "collision_diff", { { "a/math/math.ww", "package math;\n" "export fn pick(x: int) int = { return x + 100; };\n" }, { "b/math/math.ww", "package math;\n" "export fn pick() int = { return 222; };\n" }, { "one/one.ww", "package one;\n" "import a.math;\n" "export fn getone() int = { return math.pick(11); };\n" }, { "two/two.ww", "package two;\n" "import b.math;\n" "export fn gettwo() int = { return math.pick(); };\n" }, { NULL, NULL } }, "package main;\n" "import one;\n" "import two;\n" "export fn main() int = {\n" " if (one.getone() != 111) { return 1; };\n" " if (two.gettwo() != 222) { return 2; };\n" " return 0;\n" "};\n", 0 }, }; /* write_file — create `dir/rel` in an already-owned package tree. */ static int write_file(const char *dir, const char *rel, const char *content, int *acquired) { char path[512]; *acquired = 0; snprintf(path, sizeof path, "%s/%s", dir, rel); FILE *f = fopen(path, "wb"); if (!f) return -1; *acquired = 1; int rc = fputs(content, f) == EOF ? -1 : 0; if (fclose(f) != 0) rc = -1; return rc; } struct layout_owned { int root, a, amath, b, bmath, one, two; int files[6], mainfile; }; /* Remove exactly the files and semantic directories acquired for one row. */ static int cleanup_layout(const char *dir, const struct row *r, int byteid, const struct layout_owned *owned, const int started[2]) { char path[512], cmd[1024]; int fail = 0; if (byteid) { const char *tags[] = { "cstage", "wwstage" }; for (int i = 0; i < 2; i++) { if (!started[i]) continue; snprintf(path, sizeof path, "%s/%s.sepwork", dir, tags[i]); snprintf(cmd, sizeof cmd, "rm -rf '%s'", path); if (runwait(cmd) != 0) fail = 1; snprintf(path, sizeof path, "%s/%s", dir, tags[i]); if (unlink(path) != 0 && access(path, F_OK) == 0) fail = 1; snprintf(path, sizeof path, "%s/%s.s", dir, tags[i]); if (unlink(path) != 0 && access(path, F_OK) == 0) fail = 1; } } else { if (started[0]) { snprintf(path, sizeof path, "%s/main.sepwork", dir); snprintf(cmd, sizeof cmd, "rm -rf '%s'", path); if (runwait(cmd) != 0) fail = 1; snprintf(path, sizeof path, "%s/main", dir); if (unlink(path) != 0 && access(path, F_OK) == 0) fail = 1; } } if (owned->mainfile) { snprintf(path, sizeof path, "%s/main.ww", dir); if (unlink(path) != 0 && access(path, F_OK) == 0) fail = 1; } for (int k = 0; r->files[k].path; k++) { if (!owned->files[k]) continue; snprintf(path, sizeof path, "%s/%s", dir, r->files[k].path); if (unlink(path) != 0 && access(path, F_OK) == 0) fail = 1; } if (owned->amath) { snprintf(path, sizeof path, "%s/a/math", dir); if (rmdir(path) != 0 && access(path, F_OK) == 0) fail = 1; } if (owned->a) { snprintf(path, sizeof path, "%s/a", dir); if (rmdir(path) != 0 && access(path, F_OK) == 0) fail = 1; } if (owned->bmath) { snprintf(path, sizeof path, "%s/b/math", dir); if (rmdir(path) != 0 && access(path, F_OK) == 0) fail = 1; } if (owned->b) { snprintf(path, sizeof path, "%s/b", dir); if (rmdir(path) != 0 && access(path, F_OK) == 0) fail = 1; } if (owned->one) { snprintf(path, sizeof path, "%s/one", dir); if (rmdir(path) != 0 && access(path, F_OK) == 0) fail = 1; } if (owned->two) { snprintf(path, sizeof path, "%s/two", dir); if (rmdir(path) != 0 && access(path, F_OK) == 0) fail = 1; } if (owned->root && rmdir(dir) != 0) fail = 1; return fail ? -1 : 0; } /* layout — acquire and lay out the row's package tree + root under `dir`. */ static int layout(const char *dir, const struct row *r, struct layout_owned *owned) { char path[512]; memset(owned, 0, sizeof *owned); if (mkdir(dir, 0755) != 0) return -1; owned->root = 1; snprintf(path, sizeof path, "%s/a", dir); if (mkdir(path, 0755) != 0) goto fail; owned->a = 1; snprintf(path, sizeof path, "%s/a/math", dir); if (mkdir(path, 0755) != 0) goto fail; owned->amath = 1; snprintf(path, sizeof path, "%s/b", dir); if (mkdir(path, 0755) != 0) goto fail; owned->b = 1; snprintf(path, sizeof path, "%s/b/math", dir); if (mkdir(path, 0755) != 0) goto fail; owned->bmath = 1; snprintf(path, sizeof path, "%s/one", dir); if (mkdir(path, 0755) != 0) goto fail; owned->one = 1; snprintf(path, sizeof path, "%s/two", dir); if (mkdir(path, 0755) != 0) goto fail; owned->two = 1; for (int k = 0; r->files[k].path; k++) if (write_file(dir, r->files[k].path, r->files[k].content, &owned->files[k]) != 0) goto fail; if (write_file(dir, "main.ww", r->root, &owned->mainfile) != 0) goto fail; return 0; fail: { int started[2] = { 0, 0 }; if (cleanup_layout(dir, r, 0, owned, started) != 0) fprintf(stderr, "m1usehint_run: temporary cleanup failed during setup\n"); } return -1; } /* run_build — build+run the row's root via `driver`; return the binary's * exit code, or -1 on a build failure. */ static int run_build(const char *driver, const struct row *r, int i, int *cleanup_failed) { char dir[64], cmd[2048]; struct layout_owned owned; int started[2] = { 0, 0 }; *cleanup_failed = 0; snprintf(dir, sizeof dir, "/tmp/m1usehint_%d_%d", getpid(), i); if (layout(dir, r, &owned) != 0) return -2; /* #93 sep layout: `-o main` emits per-unit asm under * main.sepwork/ (each dir-pkg in its own .s); binary stays * /main. The whole tree is removed below. */ snprintf(cmd, sizeof cmd, "cd '%s' && %s build -o main " "main.ww 2>/dev/null", dir, driver); started[0] = 1; int brc = runwait(cmd); int got = -1; if (brc == 0) { char outbin[128]; snprintf(outbin, sizeof outbin, "%s/main", dir); got = runwait(outbin); } if (cleanup_layout(dir, r, 0, &owned, started) != 0) { *cleanup_failed = 1; } return brc == 0 ? got : -1; } /* byteid — build the leaf_collision unit with both drivers and cmp the * concatenated per-package .s (rule 10). Returns 0 on byte-identical. */ static int byteid(const char *cdrv, const char *wdrv, const struct row *r, int *cleanup_failed) { char dir[64], cmd[2048]; char cstem[128], wstem[128], cs[160], ws[160]; struct layout_owned owned; int started[2] = { 0, 0 }; *cleanup_failed = 0; snprintf(dir, sizeof dir, "/tmp/m1usehint_bid_%d", getpid()); if (layout(dir, r, &owned) != 0) return -1; snprintf(cstem, sizeof cstem, "%s/cstage", dir); snprintf(wstem, sizeof wstem, "%s/wwstage", dir); snprintf(cs, sizeof cs, "%s.s", cstem); snprintf(ws, sizeof ws, "%s.s", wstem); /* #93 sep layout: each stage emits per-unit asm under .sepwork/ * (a.math.s, b.math.s, one.s, two.s, __root.s). The rule-10 byte-id * gate concats each tree's per-unit *.s (sorted glob order is * deterministic) into a flat .s and cmps the two. */ int rc = -1; snprintf(cmd, sizeof cmd, "cd '%s' && %s build -o '%s' " "main.ww 2>/dev/null && cat '%s.sepwork/'*.s > '%s'", dir, cdrv, cstem, cstem, cs); started[0] = 1; int cb = runwait(cmd); snprintf(cmd, sizeof cmd, "cd '%s' && %s build -o '%s' " "main.ww 2>/dev/null && cat '%s.sepwork/'*.s > '%s'", dir, wdrv, wstem, wstem, ws); started[1] = 1; int wb = runwait(cmd); if (cb == 0 && wb == 0) { snprintf(cmd, sizeof cmd, "cmp -s '%s' '%s'", cs, ws); rc = runwait(cmd); } else { fprintf(stderr, "m1usehint_run: byte-id build failed " "(cstage=%d wwstage=%d)\n", cb, wb); } if (cleanup_layout(dir, r, 1, &owned, started) != 0) { *cleanup_failed = 1; } return rc; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[1024]; if (bin[0] != '/') { char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[1024], wdrv[1024]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); struct { const char *name; const char *drv; int gated; } drivers[] = { { "cstage", cdrv, 0 }, { "wwstage", wdrv, 1 }, { NULL, NULL, 0 }, }; int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int d = 0; drivers[d].name; d++) { if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) { fprintf(stderr, "m1usehint_run: skip %s (no %s)\n", drivers[d].name, drivers[d].drv); continue; } for (int i = 0; i < n; i++) { total++; int cleanfail = 0; int got = run_build(drivers[d].drv, &rows[i], i, &cleanfail); if (got != rows[i].want_exit) { fprintf(stderr, "m1usehint_run[%s][%s]: exit=%d " "want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want_exit); fail++; } if (cleanfail) { fprintf(stderr, "m1usehint_run[%s][%s]: temporary cleanup " "failed\n", drivers[d].name, rows[i].label); fail++; } } } /* rule-10 byte-id gate on the leaf_collision unit. */ if (access(wdrv, X_OK) == 0) { total++; int cleanfail = 0; if (byteid(cdrv, wdrv, &rows[0], &cleanfail) != 0) { fprintf(stderr, "m1usehint_run: cstage.s != wwstage.s " "(byte-id break)\n"); fail++; } if (cleanfail) { fprintf(stderr, "m1usehint_run: byte-id temporary cleanup " "failed\n"); fail++; } } if (fail) { fprintf(stderr, "m1usehint_run: %d/%d failed\n", fail, total); return 1; } printf("m1usehint_run: %d/%d ok\n", total, total); return 0; }