diff --git a/Makefile b/Makefile index af7cd856..d6169a40 100644 --- a/Makefile +++ b/Makefile @@ -357,7 +357,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_dotfield_compound_run \ $(BIN)/test_nested_union_widen_run \ $(BIN)/test_sret_struct_return \ - $(BIN)/test_sret_struct_return_run \ $(BIN)/test_tagged_sret_run \ $(BIN)/test_void_error_singleton_run \ $(BIN)/test_tagged_memarg_run \ @@ -366,7 +365,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_match_nonident_idx_run \ $(BIN)/test_global_sret_run \ $(BIN)/test_sret_narrow_field \ - $(BIN)/test_sret_narrow_field_run \ $(BIN)/test_match_slice_variant \ $(BIN)/test_match_slice_variant_run \ $(BIN)/test_alias_decl_order_size_run \ @@ -480,7 +478,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_lenidx_run \ $(BIN)/test_zeroinit_run \ $(BIN)/test_tuple_sret_callee \ - $(BIN)/test_tuple_sret_receive_run \ $(BIN)/test_append_wide_elem \ $(BIN)/test_delete_elem \ $(BIN)/test_delete_range \ @@ -559,9 +556,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_arraytoslice_run \ $(BIN)/test_arrlit_slice_run \ $(BIN)/test_valstruct_subsize_run \ - $(BIN)/test_aggret_source_run \ - $(BIN)/test_oddstruct_byval_ret_run \ - $(BIN)/test_chained_dot_struct_copy_run \ $(BIN)/test_continue_run \ $(BIN)/test_def_mangle_run \ $(BIN)/test_arr_u16_store_run \ @@ -1818,11 +1812,6 @@ $(BIN)/test_tuple_sret_callee: test/wcc/798_tuple_sret_callee.c \ # return-forward. Runtime round-trip (the net Fold A deferred — byte-id is # blind to a SEND/RECEIVE layout mismatch) + cs==ww byte-id. Self-contained # single-file probes (953 model), no selfhost traversal. -$(BIN)/test_tuple_sret_receive_run: test/wcc/799_tuple_sret_receive_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - # #34: append() stores the full element width per element kind — runtime # readback (tagged box / str+slice 3-word header / struct fill / spread # word-copy / 2-append realloc survival) + per-row cs==ww byte-id (which @@ -2174,12 +2163,6 @@ $(BIN)/test_sret_struct_return: test/wcc/721_sret_struct_return.c \ $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_sret_struct_return_run: test/wcc/925_sret_struct_return_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_tagged_sret_run: test/wcc/926_tagged_sret_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ @@ -2226,12 +2209,6 @@ $(BIN)/test_sret_narrow_field: test/wcc/730_sret_narrow_field.c \ $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_sret_narrow_field_run: test/wcc/930_sret_narrow_field_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_match_slice_variant: test/wcc/722_match_slice_variant.c \ $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) $(CC) $(CFLAGS) -o $@ $< @@ -3119,23 +3096,6 @@ $(BIN)/test_valstruct_subsize_run: test/wcc/949_valstruct_subsize_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_aggret_source_run: test/wcc/949_aggret_source_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_oddstruct_byval_ret_run: test/wcc/949_oddstruct_byval_ret_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_chained_dot_struct_copy_run: test/wcc/949_chained_dot_struct_copy_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_continue_run: test/wcc/911_continue_run.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) @@ -3346,7 +3306,7 @@ test-lang: all LANGBYTEID_DIR = $(OUT)/langbyteid LANGBYTEID_FILES = $(wildcard test/lang/*_test.ww) LANGBYTEID_VERB = test -c -LANGBYTEID_EXPECTED_MIN = 43 +LANGBYTEID_EXPECTED_MIN = 49 $(if $(LANGBYTEID_FILES),,$(error test-lang-byteid: empty corpus)) test-lang-byteid: all @set -e; \ diff --git a/test/lang/aggret_source_test.ww b/test/lang/aggret_source_test.ww new file mode 100644 index 00000000..5a9394cd --- /dev/null +++ b/test/lang/aggret_source_test.ww @@ -0,0 +1,105 @@ +// aggret_source_test — aggregate return-by-value from every ADDRESSABLE source +// shape (#272), migrated from test/wcc/949_aggret_source_run.c. The N_RETURN +// aggregate arms gated the source on N_IDENT || N_STRUCTLIT; every OTHER +// aggregate rvalue (array literal, struct/array field N_DOT, array element +// N_INDEX, deref *p) fell to the scalar-AX default = a silent truncation to the +// first 8 bytes, and both stages emitted byte-IDENTICAL wrong asm (#263 pure +// form — byte-id alone could NOT catch it). Each @test asserts every returned +// member as a primitive (a dropped word fails); T2 keeps the cs==ww net. Also +// covers the #272 commit-2 caller-half: receive into a GLOBAL ≤24B / >24B array. + +package aggret_source_test; + +type box_da = struct { a: [3]i64 }; +type pair_sd = struct { a: i64, b: i64 }; +type box_sd = struct { p: pair_sd }; +type pair_sr = struct { a: i64, b: i64 }; + +let g2_idx: [2][3]i64 = [[1i64,2i64,3i64],[4i64,5i64,6i64]]; +let g_recv: [3]i64 = [0i64, 0i64, 0i64]; +let g_recv4: [4]i64 = [0i64,0i64,0i64,0i64]; + +fn mk_arrlit() [3]i64 = { return [1i64, 2i64, 3i64]; }; +fn mk_da() [3]i64 = { let o: box_da = box_da { a = [10i64, 20i64, 30i64] }; return o.a; }; +fn mk_idx() [3]i64 = { return g2_idx[1]; }; +fn mk_deref(p: *[3]i64) [3]i64 = { return *p; }; +fn mk_ident() [3]i64 = { let a: [3]i64 = [2i64, 4i64, 6i64]; return a; }; +fn mk_deref4(p: *[4]i64) [4]i64 = { return *p; }; +fn mk_sd() pair_sd = { let bx: box_sd = box_sd { p = pair_sd { a = 3i64, b = 4i64 } }; return bx.p; }; +fn mk_sr(p: *pair_sr) pair_sr = { return *p; }; +fn mk_grecv() [3]i64 = { return [4i64, 5i64, 6i64]; }; +fn mk_grecv4(p: *[4]i64) [4]i64 = { return *p; }; + +@test fn arrlit() void = { + let r: [3]i64 = mk_arrlit(); + assert(r[0] == 1i64); + assert(r[1] == 2i64); + assert(r[2] == 3i64); +}; + +@test fn dot_arrfield() void = { + let r: [3]i64 = mk_da(); + assert(r[0] == 10i64); + assert(r[1] == 20i64); + assert(r[2] == 30i64); +}; + +@test fn index_elem() void = { + let r: [3]i64 = mk_idx(); + assert(r[0] == 4i64); + assert(r[1] == 5i64); + assert(r[2] == 6i64); +}; + +@test fn deref() void = { + let a: [3]i64 = [7i64, 8i64, 9i64]; + let r: [3]i64 = mk_deref(&a); + assert(r[0] == 7i64); + assert(r[1] == 8i64); + assert(r[2] == 9i64); +}; + +@test fn ident_ctl() void = { + let r: [3]i64 = mk_ident(); + assert(r[0] == 2i64); + assert(r[1] == 4i64); + assert(r[2] == 6i64); +}; + +@test fn deref_sret() void = { + let a: [4]i64 = [1i64, 2i64, 3i64, 4i64]; + let r: [4]i64 = mk_deref4(&a); + assert(r[0] == 1i64); + assert(r[1] == 2i64); + assert(r[2] == 3i64); + assert(r[3] == 4i64); +}; + +@test fn struct_dot() void = { + let r: pair_sd = mk_sd(); + assert(r.a == 3i64); + assert(r.b == 4i64); +}; + +@test fn struct_deref() void = { + let x: pair_sr = pair_sr { a = 5i64, b = 9i64 }; + let r: pair_sr = mk_sr(&x); + assert(r.a == 5i64); + assert(r.b == 9i64); +}; + +@test fn global_recv() void = { + g_recv = mk_grecv(); + assert(g_recv[0] == 4i64); + assert(g_recv[1] == 5i64); + assert(g_recv[2] == 6i64); +}; + +@test fn global_recv_sret() void = { + let a: [4]i64 = [4i64, 5i64, 6i64, 7i64]; + g_recv4 = mk_grecv4(&a); + assert(g_recv4[0] == 4i64); + assert(g_recv4[1] == 5i64); + assert(g_recv4[2] == 6i64); + assert(g_recv4[3] == 7i64); +}; diff --git a/test/lang/chained_dot_struct_copy_test.ww b/test/lang/chained_dot_struct_copy_test.ww new file mode 100644 index 00000000..e7f28d8d --- /dev/null +++ b/test/lang/chained_dot_struct_copy_test.ww @@ -0,0 +1,113 @@ +// chained_dot_struct_copy_test — depth-≥2 chained-DOT struct-field store from a +// struct IDENT source (`t.m.l = s;`, lhs.lhs is itself an N_DOT), migrated from +// test/wcc/949_chained_dot_struct_copy_run.c (#107-class sibling). The copy +// length is the field's NATURAL (non-slot-padded) struct size; cstage's tail +// selector handled only {4,1} and over-MOVQ'd every other tail {2,3,5,6,7}, +// clobbering the @packed neighbour `z` that follows the field within 8 bytes — +// silently, and divergent from wwstage's descending 8/4/2/1 ladder. The fix +// aligns cstage UP. The oracle is the neighbour `z` read back AFTER the copy +// (a wrong width over-writes it); T2 keeps the cs==ww net. Coverage = the tail +// widths the bug lived in {2,3,5,6,7} plus boundaries {0,1,4} + the via-CX +// (global dest) arm a BP-local `t` never reaches. + +package chained_dot_struct_copy_test; + +type L2 = struct { a: u16, b: [8]u8 }; +type M2 = struct @packed { l: L2, z: u8 }; +type T2 = struct { m: M2, tail: u64 }; + +type L3 = struct @packed { a: u8, b: u8, c: u8 }; +type M3 = struct @packed { l: L3, z: u8 }; +type T3 = struct { m: M3, tail: u64 }; + +type L4 = struct @packed { a: u32 }; +type M4 = struct @packed { l: L4, z: u8 }; +type T4 = struct { m: M4, tail: u64 }; + +type L5 = struct @packed { a: u8, b: u32 }; +type M5 = struct @packed { l: L5, z: u8 }; +type T5 = struct { m: M5, tail: u64 }; + +type L6 = struct @packed { a: u16, b: u32 }; +type M6 = struct @packed { l: L6, z: u8 }; +type T6 = struct { m: M6, tail: u64 }; + +type L7 = struct @packed { a: u8, b: u16, c: u32 }; +type M7 = struct @packed { l: L7, z: u8 }; +type T7 = struct { m: M7, tail: u64 }; + +type Lg = struct { a: u16, b: [8]u8 }; +type Mg = struct @packed { l: Lg, z: u8 }; +type Tg = struct { m: Mg, tail: u64 }; + +type L1 = struct @packed { a: u8 }; +type M1 = struct @packed { l: L1, z: u8 }; +type T1 = struct { m: M1, tail: u64 }; + +type L0 = struct { a: i64, b: i64 }; +type M0 = struct { l: L0, z: u8 }; +type T0 = struct { m: M0, tail: u64 }; + +let g_viacx: Tg = Tg { m = Mg { l = Lg { a = 0u16, b = [0u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] }, z = 222u8 }, tail = 0u64 }; + +@test fn tail2_leaf10() void = { + let s = L2 { a = 0u16, b = [9u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] }; + let t = T2 { m = M2 { l = L2 { a = 0u16, b = [0u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] }, z = 222u8 }, tail = 0u64 }; + t.m.l = s; + assert(t.m.z: i32 == 222); +}; + +@test fn tail3_leaf3() void = { + let s = L3 { a = 1u8, b = 2u8, c = 3u8 }; + let t = T3 { m = M3 { l = L3 { a = 0u8, b = 0u8, c = 0u8 }, z = 88u8 }, tail = 0u64 }; + t.m.l = s; + assert(t.m.z: i32 == 88); +}; + +@test fn tail4_leaf4() void = { + let s = L4 { a = 7u32 }; + let t = T4 { m = M4 { l = L4 { a = 0u32 }, z = 123u8 }, tail = 0u64 }; + t.m.l = s; + assert(t.m.z: i32 == 123); +}; + +@test fn tail5_leaf5() void = { + let s = L5 { a = 1u8, b = 0u32 }; + let t = T5 { m = M5 { l = L5 { a = 0u8, b = 0u32 }, z = 177u8 }, tail = 0u64 }; + t.m.l = s; + assert(t.m.z: i32 == 177); +}; + +@test fn tail6_leaf6() void = { + let s = L6 { a = 1u16, b = 0u32 }; + let t = T6 { m = M6 { l = L6 { a = 0u16, b = 0u32 }, z = 99u8 }, tail = 0u64 }; + t.m.l = s; + assert(t.m.z: i32 == 99); +}; + +@test fn tail7_leaf7() void = { + let s = L7 { a = 1u8, b = 2u16, c = 0u32 }; + let t = T7 { m = M7 { l = L7 { a = 0u8, b = 0u16, c = 0u32 }, z = 111u8 }, tail = 0u64 }; + t.m.l = s; + assert(t.m.z: i32 == 111); +}; + +@test fn viacx_global_tail2() void = { + let s = Lg { a = 0u16, b = [9u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] }; + g_viacx.m.l = s; + assert(g_viacx.m.z: i32 == 222); +}; + +@test fn tail1_leaf1() void = { + let s = L1 { a = 5u8 }; + let t = T1 { m = M1 { l = L1 { a = 0u8 }, z = 144u8 }, tail = 0u64 }; + t.m.l = s; + assert(t.m.z: i32 == 144); +}; + +@test fn tail0_leaf16() void = { + let s = L0 { a = 5i64, b = 6i64 }; + let t = T0 { m = M0 { l = L0 { a = 0i64, b = 0i64 }, z = 200u8 }, tail = 0u64 }; + t.m.l = s; + assert(t.m.z: i32 == 200); +}; diff --git a/test/lang/oddstruct_byval_ret_test.ww b/test/lang/oddstruct_byval_ret_test.ww new file mode 100644 index 00000000..e11dd5d1 --- /dev/null +++ b/test/lang/oddstruct_byval_ret_test.ww @@ -0,0 +1,75 @@ +// oddstruct_byval_ret_test — by-value RETURN of a struct whose total size is an +// "odd" sub-8 width {3,5,6,7} (a single INTEGER eightbyte in RAX), migrated from +// test/wcc/949_oddstruct_byval_ret_run.c (#107). Pre-#107 cstage's N_LET receive +// fell past every arm for these sizes and SILENTLY DROPPED the CALL (exit 0, +// reading an uninit slot); wwstage's generic fallback emitted a single MOVQ, so +// it was both a silent miscompile AND a gate-blind cs≠ww divergence. The #107 +// fix mirrors wwstage's single-MOVQ receive. Each @test reads back the planted +// fields as primitives (the whole eightbyte must reach the caller, not just AX's +// low byte); T2 keeps the cs==ww net. Sizes 8/12/24 lock the existing +// sized-tail / multi-word receive paths were not perturbed. + +package oddstruct_byval_ret_test; + +type T3 = struct { a: u8, b: u8, c: u8 }; +type T5 = struct @packed { a: u8, b: u32 }; +type T6 = struct { a: u16, b: u16, c: u16 }; +type T7 = struct @packed { a: u8, b: u16, c: u32 }; +type T8 = struct { a: i32, b: i32 }; +type T12 = struct { a: i32, b: i32, c: i32 }; +type T24 = struct { a: i64, b: i64, c: i64 }; + +fn mk3() T3 = { return T3 { a = 116u8, b = 22u8, c = 33u8 }; }; +fn mk5() T5 = { return T5 { a = 9u8, b = 116u32 }; }; +fn mk6() T6 = { return T6 { a = 9u16, b = 22u16, c = 116u16 }; }; +fn mk7() T7 = { return T7 { a = 9u8, b = 22u16, c = 116u32 }; }; +fn mk8() T8 = { return T8 { a = 116, b = 22 }; }; +fn mk12() T12 = { return T12 { a = 9, b = 22, c = 116 }; }; +fn mk24() T24 = { return T24 { a = 9i64, b = 22i64, c = 116i64 }; }; + +@test fn s3_u8x3_fieldA() void = { + let r: T3 = mk3(); + assert(r.a: i32 == 116); + assert(r.b: i32 == 22); + assert(r.c: i32 == 33); +}; + +@test fn s5_packed_fieldB() void = { + let r: T5 = mk5(); + assert(r.a: i32 == 9); + assert(r.b: i32 == 116); +}; + +@test fn s6_u16x3_fieldC() void = { + let r: T6 = mk6(); + assert(r.a: i32 == 9); + assert(r.b: i32 == 22); + assert(r.c: i32 == 116); +}; + +@test fn s7_packed_fieldC() void = { + let r: T7 = mk7(); + assert(r.a: i32 == 9); + assert(r.b: i32 == 22); + assert(r.c: i32 == 116); +}; + +@test fn s8_i32x2_fieldA() void = { + let r: T8 = mk8(); + assert(r.a == 116); + assert(r.b == 22); +}; + +@test fn s12_i32x3_fieldC() void = { + let r: T12 = mk12(); + assert(r.a == 9); + assert(r.b == 22); + assert(r.c == 116); +}; + +@test fn s24_i64x3_fieldC() void = { + let r: T24 = mk24(); + assert(r.a == 9i64); + assert(r.b == 22i64); + assert(r.c == 116i64); +}; diff --git a/test/lang/sret_narrow_field_test.ww b/test/lang/sret_narrow_field_test.ww new file mode 100644 index 00000000..e949f17d --- /dev/null +++ b/test/lang/sret_narrow_field_test.ww @@ -0,0 +1,81 @@ +// sret_narrow_field_test — sret callee's narrow trailing-field copy (#33), +// migrated from test/wcc/930_sret_narrow_field_run.c. A >24B struct that ends in +// a narrow primitive (bool / u8 / i16 / i32) must copy exactly that field's +// width; pre-fix cstage's N_IDENT word-copy tail used a slot-padded MOVQ that +// swept the bordering @sretarg save byte into the trailing field. Each @test +// asserts the trailing field round-trips as a primitive; T2 keeps the cs==ww net. +// The mixed row interleaves bool + i32 + i64 after the 24B slice payload so each +// trailing field must emit its declared width at its natural offset. + +package sret_narrow_field_test; + +type tbool = struct { a: i32, s: []u8, r: bool }; +type tu8 = struct { a: i32, s: []u8, r: u8 }; +type ti16 = struct { a: i32, s: []u8, r: i16 }; +type ti32 = struct { a: i32, s: []u8, r: i32 }; +type tmix = struct { s: []u8, r: bool, n: i32, k: i64 }; + +fn mk_bt() tbool = { + let v: tbool; let z: []u8; + v.s = z; v.a = 7; v.r = true; + return v; +}; +fn mk_bf() tbool = { + let v: tbool; let z: []u8; + v.s = z; v.a = 9; v.r = false; + return v; +}; +fn mk_u8() tu8 = { + let v: tu8; let z: []u8; + v.s = z; v.a = 0; v.r = 0xFFu8; + return v; +}; +fn mk_i16() ti16 = { + let v: ti16; let z: []u8; + v.s = z; v.a = 0; v.r = -123i16; + return v; +}; +fn mk_i32() ti32 = { + let v: ti32; let z: []u8; + v.s = z; v.a = 0; v.r = -424242; + return v; +}; +fn mk_mix() tmix = { + let v: tmix; let z: []u8; + v.s = z; v.r = true; v.n = 1234567; v.k = 9876543210i64; + return v; +}; + +@test fn bool_true() void = { + let x: tbool = mk_bt(); + assert(x.a == 7); + assert(x.r); +}; + +@test fn bool_false() void = { + let x: tbool = mk_bf(); + assert(x.a == 9); + assert(!x.r); +}; + +@test fn u8_high_bit() void = { + let x: tu8 = mk_u8(); + assert(x.r == 0xFFu8); +}; + +@test fn i16_negative() void = { + let x: ti16 = mk_i16(); + assert(x.r == -123i16); +}; + +@test fn i32_negative() void = { + let x: ti32 = mk_i32(); + assert(x.r == -424242); +}; + +@test fn mixed_narrow_after_slice() void = { + let x: tmix = mk_mix(); + assert(x.r); + assert(x.n == 1234567); + assert(x.k == 9876543210i64); +}; diff --git a/test/lang/sret_struct_return_test.ww b/test/lang/sret_struct_return_test.ww new file mode 100644 index 00000000..413e7689 --- /dev/null +++ b/test/lang/sret_struct_return_test.ww @@ -0,0 +1,176 @@ +// sret_struct_return_test — System V AMD64 sret (>24B struct return) end-to-end +// round-trip, migrated from test/wcc/925_sret_struct_return_run.c (#23). Values +// must cross the >24B struct-return boundary intact; pre-#23 both stages were +// broken differently (cstage skipped the CALL emit, wwstage truncated to AX), so +// asm byte-id was blind. Each @test asserts every returned field as a primitive, +// and T2 (test-lang-byteid) keeps the cs==ww net the .c twin's dual-driver gave. +// Coverage: 32B/40B/nested/slice-payload shapes, reassign-receive, the +// struct16-by-value-arg collision (#11 sister), N_IDENT return rhs, and the +// return-forwarding path (#9) for simple / multi-arg / slice-payload shapes. + +package sret_struct_return_test; + +type quad = struct { a: i64, b: i64, c: i64, d: i64 }; +type five = struct { a: i64, b: i64, c: i64, d: i64, e: i64 }; +type decoder = struct { offs: i64, src: []u8 }; +type pair = struct { x: i64, y: i64 }; +type inner = struct { p: i64, q: i64 }; +type outer = struct { i: inner, s: i64, t: i64 }; +type pos = struct { x: i64, y: i64, z: i64, w: i64 }; + +fn mk_quad(x: i64) quad = { + return quad { a = x, b = x + 1i64, c = x + 2i64, d = x + 3i64 }; +}; + +fn mk_dec(s: []u8) decoder = { + let r: decoder; + r.offs = 42i64; + r.src = s; + return r; +}; + +fn mk_five() five = { + return five { a = 1i64, b = 2i64, c = 3i64, d = 4i64, e = 5i64 }; +}; + +fn mk_outer() outer = { + return outer { + i = inner { p = 100i64, q = 200i64 }, + s = 300i64, t = 400i64 + }; +}; + +fn mk_quad_s(s: i64) quad = { + return quad { a = s, b = s, c = s, d = s }; +}; + +fn mk_quad_pair(p: pair, k: i64) quad = { + return quad { a = p.x, b = p.y, c = k, d = p.x + p.y + k }; +}; + +fn mk_pos() pos = { + let r: pos; + r.x = 11i64; + r.y = 22i64; + r.z = 33i64; + r.w = 44i64; + return r; +}; + +fn fwd_inner(x: i64) quad = { + return quad { a = x, b = x + 1i64, c = x + 2i64, d = x + 3i64 }; +}; +fn fwd_outer(x: i64) quad = { + return fwd_inner(x); +}; + +fn fwd2_inner(p: pair, k: i64) quad = { + return quad { a = p.x, b = p.y, c = k, d = p.x + p.y + k }; +}; +fn fwd2_outer(p: pair, k: i64) quad = { + return fwd2_inner(p, k); +}; + +fn fwddec_inner(s: []u8) decoder = { + let r: decoder; + r.offs = 99i64; + r.src = s; + return r; +}; +fn fwddec_outer(s: []u8) decoder = { + return fwddec_inner(s); +}; + +@test fn quad_i64_roundtrip() void = { + let q: quad = mk_quad(10i64); + assert(q.a == 10i64); + assert(q.b == 11i64); + assert(q.c == 12i64); + assert(q.d == 13i64); +}; + +@test fn decoder_slice_payload() void = { + let buf: [3]u8; + buf[0] = 0xa1u8; + buf[1] = 0xb2u8; + buf[2] = 0xc3u8; + let d: decoder = mk_dec(buf[0:3]); + assert(d.offs == 42i64); + assert(d.src.len == 3); + assert(d.src[0] == 0xa1u8); + assert(d.src[1] == 0xb2u8); + assert(d.src[2] == 0xc3u8); +}; + +@test fn five_i64_roundtrip() void = { + let f: five = mk_five(); + assert(f.a == 1i64); + assert(f.b == 2i64); + assert(f.c == 3i64); + assert(f.d == 4i64); + assert(f.e == 5i64); +}; + +@test fn nested_struct_payload() void = { + let o: outer = mk_outer(); + assert(o.i.p == 100i64); + assert(o.i.q == 200i64); + assert(o.s == 300i64); + assert(o.t == 400i64); +}; + +@test fn reassign_receive() void = { + let q: quad; + q = mk_quad_s(7i64); + assert(q.a == 7i64); + assert(q.b == 7i64); + assert(q.c == 7i64); + assert(q.d == 7i64); +}; + +@test fn sret_with_struct16_arg() void = { + let p: pair = pair { x = 3i64, y = 5i64 }; + let q: quad = mk_quad_pair(p, 11i64); + assert(q.a == 3i64); + assert(q.b == 5i64); + assert(q.c == 11i64); + assert(q.d == 19i64); +}; + +@test fn ident_return_rhs() void = { + let p: pos = mk_pos(); + assert(p.x == 11i64); + assert(p.y == 22i64); + assert(p.z == 33i64); + assert(p.w == 44i64); +}; + +@test fn forward_simple() void = { + let q: quad = fwd_outer(10i64); + assert(q.a == 10i64); + assert(q.b == 11i64); + assert(q.c == 12i64); + assert(q.d == 13i64); +}; + +@test fn forward_multi_arg() void = { + let p: pair = pair { x = 4i64, y = 6i64 }; + let q: quad = fwd2_outer(p, 9i64); + assert(q.a == 4i64); + assert(q.b == 6i64); + assert(q.c == 9i64); + assert(q.d == 19i64); +}; + +@test fn forward_slice_payload() void = { + let buf: [3]u8; + buf[0] = 0x11u8; + buf[1] = 0x22u8; + buf[2] = 0x33u8; + let d: decoder = fwddec_outer(buf[0:3]); + assert(d.offs == 99i64); + assert(d.src.len == 3); + assert(d.src[0] == 0x11u8); + assert(d.src[1] == 0x22u8); + assert(d.src[2] == 0x33u8); +}; diff --git a/test/lang/tuple_sret_receive_test.ww b/test/lang/tuple_sret_receive_test.ww new file mode 100644 index 00000000..d623ef9c --- /dev/null +++ b/test/lang/tuple_sret_receive_test.ww @@ -0,0 +1,60 @@ +// tuple_sret_receive_test — wide tuple-return / sret RECEIVE side (#10 Fold B), +// migrated from test/wcc/799_tuple_sret_receive_run.c. A fn returning a tuple +// over the 4-GP cap (`([]u8, []u8)` = 6 GP eightbytes) sret's into the caller's +// dest; each receive form must lay the elements out at the SAME packed offset +// the SEND wrote. byte-id alone is blind to a receive that mis-offsets (both +// stages would be wrong the same way), so each @test RUNS the round-trip and +// asserts the bytes arrive intact; T2 (test-lang-byteid) keeps the cs==ww net. +// +// Data is read back by INDEXING the slice elements (t.0[i] / a[i]); len() is +// taken only on DESTRUCTURED bindings (plain slice locals), never `len(t.N)` — +// that is a SEPARATE N_DOT-tuple+len composition bug, deliberately not exercised. + +package tuple_sret_receive_test; + +fn mk(a: []u8, b: []u8) ([]u8, []u8) = { return (a, b); }; +fn fwd(a: []u8, b: []u8) ([]u8, []u8) = { return mk(a, b); }; + +@test fn destructure() void = { + let buf: [8]u8 = [10u8, 11u8, 12u8, 13u8, 20u8, 21u8, 22u8, 23u8]; + let x: []u8 = buf[0:4]; + let y: []u8 = buf[4:8]; + let (a, b) = mk(x, y); + assert(len(a) == 4); + assert(len(b) == 4); + assert(a[0] == 10u8); + assert(a[3] == 13u8); + assert(b[0] == 20u8); + assert(b[3] == 23u8); +}; + +@test fn single_var_let() void = { + let buf: [8]u8 = [10u8, 11u8, 12u8, 13u8, 20u8, 21u8, 22u8, 23u8]; + let x: []u8 = buf[0:4]; + let y: []u8 = buf[4:8]; + let t = mk(x, y); + assert(t.0[0] == 10u8); + assert(t.0[3] == 13u8); + assert(t.1[0] == 20u8); + assert(t.1[3] == 23u8); +}; + +@test fn return_forward() void = { + let buf: [8]u8 = [10u8, 11u8, 12u8, 13u8, 20u8, 21u8, 22u8, 23u8]; + let x: []u8 = buf[0:4]; + let y: []u8 = buf[4:8]; + let (a, b) = fwd(x, y); + assert(len(a) == 4); + assert(a[0] == 10u8); + assert(b[3] == 23u8); +}; + +@test fn reassign() void = { + let buf: [8]u8 = [10u8, 11u8, 12u8, 13u8, 20u8, 21u8, 22u8, 23u8]; + let x: []u8 = buf[0:4]; + let y: []u8 = buf[4:8]; + let t = mk(x, y); + t = mk(y, x); + assert(t.0[0] == 20u8); + assert(t.1[0] == 10u8); +}; diff --git a/test/wcc/799_tuple_sret_receive_run.c b/test/wcc/799_tuple_sret_receive_run.c deleted file mode 100644 index 37e438df..00000000 --- a/test/wcc/799_tuple_sret_receive_run.c +++ /dev/null @@ -1,231 +0,0 @@ -/* - * 799_tuple_sret_receive — project #10 Fold B (wide tuple-return / sret, - * RECEIVE side). Fold A (798) made the CALLEE emit an over-capacity tuple - * return (> 4 GP / > 2 SSE eightbytes) via sret, but every receive site - * stayed LOUD-STOPPED, so such a fn was not yet usefully callable. Fold B - * wires the call/receive end: - * - single-var-let `let t = f();` — t's slot IS the sret dest; the - * callee writes the whole tuple - * there, t.0/t.1 read by offset. - * - destructure `let (a, b) = f();` — the callee sret's into the - * @sretscr slot, then each element - * is copied out to its binding at - * the SAME packed offset the SEND - * wrote (foff += element size). - * - reassign `t = f();` — same as single-var, into t's slot. - * - return-forward `return f();` — outer @sretarg threads to inner. - * - * THIS IS THE RUNTIME NET Fold A deferred: byte-id alone is blind to a - * receive that lays the elements out at a different offset than the SEND - * (both stages would be wrong the same way). Each row both (a) RUNS the - * round-trip under cstage and asserts the data arrives intact, and (b) - * asserts w6c vs w6c_ww .s byte-identity (rule-10). The shape is the - * bytes.cut target — ([]u8, []u8) = 6 GP eightbytes, over the 4-GP cap. - * - * Data is read back by INDEXING the slice elements (t.0[i] / a[i]); the - * destructure row also asserts len() on its bindings. NOTE: len(t.N) on a - * tuple-element slice is a SEPARATE, pre-existing N_DOT-tuple+len - * composition bug (reads .ptr, not .len) orthogonal to the sret receive — - * it is deliberately NOT exercised here (filed for follow-up). - * - * GATE POLARITY: must stay GREEN. A wrong exit means the receive lays the - * tuple out inconsistently with the SEND (silent corruption); a byte-id - * FAIL means cstage and wwstage diverged on the receive (rule-10). - */ -#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) destructure round-trip: bindings carry both halves; len() on a - * destructured binding works, and indexing reads the right bytes. */ - { "destructure", - "package main;\n" - "fn mk(a: []u8, b: []u8) ([]u8, []u8) = { return (a, b); };\n" - "export fn main() i32 = {\n" - " let buf: [8]u8 = [10u8, 11u8, 12u8, 13u8, 20u8, 21u8, 22u8, 23u8];\n" - " let x: []u8 = buf[0:4];\n" - " let y: []u8 = buf[4:8];\n" - " let (a, b) = mk(x, y);\n" - " if (len(a) != 4) { return 1; };\n" - " if (len(b) != 4) { return 2; };\n" - " if (a[0] != 10u8) { return 3; };\n" - " if (a[3] != 13u8) { return 4; };\n" - " if (b[0] != 20u8) { return 5; };\n" - " if (b[3] != 23u8) { return 6; };\n" - " return 0;\n" - "};\n", 0 }, - /* (b) single-var-let + positional field read (t.0 / t.1 by offset, - * indexed). Locks that the whole tuple materialises in t's slot. */ - { "single_var_let", - "package main;\n" - "fn mk(a: []u8, b: []u8) ([]u8, []u8) = { return (a, b); };\n" - "export fn main() i32 = {\n" - " let buf: [8]u8 = [10u8, 11u8, 12u8, 13u8, 20u8, 21u8, 22u8, 23u8];\n" - " let x: []u8 = buf[0:4];\n" - " let y: []u8 = buf[4:8];\n" - " let t = mk(x, y);\n" - " if (t.0[0] != 10u8) { return 1; };\n" - " if (t.0[3] != 13u8) { return 2; };\n" - " if (t.1[0] != 20u8) { return 3; };\n" - " if (t.1[3] != 23u8) { return 4; };\n" - " return 0;\n" - "};\n", 0 }, - /* (c) return-forward: outer fn forwards inner's over-cap tuple via the - * shared @sretarg (cg_sret_forward), no intermediate materialise. */ - { "return_forward", - "package main;\n" - "fn mk(a: []u8, b: []u8) ([]u8, []u8) = { return (a, b); };\n" - "fn fwd(a: []u8, b: []u8) ([]u8, []u8) = { return mk(a, b); };\n" - "export fn main() i32 = {\n" - " let buf: [8]u8 = [10u8, 11u8, 12u8, 13u8, 20u8, 21u8, 22u8, 23u8];\n" - " let x: []u8 = buf[0:4];\n" - " let y: []u8 = buf[4:8];\n" - " let (a, b) = fwd(x, y);\n" - " if (len(a) != 4) { return 1; };\n" - " if (a[0] != 10u8) { return 2; };\n" - " if (b[3] != 23u8) { return 3; };\n" - " return 0;\n" - "};\n", 0 }, - /* (d) whole-tuple reassign `t = f();` into an existing slot. */ - { "reassign", - "package main;\n" - "fn mk(a: []u8, b: []u8) ([]u8, []u8) = { return (a, b); };\n" - "export fn main() i32 = {\n" - " let buf: [8]u8 = [10u8, 11u8, 12u8, 13u8, 20u8, 21u8, 22u8, 23u8];\n" - " let x: []u8 = buf[0:4];\n" - " let y: []u8 = buf[4:8];\n" - " let t = mk(x, y);\n" - " t = mk(y, x);\n" - " if (t.0[0] != 20u8) { return 1; };\n" - " if (t.1[0] != 10u8) { 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, "tuple_sret_receive: 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/wwtsr_%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 in a scratch dir. */ - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwtsr_%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/wwtsr_%d_%d_cs.s", getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwtsr_%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 tuple-sret-receive tests failed\n", fail, n); - return 1; - } - printf("tuple_sret_receive: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -} diff --git a/test/wcc/925_sret_struct_return_run.c b/test/wcc/925_sret_struct_return_run.c deleted file mode 100644 index eaaf6dba..00000000 --- a/test/wcc/925_sret_struct_return_run.c +++ /dev/null @@ -1,348 +0,0 @@ -/* - * 925_sret_struct_return_run — Class B semantic test for the System V - * AMD64 sret ABI lowering (task #23). End-to-end round-trip pinning - * that values cross the >24B struct-return boundary intact under - * both cstage and wwstage. - * - * Class A asm-presence is pinned at 721; Class B (this file) catches - * shared miscompiles that asm byte-id can't see. Pre-#23 both stages - * were broken in different ways — cstage skipped the CALL emit - * (exit 11), wwstage truncated to AX only (segfault). Post-#23 both - * round-trip cleanly across 32B / 40B / nested / slice-payload - * shapes. - * - * Critical row: a 25B+ struct that is BOTH returned AND passed by- - * value as an arg (collision with #11's struct-by-value param - * decompose). Catches arg-shift bugs where the hidden RDI displaces - * a declared struct-by-value arg into the wrong reg. - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want; }; - -static const struct row rows[] = { - /* 32B four-i64: smallest sret shape. All four words must - * round-trip; pre-#23 wwstage dropped DX/CX/R8 silently. */ - { "quad_i64_roundtrip", - "type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n" - "fn mk(x: i64) quad = {\n" - " return quad { a = x, b = x + 1i64, c = x + 2i64, d = x + 3i64 };\n" - "};\n" - "export fn main() i32 = {\n" - " let q: quad = mk(10i64);\n" - " if (q.a != 10i64) { return 1; };\n" - " if (q.b != 11i64) { return 2; };\n" - " if (q.c != 12i64) { return 3; };\n" - " if (q.d != 13i64) { return 4; };\n" - " return 0;\n" - "};\n", - 0 }, - /* utf8 decoder shape — the surfacing case for #23. i64 + []u8 - * (slice ptr/len/cap). Slice-tail words pre-#23 leaked into - * the wrong slot or stayed garbage. */ - { "decoder_slice_payload", - "type decoder = struct { offs: i64, src: []u8 };\n" - "fn mk(s: []u8) decoder = {\n" - " let r: decoder;\n" - " r.offs = 42i64;\n" - " r.src = s;\n" - " return r;\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [3]u8;\n" - " buf[0] = 0xa1u8;\n" - " buf[1] = 0xb2u8;\n" - " buf[2] = 0xc3u8;\n" - " let d: decoder = mk(buf[0:3]);\n" - " if (d.offs != 42i64) { return 1; };\n" - " if (d.src.len != 3) { return 2; };\n" - " if (d.src[0] != 0xa1u8) { return 3; };\n" - " if (d.src[1] != 0xb2u8) { return 4; };\n" - " if (d.src[2] != 0xc3u8) { return 5; };\n" - " return 0;\n" - "};\n", - 0 }, - /* 40B five-i64: second past-24B size. Exercises @sretscr / - * @sretarg sizing for a larger struct in the same fn family. */ - { "five_i64_roundtrip", - "type five = struct { a: i64, b: i64, c: i64, d: i64, e: i64 };\n" - "fn mk() five = {\n" - " return five { a = 1i64, b = 2i64, c = 3i64, d = 4i64, e = 5i64 };\n" - "};\n" - "export fn main() i32 = {\n" - " let f: five = mk();\n" - " if (f.a != 1i64) { return 1; };\n" - " if (f.b != 2i64) { return 2; };\n" - " if (f.c != 3i64) { return 3; };\n" - " if (f.d != 4i64) { return 4; };\n" - " if (f.e != 5i64) { return 5; };\n" - " return 0;\n" - "};\n", - 0 }, - /* Nested struct payload: outer 32B struct contains a 16B inner. - * Pin that the inner's field offsets land correctly via the - * `cg_structlit_fill` recursion through *(@sretarg). */ - { "nested_struct_payload", - "type inner = struct { p: i64, q: i64 };\n" - "type outer = struct { i: inner, s: i64, t: i64 };\n" - "fn mk() outer = {\n" - " return outer {\n" - " i = inner { p = 100i64, q = 200i64 },\n" - " s = 300i64, t = 400i64\n" - " };\n" - "};\n" - "export fn main() i32 = {\n" - " let o: outer = mk();\n" - " if (o.i.p != 100i64) { return 1; };\n" - " if (o.i.q != 200i64) { return 2; };\n" - " if (o.s != 300i64) { return 3; };\n" - " if (o.t != 400i64) { return 4; };\n" - " return 0;\n" - "};\n", - 0 }, - /* Reassignment receive: `let x: T;` followed by `x = mk();` - * routes through cgassign's sret branch (vs cglet's). */ - { "reassign_receive", - "type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n" - "fn mk(s: i64) quad = {\n" - " return quad { a = s, b = s, c = s, d = s };\n" - "};\n" - "export fn main() i32 = {\n" - " let q: quad;\n" - " q = mk(7i64);\n" - " if (q.a != 7i64) { return 1; };\n" - " if (q.b != 7i64) { return 2; };\n" - " if (q.c != 7i64) { return 3; };\n" - " if (q.d != 7i64) { return 4; };\n" - " return 0;\n" - "};\n", - 0 }, - /* Collision row: a 16B struct passed by-value as an arg into a - * fn that ALSO returns a >24B struct. Pre-fix the hidden RDI - * would displace SI/DX (the struct-by-value pair) — caller would - * pop the i64 struct-words into DX/CX and emit LEAQ into DI, - * leaving SI uninitialised and the callee reading garbage. The - * arg-shift fix routes the int-arg cursor starting at 1, so the - * 16B struct lands cleanly in SI/DX. (Sister site of #11.) */ - { "sret_with_struct16_arg", - "type pair = struct { x: i64, y: i64 };\n" - "type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n" - "fn mk(p: pair, k: i64) quad = {\n" - " return quad { a = p.x, b = p.y, c = k, d = p.x + p.y + k };\n" - "};\n" - "export fn main() i32 = {\n" - " let p: pair = pair { x = 3i64, y = 5i64 };\n" - " let q: quad = mk(p, 11i64);\n" - " if (q.a != 3i64) { return 1; };\n" - " if (q.b != 5i64) { return 2; };\n" - " if (q.c != 11i64) { return 3; };\n" - " if (q.d != 19i64) { return 4; };\n" - " return 0;\n" - "};\n", - 0 }, - /* N_IDENT return rhs: callee builds the struct in a local - * `let r: T;` then `return r;` — exercises the word-copy-from- - * rhs-slot arm of cgreturn (vs the structlit fill arm). */ - { "ident_return_rhs", - "type pos = struct { x: i64, y: i64, z: i64, w: i64 };\n" - "fn mk() pos = {\n" - " let r: pos;\n" - " r.x = 11i64;\n" - " r.y = 22i64;\n" - " r.z = 33i64;\n" - " r.w = 44i64;\n" - " return r;\n" - "};\n" - "export fn main() i32 = {\n" - " let p: pos = mk();\n" - " if (p.x != 11i64) { return 1; };\n" - " if (p.y != 22i64) { return 2; };\n" - " if (p.z != 33i64) { return 3; };\n" - " if (p.w != 44i64) { return 4; };\n" - " return 0;\n" - "};\n", - 0 }, - /* sret return-forwarding (task #9 follow-up to #23): outer fn's - * body is `return inner(args...)` where outer and inner both - * return the same >24B struct shape. Outer reloads its own - * @sretarg into RDI and forwards directly into outer's caller- - * prealloc dest — no temporary in outer's frame, no struct copy. - * Pre-fix both stages emitted a compile-time fatal at this - * shape; the user's only workaround was `let r = inner(...); - * return r;` (which materialised an intermediate copy). */ - { "forward_simple", - "type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n" - "fn inner(x: i64) quad = {\n" - " return quad { a = x, b = x + 1i64, c = x + 2i64, d = x + 3i64 };\n" - "};\n" - "fn outer(x: i64) quad = {\n" - " return inner(x);\n" - "};\n" - "export fn main() i32 = {\n" - " let q: quad = outer(10i64);\n" - " if (q.a != 10i64) { return 1; };\n" - " if (q.b != 11i64) { return 2; };\n" - " if (q.c != 12i64) { return 3; };\n" - " if (q.d != 13i64) { return 4; };\n" - " return 0;\n" - "};\n", - 0 }, - /* Non-trivial inner args: multi-field struct + scalar, exercising - * arg-marshalling didn't regress under the forwarding path (sister - * concern to the 'sret_with_struct16_arg' row above). Outer - * forwards a pair-by-value plus a scalar; inner places fields into - * the >24B return shape. */ - { "forward_multi_arg", - "type pair = struct { x: i64, y: i64 };\n" - "type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n" - "fn inner(p: pair, k: i64) quad = {\n" - " return quad { a = p.x, b = p.y, c = k, d = p.x + p.y + k };\n" - "};\n" - "fn outer(p: pair, k: i64) quad = {\n" - " return inner(p, k);\n" - "};\n" - "export fn main() i32 = {\n" - " let p: pair = pair { x = 4i64, y = 6i64 };\n" - " let q: quad = outer(p, 9i64);\n" - " if (q.a != 4i64) { return 1; };\n" - " if (q.b != 6i64) { return 2; };\n" - " if (q.c != 9i64) { return 3; };\n" - " if (q.d != 19i64) { return 4; };\n" - " return 0;\n" - "};\n", - 0 }, - /* Slice-payload forwarding (utf8 iterator shape): outer forwards a - * decoder { i64, []u8 } through inner; the slice ptr/len/cap must - * survive the forward intact via the @sretarg pointer chain. */ - { "forward_slice_payload", - "type decoder = struct { offs: i64, src: []u8 };\n" - "fn inner(s: []u8) decoder = {\n" - " let r: decoder;\n" - " r.offs = 99i64;\n" - " r.src = s;\n" - " return r;\n" - "};\n" - "fn outer(s: []u8) decoder = {\n" - " return inner(s);\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [3]u8;\n" - " buf[0] = 0x11u8;\n" - " buf[1] = 0x22u8;\n" - " buf[2] = 0x33u8;\n" - " let d: decoder = outer(buf[0:3]);\n" - " if (d.offs != 99i64) { return 1; };\n" - " if (d.src.len != 3) { return 2; };\n" - " if (d.src[0] != 0x11u8) { return 3; };\n" - " if (d.src[1] != 0x22u8) { return 4; };\n" - " if (d.src[2] != 0x33u8) { return 5; };\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/sret_run_%d_%d.ww", getpid(), i); - snprintf(tmpdir, sizeof tmpdir, "/tmp/sret_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, - "sret_struct_return_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, - "sret_struct_return_run[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (fail) { - fprintf(stderr, - "sret_struct_return_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("sret_struct_return_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/930_sret_narrow_field_run.c b/test/wcc/930_sret_narrow_field_run.c deleted file mode 100644 index 387b7d39..00000000 --- a/test/wcc/930_sret_narrow_field_run.c +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 930_sret_narrow_field_run — Class B semantic round-trip for the - * sret callee's narrow trailing-field copy (task #33). End-to-end - * pins that values cross the >24B struct-return boundary intact - * when the struct ends in a narrow primitive (bool / u8 / i16 / - * i32), including a mixed-width struct that interleaves several - * narrow fields after the 24B slice payload. - * - * Pre-fix cstage's N_IDENT word-copy tail used slot-padded - * `rt->size` and emitted MOVQ for the narrow trailing field — - * the narrow source slot was BP-relative low and bordered the - * @sretarg save (8B), so the 8-byte read at offset 32 swept in - * the saved RDI byte and either wrote garbage past the bool / - * misaligned the next stack slot at the callee's frame edge. - * - * Class A asm-presence is pinned at 730; this file catches - * shared miscompiles that asm byte-id can't see. - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want; }; - -static const struct row rows[] = { - /* Trailing bool — the original surfacing case (lib/strings - * iterator shape). Pre-fix the bool round-tripped to a - * neighbour byte (the @sretarg low byte at -8(BP)), which on - * the local stack frame was 0x01 (the saved RDI pointer low - * byte). Test sets r=true and confirms the value survives. */ - { "bool_true", - "type t = struct { a: i32, s: []u8, r: bool };\n" - "fn mk() t = {\n" - " let v: t; let z: []u8;\n" - " v.s = z; v.a = 7; v.r = true;\n" - " return v;\n" - "};\n" - "export fn main() i32 = {\n" - " let x: t = mk();\n" - " if (x.a != 7) { return 1; };\n" - " if (!x.r) { return 2; };\n" - " return 0;\n" - "};\n", - 0 }, - { "bool_false", - "type t = struct { a: i32, s: []u8, r: bool };\n" - "fn mk() t = {\n" - " let v: t; let z: []u8;\n" - " v.s = z; v.a = 9; v.r = false;\n" - " return v;\n" - "};\n" - "export fn main() i32 = {\n" - " let x: t = mk();\n" - " if (x.a != 9) { return 1; };\n" - " if (x.r) { return 2; };\n" - " return 0;\n" - "};\n", - 0 }, - /* Trailing u8 with a high bit set. Pre-fix MOVQ-load at the - * source slot's offset would clobber the high 7 bytes of the - * read into AX with whatever lived above the byte slot. */ - { "u8_high_bit", - "type t = struct { a: i32, s: []u8, r: u8 };\n" - "fn mk() t = {\n" - " let v: t; let z: []u8;\n" - " v.s = z; v.a = 0; v.r = 0xFFu8;\n" - " return v;\n" - "};\n" - "export fn main() i32 = {\n" - " let x: t = mk();\n" - " if (x.r != 0xFFu8) { return 1; };\n" - " return 0;\n" - "};\n", - 0 }, - /* Trailing i16 with a negative bit pattern: the MOVW path - * is exercised, and the receive site's sign extension must - * read only 2 bytes. */ - { "i16_negative", - "type t = struct { a: i32, s: []u8, r: i16 };\n" - "fn mk() t = {\n" - " let v: t; let z: []u8;\n" - " v.s = z; v.a = 0; v.r = -123i16;\n" - " return v;\n" - "};\n" - "export fn main() i32 = {\n" - " let x: t = mk();\n" - " if (x.r != -123i16) { return 1; };\n" - " return 0;\n" - "};\n", - 0 }, - /* Trailing i32 negative: exercises the MOVL-tail path of the - * fixed loop (k+4<=sz arm; pre-fix the k+8<=40 arm absorbed - * this offset too). */ - { "i32_negative", - "type t = struct { a: i32, s: []u8, r: i32 };\n" - "fn mk() t = {\n" - " let v: t; let z: []u8;\n" - " v.s = z; v.a = 0; v.r = -424242;\n" - " return v;\n" - "};\n" - "export fn main() i32 = {\n" - " let x: t = mk();\n" - " if (x.r != -424242) { return 1; };\n" - " return 0;\n" - "};\n", - 0 }, - /* Mixed-width narrows interleaved after the 24B slice: bool + - * i32 + i64 mix into one struct. All three trailing fields - * survive intact only if each emits its declared width per - * field's natural offset/size. */ - { "mixed_narrow_after_slice", - "type t = struct { s: []u8, r: bool, n: i32, k: i64 };\n" - "fn mk() t = {\n" - " let v: t; let z: []u8;\n" - " v.s = z; v.r = true; v.n = 1234567; v.k = 9876543210i64;\n" - " return v;\n" - "};\n" - "export fn main() i32 = {\n" - " let x: t = mk();\n" - " if (!x.r) { return 1; };\n" - " if (x.n != 1234567) { return 2; };\n" - " if (x.k != 9876543210i64) { 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/sret_nr_%d_%d.ww", getpid(), i); - snprintf(tmpdir, sizeof tmpdir, "/tmp/sret_nr_%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, - "sret_narrow_field_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, - "sret_narrow_field_run[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (fail) { - fprintf(stderr, - "sret_narrow_field_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("sret_narrow_field_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/949_aggret_source_run.c b/test/wcc/949_aggret_source_run.c deleted file mode 100644 index d3fa413d..00000000 --- a/test/wcc/949_aggret_source_run.c +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 949_aggret_source_run — runtime + byte-id net for #272: aggregate - * return-by-value from every ADDRESSABLE source shape, not just the - * #267 N_IDENT / N_STRUCTLIT pair. - * - * Root: the N_RETURN aggregate arms gated the return source on - * N_IDENT || N_STRUCTLIT. Every OTHER aggregate rvalue — an array - * literal (`return [..]`), a struct/array field (`return o.f`, N_DOT), - * an array element (`return a[i]`, N_INDEX), a deref (`return *p`) — - * fell through to the scalar-AX default below = a silent truncation to - * the first 8 bytes. Both stages emitted byte-IDENTICAL wrong asm - * (the byte-id gate alone could NOT catch it — #263 in its purest - * form), so each row asserts the RUNTIME value (full readback: every - * member summed, so a dropped word fails) AND cs==ww byte-id. - * - * Fix (#272 commit-1): both N_RETURN arms (≤24B @retscr and >24B sret) - * funnel N_ARRLIT through the literal element fill and N_DOT/N_INDEX/ - * deref through aggarg_srcaddr + the #265/#268 whole-aggregate copy — - * the return mirror of the arg-side closure #271 landed. Closes the - * class for struct AND array returns; a close-by-construction loud-stop - * guards any future unhandled shape from reaching the scalar default. - * - * Rows: source-kind {array-literal, struct field (N_DOT), array element - * (N_INDEX), deref (*p), named-ident control, >24B deref (sret arm), - * struct field, struct deref} × return. Each callee returns an - * aggregate whose members the caller sums in full. - */ -#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[] = { - /* array literal — `return [1,2,3]` (N_ARRLIT, ≤24B @retscr). */ - { "arrlit", - "package main;\n" - "fn mk() [3]i64 = { return [1i64, 2i64, 3i64]; };\n" - "export fn main() i32 = { let r: [3]i64 = mk();\n" - " return (r[0]+r[1]+r[2]): i32; };\n", 6 }, - /* struct array-field — `return o.a` (N_DOT, ≤24B). */ - { "dot_arrfield", - "package main;\n" - "type box = struct { a: [3]i64 };\n" - "fn mk() [3]i64 = { let o: box = box { a = [10i64, 20i64, 30i64] };\n" - " return o.a; };\n" - "export fn main() i32 = { let r: [3]i64 = mk();\n" - " return (r[0]+r[1]+r[2]): i32; };\n", 60 }, - /* array element — `return g2[1]` (N_INDEX, ≤24B). Source is a - * fully-init global 2D array (the element-store path is a separate - * #270/#273 item; the global static-init is wired). */ - { "index_elem", - "package main;\n" - "let g2: [2][3]i64 = [[1i64,2i64,3i64],[4i64,5i64,6i64]];\n" - "fn mk() [3]i64 = { return g2[1]; };\n" - "export fn main() i32 = { let r: [3]i64 = mk();\n" - " return (r[0]+r[1]+r[2]): i32; };\n", 15 }, - /* deref — `return *p` (N_UN TK_STAR, ≤24B). */ - { "deref", - "package main;\n" - "fn mk(p: *[3]i64) [3]i64 = { return *p; };\n" - "export fn main() i32 = { let a: [3]i64 = [7i64,8i64,9i64];\n" - " let r: [3]i64 = mk(&a); return (r[0]+r[1]+r[2]): i32; };\n", 24 }, - /* named-ident CONTROL — `return a` (N_IDENT, pre-#272 path). */ - { "ident_ctl", - "package main;\n" - "fn mk() [3]i64 = { let a: [3]i64 = [2i64,4i64,6i64]; return a; };\n" - "export fn main() i32 = { let r: [3]i64 = mk();\n" - " return (r[0]+r[1]+r[2]): i32; };\n", 12 }, - /* >24B deref — [4]i64 (32B) rides the sret arm, not @retscr. */ - { "deref_sret", - "package main;\n" - "fn mk(p: *[4]i64) [4]i64 = { return *p; };\n" - "export fn main() i32 = { let a: [4]i64 = [1i64,2i64,3i64,4i64];\n" - " let r: [4]i64 = mk(&a);\n" - " return (r[0]+r[1]+r[2]+r[3]): i32; };\n", 10 }, - /* struct field (N_DOT) returning a STRUCT (not array) — the - * type-agnostic addressable path covers both. */ - { "struct_dot", - "package main;\n" - "type pair = struct { a: i64, b: i64 };\n" - "type box = struct { p: pair };\n" - "fn mk() pair = { let bx: box = box { p = pair { a = 3i64, b = 4i64 } };\n" - " return bx.p; };\n" - "export fn main() i32 = { let r: pair = mk();\n" - " return (r.a + r.b): i32; };\n", 7 }, - /* struct deref (*p) returning a STRUCT. */ - { "struct_deref", - "package main;\n" - "type pair = struct { a: i64, b: i64 };\n" - "fn mk(p: *pair) pair = { return *p; };\n" - "export fn main() i32 = { let x: pair = pair { a = 5i64, b = 9i64 };\n" - " let r: pair = mk(&x); return (r.a + r.b): i32; };\n", 14 }, - /* #272 commit-2 caller-half: `g = mk()` into a GLOBAL array ≤24B. - * The receive must store the full AX/DX/CX, not the 8-byte AX - * truncation (g[1]/g[2] would read 0). */ - { "global_recv", - "package main;\n" - "let g: [3]i64 = [0i64, 0i64, 0i64];\n" - "fn mk() [3]i64 = { return [4i64, 5i64, 6i64]; };\n" - "export fn main() i32 = { g = mk();\n" - " return (g[0]+g[1]+g[2]): i32; };\n", 15 }, - /* global receive into a >24B array — the sret-to-symbol path. */ - { "global_recv_sret", - "package main;\n" - "let g: [4]i64 = [0i64,0i64,0i64,0i64];\n" - "fn mk(p: *[4]i64) [4]i64 = { return *p; };\n" - "export fn main() i32 = { let a: [4]i64 = [4i64,5i64,6i64,7i64];\n" - " g = mk(&a); return (g[0]+g[1]+g[2]+g[3]): i32; };\n", 22 }, - { 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, "aggret_source: 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/wwags_%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/wwags_%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/wwags_%d_%d_cs.s", - getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwags_%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 aggret-source tests failed\n", - fail, n); - return 1; - } - printf("aggret_source: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -} diff --git a/test/wcc/949_chained_dot_struct_copy_run.c b/test/wcc/949_chained_dot_struct_copy_run.c deleted file mode 100644 index 0273072f..00000000 --- a/test/wcc/949_chained_dot_struct_copy_run.c +++ /dev/null @@ -1,334 +0,0 @@ -/* - * 949_chained_dot_struct_copy_run — #107-class sibling. A depth-≥2 - * chained-DOT struct-field store from a struct IDENT source - * (`t.m.l = s;`, lhs.lhs is itself an N_DOT) copies the source slot - * word-by-word into the destination field. The cstage walker - * (cgen.c, TY_STRUCT N_IDENT arm of the chained-DOT N_ASSIGN branch) - * ran a MOVQ run then a SINGLE sized-tail word, but its tail selector - * was `tail==4 ? MOVL : tail==1 ? MOVB : MOVQ` — every OTHER tail - * width {2,3,5,6,7} fell to an 8-byte MOVQ. - * - * The copy length is the field's NATURAL (non-slot-padded) struct - * size: a `@packed` or sub-8-aligned leaf packs at its own stride - * (struct @packed {u8,u8,u8}=3, {u16,u32}=6, …). When that stride is - * not a {1,4}+8k width, the over-MOVQ wrote 8 bytes into the - * destination FIELD and clobbered the @packed neighbour that follows - * it within 8 bytes — silently. Pre-fix `t.m.l = s` wrote s's slop - * over t.m.z (read-back 0, not the planted value), AND cstage diverged - * from wwstage, which already emitted a descending 8/4/2/1 sized tail - * (cgenexpr.ww:10083) — a gate-blind cs≠ww divergence. The fix aligns - * cstage UP to that ladder (mirrors cg_aggcopy and #107). - * - * GATE POLARITY: must stay GREEN. Two independent oracles per row: - * - the runtime exit code = the value of the @packed NEIGHBOUR field - * t.m.z, read back AFTER the `t.m.l = s` copy. A wrong copy width - * over-writes it; byte-id is BLIND to a both-wrong pair, and the - * pre-fix cstage drop was exit 0 (the planted value silently lost). - * - w6c vs w6c_ww .s byte-identity (rule 10). - * - * Leaf size coverage = the tail widths the bug lived in {2,3,5,6,7} - * plus boundaries {1,4,8} the old selector already handled (MOVB / MOVL - * / full MOVQ run, no tail) — those lock that the new descending ladder - * did NOT perturb the paths that were already correct, including the - * new ladder's own k+1<=ssz MOVB arm reached when tail is 1. The - * neighbour value is the oracle, computed in the row table (no derived - * size literal). - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want; }; - -static const struct row rows[] = { - /* tail 2: Leaf = struct{u16,[8]u8} = 10B, over-MOVQ at offset 8 - * (tail 2) clobbers the @packed neighbour z at offset 10. */ - { "tail2_leaf10", - "package main;\n" - "type Leaf = struct { a: u16, b: [8]u8 };\n" - "type Mid = struct @packed { l: Leaf, z: u8 };\n" - "type Top = struct { m: Mid, tail: u64 };\n" - "fn main() i32 = {\n" - " let s = Leaf { a = 0u16, b = [9u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] };\n" - " let t = Top { m = Mid { l = Leaf { a=0u16, b=[0u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] }, z = 222u8 }, tail = 0u64 };\n" - " t.m.l = s;\n" - " return t.m.z: i32;\n" - "};\n", - 222 }, - /* tail 3: Leaf = @packed{u8,u8,u8} = 3B, whole copy is a 3-byte - * stride; over-MOVQ at offset 0 wrote 8 bytes over z at offset 3. */ - { "tail3_leaf3", - "package main;\n" - "type Leaf = struct @packed { a: u8, b: u8, c: u8 };\n" - "type Mid = struct @packed { l: Leaf, z: u8 };\n" - "type Top = struct { m: Mid, tail: u64 };\n" - "fn main() i32 = {\n" - " let s = Leaf { a = 1u8, b = 2u8, c = 3u8 };\n" - " let t = Top { m = Mid { l = Leaf { a=0u8, b=0u8, c=0u8 }, z = 88u8 }, tail = 0u64 };\n" - " t.m.l = s;\n" - " return t.m.z: i32;\n" - "};\n", - 88 }, - /* tail 4: Leaf = @packed{u32} = 4B — MOVL (already correct - * pre-fix); locks the new ladder leaves this path identical. */ - { "tail4_leaf4", - "package main;\n" - "type Leaf = struct @packed { a: u32 };\n" - "type Mid = struct @packed { l: Leaf, z: u8 };\n" - "type Top = struct { m: Mid, tail: u64 };\n" - "fn main() i32 = {\n" - " let s = Leaf { a = 7u32 };\n" - " let t = Top { m = Mid { l = Leaf { a=0u32 }, z = 123u8 }, tail = 0u64 };\n" - " t.m.l = s;\n" - " return t.m.z: i32;\n" - "};\n", - 123 }, - /* tail 5: Leaf = @packed{u8,u32} = 5B, over-MOVQ at offset 0 wrote - * 8 bytes over z at offset 5. */ - { "tail5_leaf5", - "package main;\n" - "type Leaf = struct @packed { a: u8, b: u32 };\n" - "type Mid = struct @packed { l: Leaf, z: u8 };\n" - "type Top = struct { m: Mid, tail: u64 };\n" - "fn main() i32 = {\n" - " let s = Leaf { a = 1u8, b = 0u32 };\n" - " let t = Top { m = Mid { l = Leaf { a=0u8, b=0u32 }, z = 177u8 }, tail = 0u64 };\n" - " t.m.l = s;\n" - " return t.m.z: i32;\n" - "};\n", - 177 }, - /* tail 6: Leaf = @packed{u16,u32} = 6B. */ - { "tail6_leaf6", - "package main;\n" - "type Leaf = struct @packed { a: u16, b: u32 };\n" - "type Mid = struct @packed { l: Leaf, z: u8 };\n" - "type Top = struct { m: Mid, tail: u64 };\n" - "fn main() i32 = {\n" - " let s = Leaf { a = 1u16, b = 0u32 };\n" - " let t = Top { m = Mid { l = Leaf { a=0u16, b=0u32 }, z = 99u8 }, tail = 0u64 };\n" - " t.m.l = s;\n" - " return t.m.z: i32;\n" - "};\n", - 99 }, - /* tail 7: Leaf = @packed{u8,u16,u32} = 7B. */ - { "tail7_leaf7", - "package main;\n" - "type Leaf = struct @packed { a: u8, b: u16, c: u32 };\n" - "type Mid = struct @packed { l: Leaf, z: u8 };\n" - "type Top = struct { m: Mid, tail: u64 };\n" - "fn main() i32 = {\n" - " let s = Leaf { a = 1u8, b = 2u16, c = 0u32 };\n" - " let t = Top { m = Mid { l = Leaf { a=0u8, b=0u16, c=0u32 }, z = 111u8 }, tail = 0u64 };\n" - " t.m.l = s;\n" - " return t.m.z: i32;\n" - "};\n", - 111 }, - /* via_cx tail 2: GLOBAL destination Top. A global (or ptr-root) - * chain stores CX-relative ((CX)/8(CX)) rather than BP-relative; - * the same 4/2/1 ladder feeds the other dest mode, so this row - * locks the via_cx arm a BP-local `t` never reaches. */ - { "viacx_global_tail2", - "package main;\n" - "type Leaf = struct { a: u16, b: [8]u8 };\n" - "type Mid = struct @packed { l: Leaf, z: u8 };\n" - "type Top = struct { m: Mid, tail: u64 };\n" - "let g: Top = Top { m = Mid { l = Leaf { a=0u16, b=[0u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] }, z = 222u8 }, tail = 0u64 };\n" - "fn main() i32 = {\n" - " let s = Leaf { a = 0u16, b = [9u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] };\n" - " g.m.l = s;\n" - " return g.m.z: i32;\n" - "};\n", - 222 }, - /* tail 1: Leaf = @packed{u8} = 1B — single MOVB, no MOVQ run; - * always correct (old MOVB == new ladder's k+1<=ssz MOVB), so this - * row locks the new descending ladder's 1-byte arm against a future - * regression. */ - { "tail1_leaf1", - "package main;\n" - "type Leaf = struct @packed { a: u8 };\n" - "type Mid = struct @packed { l: Leaf, z: u8 };\n" - "type Top = struct { m: Mid, tail: u64 };\n" - "fn main() i32 = {\n" - " let s = Leaf { a = 5u8 };\n" - " let t = Top { m = Mid { l = Leaf { a=0u8 }, z = 144u8 }, tail = 0u64 };\n" - " t.m.l = s;\n" - " return t.m.z: i32;\n" - "};\n", - 144 }, - /* tail 0: Leaf = struct{i64,i64} = 16B — pure MOVQ run, no tail; - * locks the run path is undisturbed. Natural (non-packed) Mid so z - * is 8-aligned at offset 16 anyway. */ - { "tail0_leaf16", - "package main;\n" - "type Leaf = struct { a: i64, b: i64 };\n" - "type Mid = struct { l: Leaf, z: u8 };\n" - "type Top = struct { m: Mid, tail: u64 };\n" - "fn main() i32 = {\n" - " let s = Leaf { a = 5i64, b = 6i64 };\n" - " let t = Top { m = Mid { l = Leaf { a=0i64, b=0i64 }, z = 200u8 }, tail = 0u64 };\n" - " t.m.l = s;\n" - " return t.m.z: i32;\n" - "};\n", - 200 }, -}; - -static int -run_driver(const char *driver, const struct row *r, int i) -{ - char src[64], tmpdir[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/chdotcp_%d_%d.ww", getpid(), i); - snprintf(tmpdir, sizeof tmpdir, "/tmp/chdotcp_%d_d_%d", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -2; - fputs(r->src, f); - fclose(f); - - mkdir(tmpdir, 0755); - snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s build %s 2>/dev/null", - tmpdir, driver, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: build via %s failed\n", - r->label, driver); - unlink(src); rmdir(tmpdir); - return -2; - } - - const char *base = strrchr(src, '/'); - base = base ? base + 1 : src; - char outbin[128]; - 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; -} - -/* w6c (cstage) vs w6c_ww (wwstage) .s byte-id (rule 10). */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/chdotcp_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/chdotcp_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/chdotcp_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, "timeout 180 %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, "timeout 180 %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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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 cdrv[1024], wdrv[1024]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - - struct { const char *name; const char *path; int gated; } - 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 && access(drivers[d].path, X_OK) != 0) { - fprintf(stderr, "chained_dot_struct_copy: 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, - "chained_dot_struct_copy[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "chained_dot_struct_copy: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("chained_dot_struct_copy: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/949_oddstruct_byval_ret_run.c b/test/wcc/949_oddstruct_byval_ret_run.c deleted file mode 100644 index 2ab3d0cc..00000000 --- a/test/wcc/949_oddstruct_byval_ret_run.c +++ /dev/null @@ -1,261 +0,0 @@ -/* - * 949_oddstruct_byval_ret_run — task #107. A by-value RETURN of a - * struct whose total size is NOT a {1,2,4,8} sized-tail width — an - * "odd" sub-8 size 3/5/6/7 (reachable via @packed #51, and via - * natural sub-8 maxalign-1 shapes like struct{u8,u8,u8}=3) — is a - * single INTEGER eightbyte returned in RAX (ref/qbe/amd64/sysv.c - * retr: the `n*8 < size` loop runs exactly once for size ≤ 8). - * - * Pre-#107 the cstage N_LET receive (`let r: T = mk()`) fell past - * EVERY arm for these sizes: the whole-struct sized-tail arm excludes - * tails {3,5,6,7}, the memcpy arm gates on sz>8, and the no-rhs - * zero-fill requires !rhs — so cstage emitted NOTHING and SILENTLY - * DROPPED the CALL (program exit 0, reading an uninitialised slot). - * wwstage emitted `CALL; MOVQ AX, off(BP)` via its generic let - * fallback (cgenstmt.ww), so the bug was BOTH a silent miscompile AND - * a gate-blind cs≠ww divergence. The #107 fix adds a cstage arm that - * mirrors wwstage's single-MOVQ receive: the producer zero-pads AX to - * 24B and localslot rounds every slot up to 8B, so one full-word MOVQ - * captures the eightbyte cleanly. - * - * GATE POLARITY: must stay GREEN. Two independent oracles per row: - * - the runtime exit code (the load-bearing oracle: byte-id is BLIND - * to a both-wrong-identical pair, and pre-#107 cstage's drop was - * exit-0 — a green runtime here proves the CALL fires and the - * value reaches the caller, on BOTH stage drivers). - * - w6c vs w6c_ww .s byte-identity (rule 10). - * - * The expected exit is the value of the RETURNED field, COMPUTED in - * the row table (no derived size literals; the field value is the - * oracle, not a layout number) and pinned at 0..255. - * - * Size coverage targets the gap the bug lived in plus boundaries: - * 3 — struct{u8,u8,u8}, natural maxalign-1, sub-8 odd - * 5 — @packed{u8,u32}, sub-8 odd, field b crosses byte 1..4 - * 6 — struct{u16,u16,u16}, sub-8 odd, field c at byte 4 - * 7 — @packed{u8,u16,u32}, sub-8 odd, field c at byte 3..6 - * 8 — struct{i32,i32}, boundary: single full eightbyte - * 12 — struct{i32,i32,i32}, >8 two-eightbyte (sized MOVL tail) - * 24 — struct{i64,i64,i64}, three-eightbyte, top of register ABI - * The 8/12/24 rows lock that the #107 arm did NOT perturb the existing - * sized-tail / multi-word receive paths. - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want; }; - -static const struct row rows[] = { - /* size 3: natural maxalign-1, return first field (byte 0). */ - { "s3_u8x3_fieldA", - "package main;\n" - "type T = struct { a: u8, b: u8, c: u8 };\n" - "fn mk() T = { return T { a = 116u8, b = 22u8, c = 33u8 }; };\n" - "export fn main() i32 = { let r: T = mk(); return r.a: i32; };\n", - 116 }, - /* size 5 (@packed{u8,u32}): return field b (bytes 1..4) — proves the - * whole eightbyte, not just AX's low byte, reaches the caller. */ - { "s5_packed_fieldB", - "package main;\n" - "type T = struct @packed { a: u8, b: u32 };\n" - "fn mk() T = { return T { a = 9u8, b = 116u32 }; };\n" - "export fn main() i32 = { let r: T = mk(); return r.b: i32; };\n", - 116 }, - /* size 6 (3x u16): return field c (bytes 4..5). */ - { "s6_u16x3_fieldC", - "package main;\n" - "type T = struct { a: u16, b: u16, c: u16 };\n" - "fn mk() T = { return T { a = 9u16, b = 22u16, c = 116u16 }; };\n" - "export fn main() i32 = { let r: T = mk(); return r.c: i32; };\n", - 116 }, - /* size 7 (@packed{u8,u16,u32}): return field c (bytes 3..6). */ - { "s7_packed_fieldC", - "package main;\n" - "type T = struct @packed { a: u8, b: u16, c: u32 };\n" - "fn mk() T = { return T { a = 9u8, b = 22u16, c = 116u32 }; };\n" - "export fn main() i32 = { let r: T = mk(); return r.c: i32; };\n", - 116 }, - /* size 8 boundary: single full eightbyte, return first field. */ - { "s8_i32x2_fieldA", - "package main;\n" - "type T = struct { a: i32, b: i32 };\n" - "fn mk() T = { return T { a = 116, b = 22 }; };\n" - "export fn main() i32 = { let r: T = mk(); return r.a; };\n", - 116 }, - /* size 12: two eightbytes (MOVQ + MOVL tail) — unchanged by #107. */ - { "s12_i32x3_fieldC", - "package main;\n" - "type T = struct { a: i32, b: i32, c: i32 };\n" - "fn mk() T = { return T { a = 9, b = 22, c = 116 }; };\n" - "export fn main() i32 = { let r: T = mk(); return r.c; };\n", - 116 }, - /* size 24: three eightbytes, top of the register-return ABI. */ - { "s24_i64x3_fieldC", - "package main;\n" - "type T = struct { a: i64, b: i64, c: i64 };\n" - "fn mk() T = { return T { a = 9i64, b = 22i64, c = 116i64 }; };\n" - "export fn main() i32 = { let r: T = mk(); return r.c: i32; };\n", - 116 }, -}; - -static int -run_driver(const char *driver, const struct row *r, int i) -{ - char src[64], tmpdir[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/oddret_%d_%d.ww", getpid(), i); - snprintf(tmpdir, sizeof tmpdir, "/tmp/oddret_%d_d_%d", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -2; - fputs(r->src, f); - fclose(f); - - mkdir(tmpdir, 0755); - snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s build %s 2>/dev/null", - tmpdir, driver, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: build via %s failed\n", - r->label, driver); - unlink(src); rmdir(tmpdir); - return -2; - } - - const char *base = strrchr(src, '/'); - base = base ? base + 1 : src; - char outbin[128]; - 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; -} - -/* w6c (cstage) vs w6c_ww (wwstage) .s byte-id (rule 10). */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/oddret_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/oddret_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/oddret_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, "timeout 180 %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, "timeout 180 %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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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 cdrv[1024], wdrv[1024]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - - struct { const char *name; const char *path; int gated; } - 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 && access(drivers[d].path, X_OK) != 0) { - fprintf(stderr, "oddstruct_byval_ret: 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, - "oddstruct_byval_ret[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "oddstruct_byval_ret: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("oddstruct_byval_ret: %d/%d ok\n", total, total); - return 0; -}