/* * 750_mklabel_modscoped — sentinel for task #13. mklabel pre-fix * formatted labels as `__` (just the leaf). Two * top-level fns in different modules sharing a leaf (e.g. `bytes.index` * + `strings.index`) emitted colliding labels into the same combined * .s; w6a's a_intern collapsed the names and the LAST definition's * `s->addr` won — earlier fns' JNE/JMP rel32 resolved into the later * fn's body. Silent miscompile in cstage; wwstage's mklabel had the * same shape so the symmetric path was equally exposed (the wedge * just happened to fire under cstage's combined link first). * * Fix (rule 10): `.__` mirrors the * existing TEXT-directive convention (`TEXT bytes.index,$N`). w6a * already accepts `.` in label-cont (lex.c:18) so the format is a * valid Plan-9 symbol. * * row | what it pins * ----------------------------|-------------------------------- * two_modules_same_leaf | original wedge. mod1.locate / * | mod2.locate both contain `match` * | so both stamp `_match_next_1`. * | Pre-fix run aborts on mis-jump * | (exit=10); post-fix exit=0 and * | mod1.locate_match_next_1 + * | mod2.locate_match_next_1 BOTH * | appear in the .s. * bytes_strings_contains | the latent that flips live once * | task #11 lands strings.index. * | Force both bytes.contains and * | strings.contains into the link; * | confirm post-fix bytes.contains_* * | and strings.contains_* labels * | are distinct. * same_module_same_leaf | non-regression: two fns in the * | same module with the same `match` * | shape still get unique labels via * | the per-fn `labelseq` counter. */ #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 row { const char *label; int want_exit; int n_files; const char *files[8]; /* {path, src, path, src, ...} */ const char *entry; /* file to `ww build` (relative) */ int n_required; const char *required[6]; /* substrings every emitted .s must contain */ }; static const struct row rows[] = { /* Original wedge shape. mod1.locate and mod2.locate both run a * `match (needle) { case u8 / case []u8 }` over a `for` loop — * both stamp `_match_next_1` and `_loop_2` etc. Pre-fix the * second TEXT's labels overwrite the first's s->addr; mod1.locate * misjumps and returns the wrong byte offset. Post-fix the * module-qualified labels coexist. */ { "two_modules_same_leaf", 0, 3, { "mod1/mod1.ww", "package mod1;\n" "export fn locate(s: []u8, needle: (u8 | []u8)) (i32 | void) = {\n" " match (needle) {\n" " case let c: u8 => {\n" " let i: i32 = 0;\n" " for (i < s.len) {\n" " if (s[i] == c) { return i; };\n" " i += 1;\n" " };\n" " return;\n" " };\n" " case let sub: []u8 => { return; };\n" " };\n" " return;\n" "};\n", "mod2/mod2.ww", "package mod2;\n" "export fn locate(s: []u8, needle: (u8 | []u8)) (i32 | void) = {\n" " match (needle) {\n" " case let c: u8 => {\n" " let i: i32 = 0;\n" " for (i < s.len) {\n" " if (s[i] == c) { return i + 100; };\n" " i += 1;\n" " };\n" " return;\n" " };\n" " case let sub: []u8 => { return; };\n" " };\n" " return;\n" "};\n", "main.ww", "package main;\n" "import mod1;\n" "import mod2;\n" "export fn main() i32 = {\n" " let arr: [3]u8 = [65u8, 66u8, 67u8];\n" " let s: []u8 = arr[0:3];\n" " let n: u8 = 66u8;\n" " let needle: (u8 | []u8) = n;\n" " let a: (i32 | void) = mod1.locate(s, needle);\n" " let b: (i32 | void) = mod2.locate(s, needle);\n" " let av: i32 = match (a) { case let v: i32 => yield v; " "case void => yield -1; };\n" " let bv: i32 = match (b) { case let v: i32 => yield v; " "case void => yield -1; };\n" " if (av != 1) { return 10; };\n" " if (bv != 101) { return 20; };\n" " return 0;\n" "};\n" }, "main.ww", 2, { "mod1.locate_match_next_1", "mod2.locate_match_next_1" } }, /* The pre-located bytes.contains / strings.contains latent. Both * libs already export `contains`; force both into the link so the * pair coexists in main.s. */ { "bytes_strings_contains", 0, 1, { "main.ww", "package main;\n" "import bytes;\n" "import strings;\n" "export fn main() i32 = {\n" " let s: str = \"abc\";\n" " let b: []u8 = strings.toutf8(s);\n" " let n: (u8 | []u8) = 98u8;\n" " if (!bytes.contains(b, n)) { return 11; };\n" " if (!strings.contains(s, 'b')) { return 12; };\n" " return 0;\n" "};\n" }, "main.ww", 2, { "bytes.contains_", "strings.contains_" } }, /* Non-regression: same module, two fns with the same `match` * shape. Per-fn labelseq still gives unique labels — both fns' * labels carry the same `pkg.fn_` prefix but differ in the seq * suffix. */ { "same_module_same_leaf", 0, 1, { "main.ww", "package main;\n" "fn a(needle: (u8 | []u8)) i32 = {\n" " match (needle) {\n" " case let c: u8 => return 1;\n" " case let s: []u8 => return 2;\n" " };\n" " return 0;\n" "};\n" "fn b(needle: (u8 | []u8)) i32 = {\n" " match (needle) {\n" " case let c: u8 => return 3;\n" " case let s: []u8 => return 4;\n" " };\n" " return 0;\n" "};\n" "export fn main() i32 = {\n" " let x: (u8 | []u8) = 7u8;\n" " if (a(x) != 1) { return 30; };\n" " if (b(x) != 3) { return 31; };\n" " return 0;\n" "};\n" }, "main.ww", 2, { "main.a_match_next_1", "main.b_match_next_1" } }, }; /* Write `src` to `/`, mkdir'ing intermediate dirs. */ static int write_one(const char *dir, const char *rel, const char *src) { char path[512]; snprintf(path, sizeof path, "%s/%s", dir, rel); /* mkdir parent if rel has a slash. */ char *slash = strchr(path + strlen(dir) + 1, '/'); if (slash) { *slash = '\0'; mkdir(path, 0755); *slash = '/'; } FILE *f = fopen(path, "wb"); if (!f) return -1; fputs(src, f); fclose(f); return 0; } static char * slurp(const char *path, size_t *outsz) { FILE *f = fopen(path, "rb"); if (!f) return NULL; fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET); if (n < 0) { fclose(f); return NULL; } char *buf = malloc((size_t)n + 1); if (!buf) { fclose(f); return NULL; } size_t got = fread(buf, 1, (size_t)n, f); fclose(f); buf[got] = '\0'; if (outsz) *outsz = got; return buf; } static int run_row(const char *driver, const struct row *r, int idx, const char *tag) { char tmpdir[64]; snprintf(tmpdir, sizeof tmpdir, "/tmp/mklabel_%s_%d_%d", tag, getpid(), idx); mkdir(tmpdir, 0755); int fail = 0; for (int k = 0; k < r->n_files; k++) { if (write_one(tmpdir, r->files[2 * k], r->files[2 * k + 1]) != 0) { fprintf(stderr, "mklabel_modscoped[%s][%s]: write %s failed\n", tag, r->label, r->files[2 * k]); return 1; } } char cmd[2048]; /* #93 sep layout: emit asm to .sepwork/.s; pin * WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */ snprintf(cmd, sizeof cmd, "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc %s build --sep " "-o \"${b%%.ww}\" \"$b\" >/dev/null 2>&1", tmpdir, r->entry, tmpdir, driver); if (runwait(cmd) != 0) { fprintf(stderr, "mklabel_modscoped[%s][%s]: build failed\n", tag, r->label); fail++; goto cleanup; } /* Runtime check. */ char entry_bin[512]; snprintf(entry_bin, sizeof entry_bin, "%s/%s", tmpdir, r->entry); char *dot = strrchr(entry_bin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; int got = runwait(entry_bin); if (got != r->want_exit) { fprintf(stderr, "mklabel_modscoped[%s][%s]: rc=%d want=%d\n", tag, r->label, got, r->want_exit); fail++; } /* Required-substring check on the emitted .s. #93: root + every * imported pkg now compile to separate .sepwork/.s; the * cross-module label substrings (mod1.x / strings.x) live in the * per-package files, so concat ALL of them (sorted glob) — a single * __root.s read would FALSE-FAIL the multi-package rows. */ char stem[512]; snprintf(stem, sizeof stem, "%s/%s", tmpdir, r->entry); char *d2 = strrchr(stem, '.'); if (d2 && strcmp(d2, ".ww") == 0) *d2 = '\0'; char alls[600], catcmd[1200]; snprintf(alls, sizeof alls, "%s/all.s", tmpdir); snprintf(catcmd, sizeof catcmd, "cat %s.sepwork/*.s > %s 2>/dev/null", stem, alls); (void)runwait(catcmd); size_t sz = 0; char *asm_buf = slurp(alls, &sz); if (!asm_buf) { fprintf(stderr, "mklabel_modscoped[%s][%s]: cannot read %s\n", tag, r->label, alls); fail++; } else { for (int k = 0; k < r->n_required; k++) { if (strstr(asm_buf, r->required[k]) == NULL) { fprintf(stderr, "mklabel_modscoped[%s][%s]: missing label " "substring \"%s\" in %s\n", tag, r->label, r->required[k], alls); fail++; } } free(asm_buf); } cleanup: /* Best-effort cleanup. Leaving artifacts is fine on failure. */ snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir); (void)runwait(cmd); return fail; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[512]; if (bin[0] != '/') { char cwd[256]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[640], wdrv[640]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); int have_ww = (access(wdrv, X_OK) == 0); int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int i = 0; i < n; i++) { total++; fail += run_row(cdrv, &rows[i], i, "cs"); if (have_ww) { total++; fail += run_row(wdrv, &rows[i], i, "ws"); } } if (fail) { fprintf(stderr, "mklabel_modscoped: %d/%d fixtures failed\n", fail, total); return 1; } printf("mklabel_modscoped: %d/%d ok\n", total, total); return 0; }