/* * 989_nestfield_run — #44/#55 struct-layout SSoT: a struct with a nested * sub-8 composite field plus a successor must address EVERY field at the * checker's natural offset, identically on the write (construction) and * read (field-access) paths. * * THE BUG (cat-A silent miscompile, gate-blind): wwstage had TWO struct- * layout sources. `registerstruct` (selfhost/cmd/wcc/cgenutil.ww) rebuilt * each field's `fi.foff` via `fieldsize` — SLOT-padded, so a nested * `inner{x:u8,y:u8}` (size 2, slotsize 8) pushed every successor to an 8B * boundary. The READ path (cgplaceaddr/dotbaseaddr) reads the checker's * tfield.offset — NATURAL (inner align 1 → p at offset 1, z packed right * after). So ww WROTE p/z at the slot-padded offset and READ them at the * natural offset → garbage. The shape (nested sub-8 composite + a field * after it) is corpus-ABSENT — ww uses both sources on its OWN structs, so * if such a struct existed the bootstrap would mis-address itself and * 400-green would be impossible; the gate cannot see it, this repro is the * proof. cstage has no structinfo — it reads tfield directly, self- * consistently natural (cmd/w6c/cgen.c). THE FIX: make ww's `fi.foff` a * VIEW of tfield.offset (lock-step walk tstruct.list + ti.fields), so the * second source collapses onto cstage's natural one (cs==ww preserved). * * Each program self-checks every field (write 1/2/3/.., read back, return * the 1-based index of the first mismatch, 0 on all-correct). Pre-fix ww * constructs at slot offsets and reads at natural → a non-zero return on * the first composite-or-successor field, so cs(=0) != ww(!=0) AND ww != * want. Both stages build+run (rule-10); the want is the absolute 0. * * This is a RUNTIME cs==ww check (both stages exit 0): it proves field * offsets are natural and instruction-correct RELATIVE TO the struct base. * It deliberately does NOT gate the absolute .s frame, which is still * cs!=ww on this shape via a SEPARATE pre-existing producer — wwstage * reserves the struct LOCAL's stack slot at slot-padded slotsize, cstage * at natural (task #75). The frame-absolute teeth belong to #75's fix. */ #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: pin the absolute value; -1: cs==ww only */ }; static const struct row rows[] = { /* (1) nested2 — outer2{a:u8, p:inner}: the core divergence. Natural * p at offset 1; pre-fix slot-padded p at offset 8. Construction * (write) vs field-access (read) disagree → o.p.x / o.p.y read wrong. * Returns the 1-based index of the first mismatched field, 0 on ok. */ { "nested2", "package main;\n" "type inner = struct { x: u8, y: u8 };\n" "type outer2 = struct { a: u8, p: inner };\n" "export fn main() int = {\n" " let o: outer2 = outer2 { a = 5, p = inner { x = 6, y = 7 } };\n" " if (o.a: int != 5) { return 1; };\n" " if (o.p.x: int != 6) { return 2; };\n" " if (o.p.y: int != 7) { return 3; };\n" " return 0;\n" "};\n", 0 }, /* (2) nested3 — outer{a:u8, p:inner, z:i64}: z proves post-composite * accumulation stays natural. Pre-fix z sat at the slot-padded offset * past the 8B-padded inner; the natural read undershoots. The wide z * value (0x44444444) is verified inside the program (the exit channel * is 8-bit), so a truncated/mis-addressed z fails the in-program cmp. */ { "nested3", "package main;\n" "type inner = struct { x: u8, y: u8 };\n" "type outer = struct { a: u8, p: inner, z: i64 };\n" "export fn main() int = {\n" " let o: outer = outer { a = 1, p = inner { x = 2, y = 3 }, z = 0x44444444i64 };\n" " if (o.a: int != 1) { return 1; };\n" " if (o.p.x: int != 2) { return 2; };\n" " if (o.p.y: int != 3) { return 3; };\n" " if (o.z != 0x44444444i64) { return 4; };\n" " return 0;\n" "};\n", 0 }, /* (3) control — flat struct {a:u8, b:i64}, no sub-8 composite field. * Natural and slot-padded layouts coincide (b lands at 8 either way); * proves the fix leaves the common case unmoved. */ { "flat_ctl", "package main;\n" "type flat = struct { a: u8, b: i64 };\n" "export fn main() int = {\n" " let o: flat = flat { a = 9, b = 0x33333333i64 };\n" " if (o.a: int != 9) { return 1; };\n" " if (o.b != 0x33333333i64) { return 2; };\n" " return 0;\n" "};\n", 0 }, }; /* 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/nestfld_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/nestfld_%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, "nestfield_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, "nestfield_run[cstage][%s]: exit=%d " "want=%d (field mismatch)\n", rows[i].label, gc, rows[i].want_exit); fail++; } if (!have_ww) { fprintf(stderr, "nestfield_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, "nestfield_run[%s]: cs=%d != ww=%d " "(struct field-offset divergence — #44/#55)\n", rows[i].label, gc, gw); fail++; } if (rows[i].want_exit >= 0 && gw != rows[i].want_exit) { fprintf(stderr, "nestfield_run[wwstage][%s]: exit=%d " "want=%d (field mismatch)\n", rows[i].label, gw, rows[i].want_exit); fail++; } } if (fail) { fprintf(stderr, "nestfield_run: %d/%d checks failed\n", fail, total); return 1; } printf("nestfield_run: %d/%d ok\n", total, total); return 0; }