/* * 944_nonlit_tuple_widen_run — project #116: a NON-LITERAL tuple source * widened into a tagged box (cg_widen_tagged_store / cgwidentaggedstore). * * Pre-#116 only a bare/cast tuple LITERAL (N_TUPLE / N_CAST-of-N_TUPLE) * carried a full payload into the union box; every ADDRESSABLE non-literal * tuple source — a tuple IDENT var, a slice/array INDEX `tbl[i]`, a DEREF * `*p` — hit the `else fatal` ("tuple-typed source shape unwired"), LOUD and * IDENTICAL on both stages. This blocked fold-6's * `append(charsets[...], charclass_map[cc_idx])` (the INDEX shape). * * THE FIX (align-BOTH; both stages were loud, so there is NO runtime * reference and byte-id is BLIND — #263). A new arm resolves the source * ADDRESS through the existing place-address spine (cgplaceaddr — the same * one &base[i] / *p / ident use) into SI, then BLOCK-COPIES sum(tuple_eslot) * bytes (== the tuple type's table size under the 8B-slot layout) from the * source address into the box payload (write_off+8), and stamps the variant * tag. The source-tuple-in-memory layout already EQUALS the box-payload * layout (same tuple_eslot strides the literal loop fills), so it is a flat * address block-copy — no re-slotting, and a tagged element rides over as * its already-built box. One mechanism closes the addressable trio. * * SCOPE (kept loud, out of #116): a CALL/sret tuple result (the tuple sits * at the sret address, #40-kin) and struct-field / array-literal-element * tuple sources (loud EARLIER at their construction, #49 / #270-1c). * * Because byte-id is blind here, the RUNTIME read-back is the correctness * net (rob's obligation): EACH covered shape (ident / index / deref) builds * the box from its non-literal tuple source, match-extracts the variant, and * ASSERTS the payload survived — `len(x.0)` proves the str header rode over, * `(*x.1)(arg)` indirect-calls the fn-ptr element and checks its result * (drew's P3/P4 read-back). The tuple-LITERAL row is the already-green * regression guard. All four rows confirmed to LOUD on base da30f10. * * The read-back deliberately avoids `str !=` (a pre-existing cs!=ww * comparison-codegen divergence, rt_streq vs inline CMPQ — unrelated to * #116) so the K_RUN byte-id check isolates the widen arm. * * NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling * race does not apply (903/940/945 precedent). */ #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), cb = fgetc(fb); if (ca != cb) { rc = -1; break; } if (ca == EOF) break; } fclose(fa); fclose(fb); return rc; } #define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */ struct row { const char *label; const char *src; int kind; int want; }; static const struct row rows[] = { /* control (already green pre-#116): a CAST tuple LITERAL. Regression * guard — the literal arm must keep emitting the same fill. */ { "literal", "package main;\n" "fn fa(c: rune) bool = { return c == 'a'; };\n" "type ci = (str, *fn(c: rune) bool);\n" "type u = (rune | ci);\n" "export fn main() i32 = {\n" " let nm: str = \"ww\";\n" " let cf: *fn(c: rune) bool = &fa;\n" " let s: []u = [];\n" " append(s, ((nm, cf): ci));\n" " match (s[0]) {\n" " case let r: rune => { return 90; };\n" " case let x: ci => {\n" " if ((len(x.0): i32) != 2) { return 1; };\n" " if (!(*x.1)('a')) { return 2; };\n" " return 0;\n" " };\n" " };\n" " return 4;\n" "};\n", K_RUN, 0 }, /* IDENT: a tuple-typed local var as the widen source. */ { "ident", "package main;\n" "fn fb(c: rune) bool = { return c == 'b'; };\n" "type ci = (str, *fn(c: rune) bool);\n" "type u = (rune | ci);\n" "export fn main() i32 = {\n" " let nm: str = \"zz\";\n" " let cf: *fn(c: rune) bool = &fb;\n" " let t: ci = (nm, cf);\n" " let s: []u = [];\n" " append(s, t);\n" " match (s[0]) {\n" " case let r: rune => { return 90; };\n" " case let x: ci => {\n" " if ((len(x.0): i32) != 2) { return 1; };\n" " if (!(*x.1)('b')) { return 2; };\n" " if ((*x.1)('a')) { return 3; };\n" " return 0;\n" " };\n" " };\n" " return 4;\n" "};\n", K_RUN, 0 }, /* INDEX: `tbl[i]` — the fold-6 shape. The array is filled by tuple- * ident element stores (the foldable construction at HEAD), then the * indexed element is the widen source. */ { "index", "package main;\n" "fn fb(c: rune) bool = { return c == 'b'; };\n" "fn fa(c: rune) bool = { return c == 'a'; };\n" "type ci = (str, *fn(c: rune) bool);\n" "type u = (rune | ci);\n" "export fn main() i32 = {\n" " let n0: str = \"aa\"; let c0: *fn(c: rune) bool = &fa;\n" " let n1: str = \"zzz\"; let c1: *fn(c: rune) bool = &fb;\n" " let t0: ci = (n0, c0);\n" " let t1: ci = (n1, c1);\n" " let tbl: [2]ci = [];\n" " tbl[0] = t0;\n" " tbl[1] = t1;\n" " let s: []u = [];\n" " append(s, tbl[1]);\n" " match (s[0]) {\n" " case let r: rune => { return 90; };\n" " case let x: ci => {\n" " if ((len(x.0): i32) != 3) { return 1; };\n" " if (!(*x.1)('b')) { return 2; };\n" " if ((*x.1)('a')) { return 3; };\n" " return 0;\n" " };\n" " };\n" " return 4;\n" "};\n", K_RUN, 0 }, /* DEREF: `*p` — a pointer to a tuple var as the widen source. */ { "deref", "package main;\n" "fn fa(c: rune) bool = { return c == 'a'; };\n" "type ci = (str, *fn(c: rune) bool);\n" "type u = (rune | ci);\n" "export fn main() i32 = {\n" " let nm: str = \"qqq\";\n" " let cf: *fn(c: rune) bool = &fa;\n" " let t: ci = (nm, cf);\n" " let p: *ci = &t;\n" " let s: []u = [];\n" " append(s, *p);\n" " match (s[0]) {\n" " case let r: rune => { return 90; };\n" " case let x: ci => {\n" " if ((len(x.0): i32) != 3) { return 1; };\n" " if (!(*x.1)('a')) { return 2; };\n" " if ((*x.1)('b')) { return 3; };\n" " return 0;\n" " };\n" " };\n" " return 4;\n" "};\n", K_RUN, 0 }, }; /* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */ static int run_driver(const char *driver, const struct row *r, int i) { char tmpdir[96], src[128], outbin[128], rmcmd[160], cmd[1024]; snprintf(tmpdir, sizeof tmpdir, "/tmp/ntw_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(src, sizeof src, "%s/ntw_%d_%d.ww", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/ntw_%d_%d", tmpdir, getpid(), i); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); FILE *f = fopen(src, "wb"); if (!f) { runwait(rmcmd); return -1; } fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>&1", driver, outbin, src); int brc = runwait(cmd); if (brc != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); runwait(rmcmd); return -1; } int got = runwait(outbin); runwait(rmcmd); if (got != r->want) { fprintf(stderr, "row[%s]: %s exit %d, want %d\n", r->label, driver, got, r->want); return 1; } return 0; } /* cs==ww .s byte-id (rule 10). */ static int byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i) { char src[96], cs_s[96], ws_s[96], cmd[1024]; snprintf(src, sizeof src, "/tmp/ntw_bi_%d_%d.ww", getpid(), i); snprintf(cs_s, sizeof cs_s, "/tmp/ntw_bi_%d_%d_cs.s", getpid(), i); snprintf(ws_s, sizeof ws_s, "/tmp/ntw_bi_%d_%d_ww.s", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); int rc = 0; /* Single-file package — w6c compiles the .ww source directly (no * imports, no combined.ww amalgam needed); diff the two emissions. */ snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c failed\n", r->label); rc = 1; } else { snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, ws_s, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c_ww failed\n", r->label); rc = 1; } else if (slurp_eq(cs_s, ws_s) != 0) { fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER " "(#116 non-literal tuple widen regression)\n", r->label); rc = 1; } } unlink(src); unlink(cs_s); unlink(ws_s); return rc; } 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], w6c[640], w6c_ww[640]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); snprintf(w6c, sizeof w6c, "%s/w6c", bin); snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); struct { const char *name; const char *path; 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].path, X_OK) != 0) { fprintf(stderr, "nonlit_tuple_widen: skip %s (no %s)\n", drivers[d].name, drivers[d].path); continue; } for (int i = 0; i < n; i++) { total++; if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++; } } if (access(w6c_ww, X_OK) == 0) { for (int i = 0; i < n; i++) { total++; if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++; } } if (fail) { fprintf(stderr, "nonlit_tuple_widen: %d/%d checks failed\n", fail, total); return 1; } printf("nonlit_tuple_widen: %d/%d ok\n", total, total); return 0; }