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:
2026-06-22 09:02:40 +09:00
parent f864e9f9bc
commit 374e97b9e8
13 changed files with 562 additions and 1524 deletions

View File

@@ -379,11 +379,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_sret_narrow_field_run \
$(BIN)/test_match_slice_variant \
$(BIN)/test_match_slice_variant_run \
$(BIN)/test_str_abi_run \
$(BIN)/test_slice_store_cap_run \
$(BIN)/test_subslice_cap_run \
$(BIN)/test_subslice_ptresz_run \
$(BIN)/test_deref_slice_store_run \
$(BIN)/test_alias_decl_order_size_run \
$(BIN)/test_alias_structlit_init_run \
$(BIN)/test_array_zeroinit_run \
@@ -411,7 +406,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_const_slice_aggregate_run \
$(BIN)/test_append_structlit_evalorder_run \
$(BIN)/test_tuple_index_read_run \
$(BIN)/test_f6_header_run \
$(BIN)/test_f9_float_run \
$(BIN)/test_xmod_fnptr_const_run \
$(BIN)/test_tuple_slot_layout_run \
@@ -2409,42 +2403,12 @@ $(BIN)/test_match_slice_variant_run: test/wcc/926_match_slice_variant_run.c \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_str_abi_run: test/wcc/928_str_abi_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_str_forrange_loopvar_run: test/wcc/940_str_forrange_loopvar_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_slice_store_cap_run: test/wcc/941_slice_store_cap_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_subslice_cap_run: test/wcc/942_subslice_cap_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_subslice_ptresz_run: test/wcc/943_subslice_ptresz_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_deref_slice_store_run: test/wcc/944_deref_slice_store_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_alias_decl_order_size_run: test/wcc/944_alias_decl_order_size_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
@@ -2630,12 +2594,6 @@ $(BIN)/test_tuple_index_read_run: test/wcc/947_tuple_index_read_run.c \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_f6_header_run: test/wcc/949_f6_header_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_f9_float_run: test/wcc/949_f9_float_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
@@ -3594,7 +3552,7 @@ test-lang: all
LANGBYTEID_DIR = $(OUT)/langbyteid
LANGBYTEID_FILES = $(wildcard test/lang/*_test.ww)
LANGBYTEID_VERB = test -c
LANGBYTEID_EXPECTED_MIN = 16
LANGBYTEID_EXPECTED_MIN = 22
$(if $(LANGBYTEID_FILES),,$(error test-lang-byteid: empty corpus))
test-lang-byteid: all
@set -e; \

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

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

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

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

View File

@@ -1,285 +0,0 @@
/*
* 928_str_abi_run — end-to-end runtime coverage for the str->24B
* {ptr,len,cap} 3-reg ABI (Commit #1 / Phase 3, str IS []u8).
*
* The scratch str-ABI probe matrix (task #3) only diffs asm byte-id;
* byte-identity proves the two stages agree, NOT that the emitted code
* is correct (a shared miscompile passes byte-id silently). This file
* pins the *runtime* contract: build each fixture through both the
* cstage `ww` and the wwstage `ww_ww` driver and confirm the program's
* own assertions hold (exit 0).
*
* Covers the ABI dimensions that exercise the new 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) and (str,i64) tuple return shapes, deref-store `*p = s`,
* and []str index write+read. Phase 2 collapse folds (str IS []u8):
* str byte-index read `s[i]` (element stride via the type table,
* commit a5ca21d) and str widened into a tagged-union variant (the
* payload store folded onto the common slice arm, commit b416e71).
*
* Deliberately NOT covered here (known, separately-tracked gaps found
* during Commit #1 review — both byte-identical across stages, so the
* byte-id gates stay green):
* - str-containing struct passed BY VALUE: now >16B, falls into the
* general ">16B struct byval" limitation (a non-str 24B struct
* byval mis-compiles the same way); not a str-specific defect.
* (task #10)
* - `.cap` VALUE of a top-level str GLOBAL reads 0, not len: the
* str-literal global DATAW emits only the 16B {ptr,len} payload,
* not the 24B header — byte-identical across stages, but the cap
* word is never initialised. Distinct from the .cap field/global
* link-error (task #11), which IS fixed: `.cap` on a str field
* (stored value) and the global field-read now compile and agree.
*/
#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[] = {
/* Literal: cap = len for a static literal (no spare storage),
* and .cap on a LOCAL str reads back. The new third word. */
{ "literal_len_cap",
"export fn main() i32 = {\n"
" let s: str = \"hello\";\n"
" if (s.len: i32 != 5) { return 1; };\n"
" if (s.cap: i32 != 5) { return 2; };\n"
" return 0;\n"
"};\n",
0 },
/* Arg: str passed as a 3-word arg (ptr,len,cap), len read in
* the callee. Both a let-bound str and a bare literal arg. */
{ "arg_len",
"fn slen(s: str) i32 = { return s.len: i32; };\n"
"export fn main() i32 = {\n"
" let s: str = \"hello\";\n"
" if (slen(s) != 5) { return 1; };\n"
" if (slen(\"hi\") != 2) { return 2; };\n"
" return 0;\n"
"};\n",
0 },
/* Return: callee returns a str in AX/BX/CX (no AX:DX shuffle —
* str returns exactly like a slice now). */
{ "return_str",
"fn greet() str = { return \"hello world\"; };\n"
"export fn main() i32 = {\n"
" let g: str = greet();\n"
" if (g.len: i32 != 11) { return 1; };\n"
" return 0;\n"
"};\n",
0 },
/* Struct field: 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 here). */
{ "struct_field_store_load",
"type box = struct { s: str, n: i32 };\n"
"export fn main() i32 = {\n"
" let b: box;\n"
" b.s = \"abcd\";\n"
" b.n = 7i32;\n"
" if (b.s.len: i32 != 4) { return 1; };\n"
" if (b.n != 7) { return 2; };\n"
" if (b.s.cap: i32 != 4) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* Tuple (i64, str) return: AX=scalar, DX=ptr, CX=len, R8=cap;
* 32B receive slot. */
{ "tuple_int_str",
"fn pair() (i64, str) = { return (42i64, \"hello\"); };\n"
"export fn main() i32 = {\n"
" let n, s = pair();\n"
" if (n: i32 != 42) { return 1; };\n"
" if (s.len: i32 != 5) { return 2; };\n"
" return 0;\n"
"};\n",
0 },
/* Tuple (str, i64) return: reversed order, registers keyed by
* element type not position. */
{ "tuple_str_int",
"fn pair() (str, i64) = { return (\"hi\", 7i64); };\n"
"export fn main() i32 = {\n"
" let s, n = pair();\n"
" if (s.len: i32 != 2) { return 1; };\n"
" if (n: i32 != 7) { return 2; };\n"
" return 0;\n"
"};\n",
0 },
/* Deref-store: `*p = s` writes all three words through the
* pointer (cap stashed across the pointer eval). */
{ "deref_store",
"fn setit(p: *str, v: str) void = { *p = v; };\n"
"export fn main() i32 = {\n"
" let s: str = \"hello\";\n"
" let d: str;\n"
" setit(&d, s);\n"
" if (d.len: i32 != 5) { return 1; };\n"
" return 0;\n"
"};\n",
0 },
/* []str index write + read: the str-element store pushes
* cap/len and writes 3 words; the read loads them back. Guards
* the str-element gate against the slice=24B collision (#7/754,
* write-side). */
{ "index_write_read",
"export fn main() i32 = {\n"
" let xs: [2]str;\n"
" xs[0] = \"hi\";\n"
" xs[1] = \"abc\";\n"
" if (xs[0].len: i32 != 2) { return 1; };\n"
" if (xs[1].len: i32 != 3) { return 2; };\n"
" return 0;\n"
"};\n",
0 },
/* str byte index read: `s[i]` strides by the u8 element size (1),
* routed through the type table post-collapse (a5ca21d). Guards
* the N_INDEX str-element stride against a slice-width (24B)
* miscompute now that str shares the slice path. */
{ "str_index_read",
"export fn main() i32 = {\n"
" let s: str = \"hello\";\n"
" if (s[0]: i32 != 104) { return 1; };\n"
" if (s[1]: i32 != 101) { return 2; };\n"
" if (s[4]: i32 != 111) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* str widened into a tagged-union variant: the payload store
* folds onto the common slice arm (3-word ptr/len/cap @
* slot+8/+16/+24 + tag, b416e71). Exercises literal, str-variable
* and a str returned from a fn into `(str | i64)`, plus the i64
* arm so the variant-tag selection is checked both ways. */
{ "tagged_union_str_store",
"type sv = (str | i64);\n"
"fn wrap(s: str) sv = { return s; };\n"
"export fn main() i32 = {\n"
" let x: sv = \"hello\";\n"
" match (x) {\n"
" case let s: str => { if (s.len: i32 != 5) { return 1; }; };\n"
" case let n: i64 => { return 2; };\n"
" };\n"
" let a: str = \"world\";\n"
" let y: sv = a;\n"
" match (y) {\n"
" case let s: str => { if (s.len: i32 != 5) { return 3; }; };\n"
" case let n: i64 => { return 4; };\n"
" };\n"
" let z: sv = wrap(\"abcd\");\n"
" match (z) {\n"
" case let s: str => { if (s.len: i32 != 4) { return 5; }; };\n"
" case let n: i64 => { return 6; };\n"
" };\n"
" let w: sv = 42i64;\n"
" match (w) {\n"
" case let s: str => { return 7; };\n"
" case let n: i64 => { if (n: i32 != 42) { return 8; }; };\n"
" };\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/strabi_run_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/strabi_run_%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,
"str_abi_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,
"str_abi_run[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (fail) {
fprintf(stderr, "str_abi_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("str_abi_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,244 +0,0 @@
/*
* 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;
}

View File

@@ -1,221 +0,0 @@
/*
* 942_subslice_cap_run — runtime coverage for task #20: a sub-slice
* `base[lo:hi]` must set its capacity word to base_cap - lo (the storage
* remaining to the underlying end; Go/Hare-identical), NOT hi - lo (the
* new length). base_cap is the array length N for `[N]T`, or the carried
* .capacity (+16) for a slice/str base. Cite (drew, in-tree): harec
* ref/harec/src/eval.c:1017 (slice: slice.cap -= start), eval.c:1024
* (array: cap = array.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 (the cap read returns hi-lo, or the append
* row reallocs instead of filling the base's spare).
*
* Rows (cstage cmd/w6c/cgen.c cg_base_cap + the N_SLICE value / call-arg
* paths; wwstage cgenexpr.ww cgbasecap + cgslice + cgenutil.ww twin):
* A array base, hi < N: `a[1:3]` over [8]u8 -> len 2, cap 8-1 = 7.
* B slice base with spare cap: p{len=6,cap=8}; `p[1:4]` -> len 3,
* cap 8-1 = 7 (cap carried from the header at +16, minus lo).
* C str base (D1: str[lo:hi] yields str, real .capacity, NO downgrade):
* sb = "hello" {len 5, cap 5}; `sb[1:3]` -> len 2, cap 5-1 = 4,
* sb[1] == 'e' (101).
* D append-no-realloc (strongest): `s = buf[1:3]` over a zeroed [8]u8
* has len 2, cap 7; append(s, 99) must fill buf's spare at lo+len = 3
* WITHOUT realloc. A `cap = len` stage sees len == cap (full) and
* reallocs into a fresh buffer, leaving buf[3] == 0.
* E hi-default with spare cap: p{len=6,cap=8}; `p[2:]` (hi defaults to
* base.len=6) -> len 4, cap 8-2 = 6 != len. Covers the distinct
* default-hi path; pre-fix cap = (defaulted hi - lo) = len = 4.
*
* In every row cap != len, so a dropped/wrong cap is caught directly.
* Verified pass-after (exit 0) on BOTH the cstage `ww` and wwstage
* `ww_ww` drivers; each row's cap assertion fails on a pre-fix stage.
* NNN < 950, self-contained (/tmp, no imports), so rule-14's selfhost-
* sibling race does not apply (mirrors the 928/932/941 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 — array base, hi < N. cap = N(8) - lo(1) = 7 != len(2). */
{ "subslice_array_hilt_n",
"export fn main() i32 = {\n"
" let a: [8]u8;\n"
" let i: i32 = 0;\n"
" for (i < 8) { a[i] = (20 + i): u8; i += 1; };\n"
" let s: []u8 = a[1:3];\n"
" if (s.cap: i32 != 7) { return 1; };\n"
" if (s.len: i32 != 2) { return 2; };\n"
" if (s[0] != 21u8) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* 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. */
{ "subslice_slice_spare_cap",
"export fn main() i32 = {\n"
" let a: [8]u8;\n"
" let i: i32 = 0;\n"
" for (i < 8) { a[i] = (40 + i): u8; i += 1; };\n"
" let p: []u8; p.ptr = &a[0]; p.len = 6; p.cap = 8;\n"
" let s: []u8 = p[1:4];\n"
" if (s.cap: i32 != 7) { return 1; };\n"
" if (s.len: i32 != 3) { return 2; };\n"
" if (s[0] != 41u8) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* C — str base (str[lo:hi] yields str, real .capacity). cap =
* sb.cap(5) - lo(1) = 4 != len(2). */
{ "subslice_str_base",
"export fn main() i32 = {\n"
" let sb: str = \"hello\";\n"
" let s: str = sb[1:3];\n"
" if (s.cap: i32 != 4) { return 1; };\n"
" if (s.len: i32 != 2) { return 2; };\n"
" if (s[0] != 101u8) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* D — append-no-realloc. 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
* reallocs (len==cap) and leaves buf[3] == 0. */
{ "subslice_append_no_realloc",
"export fn main() i32 = {\n"
" let buf: [8]u8;\n"
" let i: i32 = 0;\n"
" for (i < 8) { buf[i] = 0u8; i += 1; };\n"
" let s: []u8 = buf[1:3];\n"
" append(s, 99u8);\n"
" if (s.cap: i32 != 7) { return 1; };\n"
" if (s.len: i32 != 3) { return 2; };\n"
" if (s[2] != 99u8) { return 3; };\n"
" if (buf[3] != 99u8) { return 4; };\n"
" return 0;\n"
"};\n",
0 },
/* E — hi-default with spare cap. p{len=6,cap=8}; `p[2:]` defaults hi
* to base.len=6, so len = 6-2 = 4, but cap = base.cap(8) - lo(2) = 6 !=
* len. Pre-fix the defaulted-hi path set cap = (hi - lo) = len = 4. */
{ "subslice_hidefault_spare_cap",
"export fn main() i32 = {\n"
" let a: [8]u8;\n"
" let i: i32 = 0;\n"
" for (i < 8) { a[i] = (60 + i): u8; i += 1; };\n"
" let p: []u8; p.ptr = &a[0]; p.len = 6; p.cap = 8;\n"
" let s: []u8 = p[2:];\n"
" if (s.cap: i32 != 6) { return 1; };\n"
" if (s.len: i32 != 4) { return 2; };\n"
" if (s[0] != 62u8) { 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/subslicecap_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/subslicecap_%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,
"subslice_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,
"subslice_cap_run[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (fail) {
fprintf(stderr, "subslice_cap_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("subslice_cap_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,223 +0,0 @@
/*
* 943_subslice_ptresz_run — runtime coverage for project #76: a sub-slice
* `base[lo:hi]` must advance its DATA pointer by lo*esz (BYTES), not by lo
* (element COUNT). 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 elements.
*
* Before the fix BOTH stages emitted ptr = base + lo (unscaled), so the
* first element of the sub-slice was read at byte offset `lo` into the
* base storage — 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 is observably wrong (returns garbage, not the
* expected element).
*
* Sites exercised (all four; cstage cmd/w6c/cgen.c N_SLICE value path +
* N_SLICE call-arg fast-path; wwstage cgenexpr.ww cgslice + cgenutil.ww
* pushargsrev twin) — esz scaled via the type table, mirroring the
* cgindex idiom (rule 13):
* A esz=4 array base, let-form (value path): `a[2:5]` over [8]i32,
* a[i]=1000+i -> s[0]==1002, s[1]==1003 (pre-fix reads byte off 2).
* B esz=2 array base, let-form: `a[3:6]` over [8]i16 -> s[0]==103.
* C esz=8 array base, let-form: `a[1:4]` over [8]i64 -> s[0]==5001.
* D esz=4 slice base, let-form: p{ptr=&a,len=6,cap=8}; `p[2:5]` ->
* s[0]==1002 (base ptr carried from the header, then +lo*esz).
* E esz=4 call-arg (the pushargsrev/cgen.c:4646 twin): pass `a[3:6]`
* to a fn reading s[0]/s[1] -> 1003 / 1004.
* F esz=8 call-arg: pass `a[2:5]` over [8]i64 -> s[0]==5002, s[1]==5003.
*
* NNN < 950, self-contained (/tmp, no imports), so rule-14's selfhost-
* sibling race does not apply (mirrors the 928/932/941/942 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 — esz=4 array base, let-form (value path). s[0]=a[2]=1002. */
{ "subslice_esz4_array_let",
"export fn main() i32 = {\n"
" let a: [8]i32;\n"
" let i: i32 = 0;\n"
" for (i < 8) { a[i] = 1000 + i; i += 1; };\n"
" let s: []i32 = a[2:5];\n"
" if (s.len: i32 != 3) { return 1; };\n"
" if (s[0] != 1002) { return 2; };\n"
" if (s[1] != 1003) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* B — esz=2 array base, let-form. s[0]=a[3]=103. */
{ "subslice_esz2_array_let",
"export fn main() i32 = {\n"
" let a: [8]i16;\n"
" let i: i32 = 0;\n"
" for (i < 8) { a[i] = (100 + i): i16; i += 1; };\n"
" let s: []i16 = a[3:6];\n"
" if (s.len: i32 != 3) { return 1; };\n"
" if (s[0]: i32 != 103) { return 2; };\n"
" if (s[1]: i32 != 104) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* C — esz=8 array base, let-form. s[0]=a[1]=5001. */
{ "subslice_esz8_array_let",
"export fn main() i32 = {\n"
" let a: [8]i64;\n"
" let i: i32 = 0;\n"
" for (i < 8) { a[i] = (5000 + i): i64; i += 1; };\n"
" let s: []i64 = a[1:4];\n"
" if (s.len: i32 != 3) { return 1; };\n"
" if (s[0]: i32 != 5001) { return 2; };\n"
" if (s[1]: i32 != 5002) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* D — esz=4 slice base, let-form. base ptr from header + lo*esz.
* s[0]=a[2]=1002. */
{ "subslice_esz4_slice_let",
"export fn main() i32 = {\n"
" let a: [8]i32;\n"
" let i: i32 = 0;\n"
" for (i < 8) { a[i] = 1000 + i; i += 1; };\n"
" let p: []i32; p.ptr = &a[0]; p.len = 6; p.cap = 8;\n"
" let s: []i32 = p[2:5];\n"
" if (s.len: i32 != 3) { return 1; };\n"
" if (s[0] != 1002) { return 2; };\n"
" if (s[1] != 1003) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* E — esz=4 call-arg (pushargsrev / cgen.c:4646 twin). The fn reads
* s[0]/s[1] of `a[3:6]` -> 1003 / 1004. */
{ "subslice_esz4_call_arg",
"fn e0(s: []i32) i32 = { return s[0]; };\n"
"fn e1(s: []i32) i32 = { return s[1]; };\n"
"export fn main() i32 = {\n"
" let a: [8]i32;\n"
" let i: i32 = 0;\n"
" for (i < 8) { a[i] = 1000 + i; i += 1; };\n"
" if (e0(a[3:6]) != 1003) { return 1; };\n"
" if (e1(a[3:6]) != 1004) { return 2; };\n"
" return 0;\n"
"};\n",
0 },
/* F — esz=8 call-arg. The fns read s[0]/s[1] of `a[2:5]` -> 5002 / 5003. */
{ "subslice_esz8_call_arg",
"fn f0(s: []i64) i64 = { return s[0]; };\n"
"fn f1(s: []i64) i64 = { return s[1]; };\n"
"export fn main() i32 = {\n"
" let a: [8]i64;\n"
" let i: i32 = 0;\n"
" for (i < 8) { a[i] = (5000 + i): i64; i += 1; };\n"
" if (f0(a[2:5]): i32 != 5002) { return 1; };\n"
" if (f1(a[2:5]): i32 != 5003) { return 2; };\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/subsliceptr_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/subsliceptr_%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,
"subslice_ptresz_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,
"subslice_ptresz_run[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (fail) {
fprintf(stderr, "subslice_ptresz_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("subslice_ptresz_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,215 +0,0 @@
/*
* 944_deref_slice_store_run — runtime coverage for project #79: 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}. The `*p = v` deref-store arm was kind-gated on str ONLY; a
* slice fell to the 1-word fldstoreop 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` (kind-OR, never a sz==24 test, which would also catch
* >16B structs). This is the project #75 str-only-gate one level down
* (deref-store).
*
* SYMMETRIC across both stages (cstage cmd/w6c/cgen.c:3792/3800 + wwstage
* cgenexpr.ww `*p=v` twin both dropped identically), so byte-id + cs==ww +
* 990-997 are all gate-BLIND here — only a store->read ROUNDTRIP catches it.
*
* Rows (each store paired with a read mirror; cap!=len in every row so a
* dropped len OR cap is caught):
* A d_slice_direct — `*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`, the whole-local slice assign, NOT the
* site under test); a 1-word `*pp=p` leaves q's cap=5, the fix lands 8.
* B d_slice_thru — same store, read back THROUGH the pointer via the
* field-deref read `(*pp).cap`/`(*pp).len` (already 3-word), confirming
* the stored header survives a pointer-side read.
* C control str-deref — `*pp = "hello"` over a poisoned `d="xy"`; the str
* arm we widen AROUND, must still land len=5 (regression guard).
* D control (*p).field — `(*pb).s = p` into a struct{s:[]u8} field; a
* SEPARATE branch (explicit-deref N_DOT), already 3-word, untouched.
*
* The whole-deref READ-into-let `let v = *pp` is deliberately NOT used: for
* a slice it drops len+cap (ptr-only) while the str form is 3-word — a
* SEPARATE read-side gate hole, filed apart from this store fold (project
* #81, the read-twin of #79). The element-index read `(*pp)[i]` is likewise
* avoided (index-through-deref-field diverges cs vs ww — also separate,
* project #82); ptr is covered by row A's dst[0].
*
* Verified fail-before (cstage store emits 1-word ptr-only, row A reads the
* poison cap=5 -> exit 1) / pass-after (3-word store, exit 0) on BOTH the
* cstage `ww` and wwstage `ww_ww` drivers, byte-identical asm.
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
* race does not apply (mirrors the 941/942/943 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 — d_slice_direct: poison dst via the proven 3-word `dst = q`,
* then `*pp = p` and read the direct local. p:{cap=8,len=2,[0]=104},
* q:{cap=5,len=4}. A dropped store keeps q's cap=5. */
{ "d_slice_direct",
"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 qb: [8]u8;\n"
" let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;\n"
" let dst: []u8 = q;\n"
" let pp: *[]u8 = &dst;\n"
" *pp = p;\n"
" if (dst.cap: i32 != 8) { return 1; };\n"
" if (dst.len: i32 != 2) { return 2; };\n"
" if (dst[0] != 104u8) { return 3; };\n"
" return 0;\n"
"};\n",
0 },
/* B — d_slice_thru: same store, read back THROUGH the pointer via the
* 3-word field-deref read `(*pp).cap`/`.len`. */
{ "d_slice_thru",
"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 qb: [8]u8;\n"
" let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;\n"
" let dst: []u8 = q;\n"
" let pp: *[]u8 = &dst;\n"
" *pp = p;\n"
" if ((*pp).cap: i32 != 8) { return 1; };\n"
" if ((*pp).len: i32 != 2) { return 2; };\n"
" return 0;\n"
"};\n",
0 },
/* C — control str-deref: the str arm the fix widens around. Poison
* d="xy" then `*pp="hello"`; must still land len=5. */
{ "d_str_control",
"export fn main() i32 = {\n"
" let d: str = \"xy\";\n"
" let pp: *str = &d;\n"
" *pp = \"hello\";\n"
" if (d.len: i32 != 5) { return 1; };\n"
" return 0;\n"
"};\n",
0 },
/* D — control (*p).field: explicit-deref field store, a separate
* already-3-word branch. `(*pb).s = p` into a struct{s:[]u8}. */
{ "d_field_control",
"type box = struct { s: []u8 };\n"
"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 b: box;\n"
" let pb: *box = &b;\n"
" (*pb).s = p;\n"
" if (b.s.cap: i32 != 8) { return 1; };\n"
" if (b.s.len: i32 != 2) { return 2; };\n"
" if (b.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/derefslicestore_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/derefslicestore_%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,
"deref_slice_store_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,
"deref_slice_store_run[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (fail) {
fprintf(stderr, "deref_slice_store_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("deref_slice_store_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,293 +0,0 @@
/*
* 949_f6_header_run — F6 24B str/slice header partial-load/store cluster.
*
* Three members of the cat-A drain F6 sub-train, each a 24B-header
* (ptr,len,cap) materialize choke-point that dropped one or more words:
*
* #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 (`MOVQ BX, CX`) — wwstage gated the synth on a
* local-ident source only, leaving CX = the str's stale word-16
* garbage cap. cstage was already type-keyed (TY_STR→TY_SLICE) and
* correct, so this aligns wwstage UP; the rows pin cs==ww byte-id.
*
* #28 (#263 both-stages): a tuple slice-element read `t.0` over a
* `([]u8, i64)` loaded only the ptr word — the str arm handled str
* but a SLICE element fell to the scalar tail, so len/cap took
* stale registers (a clobber() call between build and read leaves
* junk in BX/CX). Both stages were byte-id-WRONG; fixed together.
*
* #29 (#263 both-stages): a chained-dot str leaf `o.i.s` (depth-2)
* emitted only (ptr,len) — the cap word was dropped, so a junk
* strlit before the chain left CX stale. Both stages byte-id-WRONG.
*
* POLARITY: #19 cstage is the byte-id ref (align wwstage UP). #28/#29 are
* #263 (both byte-id-WRONG) — the fix lands BOTH halves together, so the
* rows assert both-stages-CORRECT post-fix (no residual rows); the
* runtime read-back with DISTINCT values (cap=3/len=5/cap=5) is the net
* a byte-id gate alone was blind to.
*
* NNN<950, self-contained (/tmp, no imports) — rule-14's selfhost-sibling
* race does not apply (941/944/945/947 precedent). Every K_RUN row also
* pins cstage/wwstage asm byte-id.
*/
#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;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */
struct row { const char *label; const char *src; int want;
int kind; const char *experr; };
static const struct row rows[] = {
/* #19: str→[]u8 cast from a GLOBAL ident source, threaded through a
* call return. cap must == len == 3. Pre-fix wwstage left CX = the
* str's stale word-16 (rc=120). */
{ "global_str_cap",
"package main;\n"
"let G: str = \"abc\";\n"
"fn f() []u8 = { return G: []u8; };\n"
"export fn main() i32 = {\n"
" let s: []u8 = f();\n"
" return s.cap: i32;\n"
"};\n", 3, K_RUN, NULL },
/* #19 sibling: str→[]u8 cast from a struct FIELD source. Same
* type-keyed synth path; distinct len (5) catches a dropped cap. */
{ "field_str_cap",
"package main;\n"
"type box = struct { s: str, x: i64 };\n"
"export fn main() i32 = {\n"
" let b: box = box { s = \"hello\", x = 0 };\n"
" let v: []u8 = b.s: []u8;\n"
" return v.cap: i32;\n"
"};\n", 5, K_RUN, NULL },
/* #19 control: LOCAL str source (the pre-fix-covered shape) still
* emits the synth — guards against a regression of the common case. */
{ "local_str_cap",
"package main;\n"
"export fn main() i32 = {\n"
" let s: str = \"abcd\";\n"
" let v: []u8 = s: []u8;\n"
" return v.cap: i32;\n"
"};\n", 4, K_RUN, NULL },
/* #28 (#263 both-stages): tuple slice-element read `t.0` over a
* ([]u8, i64). A clobber() call between the tuple 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. Reads len
* == 5 (distinct from any stale value) → 0. */
{ "tuple_slice_elem",
"package main;\n"
"fn clobber() i64 = {\n"
" let a: i64 = 111;\n"
" let b: i64 = 222;\n"
" return a + b;\n"
"};\n"
"export fn main() i32 = {\n"
" let xs: []u8 = [1u8, 2u8, 3u8, 4u8, 5u8];\n"
" let t: ([]u8, i64) = (xs, 7);\n"
" let junk: i64 = clobber();\n"
" let ys: []u8 = t.0;\n"
" if (ys.len == 5) { return 0; };\n"
" return 1;\n"
"};\n", 0, K_RUN, NULL },
/* #28 sibling: the slice element sits at tuple position 1 (foff != 0:
* an i64 occupies slot 0, the []u8 header starts at +8). Exercises the
* widened arm's offset arithmetic, which the position-0 row leaves
* untested. Same clobber()-between-build-and-read trigger; len == 6. */
{ "tuple_slice_elem_pos1",
"package main;\n"
"fn clobber() i64 = {\n"
" let a: i64 = 111;\n"
" let b: i64 = 222;\n"
" return a + b;\n"
"};\n"
"export fn main() i32 = {\n"
" let xs: []u8 = [1u8, 2u8, 3u8, 4u8, 5u8, 6u8];\n"
" let t: (i64, []u8) = (9, xs);\n"
" let junk: i64 = clobber();\n"
" let ys: []u8 = t.1;\n"
" if (ys.len == 6) { return 0; };\n"
" return 1;\n"
"};\n", 0, K_RUN, NULL },
/* #29 (#263 both-stages): 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 (the junk's cap) instead of 5. Now the
* str leaf loads all three header words like the slice arm. */
{ "chained_str_cap",
"package main;\n"
"type inner = struct { s: str, x: i64 };\n"
"type outer = struct { i: inner, y: i64 };\n"
"export fn main() i32 = {\n"
" let iv: inner = inner { s = \"hello\", x = 0 };\n"
" let o: outer = outer { i = iv, y = 0 };\n"
" let junk: str = \"ab\";\n"
" let t: str = o.i.s;\n"
" return t.cap: i32;\n"
"};\n", 5, K_RUN, NULL },
/* #29 sibling: the str leaf sits at a NON-ZERO field offset within the
* inner struct (a leading i64 pad pushes `s` to +8, so totaloff != 0).
* Exercises the chained-leaf offset arithmetic the offset-0 row leaves
* untested. Junk strlit before the chain; cap == len == 7. */
{ "chained_str_cap_offset",
"package main;\n"
"type inner = struct { pad: i64, s: str };\n"
"type outer = struct { i: inner, y: i64 };\n"
"export fn main() i32 = {\n"
" let iv: inner = inner { pad = 0, s = \"worldly\" };\n"
" let o: outer = outer { i = iv, y = 0 };\n"
" let junk: str = \"ab\";\n"
" let t: str = o.i.s;\n"
" return t.cap: i32;\n"
"};\n", 7, K_RUN, NULL },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[96], tmpdir[96], errf[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/f6h_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/f6h_%d_d_%d", getpid(), i);
snprintf(errf, sizeof errf, "/tmp/f6h_%d_e_%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 >/dev/null 2>%s",
tmpdir, driver, src, errf);
int brc = runwait(cmd);
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
unlink(src); unlink(errf); rmdir(tmpdir);
return -1;
}
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char outbin[256];
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); unlink(errf); rmdir(tmpdir);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[96], cs[96], ws[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/f6h_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/f6h_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/f6h_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
unlink(src);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
bin, ws, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
unlink(src); unlink(cs);
return -1;
}
int rc = slurp_eq(cs, ws);
if (rc != 0)
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
r->label);
unlink(src); unlink(cs); unlink(ws);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[2080];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[2120], wdrv[2120];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
total++;
if (run_driver(cdrv, &rows[i], i) != 0) fail++;
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
}
for (int i = 0; i < n; i++) {
if (rows[i].kind == K_BUILDERR)
continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "f6_header: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("f6_header: %d/%d ok\n", total, total);
return 0;
}