/* * 989_structlocal_frame (#75) — a struct LOCAL's stack slot must be the * struct's NATURAL size rounded to the 8B grain, identically in both * stages. wwstage's slotsize() TY_STRUCT arm (selfhost/cmd/wcc/ * cgenutil.ww) returned ti.slotsize — the SUM of the slot-padded field * widths — so a sub-8 nested-composite struct local over-reserved: a * nested inner{x:u8,y:u8} (size 2, slotsize 8) padded its in-struct * footprint, and the local's frame slot inherited that pad. cstage has * no slotsize SSoT — it reserves the local at f->type->size, the * checker's NATURAL r.size (cmd/w6c/cgen.c). So on outer{a:u8, * p:inner{x:u8,y:u8}, z:i64} ww emitted frame $32 / struct-base -24(BP) * while cstage emitted $16 / -16(BP): a uniform -8 BP shift on every * field access. Both stages exit 0 (each self-consistent), so it is * runtime-invisible — but it is a cs!=ww .s divergence (rule 10) and a * latent byte-id gate-landmine the day such a struct enters the corpus. * Same dual-SSoT leak as #44 (field-OFFSET) / #55, one notion over: * struct-local-slot-SIZE. * * THE FIX: slotsize()'s TY_STRUCT arm returns round8(ti.size). The * TUPLE arm (8B/elem slot, user ruling #60) and ARRAY arm (element * stride, #48) keep ti.slotsize — deliberate, ruled divergences. * * This is the FRAME-ABSOLUTE proof: each fixture is compiled through * cstage w6c AND wwstage w6c_ww and the emitted .s is byte-diffed (the * 683 asm_byte_identical pattern). Byte-identity subsumes the frame: * pre-fix nested3 differs at `TEXT main,$32` vs `$16`, `SUBQ $32` vs * `$16`, and every `-24(BP)` vs `-16(BP)`; post-fix the two .s are * byte-for-byte equal. The sibling 989_nestfield_run is the RUNTIME * exit-code proof (deliberately NOT frame-gated — see its header); this * file is the .s/frame-absolute one #75 owes. */ #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; }; static const struct row rows[] = { /* nested3 — the core divergence. outer{a:u8, p:inner{x:u8,y:u8}, * z:i64}: natural size 16 (a@0, p@1 packed, z@8), slot-padded sum 24 * (a:8 + p:8 + z:8). Pre-fix ww frame $32 / base -24(BP); cstage * $16 / -16(BP). The successor z makes the -8 shift visible on a * wide field too. */ { "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" }, /* tail-varied — same sub-8 nested composite, a narrower (u32) tail. * outt{a:u8, p:inner{x:u8,y:u8}, w:u32}: natural size 8 (w@4), * slot-padded sum larger. Proves the fix on a second sub-8 shape * whose natural size differs from nested3's. */ { "tail_u32", "package main;\n" "type inner = struct { x: u8, y: u8 };\n" "type outt = struct { a: u8, p: inner, w: u32 };\n" "export fn main() int = {\n" " let o: outt = outt { a = 1, p = inner { x = 2, y = 3 }," " w = 0x1234u32 };\n" " if (o.p.y: int != 3) { return 3; };\n" " if (o.w != 0x1234u32) { return 5; };\n" " return 0;\n" "};\n" }, /* flat control — flat{a:u8, b:i64}, no sub-8 composite field. * Natural and slot-padded coincide (b@8 either way), so the .s is * byte-id BEFORE and after; proves the fix leaves the common case * unmoved (no spurious frame change on plain structs). */ { "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" }, }; /* asm_byte_identical — emit .s via cstage w6c and wwstage w6c_ww and * diff (the 683_arr_strslice_elem harness). Pre-#75 a sub-8 nested * struct local over-reserved its frame slot on wwstage, so the .s * diverged (frame size, SUBQ, every field BP offset); the natural-size * slot makes the two byte-for-byte equal. */ static int asm_byte_identical(const char *bin, const struct row *r, int i) { char src[64], cs[64], ws[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/slfr_%d_%d.ww", getpid(), i); snprintf(cs, sizeof cs, "/tmp/slfr_%d_%d_c.s", getpid(), i); snprintf(ws, sizeof ws, "/tmp/slfr_%d_%d_w.s", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); if (runwait(cmd) != 0) { fprintf(stderr, "structlocal_frame[%s]: w6c errored\n", r->label); unlink(src); return -1; } snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", bin, ws, src); if (runwait(cmd) != 0) { fprintf(stderr, "structlocal_frame[%s]: w6c_ww errored\n", r->label); unlink(src); unlink(cs); return -1; } FILE *fc = fopen(cs, "rb"); FILE *fw = fopen(ws, "rb"); int rc = 0; if (!fc || !fw) { rc = -1; } else { for (;;) { int a = fgetc(fc); int b = fgetc(fw); if (a != b) { rc = -1; break; } if (a == EOF) break; } } if (fc) fclose(fc); if (fw) fclose(fw); if (rc != 0) fprintf(stderr, "structlocal_frame[%s]: cstage vs wwstage " "asm differs (#75 struct-local frame)\n", r->label); unlink(src); unlink(cs); unlink(ws); return rc; } 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 wdrv[1024]; snprintf(wdrv, sizeof wdrv, "%s/w6c_ww", bin); if (access(wdrv, X_OK) != 0) { fprintf(stderr, "structlocal_frame: skip (no %s)\n", wdrv); printf("structlocal_frame: skipped (no wwstage)\n"); return 0; } int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int i = 0; i < n; i++) { total++; if (asm_byte_identical(bin, &rows[i], i) != 0) fail++; } if (fail) { fprintf(stderr, "structlocal_frame: %d/%d fixtures failed\n", fail, total); return 1; } printf("structlocal_frame: %d/%d ok\n", total, total); return 0; }