/* * 989_tupfieldsize_run — F7-c4 (#43): a for-range destructure over an * array of tuples must stride by the tuple's TRUE size; a slice/str/nested * tuple FIELD carries its full width, not the 8B scalar default. * * THE BUG (cat-A silent miscompile, gate-blind): paramfieldsize * (selfhost/cmd/wcc/cgenstmt.ww) — the structural sizer the for-range * destructure uses to compute the tuple stride and each field's offset — * had no N_TSLICE / N_TTUPLE arm, so a `[]T` tuple-field sized 8 (the * default) instead of its 24B header. The #270-1c array-literal guard * blocks only the literal CONSTRUCTION; the for-range DESTRUCTURE-READ * path is unguarded (ken's oracle refuted "unreachable"). For * `[2]([]u8, i64)` the wwstage strode the tuple at 16 / read field-2 at * offset 8, vs cstage's 32 / 24 — a cs≠ww divergence (the cat-A * signature). cstage's tp->type->size (cmd/w6c/cgen.c N_FORRANGE) reads * the true width. THE FIX: add the N_TSLICE arm (tyslicesize() = 24, the * slice-header SSoT, rule-13) and the N_TTUPLE arm (sum of 8B-floored * element slots, recursive), aligning wwstage UP. * * NB on the assertion: the slice-field row is pinned cs==ww (MATCH-ONLY), * NOT to an absolute value. cstage independently mis-loads a single-word * tuple-destructure element (a SEPARATE bug ken flagged out of F7 scope, * filed as task #40 — cstage's absolute value is wrong: 40 != 45), so * both stages currently land 40, not the arithmetic-expected 45. The * #43 fix's job is to remove the cs≠ww STRIDE/OFFSET divergence (40 vs 16 * → 40 vs 40); pinning cs==ww tracks exactly that and won't false-fail * when the separate destructure-load bug is later fixed (both move * together). The scalar-tuple control IS correct on both stages, so it * also pins the absolute value (no-regression teeth). * * Tuples are built by whole-tuple element store (`xs[i] = (..)`); the * `xs[i].0 = ..` field-store target and the `[[..]]` nested literal are * both independently unsupported / #270-1c-blocked (orthogonal). * * Rows (cstage `ww` always; wwstage `ww_ww` when present; rule-10): * row | shape | assert * -------------------+--------------------------------+---------------- * slice_field_tuple | for(.. [2]([]u8,i64)) | cs==ww [#43 bug] * scalar_tuple_ctl | for(.. [2](i64,i64)) = 48 | cs==ww AND == 48 */ #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; const char *src; int want_exit; /* >= 0: also pin the absolute value; -1: cs==ww only */ }; static const struct row rows[] = { /* (1) #43 — a []u8 tuple-field sized 8 not 24 → wrong stride/offset. * cs==ww only (cstage's 40≠45 is the separate destructure-load bug). */ { "slice_field_tuple", "package main;\n" "export fn main() int = {\n" " let b0: []u8 = ['a', 'b'];\n" " let b1: []u8 = ['x', 'y', 'z'];\n" " let xs: [2]([]u8, i64);\n" " xs[0] = (b0, 10);\n" " xs[1] = (b1, 30);\n" " let sum: i64 = 0;\n" " for (let (b, n) .. xs) {\n" " sum = sum + len(b): i64 + n;\n" " };\n" " return sum: int;\n" "};\n", -1 }, /* (2) control — scalar-only tuple (no slice/str field): paramfieldsize * already handled it, so c4 must not regress it. Correct on both stages: * 3+10+5+30 == 48. Pins cs==ww AND the absolute value. */ { "scalar_tuple_ctl", "package main;\n" "export fn main() int = {\n" " let xs: [2](i64, i64);\n" " xs[0] = (3, 10); xs[1] = (5, 30);\n" " let sum: i64 = 0;\n" " for (let (a, b) .. xs) { sum = sum + a + b; };\n" " return sum: int;\n" "};\n", 48 }, }; /* run_build — build+run `src` via `driver`; returns the binary's exit * code, or -1 on a build failure. */ static int run_build(const char *driver, const struct row *r, int i) { char src[64], tmpdir[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/tupfs_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/tupfs_%d_d_%d", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -2; fputs(r->src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", tmpdir, driver, src); int brc = runwait(cmd); const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char outbin[128]; snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); char *dot = strrchr(outbin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; int got = -1; if (brc == 0) got = runwait(outbin); unlink(src); unlink(outbin); rmdir(tmpdir); 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); 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++; int gc = run_build(cdrv, &rows[i], i); /* cstage must build+run */ if (gc < 0) { fprintf(stderr, "tupfieldsize_run[cstage][%s]: build/run " "failed (got %d)\n", rows[i].label, gc); fail++; continue; } if (rows[i].want_exit >= 0 && gc != rows[i].want_exit) { fprintf(stderr, "tupfieldsize_run[cstage][%s]: exit=%d " "want=%d\n", rows[i].label, gc, rows[i].want_exit); fail++; } if (!have_ww) { fprintf(stderr, "tupfieldsize_run: skip wwstage (no %s)\n", wdrv); continue; } int gw = run_build(wdrv, &rows[i], i); /* rule-10: the cat-A invariant is cs == ww */ if (gw != gc) { fprintf(stderr, "tupfieldsize_run[%s]: cs=%d != ww=%d " "(stride/offset divergence — #43)\n", rows[i].label, gc, gw); fail++; } if (rows[i].want_exit >= 0 && gw != rows[i].want_exit) { fprintf(stderr, "tupfieldsize_run[wwstage][%s]: exit=%d " "want=%d\n", rows[i].label, gw, rows[i].want_exit); fail++; } } if (fail) { fprintf(stderr, "tupfieldsize_run: %d/%d checks failed\n", fail, total); return 1; } printf("tupfieldsize_run: %d/%d ok\n", total, total); return 0; }