diff --git a/test/wcc/794_xmod_ident_prefer.c b/test/wcc/794_xmod_ident_prefer.c deleted file mode 100644 index 8626786a..00000000 --- a/test/wcc/794_xmod_ident_prefer.c +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 794_xmod_ident_prefer — project #55 close. Pins that the wwstage - * checker resolves a BARE value-ident with same-MODULE preference - * (selfhost/cmd/wcc/check.ww exprtype N_IDENT), mirroring cstage's - * scope_lookup_prefer (cmd/wcc/check.c:66). - * - * THE BUG (wwstage-CHECKER-only, #55): exprtype's N_IDENT branch looked - * a bare value-ident up via the flat-scope `scopelookup`, which bucket- - * walks and returns whichever same-leaf symbol heads the bucket — the - * LAST-registered one (scopedefineinmodule prepends). In a combined - * unit the root `main` package registers last, so a bare `v` referenced - * inside an IMPORTED module `aa` (curmod=aa) resolved to `main.v`, not - * `aa.v` — dragging the wrong decl's type. cstage routes the same site - * through scope_lookup_prefer(cur, cur_mod, name) and binds aa.v, so it - * built fine; wwstage mis-typed the use and REJECTED. This is the - * exprtype-N_IDENT member of the #55 bare-leaf cluster (sibling #56 - * fixed the N_CALL-callee path, #53 the bare-TNAME path). - * - * THE FIX (#55): scopelookup(c.cur, e.str) -> scopelookupprefer(c.cur, - * c.curmod, e.str) at the single exprtype N_IDENT site. - * - * THE FIXTURE: `aa` exports a global `v: i32` and `fn getv() i32 = - * { return v; }`. The root `main` declares a same-leaf `fn v() i64` and - * calls aa.getv(). Registration order puts main.v (the i64 fn) at the - * head of the flat-scope bucket, so a non-preferring lookup binds it. - * - cstage builds (prefer -> aa.v: i32, `return v` is i32) and runs: - * main returns aa.getv() == 7. - * - PRE-fix wwstage resolved `v` -> main.v (i64) and rejected - * `return: not assignable (i64 -> i32)` on the combined unit. - * - POST-fix wwstage prefers curmod=aa -> aa.v (i32) and ACCEPTS. - * - * DISCRIMINATOR (787 #13 model): the cstage driver emits the combined - * unit (its checker is already correct), then we re-check that combined - * with w6c_ww. PRE-fix it errored out (non-zero); POST-fix it succeeds. - * w6c_ww REJECTING the combined is the #55 red. - * - * NOTE — no cs==ww byte-id assertion here, deliberately: a minimal - * cross-module same-leaf VALUE-ident reference also trips a SEPARATE, - * still-open cgen-side bare-leaf bug (cgenexpr.ww cgident likewise uses - * the non-preferring scopelookup, so the value-load resolves the wrong - * symbol/width), which would diverge the asm independently of this - * CHECKER fix. The real #55 fmt manifestation flows through checker- - * stamped tagged-union variant indices (not a value-load), so it does - * not hit the cgen path; this minimal probe does, hence the reject-> - * accept polarity is the sound discriminator for the checker fix. The - * cgen-side sibling is tracked separately (the #55 cluster residual). - * - * GATE POLARITY: must stay GREEN. Red means either the cstage routing - * regressed (build/run) or wwstage rejected a valid same-module-preferred - * ident again (w6c_ww rejects the combined). - */ -#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 *name; const char *src; }; - -static const struct file files[] = { - { "aa.ww", - "package aa;\n" - "export let v: i32 = 7;\n" - "export fn getv() i32 = {\n" - " return v;\n" - "};\n" }, - { "main.ww", - "package main;\n" - "import aa;\n" - "fn v() i64 = { return 100; };\n" - "export fn main() i32 = {\n" - " return aa.getv();\n" - "};\n" }, - { NULL, NULL } -}; - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[2048]; - 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[2100], wdrv[2100]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - if (access(wdrv, X_OK) != 0) { - fprintf(stderr, "794: ww_ww missing — cannot run the wwstage " - "accept gate (the whole point of this test)\n"); - return 1; - } - - char dir[] = "/tmp/ww794_XXXXXX"; - if (mkdtemp(dir) == NULL) { - fprintf(stderr, "794: mkdtemp failed\n"); - return 1; - } - - char path[1024], cmd[4096]; - int rc = 0; - - for (int i = 0; files[i].name; i++) { - snprintf(path, sizeof path, "%s/%s", dir, files[i].name); - FILE *f = fopen(path, "wb"); - if (!f) { fprintf(stderr, "794: write %s\n", files[i].name); - rc = 1; goto done; } - int bad = fputs(files[i].src, f) == EOF; - if (fclose(f) != 0) bad = 1; - if (bad) { fprintf(stderr, "794: write %s\n", files[i].name); - rc = 1; goto done; } - } - - /* #94 sep layout: cstage's build (prefer is correct) compiles - * the units and links the binary under the scratch dir. NO cs==ww - * byte-id here — the #55 - * checker fix has an open cgen-side bare-leaf sibling that diverges the - * asm independently; the sound discriminator is the wwstage ACCEPT. */ - snprintf(cmd, sizeof cmd, - "cd %s && %s build -I %s -o %s/main %s/main.ww", - dir, cdrv, dir, dir, dir); - if (runwait(cmd) != 0) { - fprintf(stderr, "794: cstage build failed\n"); - rc = 1; goto done; - } - /* cstage runtime pins the routing: main returns aa.getv() == 7. */ - snprintf(path, sizeof path, "%s/main", dir); - int got = runwait(path); - if (got != 7) { - fprintf(stderr, "794: cstage exit %d, want 7\n", got); - rc = 1; goto done; - } - - /* The #55 discriminator: the wwstage driver's build runs the - * wwstage checker over the same units and must ACCEPT. PRE-fix it - * rejected (bare `v` -> main.v: i64 -> return mismatch); POST-fix it - * prefers curmod=aa -> aa.v: i32 and accepts. */ - snprintf(cmd, sizeof cmd, - "cd %s && %s build -I %s -o %s/mainww %s/main.ww " - ">/dev/null 2>&1", - dir, wdrv, dir, dir, dir); - if (runwait(cmd) != 0) { - fprintf(stderr, "794: ww_ww REJECTED the units (#55: bare " - "value-ident resolved the wrong module's same-leaf symbol)\n"); - rc = 1; goto done; - } - -done: - snprintf(cmd, sizeof cmd, "rm -rf %s/main.sepwork", dir); - if (runwait(cmd) != 0) { fprintf(stderr, "794: cleanup main.sepwork\n"); - rc = 1; } - snprintf(cmd, sizeof cmd, "rm -rf %s/mainww.sepwork", dir); - if (runwait(cmd) != 0) { fprintf(stderr, "794: cleanup mainww.sepwork\n"); - rc = 1; } - for (int i = 0; files[i].name; i++) { - snprintf(path, sizeof path, "%s/%s", dir, files[i].name); - if (unlink(path) != 0 && errno != ENOENT) { - fprintf(stderr, "794: cleanup %s\n", files[i].name); - rc = 1; - } - } - const char *children[] = { "main", "mainww", NULL }; - for (int i = 0; children[i]; i++) { - snprintf(path, sizeof path, "%s/%s", dir, children[i]); - if (unlink(path) != 0 && errno != ENOENT) { - fprintf(stderr, "794: cleanup %s\n", children[i]); - rc = 1; - } - } - if (rmdir(dir) != 0) { - fprintf(stderr, "794: cleanup directory\n"); - rc = 1; - } - if (rc) { - fprintf(stderr, "794 xmod_ident_prefer: FAILED\n"); - return 1; - } - printf("xmod_ident_prefer: ok (cstage run==7 + w6c_ww accepts combined)\n"); - return 0; -} diff --git a/test/xmod/collide_test.ww b/test/xmod/collide_test.ww index 51a62d87..b6a84afa 100644 --- a/test/xmod/collide_test.ww +++ b/test/xmod/collide_test.ww @@ -132,3 +132,148 @@ fn textcount(s: str, sym: str) i32 = { }; testenv.clean(td); }; + +// barevalue (#55 cgen-side sibling) — the migrated 794 carrier +// (test/wcc/794_xmod_ident_prefer.c, retired with this row; its header +// documents both #55 halves). A bare VALUE ident read inside an +// imported module must be classified by the checker-stamped type, not +// a unit-wide leaf table: aa exports `v: i32 = 7` and getv() reads the +// bare `v`; the root declares a same-leaf `fn v() i64`. FLAT layout +// (file-keyed import) is REQUIRED — both files fold into one unit so +// the foreign fn lands in the leaf table; a dir-keyed aa/ compiles aa +// as its own sep unit where main.v is invisible and the collision +// cannot express. Pre-fix wwstage cgen took fnretlookup's leaf +// fallback and emitted `LEAQ aa.v(SB)` (fn address, no load) — the +// binary exited 0; cstage loads 7 (LEAQ+MOVSXD). Both drivers must +// build, run 7, and the flat __root.s must be cs==ww byte-identical +// (rule 10) — the stronger assertion set the carrier deferred while +// the cgen side was open. +@test fn barevalue() void = { + let td: str = testenv.fresh(); + testenv.writefile(strings.concat(td, "/aa.ww"), strings.concat( + "package aa;\n", + "export let v: i32 = 7;\n", + "export fn getv() i32 = {\n", + " return v;\n", + "};\n")); + testenv.writefile(strings.concat(td, "/main.ww"), strings.concat( + "package main;\n", + "import aa;\n", + "fn v() i64 = { return 100; };\n", + "export fn main() i32 = {\n", + " return aa.getv();\n", + "};\n")); + let drvs: []str = ["ww", "ww_ww"]; + let tags: []str = ["cs", "ww"]; + let asms: []str = ["", ""]; + let s: i32 = 0; + for (s < 2) { + let stem: str = strings.concat(td, "/prog.", tags[s]); + let av: []str = [testenv.driver(drvs[s]), "build", "-o", stem, + "-I", td, strings.concat(td, "/main.ww")]; + if (runcode(td, strings.concat("build_", tags[s]), av) != 0) { + fail("barevalue", strings.concat(drvs[s], " build failed")); + }; + let rav: []str = [stem]; + if (runcode(td, strings.concat("run_", tags[s]), rav) != 7) { + fail("barevalue", strings.concat(drvs[s], " exit != 7 ", + "(bare v in aa.getv must LOAD aa.v, not take the ", + "foreign fn's address)")); + }; + asms[s] = testenv.readfile(strings.concat(stem, + ".sepwork/__root.s")); + s += 1; + }; + if (!testenv.same(asms[0], asms[1])) { + fail("barevalue", "cs .s != ww .s (rule 10)"); + }; + testenv.clean(td); +}; + +// defshadow — corner (c) of the same class: a foreign scalar `def` +// leaf colliding with a curmod fn. The def arm ran BEFORE +// fn-classification in wwstage cgident (cstage checks TY_FN first), +// so the bare `MSG` fn value read `MOVQ main.MSG(SB)` data where +// cstage takes the fn address; the qualified `p5aa.MSG` data read hit +// the mirror corner (d) in cgdot. Runtime pre-fix: 141, want 42. +@test fn defshadow() void = { + let td: str = testenv.fresh(); + testenv.writefile(strings.concat(td, "/p5aa.ww"), strings.concat( + "package p5aa;\n", + "export def MSG: i32 = 3;\n")); + testenv.writefile(strings.concat(td, "/main.ww"), strings.concat( + "package main;\n", + "import p5aa;\n", + "fn MSG(x: i32) i32 = { return x + 40; };\n", + "export fn main() i32 = {\n", + " let f = MSG;\n", + " return f(2) + p5aa.MSG - 3;\n", + "};\n")); + let drvs: []str = ["ww", "ww_ww"]; + let tags: []str = ["cs", "ww"]; + let asms: []str = ["", ""]; + let s: i32 = 0; + for (s < 2) { + let stem: str = strings.concat(td, "/prog.", tags[s]); + let av: []str = [testenv.driver(drvs[s]), "build", "-o", stem, + "-I", td, strings.concat(td, "/main.ww")]; + if (runcode(td, strings.concat("build_", tags[s]), av) != 0) { + fail("defshadow", strings.concat(drvs[s], " build failed")); + }; + let rav: []str = [stem]; + if (runcode(td, strings.concat("run_", tags[s]), rav) != 42) { + fail("defshadow", strings.concat(drvs[s], " exit != 42 ", + "(bare MSG is the curmod fn; p5aa.MSG is the ", + "foreign data def)")); + }; + asms[s] = testenv.readfile(strings.concat(stem, + ".sepwork/__root.s")); + s += 1; + }; + if (!testenv.same(asms[0], asms[1])) { + fail("defshadow", "cs .s != ww .s (rule 10)"); + }; + testenv.clean(td); +}; + +// modqualfnval — corner (d) positive twin: a module-QUALIFIED fn used +// as a value must still take the address arm under the stamped-type +// gate (proves the checker stamps the N_DOT fn rvalue as TY_FN). +@test fn modqualfnval() void = { + let td: str = testenv.fresh(); + testenv.writefile(strings.concat(td, "/p6aa.ww"), strings.concat( + "package p6aa;\n", + "export fn hit(x: i32) i32 = { return x + 40; };\n")); + testenv.writefile(strings.concat(td, "/main.ww"), strings.concat( + "package main;\n", + "import p6aa;\n", + "export fn main() i32 = {\n", + " let f = p6aa.hit;\n", + " return f(2);\n", + "};\n")); + let drvs: []str = ["ww", "ww_ww"]; + let tags: []str = ["cs", "ww"]; + let asms: []str = ["", ""]; + let s: i32 = 0; + for (s < 2) { + let stem: str = strings.concat(td, "/prog.", tags[s]); + let av: []str = [testenv.driver(drvs[s]), "build", "-o", stem, + "-I", td, strings.concat(td, "/main.ww")]; + if (runcode(td, strings.concat("build_", tags[s]), av) != 0) { + fail("modqualfnval", strings.concat(drvs[s], + " build failed")); + }; + let rav: []str = [stem]; + if (runcode(td, strings.concat("run_", tags[s]), rav) != 42) { + fail("modqualfnval", strings.concat(drvs[s], + " exit != 42")); + }; + asms[s] = testenv.readfile(strings.concat(stem, + ".sepwork/__root.s")); + s += 1; + }; + if (!testenv.same(asms[0], asms[1])) { + fail("modqualfnval", "cs .s != ww .s (rule 10)"); + }; + testenv.clean(td); +};