/* * 989_m1mangle_run — M1 (#22 + #32): path-qualified symbol mangling and * root-unit entry detection. Table-driven, both stages (rule-10: cstage * `ww` and wwstage `ww_ww` must AGREE and hit want_exit). * * Each row lays out a tiny package tree next to a root `main.ww` and * builds+runs the root. Because a directory import path-mangles its * symbols (`aa.bb.val` etc.), a broken hint or skip rule shows up as a * link failure (-1) or wrong runtime value — observable end-to-end. * * row | proves * -----------------+-------------------------------------------------- * nested_call | DIRECTORY import path-mangles + the qualified-ref * | hint resolves to the PATH (aa.bb.val), not the * | leaf alias — cross-boundary call returns 42 * imported_main | #32/#31: an imported pkg's `fn main` mangles * | (aa.bb.main) instead of colliding with the bare * | root entry; both link, root main runs → 7 * priv_global | a module-PRIVATE value global mangles on the path * | (aa.bb.pv) and the owning module resolves it → 9 * single_level | control: a single-level package's symbols are * | UNCHANGED (cc.v stays cc.v) — still resolves → 5 * deep_nest | 2-level nest aa.bb.cc → aa.bb.cc.val path-mangle → 3 */ #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[4]; /* {NULL,NULL}-terminated package files */ const char *root; /* root main.ww content */ int want_exit; }; static const struct row rows[] = { { "nested_call", { { "aa/bb/bb.ww", "package bb;\n" "export fn val() int = { return 42; };\n" }, { NULL, NULL } }, "package main;\n" "import aa.bb;\n" "export fn main() int = { return bb.val(); };\n", 42 }, { "imported_main", { { "aa/bb/bb.ww", "package bb;\n" "fn main() int = { return 7; };\n" "export fn run() int = { return main(); };\n" }, { NULL, NULL } }, "package main;\n" "import aa.bb;\n" "export fn main() int = { return bb.run(); };\n", 7 }, { "priv_global", { { "aa/bb/bb.ww", "package bb;\n" "let pv: int = 9;\n" "export fn getpv() int = { return pv; };\n" }, { NULL, NULL } }, "package main;\n" "import aa.bb;\n" "export fn main() int = { return bb.getpv(); };\n", 9 }, { "single_level", { { "cc/cc.ww", "package cc;\n" "export fn v() int = { return 5; };\n" }, { NULL, NULL } }, "package main;\n" "import cc;\n" "export fn main() int = { return cc.v(); };\n", 5 }, { "deep_nest", { { "aa/bb/cc/cc.ww", "package cc;\n" "export fn val() int = { return 3; };\n" }, { NULL, NULL } }, "package main;\n" "import aa.bb.cc;\n" "export fn main() int = { return cc.val(); };\n", 3 }, }; /* 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; } /* run_build — lay out the row's package tree + root under a temp dir, * build+run the 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], path[512], cmd[2048]; int brc = -1, got = -2, cleanfail = 0; int aaowned = 0, bbowned = 0, topccowned = 0, deepccowned = 0; int fileowned[4] = { 0, 0, 0, 0 }, mainowned = 0, build_started = 0; *cleanup_failed = 0; snprintf(dir, sizeof dir, "/tmp/m1mangle_%d_%d", getpid(), i); if (mkdir(dir, 0755) != 0) return -2; if (i == 3) { snprintf(path, sizeof path, "%s/cc", dir); if (mkdir(path, 0755) != 0) goto cleanup; topccowned = 1; } else { snprintf(path, sizeof path, "%s/aa", dir); if (mkdir(path, 0755) != 0) goto cleanup; aaowned = 1; snprintf(path, sizeof path, "%s/aa/bb", dir); if (mkdir(path, 0755) != 0) goto cleanup; bbowned = 1; if (i == 4) { snprintf(path, sizeof path, "%s/aa/bb/cc", dir); if (mkdir(path, 0755) != 0) goto cleanup; deepccowned = 1; } } for (int k = 0; r->files[k].path; k++) if (write_file(dir, r->files[k].path, r->files[k].content, &fileowned[k]) != 0) goto cleanup; if (write_file(dir, "main.ww", r->root, &mainowned) != 0) goto cleanup; /* #94 sep layout: cd into the build dir so the source-dir-first search * path finds the sibling package tree (mirrors Hare's CWD == * module-dir); emits per-package units to main.sepwork/ and links * the runnable binary at main. #99 (imported-pkg `fn main` mangle under * sep) is what makes the imported_main row link here. */ snprintf(cmd, sizeof cmd, "cd '%s' && %s build -o main main.ww " "2>/dev/null", dir, driver); build_started = 1; brc = runwait(cmd); got = -1; if (brc == 0) { char outbin[128]; snprintf(outbin, sizeof outbin, "%s/main", dir); got = runwait(outbin); } cleanup: if (build_started) { snprintf(path, sizeof path, "%s/main.sepwork", dir); snprintf(cmd, sizeof cmd, "rm -rf '%s'", path); if (runwait(cmd) != 0) cleanfail = 1; snprintf(path, sizeof path, "%s/main", dir); if (unlink(path) != 0 && access(path, F_OK) == 0) cleanfail = 1; } if (mainowned) { snprintf(path, sizeof path, "%s/main.ww", dir); if (unlink(path) != 0 && access(path, F_OK) == 0) cleanfail = 1; } for (int k = 0; r->files[k].path; k++) { if (!fileowned[k]) continue; snprintf(path, sizeof path, "%s/%s", dir, r->files[k].path); if (unlink(path) != 0 && access(path, F_OK) == 0) cleanfail = 1; } if (deepccowned) { snprintf(path, sizeof path, "%s/aa/bb/cc", dir); if (rmdir(path) != 0 && access(path, F_OK) == 0) cleanfail = 1; } if (topccowned) { snprintf(path, sizeof path, "%s/cc", dir); if (rmdir(path) != 0 && access(path, F_OK) == 0) cleanfail = 1; } if (bbowned) { snprintf(path, sizeof path, "%s/aa/bb", dir); if (rmdir(path) != 0 && access(path, F_OK) == 0) cleanfail = 1; } if (aaowned) { snprintf(path, sizeof path, "%s/aa", dir); if (rmdir(path) != 0 && access(path, F_OK) == 0) cleanfail = 1; } if (rmdir(dir) != 0) cleanfail = 1; *cleanup_failed = cleanfail; return brc == 0 ? got : -1; } 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, "m1mangle_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, "m1mangle_run[%s][%s]: exit=%d " "want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want_exit); fail++; } if (cleanfail) { fprintf(stderr, "m1mangle_run[%s][%s]: temporary cleanup " "failed\n", drivers[d].name, rows[i].label); fail++; } } } if (fail) { fprintf(stderr, "m1mangle_run: %d/%d fixtures failed\n", fail, total); return 1; } printf("m1mangle_run: %d/%d ok\n", total, total); return 0; }