/* * 681_arr_elem_field_write — `arr[i].field = v` (write-side counterpart * of 680_arr_elem_field). Both stages had a silent store-drop: * * cstage's chained-pointer-field-write branch (cgen.c near 1986) caught * `[N]*Struct` writes via its `!= N_IDENT` guard but skipped `[N]Struct` * value-arrays (TY_STRUCT element fails the TY_PTR guard). * * wwstage had no N_DOT(N_INDEX,...) lhs branch at all in cgassign * (cgenexpr.ww). Both shapes silently emitted no store. * * Closed in task #16 by mirroring task #8's read-side N_INDEX-lhs branch * onto the write side (fldstoreop, str/float leaf coverage, deref-or-not * for `*Struct` vs `Struct` element). * * Coverage — both array shapes, scalar/sub-word/str/float leaves, dyn * idx, and read-modify-write compound (`arr[i].x += 5`). Cstage and * wwstage on every fixture; wwstage gated on access(X_OK). */ #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; }; static const struct row rows[] = { /* [N]*Struct, i32 field write. Sole write into stk[0].x; read it * back. Returns 42. */ { "ptr_arr_i32_write", "type nd = struct { name: str, x: i32 };\n" "fn main() i32 = {\n" " let v: nd; v.name = \"hi\"; v.x = 0;\n" " let stk: [16]*nd; stk[0] = &v;\n" " stk[0].x = 42;\n" " return stk[0].x;\n" "};\n", 42 }, /* [N]*Struct, u8 field write. Sub-word store via MOVB through * fieldstoreop. Returns 9. */ { "ptr_arr_u8_write", "type nd = struct { tag: u8, pad: u8, x: i32 };\n" "fn main() i32 = {\n" " let v: nd; v.tag = 0u8; v.pad = 0u8; v.x = 0;\n" " let stk: [4]*nd; stk[0] = &v;\n" " stk[0].tag = 9u8;\n" " return stk[0].tag: i32;\n" "};\n", 9 }, /* [N]*Struct, str field write — 16B store of both ptr+len. * Returns len("hello") = 5. */ { "ptr_arr_str_write", "type nd = struct { a: i32, b: i32, name: str };\n" "fn main() i32 = {\n" " let v: nd; v.a = 0; v.b = 0; v.name = \"old\";\n" " let stk: [16]*nd; stk[0] = &v;\n" " stk[0].name = \"hello\";\n" " return stk[0].name.len: i32;\n" "};\n", 5 }, /* [N]Struct, i32 field write (value-array). The cstage chained- * pointer-field branch's TY_PTR guard skips this; without the new * value-array path the store silently drops. Returns 50. */ { "val_arr_i32_write", "type nd = struct { name: str, x: i32 };\n" "fn main() i32 = {\n" " let arr: [4]nd;\n" " arr[2].x = 50;\n" " return arr[2].x;\n" "};\n", 50 }, /* [N]Struct, u8 field write — value-array sub-word store. Returns 7. */ { "val_arr_u8_write", "type nd = struct { tag: u8, pad: u8, x: i32 };\n" "fn main() i32 = {\n" " let arr: [4]nd;\n" " arr[1].tag = 7u8;\n" " return arr[1].tag: i32;\n" "};\n", 7 }, /* [N]Struct, str field write — value-array 16B store. Returns * len("world") = 5. */ { "val_arr_str_write", "type nd = struct { name: str, x: i32 };\n" "fn main() i32 = {\n" " let arr: [4]nd;\n" " arr[1].name = \"world\";\n" " return arr[1].name.len: i32;\n" "};\n", 5 }, /* Sub-word signed: write -3i8 then read back as i32. If MOVB * stored the truncated low byte and the field's fldloadop sign- * extends correctly, the i32 read returns -3. Returns 42 when * the equality check passes, 0 otherwise. */ { "ptr_arr_i8_signed_write", "type nd = struct { tag: i8, pad: u8, x: i32 };\n" "fn main() i32 = {\n" " let v: nd; v.tag = 0i8; v.pad = 0u8; v.x = 0;\n" " let stk: [4]*nd; stk[0] = &v;\n" " stk[0].tag = -3i8;\n" " let t: i32 = stk[0].tag: i32;\n" " if (t == -3) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* Value-array sub-word signed: distinct from ptr_arr_i8_signed — * this path takes LEAQ &arr[i] (no MOVQ-to-deref) and stores the * truncated byte via fldstoreop MOVB. Read back via fldloadop * MOVSBQ sign-extends to i32; -3 round-trips. Returns 42. */ { "val_arr_i8_signed_write", "type nd = struct { tag: i8, pad: u8, x: i32 };\n" "fn main() i32 = {\n" " let arr: [4]nd;\n" " arr[1].tag = -3i8;\n" " let t: i32 = arr[1].tag: i32;\n" " if (t == -3) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* f64 field write through value-array — pins MOVSD from X0 into * field offset of &arr[i]. Returns 23. */ { "val_arr_f64_write", "type nd = struct { x: i32, d: f64 };\n" "fn main() i32 = {\n" " let arr: [4]nd;\n" " arr[1].d = 23.0f64;\n" " let v: f64 = arr[1].d;\n" " return v: i32;\n" "};\n", 23 }, /* Dyn idx through [N]*Struct write — index isn't a literal, so * the IMULQ path fires. Returns 99. */ { "ptr_arr_dyn_idx_write", "type nd = struct { name: str, x: i32 };\n" "fn main() i32 = {\n" " let a: nd; a.name = \"a\"; a.x = 0;\n" " let b: nd; b.name = \"b\"; b.x = 0;\n" " let c: nd; c.name = \"c\"; c.x = 0;\n" " let stk: [4]*nd;\n" " stk[0] = &a; stk[1] = &b; stk[2] = &c;\n" " let i: i32 = 2;\n" " stk[i].x = 99;\n" " return stk[2].x;\n" "};\n", 99 }, /* Read-modify-write compound on [N]Struct — exercises BOTH the * read (load old field) and the write (store combined). For * `arr[1].x += 5` with arr[1].x = 37 → 42. */ { "val_arr_compound_plus", "type nd = struct { name: str, x: i32 };\n" "fn main() i32 = {\n" " let arr: [4]nd;\n" " arr[1].x = 37;\n" " arr[1].x += 5;\n" " return arr[1].x;\n" "};\n", 42 }, /* Non-PLUSEQ compound (CARETEQ) on [N]Struct — exercises the XORQ * arm of the compound switch. Worker wired all six integer * compound ops; this pins one of the non-PLUSEQ arms so a future * regression in the switch table is caught. 0x2A ^ 0x14 = 0x3E * (62), then return 62 - 20 = 42. */ { "val_arr_compound_xor", "type nd = struct { name: str, x: i32 };\n" "fn main() i32 = {\n" " let arr: [4]nd;\n" " arr[2].x = 42;\n" " arr[2].x ^= 20;\n" " return arr[2].x - 20;\n" "};\n", 42 }, /* Compound through [N]*Struct — same shape but viaptr is true, * so the address compute deref-loads (BX), and the load_op picks * MOVSXD for i32. 10 += 22 → 32. */ { "ptr_arr_compound_plus", "type nd = struct { name: str, x: i32 };\n" "fn main() i32 = {\n" " let v: nd; v.name = \"a\"; v.x = 10;\n" " let stk: [4]*nd; stk[0] = &v;\n" " stk[0].x += 22;\n" " return stk[0].x;\n" "};\n", 32 }, }; static int run_driver(const char *driver, const struct row *r, int i) { char src[64], tmpdir[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/waew_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/waew_%d_d_%d", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s", tmpdir, driver, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); unlink(src); rmdir(tmpdir); return -1; } 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 = runwait(outbin); unlink(src); unlink(outbin); rmdir(tmpdir); return got; } 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]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); char wdrv[1024]; snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); struct { const char *name; const char *path; int gated_on_existence; } 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_on_existence && access(drivers[d].path, X_OK) != 0) { fprintf(stderr, "arr_elem_field_write: skip %s (no %s)\n", drivers[d].name, drivers[d].path); continue; } for (int i = 0; i < n; i++) { int got = run_driver(drivers[d].path, &rows[i], i); total++; if (got != rows[i].want) { fprintf(stderr, "arr_elem_field_write[%s][%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (fail) { fprintf(stderr, "arr_elem_field_write: %d/%d fixtures failed\n", fail, total); return 1; } printf("arr_elem_field_write: %d/%d ok\n", total, total); return 0; }