/* * 793_xmod_struct_argpush_collide_run — project #21/#224 runtime + byte-id * net. An inferred-let struct LOCAL whose type leaf collides with a * DIFFERENT module's same-leaf struct miscompiled in wwstage at THREE * caller-side register-ABI sites, all keyed off the name-resolved * (collision-prone) struct instead of the checker-STAMPED tinfo: * * - push (cgenutil.ww pushargsrev): the by-value struct call-arg * COUNT came from name-keyed structparamsize(lc.tnode). On the * collision lc.tnode is a bare leaf structlookup mis-resolves to the * foreign same-leaf struct (>16B → 0), so the fast path was skipped * and the arg dropped to a scalar single-PUSHQ — the 2nd eightbyte * (the sub-8 tail) was lost. [the original #21 site] * - recv (cgenstmt.ww cgletbody): `let s = m1.mk()` sized the * register receive via structlookup(tn.str) → the foreign struct's * word count, OVER-copying a 24B foreign over a 16B local (spilling * CX into a neighbour slot). * - field (cgenexpr.ww cgdot direct-struct arm): `s.` resolved * the offset via structlookupchain(tn) → the foreign struct's layout, * reading the field at the WRONG offset + wrong load-op. [#224, filed * not-fixed by 784/#223] * * cstage is correct at all three — it keys on args[i]->type / lu->size / * t->fields (the checker-STAMPED type), never a name re-lookup. Fix * (wwstage-only align-UP, #21/#224, ONE commit): route all three off the * stamped tinfo — structabisizetn(arg/tn.type_) for the push+recv COUNT, * tichase(dotlhs.type_).fields (tfield) for the field offset+type. * * GATE-BLIND in the bootstrap (same class as 784/#223): the trigger needs * a SECOND module contributing a same-leaf struct of a DIFFERENT size, so * the name-keyed lookup mis-resolves. A single-module synthetic resolves * correctly (structlookup's same-module pass) and stays byte-id — it does * NOT redden. Hence this 2-module probe. * * row | m1.pair (local) | m2.pair (foreign) | site(s) | exit * -----------------+----------------------+-------------------+---------------+----- * recvpush_t16 | {u64,u16} 16B | 24B | recv + push | 107 * fieldread_t16 | {u64,u16} 16B | 24B | recv + field | 107 * combined_t16 | {u64,u16} 16B | 24B | recv+field+push| 114 * recvpush_t12 | {u32,u32,u32} 12B | 24B | recv + push | 6 * * cstage `ww build` + run pins runtime; the wwstage binary is run * too (the bug WAS a wrong wwstage runtime value); raw cs.s vs ww.s cmp * over the driver-produced per-package asm pins rule-10 byte-id (the * discriminating net — pre-fix __root.s diverges at the push/recv/field). */ #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; } static int slurp_eq(const char *a, const char *b) { FILE *fa = fopen(a, "rb"); FILE *fb = fopen(b, "rb"); if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } int rc = 0; for (;;) { int ca = fgetc(fa); int cb = fgetc(fb); if (ca != cb) { rc = -1; break; } if (ca == EOF) break; } if (ferror(fa) || ferror(fb)) rc = -1; if (fclose(fa) != 0) rc = -1; if (fclose(fb) != 0) rc = -1; return rc; } struct file { const char *name; const char *src; }; struct scenario { const char *label; const struct file *files; /* name==NULL terminates */ int want_exit; }; /* m2.pair is a 24B struct sharing the leaf `pair` with m1.pair — the * name-keyed lookup's any-module fallback mis-resolves m1.pair to it. */ static const char m2_24[] = "package m2;\n" "export type pair = struct { hi: u64, mid: u64, lo: u64 };\n" "export fn use2(p: pair) i32 = { return (p.hi + p.mid + p.lo): i32; };\n"; /* ---- recvpush_t16: inferred-let recv + by-value push (the #21 site) -- */ static const struct file recvpush_t16_files[] = { { "m1.ww", "package m1;\n" "export type pair = struct { hi: u64, lo: u16 };\n" "export fn mk() pair = { return pair { hi = 100: u64, lo = 7: u16 }; };\n" "export fn consume(p: pair) i32 = { return (p.hi + (p.lo: u64)): i32; };\n" }, { "m2.ww", m2_24 }, { "main.ww", "package main;\n" "import m1;\n" "import m2;\n" "fn main() i32 = {\n" " let dummy: m2.pair;\n" " dummy.hi = 0: u64;\n" " let s = m1.mk();\n" " return m1.consume(s);\n" "};\n" }, { NULL, NULL } }; /* ---- fieldread_t16: inferred-let recv + direct field read (#224) ----- */ static const struct file fieldread_t16_files[] = { { "m1.ww", "package m1;\n" "export type pair = struct { hi: u64, lo: u16 };\n" "export fn mk() pair = { return pair { hi = 100: u64, lo = 7: u16 }; };\n" }, { "m2.ww", m2_24 }, { "main.ww", "package main;\n" "import m1;\n" "import m2;\n" "fn main() i32 = {\n" " let dummy: m2.pair;\n" " dummy.hi = 0: u64;\n" " let s = m1.mk();\n" " return (s.hi + (s.lo: u64)): i32;\n" "};\n" }, { NULL, NULL } }; /* ---- combined_t16: recv + field + push in one fn -------------------- */ static const struct file combined_t16_files[] = { { "m1.ww", "package m1;\n" "export type pair = struct { hi: u64, lo: u16 };\n" "export fn mk() pair = { return pair { hi = 100: u64, lo = 7: u16 }; };\n" "export fn consume(p: pair) i32 = { return (p.hi + (p.lo: u64)): i32; };\n" }, { "m2.ww", m2_24 }, { "main.ww", "package main;\n" "import m1;\n" "import m2;\n" "fn main() i32 = {\n" " let dummy: m2.pair;\n" " dummy.hi = 0: u64;\n" " let s = m1.mk();\n" " let x: i32 = (s.lo: i32);\n" " return m1.consume(s) + x;\n" "};\n" }, { NULL, NULL } }; /* ---- recvpush_t12: a maxalign-4 12B sub-8-tail tail shape ----------- */ static const struct file recvpush_t12_files[] = { { "m1.ww", "package m1;\n" "export type pair = struct { a: u32, b: u32, c: u32 };\n" "export fn mk() pair = { return pair { a = 1: u32, b = 2: u32, c = 3: u32 }; };\n" "export fn consume(p: pair) i32 = { return (p.a + p.b + p.c): i32; };\n" }, { "m2.ww", m2_24 }, { "main.ww", "package main;\n" "import m1;\n" "import m2;\n" "fn main() i32 = {\n" " let dummy: m2.pair;\n" " dummy.hi = 0: u64;\n" " let s = m1.mk();\n" " return m1.consume(s);\n" "};\n" }, { NULL, NULL } }; static const struct scenario scenarios[] = { { "recvpush_t16", recvpush_t16_files, 107 }, { "fieldread_t16", fieldread_t16_files, 107 }, { "combined_t16", combined_t16_files, 114 }, { "recvpush_t12", recvpush_t12_files, 6 }, }; static int run_scenario(const char *cdrv, const char *wdrv, const struct scenario *sc) { /* Private fixture dir — the driver's srcd-first import search can't * pick up a polluting same-name file (mirrors 784/#215). */ char dir[] = "/tmp/ww793_XXXXXX"; if (mkdtemp(dir) == NULL) { fprintf(stderr, "793[%s]: mkdtemp failed\n", sc->label); return -1; } char path[1024], cmd[4096]; int rc = 0; for (int i = 0; sc->files[i].name; i++) { snprintf(path, sizeof path, "%s/%s", dir, sc->files[i].name); FILE *f = fopen(path, "wb"); if (!f) { fprintf(stderr, "793[%s]: write %s\n", sc->label, sc->files[i].name); rc = -1; goto done; } int bad = fputs(sc->files[i].src, f) == EOF; if (fclose(f) != 0) bad = 1; if (bad) { fprintf(stderr, "793[%s]: write %s\n", sc->label, sc->files[i].name); rc = -1; goto done; } } /* cstage build + run pins runtime; all outputs remain under the * scratch dir. */ 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, "793[%s]: cstage build failed\n", sc->label); rc = -1; goto done; } snprintf(path, sizeof path, "%s/main", dir); int gotc = runwait(path); if (gotc != sc->want_exit) { fprintf(stderr, "793[%s]: cstage exit %d, want %d\n", sc->label, gotc, sc->want_exit); rc = -1; } /* wwstage build + run: the bug WAS a wrong wwstage runtime * value (a dropped/over-copied field), so run the wwstage binary too. */ snprintf(cmd, sizeof cmd, "cd %s && %s build -I %s -o %s/mainww %s/main.ww", dir, wdrv, dir, dir, dir); if (runwait(cmd) != 0) { fprintf(stderr, "793[%s]: ww_ww build failed\n", sc->label); rc = -1; goto done; } snprintf(path, sizeof path, "%s/mainww", dir); int gotw = runwait(path); if (gotw != sc->want_exit) { fprintf(stderr, "793[%s]: wwstage exit %d, want %d\n", sc->label, gotw, sc->want_exit); rc = -1; } /* Byte-id net (the discriminator): per-package asm under * .sepwork/.s; concat (sorted glob, identical set both * stages) for the compare. Pre-fix __root.s diverges. */ char cs_s[1024], ws_s[1024]; snprintf(cs_s, sizeof cs_s, "%s/all_cs.s", dir); snprintf(ws_s, sizeof ws_s, "%s/all_ww.s", dir); snprintf(cmd, sizeof cmd, "cat %s/main.sepwork/*.s > %s 2>/dev/null && test -s %s", dir, cs_s, cs_s); if (runwait(cmd) != 0) { fprintf(stderr, "793[%s]: cstage asm aggregation failed\n", sc->label); rc = -1; goto done; } snprintf(cmd, sizeof cmd, "cat %s/mainww.sepwork/*.s > %s 2>/dev/null && test -s %s", dir, ws_s, ws_s); if (runwait(cmd) != 0) { fprintf(stderr, "793[%s]: wwstage asm aggregation failed\n", sc->label); rc = -1; goto done; } if (slurp_eq(cs_s, ws_s) != 0) { fprintf(stderr, "793[%s]: cs.s/ww.s DIFFER (rule-10 byte-id " "violation — #21/#224 regression)\n", sc->label); rc = -1; } done: snprintf(cmd, sizeof cmd, "rm -rf %s/main.sepwork", dir); if (runwait(cmd) != 0) { fprintf(stderr, "793[%s]: cleanup main.sepwork\n", sc->label); rc = -1; } snprintf(cmd, sizeof cmd, "rm -rf %s/mainww.sepwork", dir); if (runwait(cmd) != 0) { fprintf(stderr, "793[%s]: cleanup mainww.sepwork\n", sc->label); rc = -1; } for (int i = 0; sc->files[i].name; i++) { snprintf(path, sizeof path, "%s/%s", dir, sc->files[i].name); if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "793[%s]: cleanup %s\n", sc->label, sc->files[i].name); rc = -1; } } const char *children[] = { "main", "mainww", "all_cs.s", "all_ww.s", 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, "793[%s]: cleanup %s\n", sc->label, children[i]); rc = -1; } } if (rmdir(dir) != 0) { fprintf(stderr, "793[%s]: cleanup directory\n", sc->label); rc = -1; } return rc; } 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, "793: ww_ww missing — cannot run the cs==ww " "byte-id gate (the whole point of this test)\n"); return 1; } int n = (int)(sizeof scenarios / sizeof scenarios[0]); int fail = 0; for (int i = 0; i < n; i++) { if (run_scenario(cdrv, wdrv, &scenarios[i]) != 0) fail++; } if (fail) { fprintf(stderr, "793 xmod_struct_argpush_collide: %d/%d " "scenarios failed\n", fail, n); return 1; } printf("xmod_struct_argpush_collide: %d/%d ok (cstage+wwstage run + " "cs==ww byte-id)\n", n, n); return 0; }