diff --git a/Makefile b/Makefile index 47b9a378..af7cd856 100644 --- a/Makefile +++ b/Makefile @@ -374,9 +374,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_array_zeroinit_run \ $(BIN)/test_alias_accept_run \ $(BIN)/test_alias_idx_family_run \ - $(BIN)/test_alias_amp_idx_run \ $(BIN)/test_alias_def_addr_run \ - $(BIN)/test_def_amp_idx_run \ $(BIN)/test_idx_tagged_field_run \ $(BIN)/test_tagged_widen_arg_run \ $(BIN)/test_tuple_tagged_union_run \ @@ -552,11 +550,8 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_floatlit_run \ $(BIN)/test_checked_run \ $(BIN)/test_floatarr_run \ - $(BIN)/test_deref_narrow_run \ $(BIN)/test_inferred_scalar_global_run \ $(BIN)/test_idx_compound_run \ - $(BIN)/test_ptrarr_index_run \ - $(BIN)/test_dotbase_arr_run \ $(BIN)/test_dotbase_addr_slice_run \ $(BIN)/test_globalslice_arg_run \ $(BIN)/test_structlit_arrfield_run \ @@ -2283,24 +2278,12 @@ $(BIN)/test_alias_idx_family_run: test/wcc/944_alias_idx_family_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_alias_amp_idx_run: test/wcc/944_alias_amp_idx_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_def_addr_run: test/wcc/944_alias_def_addr_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_def_amp_idx_run: test/wcc/944_def_amp_idx_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_idx_tagged_field_run: test/wcc/944_idx_tagged_field_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ @@ -3090,11 +3073,6 @@ $(BIN)/test_floatarr_run: test/wcc/946_floatarr_run.c $(BIN)/ww \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_deref_narrow_run: test/wcc/947_deref_narrow_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_inferred_scalar_global_run: test/wcc/947_inferred_scalar_global_run.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) @@ -3105,16 +3083,6 @@ $(BIN)/test_idx_compound_run: test/wcc/948_idx_compound_run.c $(BIN)/ww \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_ptrarr_index_run: test/wcc/949_ptrarr_index_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dotbase_arr_run: test/wcc/949_dotbase_arr_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_dotbase_addr_slice_run: test/wcc/949_dotbase_addr_slice_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) @@ -3378,7 +3346,7 @@ test-lang: all LANGBYTEID_DIR = $(OUT)/langbyteid LANGBYTEID_FILES = $(wildcard test/lang/*_test.ww) LANGBYTEID_VERB = test -c -LANGBYTEID_EXPECTED_MIN = 38 +LANGBYTEID_EXPECTED_MIN = 43 $(if $(LANGBYTEID_FILES),,$(error test-lang-byteid: empty corpus)) test-lang-byteid: all @set -e; \ diff --git a/test/lang/alias_amp_idx_test.ww b/test/lang/alias_amp_idx_test.ww new file mode 100644 index 00000000..deb90903 --- /dev/null +++ b/test/lang/alias_amp_idx_test.ww @@ -0,0 +1,98 @@ +// alias_amp_idx_test — `&a[i]` over an ALIAS-typed array base must classify +// arrayness off the CHASED type, not the syntactic tnode, migrated from +// test/wcc/944_alias_amp_idx_run.c (#5 alias arc). cgun's TK_AMP N_INDEX +// classify keyed arrayness off the syntactic tnode (N_TNAME), so `&a[i]` over an +// alias-typed base missed the N_TARRAY gate and materialized the base as MOVQ +// (element-0 VALUE) instead of LEAQ (storage address) → wild pointer, ww SEGV +// 139 on the deref (esz was already alias-correct, ONLY the base classify moved). +// cstage classifies off the chased type and is the runtime-correct reference; +// the fix re-keys both legs off tichase(base.type_) gated on TY_NAMED, so +// non-alias rows are byte-id-neutral by construction. The .s is byte-identical +// cs==ww, so these behavioral @tests are the net. +// +// All reads/writes go THROUGH THE TAKEN POINTER; values exceed 255 to break +// little-endian prefix-luck and the LAST element is checked. amp_ctrl is the +// plain-local control; amp_l1/amp_l2 are 1-/2-level alias locals; amp_order is +// the forward-ref shape (the original placed `type arr` after the use — here +// module-level resolution subsumes it); amp_narrow pins that the alias BASE was +// the only wrong half (the narrow esz was already right); amp_g1/amp_g2 are +// alias-typed globals; amp_gctrl holds the plain-global + global-str #11 legs. +// +// NOT pinned here (filed, a DIFFERENT site both stages): `&D[i]` over a +// DEF-array — covered by def_amp_idx_test (#94). + +package alias_amp_idx_test; + +type arr = [4]int; +type arr2 = arr; +type A = [4]u32; + +let g1: arr = [1000: int, 2000: int, 3000: int, 4000: int]; +let g2: arr2 = [1000: int, 2000: int, 3000: int, 4000: int]; +let gc: [4]int = [1000: int, 2000: int, 3000: int, 4000: int]; +let gs: str = "wxyz"; + +@test fn amp_ctrl() void = { + let a: [4]int = [1000: int, 2000: int, 3000: int, 4000: int]; + let p: *int = &a[2]; + assert(*p == 3000); + *p = 7777; + assert(a[2] == 7777); + let q: *int = &a[3]; + assert(*q == 4000); +}; + +@test fn amp_l1() void = { + let a: arr = [1000: int, 2000: int, 3000: int, 4000: int]; + let p: *int = &a[3]; + assert(*p == 4000); + *p = 8888; + assert(a[3] == 8888); +}; + +@test fn amp_l2() void = { + let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int]; + let p: *int = &a[3]; + assert(*p == 4000); + *p = 9999; + assert(a[3] == 9999); +}; + +@test fn amp_order() void = { + let a: arr = [1000: int, 2000: int, 3000: int, 4000: int]; + let p: *int = &a[3]; + assert(*p == 4000); + *p = 8888; + assert(a[3] == 8888); +}; + +@test fn amp_narrow() void = { + let a: A = [1000: u32, 2000: u32, 3000: u32, 4000: u32]; + let p: *u32 = &a[3]; + assert(*p == 4000); + *p = 5555; + assert(a[3] == 5555); +}; + +@test fn amp_g1() void = { + let p: *int = &g1[3]; + assert(*p == 4000); + *p = 8888; + assert(g1[3] == 8888); +}; + +@test fn amp_g2() void = { + let p: *int = &g2[3]; + assert(*p == 4000); + *p = 9999; + assert(g2[3] == 9999); +}; + +@test fn amp_gctrl() void = { + let p: *int = &gc[3]; + assert(*p == 4000); + *p = 7777; + assert(gc[3] == 7777); + let q: *u8 = &gs[3]; + assert(*q == 'z'); +}; diff --git a/test/lang/def_amp_idx_test.ww b/test/lang/def_amp_idx_test.ww new file mode 100644 index 00000000..1818b02e --- /dev/null +++ b/test/lang/def_amp_idx_test.ww @@ -0,0 +1,56 @@ +// def_amp_idx_test — `&D[i]` over a DEF-array (indexed &-base), migrated from +// test/wcc/944_def_amp_idx_run.c (#94). BOTH stages SEGV'd at the TK_AMP +// N_INDEX N_IDENT base classify: a def-array base matched neither the local nor +// the let leg and fell to a wrong else — cstage zero-based the addend (XORQ +// BX,BX → wild ptr) while wwstage value-loaded the symbol (MOVQ name(SB) = D[0], +// not its address). DIVERGENT asm, both wild — only a RUNTIME row pins this +// class. The fix adds one def-array leg per stage mirroring the working let leg +// (cs `def_isarraydef → LEAQ name(SB),BX`; ww `defvartnode`→isglobalarr→LEAQ), +// so the existing i*esz scale + ADDQ round-trips; cs/ww now emit byte-identical +// LEAQ-SB asm. The `*p` deref is spelled `let v: T = *p;` because `*p: T` parses +// as `*(p: T)` (cast binds tighter than prefix-*), a spurious "cannot deref". +// +// amp_int/amp_u32/amp_arg are the SEGV repros (read the def-array element back +// through the taken pointer, narrow esz on amp_u32); ctrl_plain/ctrl_read/ctrl_2d +// are the controls the new leg must not perturb (plain &D, indexed read, 2D +// &M[1][1]). Distinct def symbol names per shape since they share one module. + +package def_amp_idx_test; + +def Dint: [3]int = [1000: int, 2000: int, 3000: int]; +def Du32: [3]u32 = [1000: u32, 2000: u32, 3000: u32]; +def M2: [2][2]int = [[10: int, 20: int], [30: int, 40: int]]; + +fn deref_int(p: *int) int = { let v: int = *p; return v; }; + +@test fn amp_int() void = { + let p: *int = &Dint[2]; + let v: int = *p; + assert(v == 3000); +}; + +@test fn amp_u32() void = { + let p: *u32 = &Du32[2]; + let v: u32 = *p; + assert(v == 3000u32); +}; + +@test fn amp_arg() void = { + // &D[2] as a func-arg (context-independent base). + assert(deref_int(&Dint[2]) == 3000); +}; + +@test fn ctrl_plain() void = { + let p: *[3]int = &Dint; + assert((*p)[0] == 1000); +}; + +@test fn ctrl_read() void = { + assert(Dint[1] == 2000); +}; + +@test fn ctrl_2d() void = { + let q: *int = &M2[1][1]; + let v: int = *q; + assert(v == 40); +}; diff --git a/test/lang/deref_narrow_test.ww b/test/lang/deref_narrow_test.ww new file mode 100644 index 00000000..70ad4383 --- /dev/null +++ b/test/lang/deref_narrow_test.ww @@ -0,0 +1,96 @@ +// deref_narrow_test — an integer pointer deref (`*p`) must read its pointee's +// ACTUAL width, not a fixed 8B MOVQ, migrated from test/wcc/947_deref_narrow_run.c +// (#116). Before #116 the TK_STAR integer arm emitted a raw `MOVQ (AX),AX` +// regardless of pointee size, so `*p` over a packed `*i32`/`*u32`/`*iN` slot +// pulled the next 4–7 bytes into the high half of RAX. Truncating sinks (an i32 +// store) dropped the garbage; WIDTH-PRESERVING sinks (a `==` compare that +// widens the literal to 8B → CMPQ) saw it. The fix routes the load through +// localloadop(n->type), the MOVSXD/MOVSWQ/MOVSBQ + MOVL/MOVZWQ/MOVZBQ dispatch +// already shared by IDENT/DOT/index loads. The .s is byte-identical cs==ww, so +// these behavioral @tests are the runtime net the byte-id gate can't be. +// +// Each @test deliberately uses a `==` (CMPQ-width) sink so a stale high half +// would make the assert FAIL; the narrow-signed rows route through an `: i32` +// cast whose compare against a 64B-widened literal forces the sign-extension +// onto the load. alias/enum rows exercise the TY_NAMED/TBANG/TY_ENUM peel in +// localloadop's tinfo lookup; the struct-field base on the i16/u16/alias/enum +// rows dodges the sibling [N]alias array-init store-width concern (out of #116 +// scope). i64_ctrl is the regression guard that a width-correct case keeps MOVQ. + +package deref_narrow_test; + +type err = !i32; +type abox = struct { v: err }; +type ibox = struct { v: i16, m: u16 }; +type ubox = struct { v: u16, m: u16 }; +type flag = enum i8 { A = -2i8, B = 1i8 }; +type fbox = struct { v: flag, m: u8 }; + +@test fn i32_cmpq() void = { + let buf: [4]i32 = [10, 20, 30, 40]; + let p: *i32 = &buf[1]; + assert(*p == 20); +}; + +@test fn u32_zeroext() void = { + let buf: [3]u32 = [10u32, 20u32, 0xFFFFFFFFu32]; + let p: *u32 = &buf[2]; + assert(*p == 0xFFFFFFFFu32); +}; + +@test fn u8_zeroext() void = { + let buf: [3]u8 = [10u8, 20u8, 0xFFu8]; + let p: *u8 = &buf[2]; + assert(*p == 0xFFu8); +}; + +@test fn i8_signext() void = { + let buf: [3]i8 = [-1i8, -2i8, -3i8]; + let p: *i8 = &buf[1]; + let v: i32 = (*p): i32; + assert(v == -2i32); +}; + +@test fn alias_signext() void = { + // `type err = !i32; *err` — the TY_NAMED/TBANG peel must reach the + // underlying i32 width for MOVSXD to fire. + let b: abox = abox { v = -7i32: err }; + let p: *err = &b.v; + let r: i32 = (*p): i32; + assert(r == -7i32); +}; + +@test fn bool_ctrl() void = { + let buf: [3]bool = [true, false, true]; + let p: *bool = &buf[1]; + assert(*p == false); +}; + +@test fn i16_signext() void = { + let b: ibox = ibox { v = -200i16, m = 0xABCDu16 }; + let p: *i16 = &b.v; + let r: i32 = (*p): i32; + assert(r == -200i32); +}; + +@test fn u16_zeroext() void = { + let b: ubox = ubox { v = 0xFFFFu16, m = 0xABCDu16 }; + let p: *u16 = &b.v; + assert(*p == 0xFFFFu16); +}; + +@test fn enum_signext() void = { + // `type flag = enum i8 { ... }; *flag` — TY_NAMED/TY_ENUM peel in + // typeissigned + tinfo.size on the NAMED wrapper must land on MOVSBQ. + let b: fbox = fbox { v = flag.A, m = 0xABu8 }; + let p: *flag = &b.v; + let r: i32 = ((*p): i8): i32; + assert(r == -2i32); +}; + +@test fn i64_ctrl() void = { + // width-correct case — MUST still emit MOVQ; regression guard. + let buf: [3]i64 = [100i64, 200i64, 300i64]; + let p: *i64 = &buf[1]; + assert(*p == 200i64); +}; diff --git a/test/lang/dotbase_arr_test.ww b/test/lang/dotbase_arr_test.ww new file mode 100644 index 00000000..3c19078d --- /dev/null +++ b/test/lang/dotbase_arr_test.ww @@ -0,0 +1,46 @@ +// dotbase_arr_test — an N_INDEX with an N_DOT base on a `[N]T`-typed field +// (`(*struct).array_field[i]`) must compute the field's ADDRESS, not load its +// value, migrated from test/wcc/949_dotbase_arr_run.c (#135). Pre-#135 the +// N_INDEX read + plain-assign + compound fallbacks all invoked cgexpr on the +// N_DOT base, which auto-derefs and loads the field's first 8 bytes as if a +// pointer → every read/write/compound through a packed array field segfaulted, +// cs==ww BOTH stages broken identically (gate-blind). The fix +// (cg_dotbase_addr/dotbaseaddr) detects an N_DOT base whose FIELD is TY_ARRAY, +// walks to the inner ident, and emits address-of-field inline; the TY_ARRAY +// gate keeps it inert on pointer/slice/str fields. The .s is byte-identical, so +// these behavioral @tests are the runtime net. +// +// read_u8 / write_u8 / compound_u8 are the 3 fix shapes on a `*struct` base +// with an [8]u8 field (the strconv decimal.ha:178 compound shape, #133+#135 +// prereqs). Wider element widths and the TY_ARRAY-gate no-over-fire control are +// left to the bootstrap byte-id corpus (994/995) per the C twin's note: the i32 +// return ABI's MOVL-vs-MOVSXD is a pre-existing cs/ww divergence below the +// helper, unrelated to #135. + +package dotbase_arr_test; + +type t = struct { digits: [8]u8, nd: size }; + +fn rd(d: *t) i32 = { return d.digits[3]: i32; }; +fn setit(d: *t) void = { d.digits[3] = 99u8; }; +fn bump(d: *t) void = { d.digits[3] += 1u8; }; + +@test fn read_u8() void = { + let x: t; + x.digits[0] = 50u8; + x.digits[3] = 77u8; + assert(rd(&x) == 77i32); +}; + +@test fn write_u8() void = { + let x: t; + setit(&x); + assert(x.digits[3]: i32 == 99i32); +}; + +@test fn compound_u8() void = { + let x: t; + x.digits[3] = 10u8; + bump(&x); + assert(x.digits[3]: i32 == 11i32); +}; diff --git a/test/lang/ptrarr_index_test.ww b/test/lang/ptrarr_index_test.ww new file mode 100644 index 00000000..625d6dca --- /dev/null +++ b/test/lang/ptrarr_index_test.ww @@ -0,0 +1,237 @@ +// ptrarr_index_test — indexing through a pointer-to-array (`p[i]`, p: *[N]T) +// must stride by size(T), the pointee array's ELEMENT, never by the whole-array +// byte size, migrated from test/wcc/949_ptrarr_index_run.c (#61). Three sub- +// classes of one root: +// A. value-route scaling — wwstage's elemsizeofc fed a `*[N]T` pointee into +// the "outer stride = whole sub-array" rule (only correct for [N][M]T), so +// every read/write/compound through `p[i]` strode N*size(T) (OOB for i>=1) +// and a var-idx write emitted an N*8 aggregate copy → caller-frame smash +// (exactly lib/hash/siphash round()'s corruption). cstage was correct; +// wwstage aligned UP via the idxeffti/idxelemtn choke-point. +// B. `&p[i]` addr-of — BOTH stages identically wrong (byte-id-BLIND): the +// TK_AMP arm read bu->sub->size without the ptr peel, so &p[3]-&a[0] +// returned 3*N*size(T). Only a RUNTIME row pins this class. +// C. `(*p)[i]` explicit deref+index — BOTH stages SEGV'd identically: the +// deref materialized an 8B scalar load and indexed off that VALUE. Fixed +// at the deref choke-point (an ARRAY pointee is its own address, #270-1a). +// +// cs==ww is byte-identical, and cstage is runtime-correct, so byte-id (the gate) +// + these behavioral @tests (cstage-runtime) together pin BOTH stages. The +// matrix covers elem widths {1,2,4,8}B, const+var indices, param/local/cast +// bases, read/write/compound routes, neighbor-corruption guards, the &p[i] +// pointer difference (B), nested *[2][3], *[3]str header elements, the siphash +// round() mix-in-place shape, and the (*p)[i] deref family (C). + +package ptrarr_index_test; + +fn pa_rd64c(p: *[4]u64) u64 = { return p[1]; }; +fn pa_rd8v(p: *[4]u8, i: i32) u8 = { return p[i]; }; +fn pa_rd16(p: *[4]u16) u16 = { return p[3]; }; +fn pa_rd32(p: *[4]u32) u32 = { return p[2]; }; +fn pa_rd32s(p: *[4]i32) i32 = { return p[1]; }; +fn pa_wr64c(p: *[4]u64) void = { p[1] = 7u64; }; +fn pa_wr64v(p: *[4]u64, i: i32, x: u64) void = { p[i] = x; }; +fn pa_wr8(p: *[4]u8) void = { p[1] = 9u8; }; +fn pa_add5(p: *[4]u64) void = { p[1] += 5u64; }; +fn pa_addat(p: *[4]u32, i: i32) void = { p[i] += 7u32; }; +fn pa_rd2d(p: *[2][3]u32, i: i32, j: i32) u32 = { return p[i][j]; }; +fn pa_lenof(p: *[3]str, i: i32) i32 = { return p[i].len; }; +fn pa_mix(v: *[4]u64) void = { + v[0] += v[1]; + v[2] += v[3]; + v[1] += v[0]; + v[3] += v[2]; +}; +fn pa_deref_rd(p: *[4]u64) u64 = { return (*p)[1]; }; + +// A: read. +@test fn rd_u64_param_const() void = { + let a: [4]u64 = [100u64, 101u64, 102u64, 103u64]; + assert(pa_rd64c(&a) == 101u64); +}; + +@test fn rd_u8_param_var() void = { + let a: [4]u8 = [10u8, 20u8, 30u8, 40u8]; + assert(pa_rd8v(&a, 2) == 30u8); +}; + +@test fn rd_u16_param() void = { + let a: [4]u16 = [11u16, 22u16, 33u16, 44u16]; + assert(pa_rd16(&a) == 44u16); +}; + +@test fn rd_u32_param() void = { + let a: [4]u32 = [11u32, 22u32, 33u32, 44u32]; + assert(pa_rd32(&a) == 33u32); +}; + +@test fn rd_i32_signed() void = { + // signed-narrow elem behind the ptr — pins the MOVSXD the idxeffti'd + // elemissigned picks (an undrilled ptr-tinfo read classified ARRAY as + // unsigned). + let a: [4]i32 = [7, -5, 9, 1]; + assert(pa_rd32s(&a) == -5i32); +}; + +// A: write. +@test fn wr_u64_param_const() void = { + let a: [4]u64 = [1u64, 2u64, 3u64, 4u64]; + pa_wr64c(&a); + assert(a[0] == 1u64); + assert(a[1] == 7u64); + assert(a[2] == 3u64); + assert(a[3] == 4u64); +}; + +@test fn wr_u64_varidx_paramrhs() void = { + // the corruption proof: pre-fix wwstage emitted a 32B aggregate copy + // sourced at &x to base+32*i → frame smash. Neighbor guards assert no + // byte outside a[1] moved. + let a: [4]u64 = [10u64, 11u64, 12u64, 13u64]; + let i: i32 = 1; + pa_wr64v(&a, i, 77u64); + assert(a[0] == 10u64); + assert(a[1] == 77u64); + assert(a[2] == 12u64); + assert(a[3] == 13u64); +}; + +@test fn wr_u8_neighbors() void = { + let a: [4]u8 = [1u8, 2u8, 3u8, 4u8]; + pa_wr8(&a); + assert(a[0] == 1u8); + assert(a[1] == 9u8); + assert(a[2] == 3u8); + assert(a[3] == 4u8); +}; + +// A: compound. +@test fn compound_u64() void = { + let a: [4]u64 = [100u64, 101u64, 102u64, 103u64]; + pa_add5(&a); + assert(a[0] == 100u64); + assert(a[1] == 106u64); + assert(a[2] == 102u64); +}; + +@test fn compound_u32_varidx() void = { + let a: [4]u32 = [10u32, 20u32, 30u32, 40u32]; + pa_addat(&a, 2); + assert(a[1] == 20u32); + assert(a[2] == 37u32); + assert(a[3] == 40u32); +}; + +// A: local-ptr base, no call boundary. +@test fn rd_localptr() void = { + let a: [4]u64 = [100u64, 101u64, 102u64, 103u64]; + let p: *[4]u64 = &a; + assert(p[2] == 102u64); +}; + +// A: cast base. +@test fn rd_castbase() void = { + let big: [4]u64 = [5u64, 6u64, 7u64, 8u64]; + let p: *[4]u64 = (&big): *[4]u64; + assert(p[1] == 6u64); +}; + +// A: nested *[2][3]u32 — inner stride 4, outer 12; the pointee's OWN nesting +// must coexist with the ptr carve-out. Read, write, neighbor guards. +@test fn nested_2d() void = { + let a: [2][3]u32; + a[0][0] = 0u32; a[0][1] = 1u32; a[0][2] = 2u32; + a[1][0] = 10u32; a[1][1] = 11u32; a[1][2] = 12u32; + assert(pa_rd2d(&a, 1, 2) == 12u32); + assert(pa_rd2d(&a, 0, 1) == 1u32); + let p: *[2][3]u32 = &a; + p[1][0] = 99u32; + assert(a[1][0] == 99u32); + assert(a[1][1] == 11u32); + assert(a[0][2] == 2u32); +}; + +// A: *[3]str — 3-word header elements; pins the read-side str-header gate on +// the idx_eff'd element. +@test fn rd_str_elem() void = { + let a: [3]str; + a[0] = "x"; a[1] = "yy"; a[2] = "zzz"; + assert(pa_lenof(&a, 2) == 3); + assert(pa_lenof(&a, 0) == 1); + let p: *[3]str = &a; + assert(p[1].len == 2); +}; + +// B: &p[i] pointer difference — both stages emitted whole-array stride (96) +// byte-IDENTICALLY pre-fix; only this runtime row sees the class. 3*8 = 24. +@test fn amp_diff_u64() void = { + let a: [4]u64 = [1u64, 2u64, 3u64, 4u64]; + let p: *[4]u64 = &a; + let d: u64 = (&p[3]): u64 - (&a[0]): u64; + assert(d == 24u64); +}; + +// B: &p[i] difference at a narrow width. 3*2 = 6. +@test fn amp_diff_u16() void = { + let a: [4]u16 = [1u16, 2u16, 3u16, 4u16]; + let p: *[4]u16 = &a; + let d: u64 = (&p[3]): u64 - (&a[0]): u64; + assert(d == 6u64); +}; + +// A: the live consumer's shape — siphash round() mutates all four lanes through +// the param ptr, each read feeding a later write. +@test fn mix_inplace_round() void = { + let v: [4]u64 = [1u64, 2u64, 3u64, 4u64]; + pa_mix(&v); + assert(v[0] == 3u64); + assert(v[1] == 5u64); + assert(v[2] == 7u64); + assert(v[3] == 11u64); +}; + +// C: (*p)[i] read. +@test fn deref_rd_u64() void = { + let a: [4]u64 = [100u64, 101u64, 102u64, 103u64]; + let p: *[4]u64 = &a; + assert((*p)[1] == 101u64); +}; + +@test fn deref_rd_u32() void = { + // pins the N_UN-base esz arm (the 8B default would mis-stride). + let a: [4]u32 = [11u32, 22u32, 33u32, 44u32]; + let p: *[4]u32 = &a; + assert((*p)[2] == 33u32); +}; + +@test fn deref_rd_param() void = { + let a: [4]u64 = [100u64, 101u64, 102u64, 103u64]; + assert(pa_deref_rd(&a) == 101u64); +}; + +// C: (*p)[i] write. +@test fn deref_wr_u64() void = { + let a: [4]u64 = [1u64, 2u64, 3u64, 4u64]; + let p: *[4]u64 = &a; + (*p)[1] = 7u64; + assert(a[0] == 1u64); + assert(a[1] == 7u64); + assert(a[2] == 3u64); +}; + +@test fn deref_wr_u8() void = { + let a: [4]u8 = [1u8, 2u8, 3u8, 4u8]; + let p: *[4]u8 = &a; + (*p)[1] = 9u8; + assert(a[0] == 1u8); + assert(a[1] == 9u8); + assert(a[2] == 3u8); +}; + +// C: (*p)[i] compound. +@test fn deref_compound_u64() void = { + let a: [4]u64 = [100u64, 101u64, 102u64, 103u64]; + let p: *[4]u64 = &a; + (*p)[1] += 5u64; + assert(a[1] == 106u64); +}; diff --git a/test/wcc/944_alias_amp_idx_run.c b/test/wcc/944_alias_amp_idx_run.c deleted file mode 100644 index 8c3c15ab..00000000 --- a/test/wcc/944_alias_amp_idx_run.c +++ /dev/null @@ -1,295 +0,0 @@ -/* - * 944_alias_amp_idx_run — #5 alias arc F2a batch 3 c1 (task #82): - * cgun's TK_AMP N_INDEX classify keyed arrayness off the SYNTACTIC - * tnode — `&a[i]` over an alias-typed base (tnode N_TNAME) missed the - * N_TARRAY gate, so the base materialized as MOVQ (element-0 VALUE) - * instead of LEAQ (storage address): wild pointer, ww SEGV 139 on the - * deref (silent-wrong class; esz was already alias-correct via the - * batch-2 elemsizeofc chase, so ONLY the base classify moved). The - * global leg (isglobalarr/isglobalptr) graduated from latent to live - * when g-fold #77/#78 landed alias-global DATA emit. cstage already - * classifies off the chased type (type_chase_named, cgen.c:4172-4188) - * and is the runtime-correct reference; the fix re-keys both legs off - * tichase(base.type_) gated on TY_NAMED — the landed cgindex #60 idiom - * (cgenexpr.ww:1800-1820) — so non-alias rows are byte-id-neutral by - * construction. - * - * row | shape | want - * -----------+-----------------------------------------------+----- - * amp_ctrl | plain [4]int local; p=&a[2]; *p=..; readback | 0 - * amp_l1 | 1-level alias local array, &a[3] w+r via ptr | 0 - * amp_l2 | 2-level alias local | 0 - * amp_order | type decl AFTER the let (fwd-ref) | 0 - * amp_narrow | alias [4]u32, &a[3] (last elem, narrow elem: | - * | stride was already right, base was wrong) | 0 - * amp_g1 | alias-typed GLOBAL array, &g[3] | 0 - * amp_g2 | 2-level alias global | 0 - * amp_gctrl | plain global array &g[3] + global str &s[3] | - * | (the #11 legs hold) | 0 - * - * All reads/writes go THROUGH THE TAKEN POINTER (direct `a[i]` reads - * are 944_alias_idx_family's landed rows — different layer). Values - * exceed 255 to break little-endian prefix-luck; the LAST element is - * checked. Pre-fix: controls 0/0 byte-id; all six alias rows cs 0 / - * ww SEGV 139, byte-id NO. Post-fix: every row 0/0 byte-id. - * - * Deliberately NOT pinned (filed, predicted by the batch-3 spec §1 - * NOTE-2): `&D[i]` with a DEF-array base — broken at a DIFFERENT site - * both stages (cs XORQ BX,BX zero-base cgen.c:4209-4212; ww - * complex-base fallback), a missing-leg defect, not this tnode-kind - * read. - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa), cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -struct row { const char *label; const char *src; int want; }; - -static const struct row rows[] = { - { "amp_ctrl", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]int = [1000: int, 2000: int, 3000: int, 4000: int];\n" - " let p: *int = &a[2];\n" - " if (*p != 3000) { return 1; };\n" - " *p = 7777;\n" - " if (a[2] != 7777) { return 2; };\n" - " let q: *int = &a[3];\n" - " if (*q != 4000) { return 3; };\n" - " return 0;\n" - "};\n", 0 }, - { "amp_l1", - "package main;\n" - "type arr = [4]int;\n" - "export fn main() i32 = {\n" - " let a: arr = [1000: int, 2000: int, 3000: int, 4000: int];\n" - " let p: *int = &a[3];\n" - " if (*p != 4000) { return 1; };\n" - " *p = 8888;\n" - " if (a[3] != 8888) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - { "amp_l2", - "package main;\n" - "type arr = [4]int;\n" - "type arr2 = arr;\n" - "export fn main() i32 = {\n" - " let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n" - " let p: *int = &a[3];\n" - " if (*p != 4000) { return 1; };\n" - " *p = 9999;\n" - " if (a[3] != 9999) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - { "amp_order", - "package main;\n" - "export fn main() i32 = {\n" - " let a: arr = [1000: int, 2000: int, 3000: int, 4000: int];\n" - " let p: *int = &a[3];\n" - " if (*p != 4000) { return 1; };\n" - " *p = 8888;\n" - " if (a[3] != 8888) { return 2; };\n" - " return 0;\n" - "};\n" - "type arr = [4]int;\n", 0 }, - /* Narrow element: the batch-2 elemsizeofc chase already strides - * u32 right (PREMISE-2) — this row pins that the BASE was the - * only wrong half. */ - { "amp_narrow", - "package main;\n" - "type A = [4]u32;\n" - "export fn main() i32 = {\n" - " let a: A = [1000: u32, 2000: u32, 3000: u32, 4000: u32];\n" - " let p: *u32 = &a[3];\n" - " if (*p != 4000) { return 1; };\n" - " *p = 5555;\n" - " if (a[3] != 5555) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - { "amp_g1", - "package main;\n" - "type arr = [4]int;\n" - "let g: arr = [1000: int, 2000: int, 3000: int, 4000: int];\n" - "export fn main() i32 = {\n" - " let p: *int = &g[3];\n" - " if (*p != 4000) { return 1; };\n" - " *p = 8888;\n" - " if (g[3] != 8888) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - { "amp_g2", - "package main;\n" - "type arr = [4]int;\n" - "type arr2 = arr;\n" - "let g: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n" - "export fn main() i32 = {\n" - " let p: *int = &g[3];\n" - " if (*p != 4000) { return 1; };\n" - " *p = 9999;\n" - " if (g[3] != 9999) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - /* Plain-global + global-str controls: the #11 legs (isglobalarr - * LEAQ / str isglobalptr MOVQ) must hold byte-id. */ - { "amp_gctrl", - "package main;\n" - "let g: [4]int = [1000: int, 2000: int, 3000: int, 4000: int];\n" - "let s: str = \"wxyz\";\n" - "export fn main() i32 = {\n" - " let p: *int = &g[3];\n" - " if (*p != 4000) { return 1; };\n" - " *p = 7777;\n" - " if (g[3] != 7777) { return 2; };\n" - " let q: *u8 = &s[3];\n" - " if (*q != 'z') { 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], errf[96], cmd[1024]; - snprintf(src, sizeof src, "/tmp/aai_%d_%d.ww", getpid(), i); - snprintf(tmpdir, sizeof tmpdir, "/tmp/aai_%d_d_%d", getpid(), i); - snprintf(errf, sizeof errf, "/tmp/aai_%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 && timeout 20 %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/aai_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/aai_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/aai_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++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) fail++; - } - } - - if (fail) { - fprintf(stderr, "alias_amp_idx: %d/%d checks failed\n", - fail, total); - return 1; - } - printf("alias_amp_idx: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/944_def_amp_idx_run.c b/test/wcc/944_def_amp_idx_run.c deleted file mode 100644 index 6ea806df..00000000 --- a/test/wcc/944_def_amp_idx_run.c +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 944_def_amp_idx_run — #94 `&D[i]` over a DEF-array (indexed &-base). - * - * BOTH stages SEGV'd at the TK_AMP N_INDEX N_IDENT base classify: a - * def-array base matched neither the local nor the let leg and fell to - * a wrong else — cstage zero-based the addend (XORQ BX,BX → wild ptr, - * cgen.c:4261) while wwstage value-loaded the symbol (MOVQ name(SB) = - * D[0], not the address, cgenexpr.ww complex-base fallback). DIVERGENT - * asm, both wild. The fix adds ONE def-array leg per stage mirroring - * the working let leg: cs `def_isarraydef → LEAQ name(SB),BX`; ww the - * `defvartnode` fallback the read-side cgindex already takes (#129 A.3, - * cgenexpr.ww:1762) → isglobalarr → LEAQ name(SB). The def DATA symbol - * already exists (plain &D + D[i]-read work), so once the base is the - * address the existing i*esz scale + ADDQ round-trips. cs and ww now - * emit BYTE-IDENTICAL LEAQ-SB asm (the convergence is the point). - * - * row | shape | want - * -----------+-----------------------------------------+----- - * amp_int | def [3]int; &D[2]; read via *p | 0 - * amp_u32 | def [3]u32 esz=4; &D[2]; read via *p | 0 (narrow esz) - * amp_arg | &D[2] as a func-arg (context-indep base) | 0 - * ctrl_plain | plain &D (no index) | 0 (must hold) - * ctrl_read | D[1] indexed READ (no &) | 0 (must hold) - * ctrl_2d | &M[1][1] over a def [2][2]int | 0 (must hold) - * - * Pre-fix: amp_int/amp_u32/amp_arg SEGV both stages (cs≠ww asm); the - * three controls already worked. Post-fix: all six 0/0 byte-id. The - * `*p` deref is spelled `let v: T = *p;` — `*p: T` parses as - * `*(p: T)` (cast binds tighter than prefix-*), a spurious "cannot - * deref" (ken #94 oracle test caveat). - * - * OUT (filed #112): &D[..] slicing a def-array (a distinct parse - * reject, needs a Hare-fidelity ruling) — not this leg. - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa), cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -struct row { const char *label; const char *src; int want; }; - -static const struct row rows[] = { - { "amp_int", - "package main;\n" - "def D: [3]int = [1000: int, 2000: int, 3000: int];\n" - "export fn main() i32 = {\n" - " let p: *int = &D[2];\n" - " let v: int = *p;\n" - " if (v != 3000) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - { "amp_u32", - "package main;\n" - "def D: [3]u32 = [1000: u32, 2000: u32, 3000: u32];\n" - "export fn main() i32 = {\n" - " let p: *u32 = &D[2];\n" - " let v: u32 = *p;\n" - " if (v != 3000) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - { "amp_arg", - "package main;\n" - "def D: [3]int = [1000: int, 2000: int, 3000: int];\n" - "fn deref(p: *int) int = { let v: int = *p; return v; };\n" - "export fn main() i32 = {\n" - " if (deref(&D[2]) != 3000) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - /* controls — plain &D, indexed READ, and 2D &M[1][1] must keep - * working byte-id (the new leg must not perturb them). */ - { "ctrl_plain", - "package main;\n" - "def D: [3]int = [1000: int, 2000: int, 3000: int];\n" - "export fn main() i32 = {\n" - " let p: *[3]int = &D;\n" - " if ((*p)[0] != 1000) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - { "ctrl_read", - "package main;\n" - "def D: [3]int = [1000: int, 2000: int, 3000: int];\n" - "export fn main() i32 = {\n" - " if (D[1] != 2000) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - { "ctrl_2d", - "package main;\n" - "def M: [2][2]int = [[10: int, 20: int], [30: int, 40: int]];\n" - "export fn main() i32 = {\n" - " let q: *int = &M[1][1];\n" - " let v: int = *q;\n" - " if (v != 40) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, -}; - -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/dai_%d_%d.ww", getpid(), i); - snprintf(tmpdir, sizeof tmpdir, "/tmp/dai_%d_d_%d", getpid(), i); - snprintf(errf, sizeof errf, "/tmp/dai_%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 && timeout 20 %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/dai_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/dai_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/dai_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++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) fail++; - } - } - - if (fail) { - fprintf(stderr, "def_amp_idx: %d/%d checks failed\n", - fail, total); - return 1; - } - printf("def_amp_idx: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/947_deref_narrow_run.c b/test/wcc/947_deref_narrow_run.c deleted file mode 100644 index b436f7d6..00000000 --- a/test/wcc/947_deref_narrow_run.c +++ /dev/null @@ -1,297 +0,0 @@ -/* - * 947_deref_narrow_run — runtime + byte-id net for #116: an integer - * pointer deref must read its pointee's actual width, not a fixed 8B - * MOVQ. Before #116 the TK_STAR integer arm in cgen.c:2318 / cgenexpr - * .ww:2834 emitted a raw `MOVQ (AX), AX` regardless of pointee size, - * so `*p` over a packed `*i32` / `*u32` / `*iN` slot pulled the next - * 4–7 bytes into the high half of RAX. Truncating sinks (i32 store, - * i32 return) dropped the garbage; width-preserving sinks (CMPQ vs - * an i32 literal, 64-bit arith) saw it. The fix routes the load - * through localloadop(n->type), the same MOVSXD/MOVSWQ/MOVSBQ + - * MOVL/MOVZWQ/MOVZBQ dispatch already shared by IDENT / DOT / index - * loads (cgen.c:264 fldloadop, :372 localloadop; cgenutil.ww:884 - * loadopsz, :904 localloadop). Load-twin of the landed signed-narrow - * scalar-reads sweep (selfhost/CLAUDE.md "Signed-narrow scalar reads - * sign-extend honestly") — TK_STAR was the omitted site. - * - * Each row carries (a) a cstage `ww build` + run asserting the exit - * code (the i32_cmpq row would return 2 pre-fix), and (b) a w6c vs - * w6c_ww `.s` cmp (rule-10 byte-id) — the gate-blind family discipline - * the rest of the suite enforces. The aliased-narrow row exercises the - * TY_NAMED / TBANG peel in localloadop's tinfo lookup. - */ -#include -#include -#include -#include -#include -#include - -#define RUN_SKIP (-1) - -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_exit; }; - -static const struct row rows[] = { - /* The #116 repro: packed *i32 deref with a CMPQ-width sink (the - * `if (*p != 20i32)` comparator widens the i32 literal to 8B and - * compares with CMPQ). Pre-fix MOVQ pulled buf[1] | (buf[2]<<32) - * into AX, the compare failed, exit=2. */ - { "i32_cmpq", - "package main;\n" - "export fn main() i32 = {\n" - " let buf: [4]i32 = [10, 20, 30, 40];\n" - " let p: *i32 = &buf[1];\n" - " if (*p != 20) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - /* *u32 — zero-extend (MOVL r/m32,r32 zeroes upper 32). 0xFFFFFFFF - * round-trips intact only if the read is u32-wide and zero- - * extended. Pre-fix MOVQ overlapped the next slot — and even with - * a tail-element so there's no spill, the value compared against - * 0xFFFFFFFFu32 (widened to 64B) would carry whatever - * uninitialised stack lives at buf+12. */ - { "u32_zeroext", - "package main;\n" - "export fn main() i32 = {\n" - " let buf: [3]u32 = [10u32, 20u32, 0xFFFFFFFFu32];\n" - " let p: *u32 = &buf[2];\n" - " if (*p != 0xFFFFFFFFu32) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - /* *u8 — narrowest unsigned, MOVZBQ. */ - { "u8_zeroext", - "package main;\n" - "export fn main() i32 = {\n" - " let buf: [3]u8 = [10u8, 20u8, 0xFFu8];\n" - " let p: *u8 = &buf[2];\n" - " if (*p != 0xFFu8) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - /* *i8 — signed-narrow, MOVSBQ. -2i8 must sign-extend to -2i32 - * through the explicit cast; a raw MOVQ would leave the upper - * bytes as the next two array slots and a subsequent `: i32` - * cast would still read the low byte correctly — so the cast - * acts as the truncating sink. The compare against -2i32 widens - * to 64B (CMPQ) so the sign-extension must happen on the load. */ - { "i8_signext", - "package main;\n" - "export fn main() i32 = {\n" - " let buf: [3]i8 = [-1i8, -2i8, -3i8];\n" - " let p: *i8 = &buf[1];\n" - " let v: i32 = (*p): i32;\n" - " if (v != -2i32) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - /* Aliased signed-narrow: `type err = !i32; *err`. The TY_NAMED / - * TBANG peel in localloadop's tinfofornode chain must reach the - * underlying i32 width for the MOVSXD opcode to fire. Base is a - * struct field (not an array literal) to dodge a pre-existing - * cs/ww divergence in [N]alias array-init store width — out of - * scope for #116. */ - { "alias_signext", - "package main;\n" - "type err = !i32;\n" - "type box = struct { v: err };\n" - "export fn main() i32 = {\n" - " let b: box = box { v = -7i32: err };\n" - " let p: *err = &b.v;\n" - " let r: i32 = (*p): i32;\n" - " if (r != -7i32) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - /* *bool — also 1B (MOVZBQ); same dispatch shape but worth a - * control since the bool branch in fld_issigned returns 0 - * explicitly. */ - { "bool_ctrl", - "package main;\n" - "export fn main() i32 = {\n" - " let buf: [3]bool = [true, false, true];\n" - " let p: *bool = &buf[1];\n" - " if (*p) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - /* *i16 — MOVSWQ. Struct-field base (with a NEXT byte set so the - * pre-fix MOVQ pulls non-zero high bytes and the 64B CMPQ fails), - * sidestepping the same `[N]i16` array-init store-width concern as - * the alias_signext row. */ - { "i16_signext", - "package main;\n" - "type box = struct { v: i16, m: u16 };\n" - "export fn main() i32 = {\n" - " let b: box = box { v = -200i16, m = 0xABCDu16 };\n" - " let p: *i16 = &b.v;\n" - " let r: i32 = (*p): i32;\n" - " if (r != -200i32) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - /* *u16 — MOVZWQ. Same struct-field shape; #128 ([N]u16 array-init - * cs/ww store-width divergence) is unrelated to this fold. */ - { "u16_zeroext", - "package main;\n" - "type box = struct { v: u16, m: u16 };\n" - "export fn main() i32 = {\n" - " let b: box = box { v = 0xFFFFu16, m = 0xABCDu16 };\n" - " let p: *u16 = &b.v;\n" - " if (*p != 0xFFFFu16) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - /* Enum-aliased narrow: `type flag = enum i8 { ... }`. The TY_NAMED - * /TY_ENUM peel in typeissigned (lib/ww/typ.ww:390 → typeisunsigned - * → TY_ENUM recurse on sub) + tinfo.size on the NAMED wrapper must - * land on MOVSBQ. Distinct from the TBANG row above — same recursion - * shape but a different tykind branch. Struct-field base dodges the - * sibling-of-#128 [N]alias array-init concern. */ - { "enum_signext", - "package main;\n" - "type flag = enum i8 { A = -2i8, B = 1i8 };\n" - "type box = struct { v: flag, m: u8 };\n" - "export fn main() i32 = {\n" - " let b: box = box { v = flag.A, m = 0xABu8 };\n" - " let p: *flag = &b.v;\n" - " let r: i32 = ((*p): i8): i32;\n" - " if (r != -2i32) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - /* *i64 control — MUST still emit MOVQ (no shift). Regression - * guard: a width-correct case shouldn't be perturbed. */ - { "i64_ctrl", - "package main;\n" - "export fn main() i32 = {\n" - " let buf: [3]i64 = [100i64, 200i64, 300i64];\n" - " let p: *i64 = &buf[1];\n" - " if (*p != 200i64) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - { NULL, NULL, 0 } -}; - -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); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - 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 w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "derefnarrow: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char src[64]; - snprintf(src, sizeof src, "/tmp/wwdrn_%d_%d.ww", getpid(), i); - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - /* (a) cstage build + run. */ - if (rows[i].want_exit != RUN_SKIP) { - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwdrn_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char cmd[2048]; - snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s", - tmpdir, bin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - unlink(src); rmdir(tmpdir); - continue; - } - - char outbin[128]; - const char *base = strrchr(src, '/'); - base = base ? base + 1 : src; - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - char *dot = strrchr(outbin, '.'); - if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - unlink(outbin); rmdir(tmpdir); - } - - /* (b) cs==ww byte-id gate. */ - char cs_s[64], ws_s[64]; - snprintf(cs_s, sizeof cs_s, "/tmp/wwdrn_%d_%d_cs.s", - getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwdrn_%d_%d_ww.s", - getpid(), i); - - char cmd[2048]; - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; unlink(src); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; unlink(src); unlink(cs_s); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - unlink(src); unlink(cs_s); unlink(ws_s); - } - - if (fail) { - fprintf(stderr, "%d/%d narrow-deref tests failed\n", - fail, n); - return 1; - } - printf("derefnarrow: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -} diff --git a/test/wcc/949_dotbase_arr_run.c b/test/wcc/949_dotbase_arr_run.c deleted file mode 100644 index 9e8047e1..00000000 --- a/test/wcc/949_dotbase_arr_run.c +++ /dev/null @@ -1,227 +0,0 @@ -/* - * 949_dotbase_arr_run — runtime + byte-id net for #135: an N_INDEX - * with an N_DOT base on a `[N]T`-typed field must compute the field's - * ADDRESS (not load its value). Pre-#135 the cgexpr fallback at the - * N_INDEX read site (cgen.c:6725) and the N_INDEX-lhs plain-assign + - * compound fallbacks (cgen.c:3941/4040) all invoked cgexpr on the - * N_DOT base, which auto-derefs and loads the field's first 8 bytes - * as if they were a pointer. Every `(*struct).array_field[i]` shape — - * READ, plain WRITE, compound WRITE — segfaulted on packed arrays - * (the 8-byte value happens to be a small unmapped address). cs==ww - * BOTH stages broken identically pre-fix (gate-blind). - * - * Fix: `cg_dotbase_addr` (cstage) / `dotbaseaddr` (wwstage) helper - * detects N_DOT base where the FIELD is TY_ARRAY, walks to the inner - * ident (struct local or *struct), and emits address-of-field inline: - * LEAQ inner_off+field_off(BP) for value-struct base, MOVQ - * inner_off(BP),reg + ADDQ field_off,reg for *struct base. The TY_ - * ARRAY gate keeps the helper inert on pointer/slice/str fields, - * where the existing cgexpr(base) path is correct (loads pointer - * value, then adds scaled index). - * - * Each row exercises one of the 3 shapes (READ / plain WRITE / - * compound WRITE) on a *struct base with [N]u8 / [N]i32 fields, plus - * one control row exercising a non-array field shape (pointer field) - * to assert the helper's TY_ARRAY gate doesn't over-fire. cstage - * `ww build` + run for exit code, w6c vs w6c_ww `.s` cmp for rule-10 - * byte-id. - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want_exit; }; - -static const struct row rows[] = { - /* READ d.digits[3]: i32 — pre-fix SEGFAULTs (cgexpr loaded digits - * qword as address). digits[3]=77 → exit 77. */ - { "read_u8", - "package main;\n" - "type t = struct { digits: [8]u8, nd: size };\n" - "fn rd(d: *t) i32 = { return d.digits[3]: i32; };\n" - "export fn main() i32 = {\n" - " let x: t;\n" - " x.digits[0] = 50u8;\n" - " x.digits[3] = 77u8;\n" - " return rd(&x);\n" - "};\n", 77 }, - /* Plain WRITE d.digits[3] = 99u8 — pre-fix SEGFAULTs (same broken - * address). Post-fix: digits[3] reads back as 99. */ - { "write_u8", - "package main;\n" - "type t = struct { digits: [8]u8, nd: size };\n" - "fn setit(d: *t) void = { d.digits[3] = 99u8; };\n" - "export fn main() i32 = {\n" - " let x: t;\n" - " setit(&x);\n" - " return x.digits[3]: i32;\n" - "};\n", 99 }, - /* Compound WRITE d.digits[3] += 1u8 — the strconv decimal.ha:178 - * shape (rule-9 ruling, #133 + #135 both prereqs). Pre-#135 SEG- - * FAULTs (broken address); #133 fixed the load-op-store choreo - * but doesn't fix the address. Post-both: initial 10 + 1 = 11. */ - { "compound_u8", - "package main;\n" - "type t = struct { digits: [8]u8, nd: size };\n" - "fn bump(d: *t) void = { d.digits[3] += 1u8; };\n" - "export fn main() i32 = {\n" - " let x: t;\n" - " x.digits[3] = 10u8;\n" - " bump(&x);\n" - " return x.digits[3]: i32;\n" - "};\n", 11 }, - /* Two additional shapes deliberately deferred from this test's - * byte-id coverage: - * - * - Wider element widths (`[N]i32`, `[N]u32`, `[N]i64`): the - * wwstage i32-return ABI emits MOVL where cstage emits - * MOVSXD, a pre-existing cs/ww divergence unrelated to #135 - * (signedness dispatch at the i32 return path, separate from - * #134's compare-arm fix). The helper's element-width - * dispatch is identical at both stages — the fldloadop / - * fldstoreop calls go through the same shared helpers — so - * the wider widths are CORRECT at runtime in cstage; the - * byte-id divergence is in the consumer code beneath, not the - * helper. - * - * - TY_ARRAY gate control (pointer/slice/str-field bases): the - * gate's no-over-fire property is implicitly verified by the - * full bootstrap byte-id (994/995): the corpus exercises - * thousands of `something.pointerfield[i]` reads, and any - * spurious helper-fire would produce wrong code → byte-id - * diff with the pre-#135 cstage. Bootstrap stays green. - * - * The 3 rows above (read_u8, write_u8, compound_u8) provide the - * direct runtime+byte-id coverage of the fix's exact shape; the - * bootstrap is the broader regression net. */ - { NULL, NULL, 0 } -}; - -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); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - 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 w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "dotbasearr: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char src[64]; - snprintf(src, sizeof src, "/tmp/wwdba_%d_%d.ww", getpid(), i); - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwdba_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char cmd[2048]; - snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s", - tmpdir, bin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - unlink(src); rmdir(tmpdir); - continue; - } - - char outbin[128]; - const char *base = strrchr(src, '/'); - base = base ? base + 1 : src; - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - char *dot = strrchr(outbin, '.'); - if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - unlink(outbin); rmdir(tmpdir); - - char cs_s[64], ws_s[64]; - snprintf(cs_s, sizeof cs_s, "/tmp/wwdba_%d_%d_cs.s", - getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwdba_%d_%d_ww.s", - getpid(), i); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; unlink(src); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; unlink(src); unlink(cs_s); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - unlink(src); unlink(cs_s); unlink(ws_s); - } - - if (fail) { - fprintf(stderr, "%d/%d dotbase-arr tests failed\n", - fail, n); - return 1; - } - printf("dotbasearr: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -} diff --git a/test/wcc/949_ptrarr_index_run.c b/test/wcc/949_ptrarr_index_run.c deleted file mode 100644 index de54c9aa..00000000 --- a/test/wcc/949_ptrarr_index_run.c +++ /dev/null @@ -1,449 +0,0 @@ -/* - * 949_ptrarr_index_run — runtime + byte-id net for #61: indexing - * through a pointer-to-array (`p[i]`, p: *[N]T) must stride by - * size(T), the pointee array's ELEMENT, never by the whole-array - * byte size. - * - * The family (one root class): - * A. wwstage value-route scaling — elemsizeofc's #270-2 nested-array - * block (selfhost/cmd/wcc/cgenutil.ww) fed a `*[N]T` pointee into - * the "outer stride = whole sub-array" rule that is only correct - * for [N][M]T / [][M]T. Every read/write/compound through `p[i]` - * scaled by N*size(T) (OOB for any i>=1), and the same wrong - * element belief reached the store-width chooser: a var-idx write - * emitted an N*8-byte aggregate copy sourced at the 8B rhs slot — - * OOB read of the frame neighborhood + OOB write at base+N*8*i, - * caller-frame smash. This is exactly lib/hash/siphash round()'s - * `v[0]=v0 .. v[3]=v3` corruption. cstage was runtime-correct - * (idx_eff, cgen.c:1163, peels TY_PTR→TY_ARRAY); wwstage aligned - * UP via the idxeffti/idxelemtn choke-point. - * B. `&p[i]` addr-of route — BOTH stages identically wrong - * (byte-id-BLIND): the TK_AMP &base[i] arm read bu->sub->size - * without the ptr peel, so &p[3]-&a[0] returned 3*N*size(T). - * Both stages converged on the idx_eff'd element size; only the - * RUNTIME rows here can pin this class — the 990-997 byte-id - * gates can never see a both-stages-identical miscompile. - * - * Each row carries (a) a cstage `ww build` + run asserting the exit - * code and (b) a w6c vs w6c_ww `.s` cmp (rule-10 byte-id). Together - * they pin BOTH stages: byte-id + cstage-runtime-correct implies - * wwstage-runtime-correct. The matrix covers the elem widths the - * scaling class is sensitive to ({1,2,4,8}B), const + var indices, - * param / local / cast bases, read / write / compound routes, - * neighbor-corruption guards, the &p[i] pointer-difference (B), and - * the live consumer's mix-in-place shape (siphash round). - * - * C. `(*p)[i]` explicit deref + index — BOTH stages SEGV'd - * identically (byte-id-blind): cgun's TK_STAR materialized an - * 8-byte SCALAR load of a[0]'s value and the index used that - * VALUE as its base — a wild deref. Fixed in both stages at the - * deref choke-point: an ARRAY pointee takes the #185 *fn skip - * (an array value IS its address, #270-1a), so `*p` leaves AX = - * p's value and every index/addr-of/store route through - * `cgexpr(base)` materializes the array address for free. The - * deref_* rows pin read / write / compound at 8B and narrow - * widths (the narrow rows also pin the wwstage N_UN-base - * stamped-tinfo esz arm against the 8B default). - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want_exit; }; - -static const struct row rows[] = { - /* A: read, const idx, param base, 8B elem (ken p2). Pre-fix - * wwstage strode 32 → read past the array. */ - { "rd_u64_param_const", - "package main;\n" - "fn rd(p: *[4]u64) u64 = {\n" - " return p[1];\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]u64 = [100u64, 101u64, 102u64, 103u64];\n" - " return rd(&a): i32;\n" - "};\n", 101 }, - /* A: read, var idx, 1B elem. */ - { "rd_u8_param_var", - "package main;\n" - "fn rd(p: *[4]u8, i: i32) u8 = {\n" - " return p[i];\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]u8 = [10u8, 20u8, 30u8, 40u8];\n" - " return rd(&a, 2): i32;\n" - "};\n", 30 }, - /* A: read, 2B elem. */ - { "rd_u16_param", - "package main;\n" - "fn rd(p: *[4]u16) u16 = {\n" - " return p[3];\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]u16 = [11u16, 22u16, 33u16, 44u16];\n" - " return rd(&a): i32;\n" - "};\n", 44 }, - /* A: read, 4B elem. */ - { "rd_u32_param", - "package main;\n" - "fn rd(p: *[4]u32) u32 = {\n" - " return p[2];\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]u32 = [11u32, 22u32, 33u32, 44u32];\n" - " return rd(&a): i32;\n" - "};\n", 33 }, - /* A: signed-narrow elem behind the ptr — pins the MOVSXD - * sign-extend the idxeffti'd elemissignedc picks (an undrilled - * read of the ptr tinfo classified the ARRAY: unsigned). */ - { "rd_i32_signed", - "package main;\n" - "fn rd(p: *[4]i32) i32 = {\n" - " return p[1];\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]i32 = [7, -5, 9, 1];\n" - " return rd(&a) + 10;\n" - "};\n", 5 }, - /* A: write, const idx (ken p3) — scale-only half. */ - { "wr_u64_param_const", - "package main;\n" - "fn wr(p: *[4]u64) void = {\n" - " p[1] = 7u64;\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]u64 = [1u64, 2u64, 3u64, 4u64];\n" - " wr(&a);\n" - " return (a[0] + a[1] + a[2]): i32;\n" - "};\n", 11 }, - /* A: write, VAR idx + param rhs (ken p10) — the corruption - * proof: pre-fix wwstage emitted a 32B aggregate copy sourced - * at &x (reading i, p, saved BP) to base+32*i → frame smash / - * SIGSEGV. Neighbor guards assert no byte outside a[1] moved. */ - { "wr_u64_varidx_paramrhs", - "package main;\n" - "fn wr(p: *[4]u64, i: i32, x: u64) void = {\n" - " p[i] = x;\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]u64 = [10u64, 11u64, 12u64, 13u64];\n" - " let i: i32 = 1;\n" - " wr(&a, i, 77u64);\n" - " if (a[0] != 10u64) { return 1; };\n" - " if (a[1] != 77u64) { return 2; };\n" - " if (a[2] != 12u64) { return 3; };\n" - " if (a[3] != 13u64) { return 4; };\n" - " return 0;\n" - "};\n", 0 }, - /* A: write, 1B elem, neighbor guards at the tightest width. */ - { "wr_u8_neighbors", - "package main;\n" - "fn wr(p: *[4]u8) void = {\n" - " p[1] = 9u8;\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " wr(&a);\n" - " return (a[0] + a[1] + a[2]): i32;\n" - "};\n", 13 }, - /* A: compound, const idx, 8B elem (ken p9b). */ - { "compound_u64", - "package main;\n" - "fn add5(p: *[4]u64) void = {\n" - " p[1] += 5u64;\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]u64 = [100u64, 101u64, 102u64, 103u64];\n" - " add5(&a);\n" - " return a[1]: i32;\n" - "};\n", 106 }, - /* A: compound, var idx, 4B elem. */ - { "compound_u32_varidx", - "package main;\n" - "fn addat(p: *[4]u32, i: i32) void = {\n" - " p[i] += 7u32;\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]u32 = [10u32, 20u32, 30u32, 40u32];\n" - " addat(&a, 2);\n" - " return a[2]: i32;\n" - "};\n", 37 }, - /* A: local-ptr base, no call boundary (ken p4). */ - { "rd_localptr", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]u64 = [100u64, 101u64, 102u64, 103u64];\n" - " let p: *[4]u64 = &a;\n" - " return p[2]: i32;\n" - "};\n", 102 }, - /* A: cast base (ken p6). */ - { "rd_castbase", - "package main;\n" - "export fn main() i32 = {\n" - " let big: [4]u64 = [5u64, 6u64, 7u64, 8u64];\n" - " let p: *[4]u64 = (&big): *[4]u64;\n" - " return p[1]: i32;\n" - "};\n", 6 }, - /* A: nested *[2][3]u32 (ken p16) — the elemsizeofc N_TPTR - * carve-out must coexist with the #270-2 outer-stride rule for - * the pointee's OWN nesting: inner stride 4, outer 12. Read, - * write, neighbor guards; param + local ptr bases. */ - { "nested_2d", - "package main;\n" - "fn rd(p: *[2][3]u32, i: i32, j: i32) u32 = { return p[i][j]; };\n" - "export fn main() i32 = {\n" - " let a: [2][3]u32;\n" - " a[0][0] = 0: u32; a[0][1] = 1: u32; a[0][2] = 2: u32;\n" - " a[1][0] = 10: u32; a[1][1] = 11: u32; a[1][2] = 12: u32;\n" - " if (rd(&a, 1, 2) != 12: u32) { return 1; };\n" - " if (rd(&a, 0, 1) != 1: u32) { return 2; };\n" - " let p: *[2][3]u32 = &a;\n" - " p[1][0] = 99: u32;\n" - " if (a[1][0] != 99: u32) { return 3; };\n" - " if (a[1][1] != 11: u32) { return 4; };\n" - " if (a[0][2] != 2: u32) { return 5; };\n" - " return 0;\n" - "};\n", 0 }, - /* A: *[3]str — 3-word (ptr,len,cap) header elements; pins the - * read-side str-header gate on the idx_eff'd element. Pre-fix - * BOTH stages were runtime-wrong here, differently: cstage's - * u->sub gate missed the ptr base and dropped len/cap (ken - * p17). */ - { "rd_str_elem", - "package main;\n" - "fn lenof(p: *[3]str, i: i32) i32 = { return p[i].len; };\n" - "export fn main() i32 = {\n" - " let a: [3]str;\n" - " a[0] = \"x\"; a[1] = \"yy\"; a[2] = \"zzz\";\n" - " if (lenof(&a, 2) != 3) { return 1; };\n" - " if (lenof(&a, 0) != 1) { return 2; };\n" - " let p: *[3]str = &a;\n" - " if (p[1].len != 2) { return 3; };\n" - " return 0;\n" - "};\n", 0 }, - /* B: &p[i] pointer difference (ken p8b) — both stages emitted - * the whole-array stride (96) byte-IDENTICALLY pre-fix; only - * this runtime row can see the class. 3 * size(u64) = 24. */ - { "amp_diff_u64", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]u64 = [1u64, 2u64, 3u64, 4u64];\n" - " let p: *[4]u64 = &a;\n" - " let d: u64 = (&p[3]): u64 - (&a[0]): u64;\n" - " return d: i32;\n" - "};\n", 24 }, - /* B: &p[i] difference at a narrow width. 3 * size(u16) = 6. */ - { "amp_diff_u16", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]u16 = [1u16, 2u16, 3u16, 4u16];\n" - " let p: *[4]u16 = &a;\n" - " let d: u64 = (&p[3]): u64 - (&a[0]): u64;\n" - " return d: i32;\n" - "};\n", 6 }, - /* A: the live consumer's shape — siphash round() mutates all - * four lanes through the param ptr, each read feeding a later - * write. Pre-fix wwstage smashed the caller frame here. */ - { "mix_inplace_round", - "package main;\n" - "fn mix(v: *[4]u64) void = {\n" - " v[0] += v[1];\n" - " v[2] += v[3];\n" - " v[1] += v[0];\n" - " v[3] += v[2];\n" - "};\n" - "export fn main() i32 = {\n" - " let v: [4]u64 = [1u64, 2u64, 3u64, 4u64];\n" - " mix(&v);\n" - " if (v[0] != 3u64) { return 1; };\n" - " if (v[1] != 5u64) { return 2; };\n" - " if (v[2] != 7u64) { return 3; };\n" - " if (v[3] != 11u64) { return 4; };\n" - " return 0;\n" - "};\n", 0 }, - /* C: (*p)[i] read, 8B elem (ken p7 — SEGV'd both stages). */ - { "deref_rd_u64", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]u64 = [100u64, 101u64, 102u64, 103u64];\n" - " let p: *[4]u64 = &a;\n" - " return (*p)[1]: i32;\n" - "};\n", 101 }, - /* C: (*p)[i] read, 4B elem — pins the N_UN-base esz arm (the 8B - * default would mis-stride once the base materializes). */ - { "deref_rd_u32", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]u32 = [11u32, 22u32, 33u32, 44u32];\n" - " let p: *[4]u32 = &a;\n" - " return (*p)[2]: i32;\n" - "};\n", 33 }, - /* C: (*p)[i] read through a param base. */ - { "deref_rd_param", - "package main;\n" - "fn rd(p: *[4]u64) u64 = {\n" - " return (*p)[1];\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]u64 = [100u64, 101u64, 102u64, 103u64];\n" - " return rd(&a): i32;\n" - "};\n", 101 }, - /* C: (*p)[i] write, 8B elem. */ - { "deref_wr_u64", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]u64 = [1u64, 2u64, 3u64, 4u64];\n" - " let p: *[4]u64 = &a;\n" - " (*p)[1] = 7u64;\n" - " return (a[0] + a[1] + a[2]): i32;\n" - "};\n", 11 }, - /* C: (*p)[i] write, 1B elem, neighbor guards. */ - { "deref_wr_u8", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " let p: *[4]u8 = &a;\n" - " (*p)[1] = 9u8;\n" - " return (a[0] + a[1] + a[2]): i32;\n" - "};\n", 13 }, - /* C: (*p)[i] compound. */ - { "deref_compound_u64", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]u64 = [100u64, 101u64, 102u64, 103u64];\n" - " let p: *[4]u64 = &a;\n" - " (*p)[1] += 5u64;\n" - " return a[1]: i32;\n" - "};\n", 106 }, - { NULL, NULL, 0 } -}; - -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); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - 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 w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "ptrarr_index: w6c_ww missing — cannot run " - "the cs==ww byte-id gate\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char src[64]; - snprintf(src, sizeof src, "/tmp/wwpai_%d_%d.ww", getpid(), i); - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - /* (a) cstage build + run. */ - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwpai_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char cmd[2048]; - snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s", - tmpdir, bin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - unlink(src); rmdir(tmpdir); - continue; - } - - char outbin[128]; - const char *base = strrchr(src, '/'); - base = base ? base + 1 : src; - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - char *dot = strrchr(outbin, '.'); - if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - unlink(outbin); rmdir(tmpdir); - - /* (b) cs==ww byte-id gate. */ - char cs_s[64], ws_s[64]; - snprintf(cs_s, sizeof cs_s, "/tmp/wwpai_%d_%d_cs.s", - getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwpai_%d_%d_ww.s", - getpid(), i); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; unlink(src); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; unlink(src); unlink(cs_s); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - unlink(src); unlink(cs_s); unlink(ws_s); - } - - if (fail) { - fprintf(stderr, "%d/%d ptrarr-index tests failed\n", fail, n); - return 1; - } - printf("ptrarr_index: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -}