Files
ww/test/wcc/941_slice_store_cap_run.c
Hojun-Cho 451e2ebec9 cgen: slice-elem store/read -> 3-word via kind-OR (both stages)
The G-cluster gave str its 3-word {ptr,len,cap} store/read at indexed/field/chained sites, but each arm was gated on str only; the slice arm fell through to the 1-word fldstoreop default, dropping len+cap. A []T value stored through arr[i]=, arr[i].f=, *struct.f=, or value-spine o.i.f= (and read back via arr[i] / arr[i].f) silently lost length and capacity.

Widen all six arms (4 stores + 2 read mirrors) with a kind-OR (TY_STR||TY_SLICE / typeisstr||typeisslice), never a size test: str and slice are both 24B, so a width gate would fire on both and mask the missing slice arm. The str kind stays distinct and nominal -- the arm is widened, the kinds are not collapsed. cstage and wwstage mirrored.

Gate-blind class: store and read were both short, so byte-identity and cstage==wwstage stayed green on self-consistent garbage; only a runtime len/cap round-trip exposes it (test 941, table-driven, 4 shapes x 2 stages, fail-before/pass-after on both ww and ww_ww).

Deref store (*p=) and tuple-elem store (N_MLET/N_MASSIGN, distinct DX,CX,R8 return-ABI) are the same bug class but separate folds.
2026-05-25 00:20:16 +09:00

245 lines
8.7 KiB
C

/*
* 941_slice_store_cap_run — runtime coverage for task #7: a `[]T` (slice-typed)
* VALUE stored through an indexed / field / chained lhs, and read back, must
* move the full 24B {ptr,len,cap} header, not just {ptr}. Slices are 24B always;
* the G-cluster's store+read arms were kind-gated on str ONLY (the slice arm was
* never extended), so a slice value fell to the 1-word fldstoreop/fldloadop
* default and silently DROPPED len+cap. str IS []u8 since Phase 2 (#1), so the
* str 3-word machinery applies to slices verbatim — the fix widens each gate
* from `str` to `str || slice` (kind-OR, never a sz==24 test, which would also
* catch >16B structs).
*
* Sites exercised (cstage cmd/w6c/cgen.c + cgenexpr.ww twin), each store paired
* with its read mirror:
* A whole-element `xs[i] = sl` + read `xs[i]` (cgen.c store ~3650/3692;
* read N_INDEX cgslicehdr; ww cgassign elemtn + cgindex elemisslice).
* B arr[i].field `arr[i].f = sl` + read `arr[i].f` (G1-twin store; cgdot
* arrfield read).
* C chained *struct `r.sym.f = sl` + read `r.sym.f` (G2-twin store; the
* chained-*struct read was already 3-word).
* D value-spine `o.i.f = sl` + read `o.i.f` (value-struct-spine
* store; the value-spine read was already 3-word).
*
* UNLIKE the str store probes (937/938), here BOTH the store AND the read were
* 1-word before the fix, so each row is a store->read-back ROUNDTRIP: a 1-word
* store leaves the dst's len/cap at their prior value, and a 1-word read never
* loads them, so a broken stage yields a value whose len/cap are not p's. The
* full {ptr,len,cap} triple is asserted (ptr via s[0]=='h'=104, len=2, cap=8;
* cap!=len so a dropped len OR cap is caught).
*
* POISON: rows B/C/D seed the dst slot with a DIFFERENT slice q (len=4,cap=5)
* via a PROVEN 3-word slice store that is NOT the site under test — the single-
* dot field store, which was merged onto the slice arm pre-#7 (cgen.c C4.4):
* B via `pr.f = q` (pr=&arr[1], *struct field);
* C via `st.f = q` (direct field);
* D via `pi.f = q` (pi=&o.i, via-ptr field).
* So before the fix the dst keeps q's cap=5 while the read returns stale words;
* either way cap != 8. Row A is a bare roundtrip with no explicit poison: no
* proven 3-word store targets a bare slice array-element header (the same reason
* str has no standalone whole-element-store probe), so the store+read pair is
* itself the discriminator.
*
* Slices are built with explicit pseudo-field stores (`s.ptr=&buf[0]; s.len=N;
* s.cap=M`), the proven construction used by 691 — NOT sub-slicing, whose cap is
* the separate #20 fix that depends on this fold.
*
* Verified fail-before (all 4 rows exit 1, cap reads the poison/stale, not 8) /
* pass-after (exit 0) on BOTH the cstage `ww` and wwstage `ww_ww` drivers.
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
* race does not apply (mirrors the 928/932 precedent).
*/
#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[] = {
/* A — whole-element `xs[0] = p` into a [2][]u8 local array, read back
* via `xs[0]`. Exercises the N_INDEX store arm (elem_is_slice) and the
* N_INDEX read (cgslicehdr) together. No proven poison for a bare
* array-element header; the roundtrip is the probe. */
{ "slice_store_wholeelem",
"export fn main() i32 = {\n"
" let hb: [8]u8; hb[0] = 104u8;\n"
" let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;\n"
" let xs: [2][]u8;\n"
" xs[0] = p;\n"
" let e: []u8 = xs[0];\n"
" if (e.cap: i32 != 8) { return 1; };\n"
" if (e.len: i32 != 2) { return 2; };\n"
" if (e[0] != 104u8) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* B — `arr[i].f = p` into a [N]rec field (G1-twin). Poison arr[1].f
* (cap=5,len=4,'q') via &arr[1] + the proven single-dot *struct field
* store; then the field-of-indexed store lands p (cap=8,len=2,'h'). */
{ "slice_store_arrfield",
"type rec = struct { f: []u8 };\n"
"export fn main() i32 = {\n"
" let qb: [8]u8; qb[0] = 113u8;\n"
" let hb: [8]u8; hb[0] = 104u8;\n"
" let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;\n"
" let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;\n"
" let arr: [3]rec;\n"
" let pr: *rec = &arr[1];\n"
" pr.f = q;\n"
" arr[1].f = p;\n"
" let s: []u8 = arr[1].f;\n"
" if (s.cap: i32 != 8) { return 1; };\n"
" if (s.len: i32 != 2) { return 2; };\n"
" if (s[0] != 104u8) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* C — chained `r.sym.f = p` where r.sym is a *inner (G2-twin). Poison
* the pointee field via the proven direct field store (`st.f = q`); the
* chained store derefs r.sym and overwrites st.f. */
{ "slice_store_chained_ptr",
"type inner = struct { f: []u8 };\n"
"type outer = struct { sym: *inner };\n"
"export fn main() i32 = {\n"
" let qb: [8]u8; qb[0] = 113u8;\n"
" let hb: [8]u8; hb[0] = 104u8;\n"
" let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;\n"
" let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;\n"
" let st: inner;\n"
" st.f = q;\n"
" let r: outer;\n"
" r.sym = &st;\n"
" r.sym.f = p;\n"
" let s: []u8 = r.sym.f;\n"
" if (s.cap: i32 != 8) { return 1; };\n"
" if (s.len: i32 != 2) { return 2; };\n"
" if (s[0] != 104u8) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* D — value-spine `o.i.f = p` where o.i is a value-struct field (not a
* pointer). Poison o.i.f via &o.i + the proven via-ptr field store
* (`pi.f = q`); the value-spine store walks o.i and overwrites .f. */
{ "slice_store_valuespine",
"type inner = struct { f: []u8 };\n"
"type outer = struct { i: inner };\n"
"export fn main() i32 = {\n"
" let qb: [8]u8; qb[0] = 113u8;\n"
" let hb: [8]u8; hb[0] = 104u8;\n"
" let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;\n"
" let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;\n"
" let o: outer;\n"
" let pi: *inner = &o.i;\n"
" pi.f = q;\n"
" o.i.f = p;\n"
" let s: []u8 = o.i.f;\n"
" if (s.cap: i32 != 8) { return 1; };\n"
" if (s.len: i32 != 2) { return 2; };\n"
" if (s[0] != 104u8) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[96], tmpdir[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/slicestore_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/slicestore_%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[160];
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[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[640];
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,
"slice_store_cap_run: 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,
"slice_store_cap_run[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (fail) {
fprintf(stderr, "slice_store_cap_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("slice_store_cap_run: %d/%d ok\n", total, total);
return 0;
}