test: migrate str/slice header family-1 batch to test/lang @test, retire C twins (fold-2)
Migrate the slice/str-header core of drew's Family 1 from bespoke build+run C twins to test/lang @test, retiring each now-redundant twin in the same commit. Runtime coverage MOVES from $(TESTS) to test-lang (T1 runs+asserts via `ww test`) + test-lang-byteid (T2 keeps cs==ww .s byte-id); coverage is preserved, the $(TESTS) headline drops 6 (440->434). All asserts are primitive int/u8/bool comparisons (no fmt/strconv in the assert path); the slice-store families poison the slot (cap!=len) and read it back so a dropped data word FAILS. 928_str_abi_run.c -> str_abi_test.ww (str 24B ABI: .len/.cap across literal/arg/return/field/tuple/deref/index/tagged) 941_slice_store_cap_run.c -> slice_store_cap_test.ww (slice value store through indexed/field/chained lhs writes full 24B header; cap==8) 942_subslice_cap_run.c -> subslice_cap_test.ww (sub-slice cap = base_cap-lo; array/slice/str/append-no-realloc/hi-default) 943_subslice_ptresz_run.c -> subslice_ptresz_test.ww (sub-slice ptr advances lo*esz bytes; esz 2/4/8, let + call-arg) 944_deref_slice_store_run.c-> deref_slice_store_test.ww(*p=sliceval whole-deref store writes 24B header; cap==8) 949_f6_header_run.c -> f6_header_test.ww (str/slice header partial load/store; .cap/.len after clobber) Bump LANGBYTEID_EXPECTED_MIN 16->22 to ratchet the new corpus floor.
This commit is contained in:
69
test/lang/deref_slice_store_test.ww
Normal file
69
test/lang/deref_slice_store_test.ww
Normal file
@@ -0,0 +1,69 @@
|
||||
// deref_slice_store_test — a []T (slice-typed) VALUE stored through a
|
||||
// WHOLE-deref lhs `*p = sliceval` must move the full 24B {ptr,len,cap} header
|
||||
// (ref/hare/rt/ensure.ha:4-8), not just {ptr}, migrated from
|
||||
// test/wcc/944_deref_slice_store_run.c (project #79). The `*p = v` deref-store
|
||||
// arm was kind-gated on str ONLY; a slice fell to the 1-word default and
|
||||
// silently DROPPED len+cap. str IS []u8 since Phase 2 (#1), so the str 3-word
|
||||
// stash+store machinery applies to slices verbatim; the fix widens the gate
|
||||
// from `str` to `str || slice`. SYMMETRIC across both stages (cs==ww,
|
||||
// byte-id-BLIND), so only a store->read ROUNDTRIP catches it.
|
||||
//
|
||||
// cap != len in every row so a dropped len OR cap is caught.
|
||||
|
||||
package deref_slice_store_test;
|
||||
|
||||
type box = struct { s: []u8 };
|
||||
|
||||
@test fn d_slice_direct() void = {
|
||||
// A — `*pp = p`, read back via the DIRECT local `dst`. dst is POISONED
|
||||
// first with a different slice q (len=4,cap=5) via a PROVEN 3-word store
|
||||
// (`dst = q`, NOT the site under test); a 1-word `*pp=p` leaves q's cap=5,
|
||||
// the fix lands 8.
|
||||
let hb: [8]u8; hb[0] = 104u8;
|
||||
let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;
|
||||
let qb: [8]u8;
|
||||
let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;
|
||||
let dst: []u8 = q;
|
||||
let pp: *[]u8 = &dst;
|
||||
*pp = p;
|
||||
assert(dst.cap: i32 == 8);
|
||||
assert(dst.len: i32 == 2);
|
||||
assert(dst[0] == 104u8);
|
||||
};
|
||||
|
||||
@test fn d_slice_thru() void = {
|
||||
// B — same store, read back THROUGH the pointer via the 3-word field-deref
|
||||
// read `(*pp).cap`/`.len` (already 3-word), confirming the stored header
|
||||
// survives a pointer-side read.
|
||||
let hb: [8]u8; hb[0] = 104u8;
|
||||
let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;
|
||||
let qb: [8]u8;
|
||||
let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;
|
||||
let dst: []u8 = q;
|
||||
let pp: *[]u8 = &dst;
|
||||
*pp = p;
|
||||
assert((*pp).cap: i32 == 8);
|
||||
assert((*pp).len: i32 == 2);
|
||||
};
|
||||
|
||||
@test fn d_str_control() void = {
|
||||
// C — control str-deref: the str arm the fix widens around must still land
|
||||
// len=5 (regression guard). Poison d="xy" then `*pp="hello"`.
|
||||
let d: str = "xy";
|
||||
let pp: *str = &d;
|
||||
*pp = "hello";
|
||||
assert(d.len: i32 == 5);
|
||||
};
|
||||
|
||||
@test fn d_field_control() void = {
|
||||
// D — control (*p).field: explicit-deref field store, a separate
|
||||
// already-3-word branch. `(*pb).s = p` into a struct{s:[]u8}.
|
||||
let hb: [8]u8; hb[0] = 104u8;
|
||||
let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;
|
||||
let b: box;
|
||||
let pb: *box = &b;
|
||||
(*pb).s = p;
|
||||
assert(b.s.cap: i32 == 8);
|
||||
assert(b.s.len: i32 == 2);
|
||||
assert(b.s[0] == 104u8);
|
||||
};
|
||||
104
test/lang/f6_header_test.ww
Normal file
104
test/lang/f6_header_test.ww
Normal file
@@ -0,0 +1,104 @@
|
||||
// f6_header_test — 24B str/slice header partial load/store; .cap/.len read back
|
||||
// after a clobber, migrated from test/wcc/949_f6_header_run.c. Three closed
|
||||
// sub-bugs, all cs==ww-blind at the byte-id gates so a behavioral @test is the
|
||||
// net:
|
||||
// #19 (align-UP, wwstage-only): a str->[]u8 cast from a NON-LOCAL source
|
||||
// (global ident / struct field / call result) missed the cap=len synth,
|
||||
// leaving CX = the str's stale word-16 garbage cap.
|
||||
// #28 (#263 both-stages): a tuple slice-element read `t.N` over a ([]u8,i64)
|
||||
// loaded only the ptr word — a SLICE element fell to the scalar tail, so
|
||||
// len/cap took stale registers across a clobber() call.
|
||||
// #29 (#263 both-stages): a chained-dot str leaf `o.i.s` (depth-2) dropped the
|
||||
// cap word, so a junk strlit before the chain left CX stale.
|
||||
// All asserted post-fix both-stages-CORRECT; the runtime read-back with DISTINCT
|
||||
// values (cap=3/5, len=5/6) is the net a byte-id gate alone was blind to.
|
||||
|
||||
package f6_header_test;
|
||||
|
||||
let g_abc: str = "abc";
|
||||
|
||||
type box = struct { s: str, x: i64 };
|
||||
|
||||
type innerc = struct { s: str, x: i64 };
|
||||
type outerc = struct { i: innerc, y: i64 };
|
||||
|
||||
type innero = struct { pad: i64, s: str };
|
||||
type outero = struct { i: innero, y: i64 };
|
||||
|
||||
fn castg() []u8 = { return g_abc: []u8; };
|
||||
|
||||
fn clobber() i64 = {
|
||||
let a: i64 = 111;
|
||||
let b: i64 = 222;
|
||||
return a + b;
|
||||
};
|
||||
|
||||
@test fn global_str_cap() void = {
|
||||
// #19: str->[]u8 cast from a GLOBAL ident source, threaded through a call
|
||||
// return. cap must == len == 3. Pre-fix wwstage left CX = stale word-16.
|
||||
let s: []u8 = castg();
|
||||
assert(s.cap: i32 == 3);
|
||||
};
|
||||
|
||||
@test fn field_str_cap() void = {
|
||||
// #19 sibling: str->[]u8 cast from a struct FIELD source; distinct len (5)
|
||||
// catches a dropped cap.
|
||||
let b: box = box { s = "hello", x = 0 };
|
||||
let v: []u8 = b.s: []u8;
|
||||
assert(v.cap: i32 == 5);
|
||||
};
|
||||
|
||||
@test fn local_str_cap() void = {
|
||||
// #19 control: LOCAL str source (the pre-fix-covered shape) still emits the
|
||||
// synth — guards the common case against regression.
|
||||
let s: str = "abcd";
|
||||
let v: []u8 = s: []u8;
|
||||
assert(v.cap: i32 == 4);
|
||||
};
|
||||
|
||||
@test fn tuple_slice_elem() void = {
|
||||
// #28: tuple slice-element read `t.0` over a ([]u8, i64). A clobber()
|
||||
// between the build and the read leaves junk in BX/CX; pre-fix the
|
||||
// str-only arm fell to the scalar tail (ptr word only), so ys.len read
|
||||
// stale BX. len == 5.
|
||||
let xs: []u8 = [1u8, 2u8, 3u8, 4u8, 5u8];
|
||||
let t: ([]u8, i64) = (xs, 7);
|
||||
let junk: i64 = clobber();
|
||||
let ys: []u8 = t.0;
|
||||
assert(ys.len: i32 == 5);
|
||||
assert(junk == 333);
|
||||
};
|
||||
|
||||
@test fn tuple_slice_elem_pos1() void = {
|
||||
// #28 sibling: the slice element sits at tuple position 1 (an i64 occupies
|
||||
// slot 0, the []u8 header starts at +8). Exercises the widened arm's offset
|
||||
// arithmetic. len == 6.
|
||||
let xs: []u8 = [1u8, 2u8, 3u8, 4u8, 5u8, 6u8];
|
||||
let t: (i64, []u8) = (9, xs);
|
||||
let junk: i64 = clobber();
|
||||
let ys: []u8 = t.1;
|
||||
assert(ys.len: i32 == 6);
|
||||
assert(junk == 333);
|
||||
};
|
||||
|
||||
@test fn chained_str_cap() void = {
|
||||
// #29: chained-dot str leaf `o.i.s` (depth-2) dropped the cap word — a junk
|
||||
// strlit before the chain left CX stale, so t.cap read 2 (junk's cap)
|
||||
// instead of 5. Now the str leaf loads all three header words.
|
||||
let iv: innerc = innerc { s = "hello", x = 0 };
|
||||
let o: outerc = outerc { i = iv, y = 0 };
|
||||
let junk: str = "ab";
|
||||
let t: str = o.i.s;
|
||||
assert(t.cap: i32 == 5);
|
||||
};
|
||||
|
||||
@test fn chained_str_cap_offset() void = {
|
||||
// #29 sibling: the str leaf sits at a NON-ZERO field offset (a leading i64
|
||||
// pad pushes `s` to +8). Exercises the chained-leaf offset arithmetic;
|
||||
// cap == len == 7.
|
||||
let iv: innero = innero { pad = 0, s = "worldly" };
|
||||
let o: outero = outero { i = iv, y = 0 };
|
||||
let junk: str = "ab";
|
||||
let t: str = o.i.s;
|
||||
assert(t.cap: i32 == 7);
|
||||
};
|
||||
93
test/lang/slice_store_cap_test.ww
Normal file
93
test/lang/slice_store_cap_test.ww
Normal file
@@ -0,0 +1,93 @@
|
||||
// slice_store_cap_test — 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}, migrated from test/wcc/941_slice_store_cap_run.c
|
||||
// (task #7). str IS []u8 since Phase 2 (#1), so the str 3-word machinery
|
||||
// applies to slices verbatim; the G-cluster store+read arms were kind-gated on
|
||||
// str ONLY, so a slice value fell to the 1-word default and silently DROPPED
|
||||
// len+cap. cs==ww held (byte-id-blind), so a behavioral @test is the net.
|
||||
//
|
||||
// BOTH the store AND the read were 1-word pre-fix, so each row is a store->read
|
||||
// ROUNDTRIP. POISON: rows B/C/D seed the dst with a DIFFERENT slice q
|
||||
// (len=4,cap=5) via a PROVEN 3-word store (single-dot field, NOT the site under
|
||||
// test); a broken stage keeps q's cap=5 while the read returns stale words —
|
||||
// either way cap != 8. Row A is a bare roundtrip (no proven 3-word store
|
||||
// targets a bare array-element header). The full {ptr,len,cap} triple is
|
||||
// asserted (ptr via s[0]==104, len==2, cap==8; cap!=len catches a dropped len
|
||||
// OR cap).
|
||||
|
||||
package slice_store_cap_test;
|
||||
|
||||
type recb = struct { f: []u8 };
|
||||
type innerc = struct { f: []u8 };
|
||||
type outerc = struct { sym: *innerc };
|
||||
type innerd = struct { f: []u8 };
|
||||
type outerd = struct { i: innerd };
|
||||
|
||||
@test fn slice_store_wholeelem() void = {
|
||||
// A — whole-element `xs[0] = p` into a [2][]u8 local array, read back via
|
||||
// `xs[0]`. No proven poison for a bare array-element header; the roundtrip
|
||||
// is the probe.
|
||||
let hb: [8]u8; hb[0] = 104u8;
|
||||
let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;
|
||||
let xs: [2][]u8;
|
||||
xs[0] = p;
|
||||
let e: []u8 = xs[0];
|
||||
assert(e.cap: i32 == 8);
|
||||
assert(e.len: i32 == 2);
|
||||
assert(e[0] == 104u8);
|
||||
};
|
||||
|
||||
@test fn slice_store_arrfield() void = {
|
||||
// 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').
|
||||
let qb: [8]u8; qb[0] = 113u8;
|
||||
let hb: [8]u8; hb[0] = 104u8;
|
||||
let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;
|
||||
let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;
|
||||
let arr: [3]recb;
|
||||
let pr: *recb = &arr[1];
|
||||
pr.f = q;
|
||||
arr[1].f = p;
|
||||
let s: []u8 = arr[1].f;
|
||||
assert(s.cap: i32 == 8);
|
||||
assert(s.len: i32 == 2);
|
||||
assert(s[0] == 104u8);
|
||||
};
|
||||
|
||||
@test fn slice_store_chained_ptr() void = {
|
||||
// C — chained `r.sym.f = p` where r.sym is a *innerc (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.
|
||||
let qb: [8]u8; qb[0] = 113u8;
|
||||
let hb: [8]u8; hb[0] = 104u8;
|
||||
let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;
|
||||
let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;
|
||||
let st: innerc;
|
||||
st.f = q;
|
||||
let r: outerc;
|
||||
r.sym = &st;
|
||||
r.sym.f = p;
|
||||
let s: []u8 = r.sym.f;
|
||||
assert(s.cap: i32 == 8);
|
||||
assert(s.len: i32 == 2);
|
||||
assert(s[0] == 104u8);
|
||||
};
|
||||
|
||||
@test fn slice_store_valuespine() void = {
|
||||
// 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.
|
||||
let qb: [8]u8; qb[0] = 113u8;
|
||||
let hb: [8]u8; hb[0] = 104u8;
|
||||
let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;
|
||||
let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;
|
||||
let o: outerd;
|
||||
let pi: *innerd = &o.i;
|
||||
pi.f = q;
|
||||
o.i.f = p;
|
||||
let s: []u8 = o.i.f;
|
||||
assert(s.cap: i32 == 8);
|
||||
assert(s.len: i32 == 2);
|
||||
assert(s[0] == 104u8);
|
||||
};
|
||||
135
test/lang/str_abi_test.ww
Normal file
135
test/lang/str_abi_test.ww
Normal file
@@ -0,0 +1,135 @@
|
||||
// str_abi_test — runtime contract for the str->24B {ptr,len,cap} 3-reg ABI
|
||||
// (Phase 3, str IS []u8), migrated from test/wcc/928_str_abi_run.c. byte-id
|
||||
// proves the two stages agree, NOT that the emitted code is correct (a shared
|
||||
// miscompile passes byte-id silently); these @test fns pin the *runtime*
|
||||
// contract — T1 (`ww test`) runs the asserts, T2 (test-lang-byteid) keeps
|
||||
// cs==ww. Covers the ABI dimensions that exercise the cap word and the AX/BX/CX
|
||||
// value / AX:DX:CX:R8 tagged+tuple register layout: str literal (cap=len), str
|
||||
// arg, str return, str struct field, the (i64,str)/(str,i64) tuple shapes,
|
||||
// deref-store `*p=s`, []str index write+read, the s[i] u8-stride byte read, and
|
||||
// str widened into a tagged-union variant.
|
||||
|
||||
package str_abi_test;
|
||||
|
||||
fn slen(s: str) i32 = { return s.len: i32; };
|
||||
|
||||
fn greet() str = { return "hello world"; };
|
||||
|
||||
type box = struct { s: str, n: i32 };
|
||||
|
||||
fn pairis() (i64, str) = { return (42i64, "hello"); };
|
||||
|
||||
fn pairsi() (str, i64) = { return ("hi", 7i64); };
|
||||
|
||||
fn setit(p: *str, v: str) void = { *p = v; };
|
||||
|
||||
type sv = (str | i64);
|
||||
|
||||
fn wrapsv(s: str) sv = { return s; };
|
||||
|
||||
@test fn literal_len_cap() void = {
|
||||
// Literal: cap == len for a static literal (no spare storage); .cap on a
|
||||
// LOCAL str reads back the new third word.
|
||||
let s: str = "hello";
|
||||
assert(s.len: i32 == 5);
|
||||
assert(s.cap: i32 == 5);
|
||||
};
|
||||
|
||||
@test fn arg_len() void = {
|
||||
// str passed as a 3-word arg (ptr,len,cap), len read in the callee; a
|
||||
// let-bound str and a bare literal arg.
|
||||
let s: str = "hello";
|
||||
assert(slen(s) == 5);
|
||||
assert(slen("hi") == 2);
|
||||
};
|
||||
|
||||
@test fn return_str() void = {
|
||||
// callee returns a str in AX/BX/CX (no AX:DX shuffle — exactly like a
|
||||
// slice now).
|
||||
let g: str = greet();
|
||||
assert(g.len: i32 == 11);
|
||||
};
|
||||
|
||||
@test fn struct_field_store_load() void = {
|
||||
// store a str into a 3-word field, read .len and .cap back (field-store
|
||||
// routes the base through DX to dodge CX=cap; the cap word is stored, so
|
||||
// b.s.cap == len).
|
||||
let b: box;
|
||||
b.s = "abcd";
|
||||
b.n = 7i32;
|
||||
assert(b.s.len: i32 == 4);
|
||||
assert(b.n == 7);
|
||||
assert(b.s.cap: i32 == 4);
|
||||
};
|
||||
|
||||
@test fn tuple_int_str() void = {
|
||||
// (i64, str) return: AX=scalar, DX=ptr, CX=len, R8=cap; 32B receive slot.
|
||||
let n, s = pairis();
|
||||
assert(n: i32 == 42);
|
||||
assert(s.len: i32 == 5);
|
||||
};
|
||||
|
||||
@test fn tuple_str_int() void = {
|
||||
// (str, i64) return: reversed order, registers keyed by element type not
|
||||
// position.
|
||||
let s, n = pairsi();
|
||||
assert(s.len: i32 == 2);
|
||||
assert(n: i32 == 7);
|
||||
};
|
||||
|
||||
@test fn deref_store() void = {
|
||||
// `*p = s` writes all three words through the pointer (cap stashed across
|
||||
// the pointer eval).
|
||||
let s: str = "hello";
|
||||
let d: str;
|
||||
setit(&d, s);
|
||||
assert(d.len: i32 == 5);
|
||||
};
|
||||
|
||||
@test fn index_write_read() void = {
|
||||
// []str index write + read: the str-element store pushes cap/len and
|
||||
// writes 3 words; the read loads them back.
|
||||
let xs: [2]str;
|
||||
xs[0] = "hi";
|
||||
xs[1] = "abc";
|
||||
assert(xs[0].len: i32 == 2);
|
||||
assert(xs[1].len: i32 == 3);
|
||||
};
|
||||
|
||||
@test fn str_index_read() void = {
|
||||
// `s[i]` strides by the u8 element size (1), routed through the type table
|
||||
// post-collapse; guards the N_INDEX str-element stride against a
|
||||
// slice-width (24B) miscompute.
|
||||
let s: str = "hello";
|
||||
assert(s[0]: i32 == 104);
|
||||
assert(s[1]: i32 == 101);
|
||||
assert(s[4]: i32 == 111);
|
||||
};
|
||||
|
||||
@test fn tagged_union_str_store() void = {
|
||||
// str widened into a tagged-union variant: the payload store folds onto
|
||||
// the common slice arm (3-word ptr/len/cap + tag). Literal, str-variable,
|
||||
// a str returned from a fn into (str | i64), plus the i64 arm so the
|
||||
// variant-tag selection is checked both ways.
|
||||
let x: sv = "hello";
|
||||
match (x) {
|
||||
case let s: str => { assert(s.len: i32 == 5); };
|
||||
case let n: i64 => { assert(false); };
|
||||
};
|
||||
let a: str = "world";
|
||||
let y: sv = a;
|
||||
match (y) {
|
||||
case let s: str => { assert(s.len: i32 == 5); };
|
||||
case let n: i64 => { assert(false); };
|
||||
};
|
||||
let z: sv = wrapsv("abcd");
|
||||
match (z) {
|
||||
case let s: str => { assert(s.len: i32 == 4); };
|
||||
case let n: i64 => { assert(false); };
|
||||
};
|
||||
let w: sv = 42i64;
|
||||
match (w) {
|
||||
case let s: str => { assert(false); };
|
||||
case let n: i64 => { assert(n: i32 == 42); };
|
||||
};
|
||||
};
|
||||
76
test/lang/subslice_cap_test.ww
Normal file
76
test/lang/subslice_cap_test.ww
Normal file
@@ -0,0 +1,76 @@
|
||||
// subslice_cap_test — a sub-slice `base[lo:hi]` must set its capacity word to
|
||||
// base_cap - lo (storage remaining to the underlying end; Go/Hare-identical),
|
||||
// NOT hi - lo (the new length), migrated from test/wcc/942_subslice_cap_run.c
|
||||
// (task #20). base_cap is the array length N for [N]T, or the carried .capacity
|
||||
// for a slice/str base. Cite (drew): harec ref/harec/src/eval.c:1017 (slice cap
|
||||
// -= start), eval.c:1024 (array cap = length - start), check.c:596 (cap>=len),
|
||||
// ref/hare/rt/ensure.ha:4-8 (capacity is a distinct field).
|
||||
//
|
||||
// Before the fix BOTH stages emitted cap = hi - lo (== len). Every row picks a
|
||||
// shape where base_cap - lo != hi - lo, so a stale `cap = len` stage is
|
||||
// observably wrong (cap reads hi-lo, or the append row reallocs instead of
|
||||
// filling the base's spare). cap != len in every row.
|
||||
|
||||
package subslice_cap_test;
|
||||
|
||||
@test fn subslice_array_hilt_n() void = {
|
||||
// A — array base, hi < N. cap = N(8) - lo(1) = 7 != len(2).
|
||||
let a: [8]u8;
|
||||
let i: i32 = 0;
|
||||
for (i < 8) { a[i] = (20 + i): u8; i += 1; };
|
||||
let s: []u8 = a[1:3];
|
||||
assert(s.cap: i32 == 7);
|
||||
assert(s.len: i32 == 2);
|
||||
assert(s[0] == 21u8);
|
||||
};
|
||||
|
||||
@test fn subslice_slice_spare_cap() void = {
|
||||
// B — slice base with spare cap. cap = base.cap(8) - lo(1) = 7 != len(3);
|
||||
// base.cap is read from the header +16, not re-derived.
|
||||
let a: [8]u8;
|
||||
let i: i32 = 0;
|
||||
for (i < 8) { a[i] = (40 + i): u8; i += 1; };
|
||||
let p: []u8; p.ptr = &a[0]; p.len = 6; p.cap = 8;
|
||||
let s: []u8 = p[1:4];
|
||||
assert(s.cap: i32 == 7);
|
||||
assert(s.len: i32 == 3);
|
||||
assert(s[0] == 41u8);
|
||||
};
|
||||
|
||||
@test fn subslice_str_base() void = {
|
||||
// C — str base (str[lo:hi] yields str, real .capacity, no downgrade).
|
||||
// cap = sb.cap(5) - lo(1) = 4 != len(2).
|
||||
let sb: str = "hello";
|
||||
let s: str = sb[1:3];
|
||||
assert(s.cap: i32 == 4);
|
||||
assert(s.len: i32 == 2);
|
||||
assert(s[0] == 101u8);
|
||||
};
|
||||
|
||||
@test fn subslice_append_no_realloc() void = {
|
||||
// D — append-no-realloc (strongest). s = buf[1:3] has cap 7 > len 2, so
|
||||
// append(s,99) fills buf's spare at lo+len = 3. A cap=len stage sees
|
||||
// len==cap (full) and reallocs, leaving buf[3] == 0.
|
||||
let buf: [8]u8;
|
||||
let i: i32 = 0;
|
||||
for (i < 8) { buf[i] = 0u8; i += 1; };
|
||||
let s: []u8 = buf[1:3];
|
||||
append(s, 99u8);
|
||||
assert(s.cap: i32 == 7);
|
||||
assert(s.len: i32 == 3);
|
||||
assert(s[2] == 99u8);
|
||||
assert(buf[3] == 99u8);
|
||||
};
|
||||
|
||||
@test fn subslice_hidefault_spare_cap() void = {
|
||||
// E — hi-default with spare cap. p{len=6,cap=8}; `p[2:]` defaults hi to
|
||||
// base.len=6, so len = 4, but cap = base.cap(8) - lo(2) = 6 != len.
|
||||
let a: [8]u8;
|
||||
let i: i32 = 0;
|
||||
for (i < 8) { a[i] = (60 + i): u8; i += 1; };
|
||||
let p: []u8; p.ptr = &a[0]; p.len = 6; p.cap = 8;
|
||||
let s: []u8 = p[2:];
|
||||
assert(s.cap: i32 == 6);
|
||||
assert(s.len: i32 == 4);
|
||||
assert(s[0] == 62u8);
|
||||
};
|
||||
84
test/lang/subslice_ptresz_test.ww
Normal file
84
test/lang/subslice_ptresz_test.ww
Normal file
@@ -0,0 +1,84 @@
|
||||
// subslice_ptresz_test — a sub-slice `base[lo:hi]` must advance its DATA
|
||||
// pointer by lo*esz (BYTES), not by lo (element COUNT), migrated from
|
||||
// test/wcc/943_subslice_ptresz_run.c (project #76). The rt invariant is
|
||||
// membsz-unit pointer arithmetic (ref/hare/rt/ensure.ha:30). For esz==1 (u8/str)
|
||||
// lo*1 == lo, so those paths are unaffected; the bug only bites esz>1.
|
||||
//
|
||||
// Before the fix BOTH stages emitted ptr = base + lo (unscaled), so the first
|
||||
// element was read at byte offset `lo` into the base — garbage straddling
|
||||
// base[0]/base[1] for any esz>1. Each row picks values where the unscaled read
|
||||
// cannot alias the scaled one, so a stale `+lo` stage returns garbage, not the
|
||||
// expected element. Exercises esz 2/4/8 over array + slice bases via the
|
||||
// let-form (value path) and the call-arg fast path.
|
||||
|
||||
package subslice_ptresz_test;
|
||||
|
||||
fn e0(s: []i32) i32 = { return s[0]; };
|
||||
fn e1(s: []i32) i32 = { return s[1]; };
|
||||
fn f0(s: []i64) i64 = { return s[0]; };
|
||||
fn f1(s: []i64) i64 = { return s[1]; };
|
||||
|
||||
@test fn subslice_esz4_array_let() void = {
|
||||
// A — esz=4 array base, let-form (value path). s[0]=a[2]=1002.
|
||||
let a: [8]i32;
|
||||
let i: i32 = 0;
|
||||
for (i < 8) { a[i] = 1000 + i; i += 1; };
|
||||
let s: []i32 = a[2:5];
|
||||
assert(s.len: i32 == 3);
|
||||
assert(s[0] == 1002);
|
||||
assert(s[1] == 1003);
|
||||
};
|
||||
|
||||
@test fn subslice_esz2_array_let() void = {
|
||||
// B — esz=2 array base, let-form. s[0]=a[3]=103.
|
||||
let a: [8]i16;
|
||||
let i: i32 = 0;
|
||||
for (i < 8) { a[i] = (100 + i): i16; i += 1; };
|
||||
let s: []i16 = a[3:6];
|
||||
assert(s.len: i32 == 3);
|
||||
assert(s[0]: i32 == 103);
|
||||
assert(s[1]: i32 == 104);
|
||||
};
|
||||
|
||||
@test fn subslice_esz8_array_let() void = {
|
||||
// C — esz=8 array base, let-form. s[0]=a[1]=5001.
|
||||
let a: [8]i64;
|
||||
let i: i32 = 0;
|
||||
for (i < 8) { a[i] = (5000 + i): i64; i += 1; };
|
||||
let s: []i64 = a[1:4];
|
||||
assert(s.len: i32 == 3);
|
||||
assert(s[0]: i32 == 5001);
|
||||
assert(s[1]: i32 == 5002);
|
||||
};
|
||||
|
||||
@test fn subslice_esz4_slice_let() void = {
|
||||
// D — esz=4 slice base, let-form. base ptr from header + lo*esz.
|
||||
// s[0]=a[2]=1002.
|
||||
let a: [8]i32;
|
||||
let i: i32 = 0;
|
||||
for (i < 8) { a[i] = 1000 + i; i += 1; };
|
||||
let p: []i32; p.ptr = &a[0]; p.len = 6; p.cap = 8;
|
||||
let s: []i32 = p[2:5];
|
||||
assert(s.len: i32 == 3);
|
||||
assert(s[0] == 1002);
|
||||
assert(s[1] == 1003);
|
||||
};
|
||||
|
||||
@test fn subslice_esz4_call_arg() void = {
|
||||
// E — esz=4 call-arg (the pushargsrev twin). The fn reads s[0]/s[1] of
|
||||
// `a[3:6]` -> 1003 / 1004.
|
||||
let a: [8]i32;
|
||||
let i: i32 = 0;
|
||||
for (i < 8) { a[i] = 1000 + i; i += 1; };
|
||||
assert(e0(a[3:6]) == 1003);
|
||||
assert(e1(a[3:6]) == 1004);
|
||||
};
|
||||
|
||||
@test fn subslice_esz8_call_arg() void = {
|
||||
// F — esz=8 call-arg. The fns read s[0]/s[1] of `a[2:5]` -> 5002 / 5003.
|
||||
let a: [8]i64;
|
||||
let i: i32 = 0;
|
||||
for (i < 8) { a[i] = (5000 + i): i64; i += 1; };
|
||||
assert(f0(a[2:5]): i32 == 5002);
|
||||
assert(f1(a[2:5]): i32 == 5003);
|
||||
};
|
||||
Reference in New Issue
Block a user