test: migrate 933-939 str-cap family to test/lang @test

Fan out the str-cap read (933-936) and store (937-939) families into in-language @test files, following the 932 str_elem_cap template. Additive: the *_run.c stay in the C corpus (they are the only wwstage-runtime net for these cs==ww byte-id-blind shapes); de-dup deferred to fold 6.

Per-shape @test fns, not a data table: each fn varies the codegen shape (base reg / chain depth / tuple return-ABI / store position), so the row-array idiom (blocked by #111) would lose coverage. Read family keeps the spoil()/register-clobber + junk==44 discrimination where the .c has it; store family pre-poisons the slot via a path distinct from the store under test. Asserts are primitives only.
This commit is contained in:
2026-06-22 03:54:54 +09:00
parent 08975c11c9
commit e1740fff10
7 changed files with 424 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
// str_arrfield_cap_test — a str-typed FIELD of an INDEXED element `arr[i].f`
// (N_INDEX-rooted N_DOT) must load the full 24B {ptr,len,cap} header, not just
// {ptr,len}, migrated from test/wcc/936_str_arrfield_cap_run.c (C4.6 arrfield,
// the LAST member of the 3-word-value-read cluster). str is 24B since Phase 2
// (#1); pre-arrfield the `arr[i].f` str arm loaded 2 words (len in BX, ptr in
// AX) and dropped cap. The miscompile was cs==ww, so the 990-997 byte-id gates
// stayed GREEN while the runtime was wrong — behavioral @test is the net. There
// is no adjacent slice-element sibling at this leaf, so the 3-word triple is
// authored directly to the canonical slice-header ABI (len->BX, cap->CX,
// ptr->AX LAST).
//
// POISONING: the store side of `arr[i].f = v` is a separate, still-broken
// store-side gap (filed; out of scope for this read-side fold). So the value
// elements (A, B) are poisoned through `&arr[i]` + a *struct field write, which
// lands a real cap into the element's +16 word; D points its element at a
// separately-built struct (the proven s1local 3-word field store).
//
// DISCRIMINATION: a 2-word read leaves CX holding whatever the index scale-
// multiply left there, never the poison; spoil() additionally interposes a
// CX-clobbering call between the build and the `arr[i].f` read, so a broken
// 2-word read observes spoil's leftover (44), never the poison cap.
package str_arrfield_cap_test;
type rec = struct { f: str };
fn spoil() i32 = {
let z: str = "zzzz";
z.cap = 44i32;
let w: str = z;
return w.cap: i32;
};
@test fn arrfield_array_value() void = {
// A — `arr[i].f` value element of a [N]rec local array (LEAQ base).
// Poison cap=8 (len=2) via &arr[1] + a *struct field store.
let p: str = "hi";
p.cap = 8i32;
let arr: [3]rec;
let pr: *rec = &arr[1];
pr.f = p;
let junk: i32 = spoil();
let s: str = arr[1].f;
assert(s.cap: i32 == 8);
assert(s.len: i32 == 2);
assert(junk == 44);
};
@test fn arrfield_slice_value() void = {
// B — `sl[i].f` value element of a []rec local slice (MOVQ slice.ptr
// base) viewing the same poisoned backing array. Poison cap=9 (len=5).
let p: str = "world";
p.cap = 9i32;
let arr: [3]rec;
let pr: *rec = &arr[1];
pr.f = p;
let sl: []rec = arr[0:3];
let junk: i32 = spoil();
let s: str = sl[1].f;
assert(s.cap: i32 == 9);
assert(s.len: i32 == 5);
assert(junk == 44);
};
@test fn arrfield_ptr_elem() void = {
// D — `arr[i].f` pointer element of a [N]*rec array (LEAQ base, MOVQ
// deref to the *rec, then the leaf field load). The element points at a
// separately-built struct so the poison rides the proven s1local 3-word
// field store, not the broken array-element store. Poison cap=7 (len=3).
let p: str = "abc";
p.cap = 7i32;
let st: rec;
st.f = p;
let arr: [3]*rec;
arr[1] = &st;
let junk: i32 = spoil();
let s: str = arr[1].f;
assert(s.cap: i32 == 7);
assert(s.len: i32 == 3);
assert(junk == 44);
};

View File

@@ -0,0 +1,70 @@
// str_arrfield_store_cap_test — STORING a str into a FIELD of an INDEXED
// element `arr[i].f = v` must write the full 24B {ptr,len,cap} header, not just
// {ptr,len}, migrated from test/wcc/937_str_arrfield_store_cap_run.c (G1 fold).
// str is 24B since Phase 2 (#1); the write-side mirror of the arrfield READ
// (936) previously stored only 2 words (ptr@+0, len@+8) and silently DROPPED
// cap. cs==ww held (byte-id-blind), so behavioral @test is the net.
//
// RHS cap!=len: each test str is a literal whose .cap is mutated to a value
// DISTINCT from its len (a bare literal carries cap==len, hiding a dropped cap).
// PRE-POISON: before the G1 store under test, all three slot words are seeded
// with a DIFFERENT str (ptr='q', len=4, cap=5) via a PROVEN already-3-word store
// path DISTINCT from G1 — A/B via &arr[i] + a *struct field store, D via the
// proven s1local field store (st.f=q). A broken 2-word store never touches +16,
// so the read-back observes the poison cap 5, never 8. The full-triple assert
// (s[0]='h'=104, len=2, cap=8) catches a register slip that clobbers ptr/len.
package str_arrfield_store_cap_test;
type rec = struct { f: str };
@test fn arrfield_store_array_value() void = {
// A — `arr[i].f = v` into a [N]S local array (LEAQ base, value
// element). Poison the slot (cap=5,len=4,'q') via &arr[1] + a *struct
// field store; then the G1 store lands the test str (cap=8,len=2,'h').
let q: str = "qqqq"; q.cap = 5i32;
let p: str = "hi"; p.cap = 8i32;
let arr: [3]rec;
let pr: *rec = &arr[1];
pr.f = q;
arr[1].f = p;
let s: str = arr[1].f;
assert(s.cap: i32 == 8);
assert(s.len: i32 == 2);
assert(s[0] == 104u8);
};
@test fn arrfield_store_slice_value() void = {
// B — `sl[i].f = v` into a []S local slice (MOVQ slice.ptr base, value
// element). The slice views the same backing array; poison the element
// through the array pointer, store and read back through the slice.
let q: str = "qqqq"; q.cap = 5i32;
let p: str = "hi"; p.cap = 8i32;
let arr: [3]rec;
let pr: *rec = &arr[1];
pr.f = q;
let sl: []rec = arr[0:3];
sl[1].f = p;
let s: str = sl[1].f;
assert(s.cap: i32 == 8);
assert(s.len: i32 == 2);
assert(s[0] == 104u8);
};
@test fn arrfield_store_ptr_elem() void = {
// D — `arr[i].f = v` into a [N]*S pointer element (LEAQ base, MOVQ deref
// to the *S, then store at the field). The element points at a
// separately-built struct poisoned via the proven s1local field store
// (st.f=q); the G1 store derefs arr[1] and overwrites st.f.
let q: str = "qqqq"; q.cap = 5i32;
let p: str = "hi"; p.cap = 8i32;
let st: rec;
st.f = q;
let arr: [3]*rec;
arr[1] = &st;
arr[1].f = p;
let s: str = arr[1].f;
assert(s.cap: i32 == 8);
assert(s.len: i32 == 2);
assert(s[0] == 104u8);
};

View File

@@ -0,0 +1,43 @@
// str_chained_field_cap_test — a CHAINED str-field read `o.p.f` (depth >= 2,
// where p is a *struct field of o and f is a str field of *p) must load the
// full 24B {ptr,len,cap} header, not just {ptr,len}, migrated from
// test/wcc/934_str_chained_field_cap_run.c (C4.6 case B). str is 24B since
// Phase 2 (#1); pre-caseB the chained *struct N_DOT str arm loaded 2 words (ptr
// in AX, len in BX) and dropped cap. The miscompile was cs==ww, so the 990-997
// byte-id gates stayed GREEN while the runtime was wrong — behavioral @test is
// the net. This is the chained sibling of str_field_cap_test (direct/single-
// deref S1/S2): the lhs cgexpr leaves AX = the inner *struct pointer, then the
// field is read off AX, cap from +16(AX), base (AX = ptr) read LAST.
//
// DISCRIMINATION: a 2-word read leaves CX untouched, so the row could
// coincidentally pass if CX happened to still carry the poison cap. spoil()
// interposes a CX-clobbering call between the field store and the chained read
// (its own str copy leaves cap=44 in CX), so a broken read observes 44 not 8.
package str_chained_field_cap_test;
type inr = struct { f: str };
type otr = struct { p: *inr };
fn spoil() i32 = {
let z: str = "zzzz";
z.cap = 44i32;
let w: str = z;
return w.cap: i32;
};
@test fn caseb_chained_field() void = {
// `o.p.f`: o a struct holding p: *inr, inr holding str field f. Poison
// cap=8 (len=2).
let p: str = "hi";
p.cap = 8i32;
let ist: inr;
ist.f = p;
let o: otr;
o.p = &ist;
let junk: i32 = spoil();
let s: str = o.p.f;
assert(s.cap: i32 == 8);
assert(s.len: i32 == 2);
assert(junk == 44);
};

View File

@@ -0,0 +1,59 @@
// str_chainfield_store_cap_test — STORING a str into a FIELD reached through a
// *struct-VALUED EXPRESSION (`r.sym.f = v`, the chained-N_DOT store) must write
// the full 24B {ptr,len,cap} header, not just {ptr,len}, migrated from
// test/wcc/938_str_chainfield_store_cap_run.c (G2 fold). str is 24B since
// Phase 2 (#1); the chained-store arm previously stored only 2 words (ptr@+0,
// len@+8) and silently DROPPED cap. cs==ww held (byte-id-blind), so behavioral
// @test is the net.
//
// RHS cap!=len: each test str is a literal whose .cap is mutated DISTINCT from
// its len (a bare literal carries cap==len, hiding a dropped cap). PRE-POISON:
// the three slot words are seeded with a DIFFERENT str (ptr='q', len=4, cap=5)
// via the PROVEN already-3-word DIRECT field store (st.f=q, N_IDENT base) —
// never the G2 chained store itself. A broken 2-word store never touches +16,
// so the read-back observes the poison cap 5, never 8. The full-triple assert
// (s[0]='h'=104, len=2, cap=8) catches a register slip that clobbers ptr/len.
package str_chainfield_store_cap_test;
type inner = struct { f: str };
type outer2 = struct { sym: *inner };
type mid = struct { b: *inner };
type outer3 = struct { a: *mid };
@test fn chainfield_store_depth2() void = {
// A — depth-2 `r.sym.f = v`. r.sym is a *inner pointing at a local st.
// Poison st.f (cap=5,len=4,'q') via the DIRECT s.f=v 3-word store; then
// the G2 chained store lands the test str (cap=8,len=2,'h') through r.sym.
let q: str = "qqqq"; q.cap = 5i32;
let p: str = "hi"; p.cap = 8i32;
let st: inner;
st.f = q;
let r: outer2;
r.sym = &st;
r.sym.f = p;
let s: str = r.sym.f;
assert(s.cap: i32 == 8);
assert(s.len: i32 == 2);
assert(s[0] == 104u8);
};
@test fn chainfield_store_depth3() void = {
// B — depth-3 `r.a.b.f = v`. Two pointer hops (r.a: *mid, mid.b: *inner).
// Poison the leaf via the DIRECT store (leaf.f=q); the G2 chained store
// derefs r.a then .b and overwrites leaf.f. The deeper base eval clobbers
// more registers, confirming the spilled triple survives.
let q: str = "qqqq"; q.cap = 5i32;
let p: str = "hi"; p.cap = 8i32;
let leaf: inner;
leaf.f = q;
let m: mid;
m.b = &leaf;
let r: outer3;
r.a = &m;
r.a.b.f = p;
let s: str = r.a.b.f;
assert(s.cap: i32 == 8);
assert(s.len: i32 == 2);
assert(s[0] == 104u8);
};

View File

@@ -0,0 +1,68 @@
// str_field_cap_test — reading a str-typed FIELD of a struct (N_DOT value
// read) must load the full 24B {ptr,len,cap} header, not just {ptr,len},
// migrated from test/wcc/933_str_field_cap_run.c (C4.6 S1+S2 case A). str is
// 24B since Phase 2 (#1); pre-C4.6 the N_DOT str-field arms loaded 2 words and
// dropped cap. The miscompile was cs==ww, so the 990-997 byte-id gates stayed
// GREEN while the runtime was wrong — a behavioral @test is the net.
//
// The global row is load-bearing: in ww the global struct-field load is a
// separate codegen arm with no slice sibling (cstage folds local+global in one
// base_reg arm; ww splits them), so a cap-drop there would hide from the
// local-field rows — hence its own @test fn.
//
// Each shape POISONS the source str so cap != len (a `.cap =` pseudo-field
// write, no malloc / no import), then observes cap through `let s: str =
// <field>` (a 3-word copy into the slot) and `s.cap` (an N_IDENT pseudo-field
// read off the slot). spoil() interposes a CX-clobbering call between the field
// store and the read so a broken 2-word read observes spoil's leftover (44),
// not a stale poison the store coincidentally left behind. .len is the control.
package str_field_cap_test;
type rec = struct { f: str };
let g: rec;
fn spoil() i32 = {
let z: str = "zzzz";
z.cap = 44i32;
let w: str = z;
return w.cap: i32;
};
@test fn s1local_field() void = {
// `st.f` direct struct local field (base BP). Poison cap=8 (len=2).
let p: str = "hi";
p.cap = 8i32;
let st: rec;
st.f = p;
let junk: i32 = spoil();
let s: str = st.f;
assert(s.cap: i32 == 8);
assert(s.len: i32 == 2);
assert(junk == 44);
};
@test fn s1global_field() void = {
// `g.f` direct struct global field (base CX path), the no-local-sibling
// arm in ww. Poison cap=9 (len=5).
let p: str = "world";
p.cap = 9i32;
g.f = p;
let s: str = g.f;
assert(s.cap: i32 == 9);
assert(s.len: i32 == 5);
};
@test fn s2ptr_field() void = {
// `pst.f` field via a *struct local (deref, base BX). Poison cap=7
// (len=3).
let p: str = "abc";
p.cap = 7i32;
let st: rec;
st.f = p;
let pst: *rec = &st;
let s: str = pst.f;
assert(s.cap: i32 == 7);
assert(s.len: i32 == 3);
};

View File

@@ -0,0 +1,62 @@
// str_massign_store_cap_test — a tuple-destructure REASSIGNMENT `a, s = call()`
// (N_MASSIGN) whose str element must write the full 24B {ptr,len,cap} header
// into the str slot, not just {ptr}, migrated from
// test/wcc/939_str_massign_store_cap_run.c (G3 fold). str is 24B since Phase 2
// (#1); the N_MASSIGN arm previously stored the str element with the bare
// scalar path (one word), dropping len/cap. cs==ww held (byte-id-blind), so
// behavioral @test is the net.
//
// N_MASSIGN vs N_MLET: `let a, s = call()` is N_MLET (fresh bindings) and
// ALREADY destructures 3-word. `a, s = call()` with a, s PRE-DECLARED is
// N_MASSIGN (reassignment) — the arm under test. The bindings MUST be
// pre-declared then reassigned WITHOUT `let`, or the parser emits N_MLET and
// this arm is never reached (silent false green).
//
// RHS cap!=len: the returned str is a literal whose .cap is mutated DISTINCT
// from its len. PRE-POISON: s is seeded with a DIFFERENT str (ptr='q', len=4,
// cap=5) via `let s: str = q` — a PROVEN already-3-word let-init copy, which
// both DECLARES s (so the comma-assign is N_MASSIGN) and poisons its slot. A
// broken 1-word store writes only s.ptr, so the read-back observes the poison
// len 4 / cap 5, never 2 / 8. The full-triple assert (s[0]='h'=104, len=2,
// cap=8) plus the scalar guard (a==5) confirm the XOR branch wires both slots.
package str_massign_store_cap_test;
fn mk() (i64, str) = {
let p: str = "hi"; p.cap = 8i32;
return (5i64, p);
};
fn mk2() (str, i64) = {
let p: str = "hi"; p.cap = 8i32;
return (p, 5i64);
};
@test fn massign_store_str_pos1() void = {
// A — `a, s = mk()`, str the 2nd tuple element (l1). a and s are
// pre-declared (so the comma-assign is N_MASSIGN, not N_MLET); s is
// poisoned (cap=5,len=4,'q') by its let-init copy. The G3 store then
// lands the test str (cap=8,len=2,'h') via (DX,CX,R8)->s, AX->a.
let q: str = "qqqq"; q.cap = 5i32;
let a: i64 = 7i64;
let s: str = q;
a, s = mk();
assert(s.cap: i32 == 8);
assert(s.len: i32 == 2);
assert(s[0] == 104u8);
assert(a == 5);
};
@test fn massign_store_str_pos0() void = {
// B — `s, a = mk2()`, str the 1st tuple element (l0). Same poison and
// test values; exercises the s0_is_str routing (DX,CX,R8)->s, AX->a from
// the other XOR side.
let q: str = "qqqq"; q.cap = 5i32;
let s: str = q;
let a: i64 = 7i64;
s, a = mk2();
assert(s.cap: i32 == 8);
assert(s.len: i32 == 2);
assert(s[0] == 104u8);
assert(a == 5);
};

View File

@@ -0,0 +1,41 @@
// str_tuple_elem_cap_test — a tuple POSITIONAL str element read `t.N` (N_IDENT
// base, TY_TUPLE) must load the full 24B {ptr,len,cap} header, not just
// {ptr,len}, migrated from test/wcc/935_str_tuple_elem_cap_run.c (C4.6 S3). str
// is 24B since Phase 2 (#1); pre-S3 the tuple str-element arm loaded 2 words
// (ptr in AX, len in BX) and dropped cap. The miscompile was cs==ww, so the
// 990-997 byte-id gates stayed GREEN while the runtime was wrong — behavioral
// @test is the net. UNLIKE the field/elem arms there is no adjacent slice-
// element arm to fold onto, so the 3-word triple is authored directly to the
// canonical slice-header ABI (AX=ptr, BX=len, CX=cap).
//
// The tuple is built by a (i64, str)-returning mk() so the poisoned cap=8 rides
// the 4-word return ABI into the tuple slot's +24 word (do NOT simplify to a
// local tuple literal — different codegen path); `t.1` then reads it back
// 3-word. spoil() interposes a CX-clobbering call between the build and the
// `t.1` read so a broken 2-word read observes 44 not 8, not a stale CX.
package str_tuple_elem_cap_test;
fn mk() (i64, str) = {
let p: str = "hi";
p.cap = 8i32;
return (5i64, p);
};
fn spoil() i32 = {
let z: str = "zzzz";
z.cap = 44i32;
let w: str = z;
return w.cap: i32;
};
@test fn tuple_positional_str_elem() void = {
// `t.1` positional str element of a local (i64, str) tuple. Poison cap=8
// (len=2) rides mk()'s return ABI into the str element's slot.
let t: (i64, str) = mk();
let junk: i32 = spoil();
let s: str = t.1;
assert(s.cap: i32 == 8);
assert(s.len: i32 == 2);
assert(junk == 44);
};