Files
ww/test/lang/str_abi_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

136 lines
4.0 KiB
Plaintext

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