diff --git a/Makefile b/Makefile index cbefd632..a6c6e936 100644 --- a/Makefile +++ b/Makefile @@ -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; \ diff --git a/test/lang/deref_slice_store_test.ww b/test/lang/deref_slice_store_test.ww new file mode 100644 index 00000000..3df3644e --- /dev/null +++ b/test/lang/deref_slice_store_test.ww @@ -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); +}; diff --git a/test/lang/f6_header_test.ww b/test/lang/f6_header_test.ww new file mode 100644 index 00000000..ac17c901 --- /dev/null +++ b/test/lang/f6_header_test.ww @@ -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); +}; diff --git a/test/lang/slice_store_cap_test.ww b/test/lang/slice_store_cap_test.ww new file mode 100644 index 00000000..bf08c60a --- /dev/null +++ b/test/lang/slice_store_cap_test.ww @@ -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); +}; diff --git a/test/lang/str_abi_test.ww b/test/lang/str_abi_test.ww new file mode 100644 index 00000000..3bd77724 --- /dev/null +++ b/test/lang/str_abi_test.ww @@ -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); }; + }; +}; diff --git a/test/lang/subslice_cap_test.ww b/test/lang/subslice_cap_test.ww new file mode 100644 index 00000000..4d042447 --- /dev/null +++ b/test/lang/subslice_cap_test.ww @@ -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); +}; diff --git a/test/lang/subslice_ptresz_test.ww b/test/lang/subslice_ptresz_test.ww new file mode 100644 index 00000000..78ba4d50 --- /dev/null +++ b/test/lang/subslice_ptresz_test.ww @@ -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); +}; diff --git a/test/wcc/928_str_abi_run.c b/test/wcc/928_str_abi_run.c deleted file mode 100644 index b68a1fb1..00000000 --- a/test/wcc/928_str_abi_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/941_slice_store_cap_run.c b/test/wcc/941_slice_store_cap_run.c deleted file mode 100644 index 9d71a4e3..00000000 --- a/test/wcc/941_slice_store_cap_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/942_subslice_cap_run.c b/test/wcc/942_subslice_cap_run.c deleted file mode 100644 index 52d203d0..00000000 --- a/test/wcc/942_subslice_cap_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/943_subslice_ptresz_run.c b/test/wcc/943_subslice_ptresz_run.c deleted file mode 100644 index cf69f013..00000000 --- a/test/wcc/943_subslice_ptresz_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/944_deref_slice_store_run.c b/test/wcc/944_deref_slice_store_run.c deleted file mode 100644 index 5d450020..00000000 --- a/test/wcc/944_deref_slice_store_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/949_f6_header_run.c b/test/wcc/949_f6_header_run.c deleted file mode 100644 index 98a79df3..00000000 --- a/test/wcc/949_f6_header_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -}