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