Files
ww/test/lang/f6_header_test.ww
Hojun-Cho 374e97b9e8 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.
2026-06-22 09:02:40 +09:00

105 lines
3.7 KiB
Plaintext

// 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);
};