Files
ww/test/wcc/681_arr_elem_field_write.c
Hojun-Cho 6354f5267c w6c+selfhost: cgassign N_DOT N_INDEX-lhs branch (closes #16)
Symmetric write-side counterpart of #8, bundled across both stages.
cstage [N]Struct write was broken (1986 gated on TY_PTR); wwstage had
no N_DOT(N_INDEX) write branch at all. New branch covers both
[N]*Struct and [N]Struct via viaptr flag, uses fldstoreop for
scalar/sub-word, MOVSS/MOVSD for float, two-MOVQ for str rhs.
Compound (PLUSEQ etc.) wired for integer scalar.
2026-05-14 00:09:46 +09:00

285 lines
8.8 KiB
C

/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
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;
}