diff --git a/Makefile b/Makefile index a6c6e936..05b9f956 100644 --- a/Makefile +++ b/Makefile @@ -276,7 +276,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_structcopytail_run \ $(BIN)/test_arrlit_tail_zero_run \ $(BIN)/test_defdim_slice_run \ - $(BIN)/test_trystr_run \ $(BIN)/test_allocalias_run \ $(BIN)/test_defdim_field_run \ $(BIN)/test_defdim_argslice_run \ @@ -293,8 +292,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_taggedidx_run \ $(BIN)/test_fnptrcollide_run \ $(BIN)/test_tagnorm_run \ - $(BIN)/test_globslicefield_run \ - $(BIN)/test_globstrslice_run \ $(BIN)/test_globtagisas_run \ $(BIN)/test_dotbasehijack_run \ $(BIN)/test_globtagreassign_run \ @@ -414,10 +411,8 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_forrange_fieldbase_run \ $(BIN)/test_errtype_compare \ $(BIN)/test_intbinop_mismatch \ - $(BIN)/test_tuple_elem_slice_len_run \ $(BIN)/test_str_forrange_loopvar_run \ $(BIN)/test_composite_call_arg \ - $(BIN)/test_composite_call_arg_run \ $(BIN)/test_letdecl_zeroinit \ $(BIN)/test_nested_if_labels \ $(BIN)/test_alias_leaf_collision \ @@ -491,10 +486,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_xmod_ident_prefer \ $(BIN)/test_xmod_valglobal_run \ $(BIN)/test_xmod_valglobal_dot_run \ - $(BIN)/test_len_strglobal_run \ - $(BIN)/test_litstr_pseudo_run \ $(BIN)/test_lenidx_run \ - $(BIN)/test_globalidx_run \ $(BIN)/test_zeroinit_run \ $(BIN)/test_tuple_sret_callee \ $(BIN)/test_tuple_sret_receive_run \ @@ -573,7 +565,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_ptrarr_index_run \ $(BIN)/test_dotbase_arr_run \ $(BIN)/test_dotbase_addr_slice_run \ - $(BIN)/test_slicecopy_assign_run \ $(BIN)/test_globalslice_arg_run \ $(BIN)/test_structlit_arrfield_run \ $(BIN)/test_defdim_struct_run \ @@ -980,17 +971,6 @@ $(BIN)/test_defdim_slice_run: test/wcc/989_defdim_slice_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -# 989_trystr_run (#16): `?`/`!` unwrap of a str-success tagged union shuffles -# the str header for ANY operand shape + variant order, keyed off the stamped -# operand type (not a name-keyed call-only first-variant lookup). Builds+runs -# on BOTH driver twins (rule-10). -$(BIN)/test_trystr_run: test/wcc/989_trystr_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - # 989_allocalias_run (#26): alloc(T{...}) for an alias T sizes + field-fills # from the alias-chased struct layout, not a name-keyed lookup of the literal # head. Builds+runs on BOTH driver twins (rule-10). @@ -1156,28 +1136,6 @@ $(BIN)/test_fnptrcollide_run: test/wcc/989_fnptrcollide_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -# 989_globslicefield_run (F8-c1, #35): a SLICE field of a module-global -# struct assigned via `g.f = ` must store the full {ptr,len,cap} -# header. Builds+runs on BOTH driver twins (rule-10); align-UP — see the -# test header. -$(BIN)/test_globslicefield_run: test/wcc/989_globslicefield_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_globstrslice_run (F8-c2, #47): a module-global str sliced with a -# default high bound and passed as a call arg (`take(g[1:])`) must load its -# length word for the hi. Builds+runs on BOTH driver twins (rule-10); -# align-UP — see the test header. -$(BIN)/test_globstrslice_run: test/wcc/989_globstrslice_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - # 989_globtagisas_run (F8-c3, #18): `is`/`as` on a module-global tagged # ident must read tag/payload from g(SB), not saved-BP. MIXED per-half: # `is` align-UP (cs==ww); `as` #263 ww-runtime-correct (cs!=ww residual, @@ -1919,23 +1877,6 @@ $(BIN)/test_xmod_valglobal_dot_run: test/wcc/796_xmod_valglobal_dot_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -# #231: len(str/slice global) must load the .len field via the global's -# address (LEAQ name(SB),CX; MOVQ 8(CX),AX), not a bogus BP-relative slot -# (cstage) or the whole header's .ptr (wwstage) — runtime + cs==ww byte-id. -$(BIN)/test_len_strglobal_run: test/wcc/797_len_strglobal_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #14: a string LITERAL `.len` is TY_UNTYPED_STR, so it misses the typed -# slice/str pseudo-field gate and fell to the N_DOT base-eval fallback — -# cstage returned AX=.ptr instead of shuffling BX=.len into AX (wwstage -# already did). Runtime + cs==ww byte-id; `.ptr` unchanged (control row). -$(BIN)/test_litstr_pseudo_run: test/wcc/801_litstr_pseudo_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - # #19: len() over an INDEXED str/slice element (`len(xs[i])`) returned the # element's .ptr, not its length (cstage == wwstage, shared gap). Runtime # (cstage build+run) + cs==ww byte-id, both dimensions per row. @@ -1944,17 +1885,6 @@ $(BIN)/test_lenidx_run: test/wcc/802_lenidx_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -# #10: indexing a GLOBAL str / GLOBAL slice (`s[i]` / `g[i]`) read a wide -# {ptr,len,cap} header with an 8-byte stride + full-word load instead of the -# element load — wwstage `cgindex` dispatched esz off the base tnode KIND -# (only N_TARRAY/N_TPTR), so a global str/slice matched neither and defaulted -# to esz=8. cstage is correct (type-driven dispatch). Runtime (cstage build+ -# run) + cs==ww byte-id, both dimensions per row. -$(BIN)/test_globalidx_run: test/wcc/803_globalidx_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - # #16: a bare `let x: T;` (no rhs) must zero-fill its slot. cgen emitted # the zero-fill only for 8B-primitive and >8B-composite slots — a SUB-8 # aggregate (`let c: [3]u8;`) fell through to NOTHING and read stack @@ -2542,13 +2472,6 @@ $(BIN)/test_overcap_tuple_field_store_run: test/wcc/940_overcap_tuple_field_stor $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -# #235: len() of a tuple-element slice/str reads .len (+8) not .ptr. -$(BIN)/test_tuple_elem_slice_len_run: test/wcc/903_tuple_elem_slice_len_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 $@ $< - # #240: mixed-scalar tuple (e.g. (int,[]u8,str)) sret layout agrees in both # stages — callee SEND foff via the return-type element size, caller RECEIVE # falls an over-cap tuple through to the sret receive (size-gated). @@ -2640,12 +2563,6 @@ $(BIN)/test_composite_call_arg: test/wcc/723_composite_call_arg.c \ $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_composite_call_arg_run: test/wcc/927_composite_call_arg_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_letdecl_zeroinit: test/wcc/724_letdecl_zeroinit.c \ $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) $(CC) $(CFLAGS) -o $@ $< @@ -3289,11 +3206,6 @@ $(BIN)/test_dotbase_addr_slice_run: test/wcc/949_dotbase_addr_slice_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_slicecopy_assign_run: test/wcc/952_slicecopy_assign_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_globalslice_arg_run: test/wcc/953_globalslice_arg_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) @@ -3552,7 +3464,7 @@ test-lang: all LANGBYTEID_DIR = $(OUT)/langbyteid LANGBYTEID_FILES = $(wildcard test/lang/*_test.ww) LANGBYTEID_VERB = test -c -LANGBYTEID_EXPECTED_MIN = 22 +LANGBYTEID_EXPECTED_MIN = 31 $(if $(LANGBYTEID_FILES),,$(error test-lang-byteid: empty corpus)) test-lang-byteid: all @set -e; \ diff --git a/test/lang/composite_call_arg_test.ww b/test/lang/composite_call_arg_test.ww new file mode 100644 index 00000000..8af23235 --- /dev/null +++ b/test/lang/composite_call_arg_test.ww @@ -0,0 +1,122 @@ +// composite_call_arg_test — a 3-reg composite (`[]u8` slice) CALL result passed +// inline as a composite arg to another call, migrated from +// test/wcc/927_composite_call_arg_run.c (#24, Class A wwstage cgen miscompile). +// Pre-fix wwstage emitted a single `PUSHQ AX` for a slice-returning CALL arg +// (losing .len/.cap) and under-popped the receiver's arg-regs by two words: the +// arg-shift collision corrupts every subsequent arg (the receiver reads the +// caller's spilled p.ptr as its own s.len). The fix gave `nodeisslice` an N_CALL +// arm (mirroring nodeisstr): both the push side (3-PUSH CX,BX,AX) and the pop +// side (extra=2) fire for slice-returning CALLs as args. Each @test KEEPS the +// `check(view(s))` call-arg-composite shape under test and asserts the receiver +// saw the right .len (a check fn returns 0 only when its internal .len checks +// pass, so the asserted 0 IS the value contract). + +package composite_call_arg_test; + +fn view(s: str) []u8 = { + let r: []u8; + r.ptr = s.ptr; + r.len = s.len; + r.cap = s.len; + return r; +}; + +fn passthrough(a: []u8) []u8 = { return a; }; + +fn check_a(a: []u8) i32 = { + if (a.len == 13) { return 0; }; + return 11; +}; + +fn check2_b(a: []u8, b: []u8) i32 = { + if (a.len != 13) { return 11; }; + if (b.len != 5) { return 12; }; + return 0; +}; + +fn check2_c(a: []u8, b: []u8) i32 = { + if (a.len != 3) { return 11; }; + if (b.len != 5) { return 12; }; + return 0; +}; + +fn check3_d(k0: i32, a: []u8, k1: i32) i32 = { + if (k0 != 7) { return 11; }; + if (a.len != 4) { return 12; }; + if (k1 != 9) { return 13; }; + return 0; +}; + +fn check_e(a: []u8) i32 = { + if (a.len == 6) { return 0; }; + return 11; +}; + +fn use2_f(a: []u8, k: i32) i32 = { + if (a.len != 13) { return 11; }; + if (k != 99) { return 12; }; + return 0; +}; + +type oserror = !i32; + +fn yield_ptr() (*u8 | oserror) = { + let p: *u8 = nil; + return p; +}; + +fn dispatch(v: (*u8 | oserror)) i32 = { + match (v) { + case let p: *u8 => return 7; + case let e: oserror => return e: i32; + }; + return -99; +}; + +@test fn canonical_slice_call() void = { + // (a) canonical f(g()) with g returning []u8. + let s: str = "hello, world!"; + assert(check_a(view(s)) == 0); +}; + +@test fn slice_call_then_letslice() void = { + // (b) f(g(), p) — strings.hasprefix shape: slice-CALL then a let-slice. + let s: str = "hello, world!"; + let p: []u8; + p.ptr = s.ptr; + p.len = 5; + p.cap = 5; + assert(check2_b(view(s), p) == 0); +}; + +@test fn two_composite_calls_args() void = { + // (c) f(g(in1), g(in2)) — two composite CALLs; the stack must hold both + // 3-word headers before the 6-POP drain. + let s1: str = "foo"; + let s2: str = "hello"; + assert(check2_c(view(s1), view(s2)) == 0); +}; + +@test fn slice_call_middle_arg() void = { + // (d) f(k0, g(), k1) — slice-CALL in the middle of three args. + let s: str = "abcd"; + assert(check3_d(7, view(s), 9) == 0); +}; + +@test fn nested_composite_calls() void = { + // (e) f(g(h(s))) — composite-in-composite nesting. + let s: str = "abcdef"; + assert(check_e(passthrough(view(s))) == 0); +}; + +@test fn slice_call_then_scalar() void = { + // (f) f(g(), 99) — slice-CALL then a scalar (pop-count mix). + let s: str = "hello, world!"; + assert(use2_f(view(s), 99) == 0); +}; + +@test fn tagged_call_regression() void = { + // (g) tagged-CALL alongside the slice path — confirms #21's natural-push + // arm composes with #24's. + assert(dispatch(yield_ptr()) == 7); +}; diff --git a/test/lang/glob_slice_field_test.ww b/test/lang/glob_slice_field_test.ww new file mode 100644 index 00000000..679a75c5 --- /dev/null +++ b/test/lang/glob_slice_field_test.ww @@ -0,0 +1,58 @@ +// glob_slice_field_test — a SLICE field of a module-GLOBAL struct, assigned +// with `g.field = `, must store the full {ptr,len,cap} 24B header (not +// just {ptr}), migrated from test/wcc/989_globslicefield_run.c (F8-c1, #35). +// The wwstage single-dot global-struct field-assign gated the 3-word DX store +// on isstrtype ONLY, so a non-str slice field fell to the 1-word scalar store +// and silently DROPPED .len+.cap; the fix widens to isstrtype||isslicetype +// (align UP to cstage's TY_STR||TY_SLICE). cs==ww held byte-id-blind, so a +// behavioral @test is the net. A dropped .len/.cap reads back 0 (a fresh global +// is zeroed), so len()/.cap directly catch it; glob_off_len pins the non-zero +// field offset; the str/scalar rows are regression controls for the pre-existing +// arm and the 1-word store the slice arm must not steal. + +package glob_slice_field_test; + +type box1 = struct { sl: []i64 }; +type box2 = struct { pad: i64, sl: []i64 }; +type box3 = struct { name: str }; +type box4 = struct { n: i64 }; + +let g1: box1; +let g2: box2; +let g3: box3; +let g4: box4; + +@test fn glob_slice_len() void = { + let b: [4]i64 = [10, 11, 12, 13]; + g1.sl = b[0:3]; + assert(len(g1.sl): i32 == 3); +}; + +@test fn glob_slice_cap() void = { + let b: [4]i64 = [10, 11, 12, 13]; + g1.sl = b[0:3]; + assert(g1.sl.cap: i32 == 4); +}; + +@test fn glob_off_len() void = { + // slice field at a NON-ZERO field offset (pad@0, sl@8) pins the + // DX+foff+8 header store. + let b: [4]i64 = [10, 11, 12, 13]; + g2.pad = 99; + g2.sl = b[0:2]; + assert(g2.pad == 99); + assert(len(g2.sl): i32 == 2); +}; + +@test fn glob_str_len() void = { + // str field on a global struct — the pre-existing isstrtype arm; a + // regression pin that the widened gate keeps correct. + g3.name = "hello"; + assert(len(g3.name): i32 == 5); +}; + +@test fn glob_scalar_n() void = { + // scalar field — the generic 1-word store the slice arm must not divert. + g4.n = 7; + assert(g4.n: i32 == 7); +}; diff --git a/test/lang/glob_str_slice_arg_test.ww b/test/lang/glob_str_slice_arg_test.ww new file mode 100644 index 00000000..fa56a381 --- /dev/null +++ b/test/lang/glob_str_slice_arg_test.ww @@ -0,0 +1,34 @@ +// glob_str_slice_arg_test — a module-GLOBAL str sliced with a DEFAULT high +// bound and passed as a call arg (`take(g[1:])`) must load its length word for +// the hi, migrated from test/wcc/989_globstrslice_run.c (F8-c2, #47). The +// wwstage N_SLICE call-arg pusharg had no global-str (N_TNAME "str") arm for a +// default hi, so AX kept the base pointer and got PUSHQ'd as the hi → len = +// ptr-lo, garbage; the fix emits the same `LEAQ g(SB),CX; MOVQ 8(CX),AX` len +// load as the global-slice arm (str IS []u8, len@+8), aligning UP to cstage. +// cs==ww was byte-id-blind; the .len asserts pin the runtime contract; the .ptr +// row proves the pointer word survives, the explicit-hi row is the control. + +package glob_str_slice_arg_test; + +let g: str = "hello"; + +fn slen(s: str) i32 = { return len(s): i32; }; +fn sfirst(s: str) i32 = { return s[0]: i32; }; + +@test fn gstr_defhi() void = { + assert(slen(g[1:]) == 4); +}; + +@test fn gstr_defhi_full() void = { + assert(slen(g[0:]) == 5); +}; + +@test fn gstr_defhi_ptr() void = { + // the ptr word is still correct: g[1:][0] == 'e' == 101. + assert(sfirst(g[1:]) == 101); +}; + +@test fn gstr_explicit() void = { + // explicit hi (hi != nil path) must not regress. + assert(slen(g[1:3]) == 2); +}; diff --git a/test/lang/global_index_test.ww b/test/lang/global_index_test.ww new file mode 100644 index 00000000..788d996a --- /dev/null +++ b/test/lang/global_index_test.ww @@ -0,0 +1,143 @@ +// global_index_test — the GLOBAL `str` / GLOBAL slice index family (s/g are +// module-level lets): the READ `s[i]`/`g[i]` (#10), the ADDR-OF `&s[i]`/`&g[i]` +// and the STORE `g[i] = v` (#11), migrated from test/wcc/803_globalidx_run.c. +// wwstage's cgindex dispatched the element size + base off the base's tnode KIND +// (only N_TARRAY / N_TPTR), so a global str (N_TNAME "str") and a global slice +// (N_TSLICE) matched neither arm: esz stayed 8 and the base fell to the +// wide-header fallback — an 8-byte stride + full-word MOVQ, reading 8 bytes at +// ptr+8 instead of the single byte at ptr+1. The store was an 8-byte +// OUT-OF-BOUNDS MOVQ instead of MOVB. cstage dispatched esz off the RESOLVED +// base TYPE (uniform), so it was correct. The fix aligns cgindex/cgun/cgassign +// UP to that type-driven dispatch. The slice rows reset the global each fn and +// sum ADJACENT elements so a mis-strided / over-wide write FAILS; local rows are +// regression pins for the already-clean local path. + +package global_index_test; + +let s: str = "ABC"; +let g: []u8; +let gi: []i32; + +@test fn gstr_first() void = { + assert(s[0]: i32 == 65); +}; + +@test fn gstr_mid() void = { + // the exact #10 repro: s[1] == 'B' == 66 (pre-fix an 8-byte word at ptr+8). + assert(s[1]: i32 == 66); +}; + +@test fn gstr_last() void = { + assert(s[2]: i32 == 67); +}; + +@test fn gstr_sum() void = { + // two byte indices summed (65+66) pins the load width — a full-word load + // would carry the high bytes. + assert(s[0]: i32 + s[1]: i32 == 131); +}; + +@test fn gslice_mid() void = { + let a: [3]u8 = [10u8, 20u8, 30u8]; + g = a; + assert(g[1]: i32 == 20); +}; + +@test fn gislice_mid() void = { + // WIDTH>1, SIGNED: g[1]-g[0] == 20-(-5) == 25 pins esz=4 + sign-extend. + let a: [3]i32 = [-5, 20, 30]; + gi = a; + assert(gi[1] - gi[0] == 25); +}; + +@test fn gstr_addr() void = { + // #11 ADDR-OF, global str: &s[1] read back == 'B' == 66. + let p: *u8 = &s[1]; + assert((*p): i32 == 66); +}; + +@test fn gslice_addr() void = { + // #11 ADDR-OF, global []u8: &g[1] read back == 20. + let a: [3]u8 = [10u8, 20u8, 30u8]; + g = a; + let p: *u8 = &g[1]; + assert((*p): i32 == 20); +}; + +@test fn gislice_addr() void = { + // #11 ADDR-OF, global []i32 WIDTH>1: &g[1] == 20 pins esz=4 stride. + let a: [3]i32 = [-5, 20, 30]; + gi = a; + let p: *i32 = &gi[1]; + assert(*p == 20); +}; + +@test fn gslice_store() void = { + // #11 STORE, global []u8: g[1]=99; g[1]+g[0]+g[2] == 99+10+30 == 139. The + // g[0]/g[2] addends are the OOB-WRITE GUARD (pre-fix a full-word MOVQ at an + // 8-byte stride wrote 99 at ptr+8 and left g[1]==20). + let a: [3]u8 = [10u8, 20u8, 30u8]; + g = a; + g[1] = 99u8; + assert(g[1]: i32 + g[0]: i32 + g[2]: i32 == 139); +}; + +@test fn gislice_store() void = { + // #11 STORE, global []i32 SIGNED: g[1]=42; g[1]+g[0]+g[2] == 42+(-5)+30 == 67. + let a: [3]i32 = [-5, 20, 30]; + gi = a; + gi[1] = 42; + assert(gi[1] + gi[0] + gi[2] == 67); +}; + +@test fn gslice_compound() void = { + // #11 COMPOUND STORE, global []u8: g[1] += 79 → 99; sum == 139. The third + // fixed arm (load-combine-store in place), pre-fix an 8-byte OOB RMW. + let a: [3]u8 = [10u8, 20u8, 30u8]; + g = a; + g[1] += 79u8; + assert(g[1]: i32 + g[0]: i32 + g[2]: i32 == 139); +}; + +@test fn gislice_compound() void = { + // #11 COMPOUND STORE, global []i32 SIGNED: g[1] += 47 → 42; g[1]+g[0]+g[2] + // == 42+(-25)+30 == 47. + let a: [3]i32 = [-25, -5, 30]; + gi = a; + gi[1] += 47; + assert(gi[1] + gi[0] + gi[2] == 47); +}; + +@test fn lstr_addr() void = { + // regression pin: local str ADDR-OF (already clean) — &s[1] == 66. + let s: str = "ABC"; + let p: *u8 = &s[1]; + assert((*p): i32 == 66); +}; + +@test fn lslice_store() void = { + // regression pin: local slice STORE (already clean) — g[1]=99; sum == 139. + let a: [3]u8 = [10u8, 20u8, 30u8]; + let g: []u8 = a; + g[1] = 99u8; + assert(g[1]: i32 + g[0]: i32 + g[2]: i32 == 139); +}; + +@test fn lstr_mid() void = { + // regression pin: local str index (already clean) — s[1] == 66. + let s: str = "ABC"; + assert(s[1]: i32 == 66); +}; + +@test fn lslice_last() void = { + // regression pin: local slice index, last elem (already clean) — g[2] == 30. + let a: [3]u8 = [10u8, 20u8, 30u8]; + let g: []u8 = a; + assert(g[2]: i32 == 30); +}; + +@test fn larr_mid() void = { + // regression pin: direct local array index (already clean) — a[1] == 20. + let a: [3]u8 = [10u8, 20u8, 30u8]; + assert(a[1]: i32 == 20); +}; diff --git a/test/lang/len_str_global_test.ww b/test/lang/len_str_global_test.ww new file mode 100644 index 00000000..c22244b6 --- /dev/null +++ b/test/lang/len_str_global_test.ww @@ -0,0 +1,22 @@ +// len_str_global_test — `len(str-global)` must load the .len word, migrated +// from test/wcc/797_len_strglobal_run.c (#231). BOTH stages were wrong, +// DIFFERENTLY (byte-id was blind because they also diverged, but no bootstrap +// source exercised the shape): cstage's len() arm took the BP-relative slot +// load for a top-level global (localfind returns 0 → bogus `MOVQ 8(BP),AX`); +// wwstage's arm only handled locals, so a global fell to cgexpr and left +// AX=.ptr. The fix emits the global .len load `LEAQ name(SB),CX; MOVQ 8(CX),AX` +// in both, routed through the post-#1 value mangle. The local-str case was +// correct in both (control). + +package len_str_global_test; + +let g: str = "hello"; + +@test fn str_global_len() void = { + assert(len(g): i32 == 5); +}; + +@test fn str_local_len() void = { + let g: str = "hello"; + assert(len(g): i32 == 5); +}; diff --git a/test/lang/lit_str_pseudo_test.ww b/test/lang/lit_str_pseudo_test.ww new file mode 100644 index 00000000..7e6c9052 --- /dev/null +++ b/test/lang/lit_str_pseudo_test.ww @@ -0,0 +1,39 @@ +// lit_str_pseudo_test — the string-LITERAL `.len` / `.ptr` pseudo-field, +// migrated from test/wcc/801_litstr_pseudo_run.c (#14). cstage was wrong, +// wwstage correct (a rule-10 divergence): `"hi".len` returned the POINTER — a +// string literal is TY_UNTYPED_STR (not TY_STR), so it missed the typed str +// pseudo-field gate in cgen.c's N_DOT and fell to `cgexpr(lhs)` (AX=.ptr) +// without the BX->AX len shuffle that wwstage's cgdot catch-all already did. +// byte-id was BLIND: no bootstrap source uses literal `.len` (devs hardcoded +// the lengths). The fix aligns cstage UP — the N_DOT fallback emits `MOVQ BX,AX` +// for `.len`; `.ptr` already returned AX. The str-VARIABLE `.len` was a control. + +package lit_str_pseudo_test; + +fn slen(s: str) i32 = { return s.len: i32; }; + +@test fn lit_len() void = { + assert("hello".len: i32 == 5); +}; + +@test fn lit_len_empty_multibyte() void = { + // "" is 0 bytes; "héllo\n" is 7 bytes (h, é=2 UTF-8 bytes, l, l, o, \n) — + // a ptr would not read 7, so this guards a .ptr/.len confusion. + assert("".len: i32 == 0); + assert("héllo\n".len: i32 == 7); +}; + +@test fn lit_ptr_deref() void = { + // .ptr still points at the bytes (first byte == 'h'), proving the fix left + // .ptr untouched; then .len == 5. + let p: *u8 = "hello".ptr; + assert(*p == 'h'); + assert("hello".len: i32 == 5); +}; + +@test fn lit_arg_passthrough() void = { + // callee-side .len on a literal passed as a str arg — the real-world shape + // (os.write(1, lit.ptr, lit.len)) that surfaced #14. + assert(slen("abcdef") == 6); + assert("abcdef".len: i32 == 6); +}; diff --git a/test/lang/slice_copy_assign_test.ww b/test/lang/slice_copy_assign_test.ww new file mode 100644 index 00000000..3d5be176 --- /dev/null +++ b/test/lang/slice_copy_assign_test.ww @@ -0,0 +1,98 @@ +// slice_copy_assign_test — bulk slice-copy-assign into a range place +// `s.arr[lo:hi] = bs` (the LHS is an N_SLICE), migrated from +// test/wcc/952_slicecopy_assign_run.c (#145). No legacy N_ASSIGN arm caught an +// N_SLICE LHS, so the statement fell through to the scalar tail and emitted +// NOTHING — a silent no-op in BOTH stages, byte-id-green (#263-class). The fix +// reuses the N_SLICE READ lowering (AX = base+lo*esz, BX = hi-lo, CX = cap), +// scales BX by the SAME read-path element width (esz; [N]u8→1, [N]i32→4), and +// emits a runtime-counted byte-granular copy loop. WRITE rows assert the copy +// lands the right bytes in [lo,hi) AND leaves bytes OUTSIDE the range untouched +// (each sums members both inside and outside, so a mis-strided / over-wide copy +// fails); esz coverage [N]u8 + [N]i32; base coverage struct-field array (N_DOT), +// via-*struct-param, and bare-local (N_IDENT). READ rows lock the reslice-consume +// companion (#5, already clean). + +package slice_copy_assign_test; + +type buffer = struct { buf: [8]u8, end: i32 }; +type box = struct { o: [8]i32 }; + +fn wr(s: *buffer, src: []u8) void = { s.buf[1:4] = src; }; + +@test fn w_u8_lo0() void = { + // struct-field [N]u8 base (the appendlit shape). + let s: buffer; + let bs: [3]u8; bs[0] = 10u8; bs[1] = 20u8; bs[2] = 30u8; + s.buf[0:3] = bs[0:3]; + assert((s.buf[0] + s.buf[1] + s.buf[2] + s.buf[3] + + s.buf[4] + s.buf[5]): i32 == 60); +}; + +@test fn w_u8_lo2() void = { + let s: buffer; + let bs: [3]u8; bs[0] = 10u8; bs[1] = 20u8; bs[2] = 30u8; + s.buf[2:5] = bs[0:3]; + assert((s.buf[0] + s.buf[1] + s.buf[2] + s.buf[3] + + s.buf[4] + s.buf[5]): i32 == 60); +}; + +@test fn w_u8_single() void = { + let s: buffer; + let bs: [1]u8; bs[0] = 42u8; + s.buf[3:4] = bs[0:1]; + assert((s.buf[2] + s.buf[3] + s.buf[4]): i32 == 42); +}; + +@test fn w_empty() void = { + // hi==lo — count=0; the loop's CMPQ $0 guard must JLE-exit before the first + // MOVB (an off-by-one underflow would clobber buf[lo] and run away). + let s: buffer; s.buf[2] = 5u8; s.buf[3] = 7u8; + let bs: [3]u8; bs[0] = 10u8; bs[1] = 20u8; bs[2] = 30u8; + s.buf[2:2] = bs[0:0]; + assert((s.buf[1] + s.buf[2] + s.buf[3] + s.buf[4]): i32 == 12); +}; + +@test fn w_i32() void = { + // esz=4 — [N]i32 field strides by the element width, not 1. + let s: box; + let bs: [2]i32; bs[0] = 40; bs[1] = 88; + s.o[1:3] = bs[0:2]; + assert((s.o[0] + s.o[1] + s.o[2] + s.o[3]): i32 == 128); +}; + +@test fn w_viaptr() void = { + // the field write through a *struct param (appendlit's `buf: *buffer`). + let s: buffer; + let bs: [3]u8; bs[0] = 11u8; bs[1] = 22u8; bs[2] = 33u8; + wr(&s, bs[0:3]); + assert((s.buf[0] + s.buf[1] + s.buf[2] + s.buf[3] + + s.buf[4]): i32 == 66); +}; + +@test fn w_local() void = { + // N_IDENT base — a bare-local array place. + let a: [8]u8; + let bs: [3]u8; bs[0] = 10u8; bs[1] = 20u8; bs[2] = 30u8; + a[2:5] = bs[0:3]; + assert((a[0] + a[1] + a[2] + a[3] + a[4] + a[5]): i32 == 60); +}; + +@test fn rd_reslice() void = { + // READ twin (#5, already clean) — reslice the field array with a runtime hi + // bound, consume as []u8. + let s: buffer; + let bs: [3]u8; bs[0] = 10u8; bs[1] = 20u8; bs[2] = 30u8; + s.buf[0:3] = bs[0:3]; + s.end = 3; + let v: []u8 = s.buf[0:s.end]; + // 10+20+30 + 3*100 == 360 (the .c twin's want=104 was 360 & 0xFF, an + // exit-code truncation; the @test asserts the true value). + assert(v[0]: i32 + v[1]: i32 + v[2]: i32 + (v.len: i32) * 100 == 360); +}; + +@test fn rd_len() void = { + let s: buffer; + s.end = 5; + let v: []u8 = s.buf[0:s.end]; + assert(v.len: i32 == 5); +}; diff --git a/test/lang/try_str_unwrap_test.ww b/test/lang/try_str_unwrap_test.ww new file mode 100644 index 00000000..39de9ede --- /dev/null +++ b/test/lang/try_str_unwrap_test.ww @@ -0,0 +1,41 @@ +// try_str_unwrap_test — the `!` unwrap of a str-success tagged union must +// shuffle the str header (DX,CX,R8) → (AX,BX,CX) for ANY operand shape and ANY +// variant order, keyed off the STAMPED operand type, migrated from +// test/wcc/989_trystr_run.c (#16). The wwstage cgtryprop/cgtryunw computed +// succisstr only for an N_CALL operand (name-keyed fnretlookupmod on the callee +// leaf) and tested the FIRST variant, so an ident-source unwrap and an +// error-first union both dropped the `MOVQ CX,BX / MOVQ R8,CX` shuffle and kept +// a stale len/cap; the fix reads the stamped operand's success-variant type via +// successvariant()+typeisstr(). A 40-byte `junk` str precedes each unwrap so a +// dropped shuffle leaks junk's len (40) into s.len: pre-fix ident_source ran +// ww=40 vs cs=2 (silent overrun). cs==ww was byte-id-blind on this shape. + +package try_str_unwrap_test; + +type e = !i32; + +fn mk_se() (str | e) = { return "hi"; }; +fn mk2_es() (e | str) = { return "abc"; }; +fn mk_se4() (str | e) = { return "wxyz"; }; + +@test fn ident_source() void = { + // ident-source unwrap (`let r = mk(); r!`) — the dropped-shuffle case. + let r: (str | e) = mk_se(); + let junk: str = "0123456789012345678901234567890123456789"; + let s: str = r!; + assert(s.len: i32 == 2); +}; + +@test fn errfirst_call() void = { + // error-first union (e | str): the success variant is NOT the first. + let junk: str = "0123456789012345678901234567890123456789"; + let s3: str = mk2_es()!; + assert(s3.len: i32 == 3); +}; + +@test fn succfirst_call() void = { + // success-first call (str | e) — the control the pre-fix path got right. + let junk: str = "0123456789012345678901234567890123456789"; + let s: str = mk_se4()!; + assert(s.len: i32 == 4); +}; diff --git a/test/lang/tuple_elem_slice_len_test.ww b/test/lang/tuple_elem_slice_len_test.ww new file mode 100644 index 00000000..7aa3ec8d --- /dev/null +++ b/test/lang/tuple_elem_slice_len_test.ww @@ -0,0 +1,64 @@ +// tuple_elem_slice_len_test — `len(t.N)` where t is a tuple and t.N is a +// slice/str-typed element, migrated from test/wcc/903_tuple_elem_slice_len_run.c +// (#235). The len() builtin special-cased only a PLAIN N_IDENT slice operand +// and an array operand; every other shape fell back to a bare `cgexpr(operand)`, +// which for a slice leaves AX=.ptr. A tuple-element read (`t.N`) loads only +// AX=.ptr, so `len(t.N)` returned the slice's .ptr word AS the length — a SILENT +// miscompile, gate-blind because the bootstrap never does len() on a +// slice-typed tuple element. The fix (both stages, byte-id) loads the element's +// .len word directly at BP + tuple_off + element_off + 8. Every element has a +// DISTINCT length so a dropped/wrong field is caught. Tuples are slice/str-ONLY +// here — a leading scalar element exercises a separate mixed-tuple sret +// divergence (filed apart, out of scope). + +package tuple_elem_slice_len_test; + +fn mk2() ([]u8, []u8) = { + let a: []u8; a.len = 3; a.cap = 7; + let b: []u8; b.len = 5; b.cap = 9; + return (a, b); +}; + +fn mk3() ([]u8, []u8, []u8) = { + let a: []u8; a.len = 3; a.cap = 7; + let b: []u8; b.len = 5; b.cap = 9; + let c: []u8; c.len = 11; c.cap = 13; + return (a, b, c); +}; + +fn mksb() (str, []u8) = { + let s: str = "abcd"; + let b: []u8; b.len = 6; b.cap = 8; + return (s, b); +}; + +fn mkbs() ([]u8, str) = { + let b: []u8; b.len = 7; b.cap = 9; + let s: str = "hi"; + return (b, s); +}; + +@test fn two_slice() void = { + let t: ([]u8, []u8) = mk2(); + assert(len(t.0): i32 == 3); + assert(len(t.1): i32 == 5); +}; + +@test fn three_slice() void = { + let t: ([]u8, []u8, []u8) = mk3(); + assert(len(t.0): i32 == 3); + assert(len(t.1): i32 == 5); + assert(len(t.2): i32 == 11); +}; + +@test fn str_slice() void = { + let t: (str, []u8) = mksb(); + assert(len(t.0): i32 == 4); + assert(len(t.1): i32 == 6); +}; + +@test fn slice_str() void = { + let t: ([]u8, str) = mkbs(); + assert(len(t.0): i32 == 7); + assert(len(t.1): i32 == 2); +}; diff --git a/test/wcc/723_composite_call_arg.c b/test/wcc/723_composite_call_arg.c index d3f16402..5b3388d2 100644 --- a/test/wcc/723_composite_call_arg.c +++ b/test/wcc/723_composite_call_arg.c @@ -13,7 +13,7 @@ * (rule 10): add an N_CALL arm to `nodeisslice` that mirrors the * existing N_CALL arm in `nodeisstr` (cgenutil.ww:574+). * - * 927_composite_call_arg_run pins the runtime behaviour; this row + * test/lang/composite_call_arg_test.ww pins the runtime behaviour; this row * pins the asm shape so a future cgen refactor that re-routes * pushargsrev can't silently regress back to the dropped-len/cap * sequence. diff --git a/test/wcc/797_len_strglobal_run.c b/test/wcc/797_len_strglobal_run.c deleted file mode 100644 index 69ed34c2..00000000 --- a/test/wcc/797_len_strglobal_run.c +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 797_len_strglobal_run — project #231. Runtime + cs==ww byte-id net for - * the `len(str-global)` miscompile. - * - * THE BUG (both stages wrong, DIFFERENTLY → byte-id was BLIND because the - * stages also DIVERGED, but no bootstrap source exercised the shape): - * cstage: a top-level str global N_IDENT fell into the len() arm's - * BP-relative slot load. localfind returns 0 for a global, so it - * emitted `MOVQ 8(BP),AX` — a bogus stack slot (runtime 72). - * wwstage: the len() arm only handled locals (localfindnode != nil); - * a global fell through to cgexpr, which loads the whole header - * and leaves AX=.ptr, not .len (runtime garbage / 37). - * The local-str case (`let g: str = "hello"; len(g)`) is correct in both - * (control), so the fix targets only the global path. - * - * THE FIX (#231): both stages emit the global .len load — LEAQ name(SB),CX; - * MOVQ 8(CX),AX (.len field; str/slice header is ptr@0/len@8/cap@16). The - * LEAQ symbol routes through the post-#1 value mangle (cstage mahint with - * c->cur_mod, wwstage emitsymnamehint with c.curmod), NOT a raw name — - * else a private same-module same-leaf str global would re-open the #1 - * collision. - * - * SCOPE NOTE: a []u8 (slice) global would exercise the same arm (the gate - * covers TY_SLICE || TY_STR), but `let g: []u8 = [...]` literal-init is - * currently REJECTED by cstage ("let g init not assignable", #233), so the - * cstage `ww build` + byte-id rows below cannot compile a slice global yet. - * Rowed once #233 lands; the str global proves the code path now. - * - * EACH ROW CARRIES BOTH DIMENSIONS (795/796 model): - * (a) cstage `ww build` + run, asserting the exit — pins the converged - * asm is runtime-correct (len("hello") == 5). - * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (they did, - * differently, pre-fix). - * - * GATE POLARITY: must stay GREEN. A wrong exit means the global .len load - * regressed; a byte-id FAIL means the stages diverged (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[] = { - /* str global: len("hello") == 5. Pre-fix cstage 72, wwstage garbage. */ - { "str_global_len", - "package main;\n" - "let g: str = \"hello\";\n" - "export fn main() i32 = { return len(g): i32; };\n", 5 }, - /* control: the local-str case both stages already get right. */ - { "str_local_len", - "package main;\n" - "export fn main() i32 = { let g: str = \"hello\"; " - "return len(g): i32; };\n", 5 }, - { 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, "len_strglobal: 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/wwlsg_%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/wwlsg_%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/wwlsg_%d_%d_cs.s", getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwlsg_%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 len str-global tests failed\n", fail, n); - return 1; - } - printf("len_strglobal: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); - return 0; -} diff --git a/test/wcc/801_litstr_pseudo_run.c b/test/wcc/801_litstr_pseudo_run.c deleted file mode 100644 index 6c424cc4..00000000 --- a/test/wcc/801_litstr_pseudo_run.c +++ /dev/null @@ -1,185 +0,0 @@ -/* - * 801_litstr_pseudo_run — BUG #14. Runtime + cs==ww byte-id net for the - * string-LITERAL `.len` / `.ptr` pseudo-field. - * - * THE BUG (cstage wrong, wwstage correct — rule-10 divergence): - * `"hi".len` returned the POINTER, not the length. A string literal is - * TY_UNTYPED_STR (not TY_STR), so it missed the typed slice/str - * pseudo-field gate in cgen.c's N_DOT and fell to the final fallback - * `cgexpr(lhs)`, which leaves AX=.ptr — `.len` then returned AX (the - * ptr) instead of shuffling BX (the len) into AX. wwstage's cgdot - * catch-all (cgenexpr.ww) already did the BX->AX shuffle, so the two - * stages DISAGREED. byte-id was BLIND: no bootstrap source uses - * literal `.len` (devs hardcoded the lengths around it, e.g. - * `os.write(2, "...".ptr, 9u64)`), so the gate never exercised it. - * The str-VARIABLE `.len` path was correct on both (control). - * - * THE FIX (#14): align cstage UP to wwstage — the final N_DOT fallback - * emits `MOVQ BX, AX` for `.len` (cgen.c). `.ptr` already returned AX - * (correct) and is unchanged. - * - * EACH ROW CARRIES BOTH DIMENSIONS (795/796/797 model): - * (a) cstage `ww build` + run, asserting the exit — pins the converged - * asm is runtime-correct (literal lengths reach the program). - * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (they did, - * pre-fix: cstage emitted the ptr, wwstage the len). - * - * GATE POLARITY: must stay GREEN. A wrong exit means literal `.len`/`.ptr` - * regressed; a byte-id FAIL means the stages diverged (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[] = { - /* literal .len: "hello".len == 5. Pre-fix cstage returned the ptr. */ - { "lit_len", - "package main;\n" - "export fn main() i32 = { return \"hello\".len: i32; };\n", 5 }, - /* empty + multi-byte: ""/"héllo\\n" — é is 2 UTF-8 bytes, so the - * literal is 7 bytes (h,é=2,l,l,o,\\n). Guards against a .ptr/.len - * length confusion (a ptr would not be 7). */ - { "lit_len_empty_multibyte", - "package main;\n" - "export fn main() i32 = { if (\"\".len != 0) { return 9; }; " - "return \"héllo\\n\".len: i32; };\n", 7 }, - /* literal .ptr still points at the bytes (deref first byte == 'h'), - * then .len == 5 — proves the fix left .ptr untouched. */ - { "lit_ptr_deref", - "package main;\n" - "export fn main() i32 = { let p: *u8 = \"hello\".ptr; " - "if (*p != 'h') { return 1; }; return \"hello\".len: i32; };\n", 5 }, - /* callee-side .len on a literal passed as a str arg — the real-world - * shape (os.write(1, lit.ptr, lit.len)) that surfaced #14. */ - { "lit_arg_passthrough", - "package main;\n" - "fn slen(s: str) i32 = { return s.len: i32; };\n" - "export fn main() i32 = { if (slen(\"abcdef\") != 6) { return 1; }; " - "return \"abcdef\".len: i32; };\n", 6 }, - { 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, "litstr_pseudo: 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/wwlsp_%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/wwlsp_%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/wwlsp_%d_%d_cs.s", getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwlsp_%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 litstr pseudo-field tests failed\n", fail, n); - return 1; - } - printf("litstr_pseudo: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); - return 0; -} diff --git a/test/wcc/803_globalidx_run.c b/test/wcc/803_globalidx_run.c deleted file mode 100644 index 7061a101..00000000 --- a/test/wcc/803_globalidx_run.c +++ /dev/null @@ -1,385 +0,0 @@ -/* - * 803_globalidx_run — BUG #10 + #11. Runtime + cs==ww byte-id net for - * the GLOBAL `str` / GLOBAL slice index family (`s/g` are module-level lets): - * #10 — the READ `s[i]` / `g[i]` (cgindex). - * #11 — the ADDR-OF `&s[i]` / `&g[i]` (cgun) and the STORE `g[i] = v` - * (cgassign). Same root as #10 (the N_TARRAY/N_TPTR kind whitelist - * in the global-resolution arm), three more sibling arms; the store - * miscompile is an 8-byte OUT-OF-BOUNDS write (full-word MOVQ instead - * of MOVB), so the store rows below also assert ADJACENT elements stay - * uncorrupted. - * - * THE BUG (wwstage WRONG, cstage correct — a rule-10 divergence): - * wwstage `cgindex` (selfhost/cmd/wcc/cgenexpr.ww) dispatched the element - * size + base-materialisation off the base's tnode KIND, enumerating only - * N_TARRAY (global `[N]T`) and N_TPTR (global `*T`). A global str (tnode - * N_TNAME "str") and a global slice (N_TSLICE) matched NEITHER arm, so esz - * stayed at the default 8 and the base fell to the wide-header fallback — - * it materialised the full {ptr,len,cap} header and indexed with an 8-byte - * stride + a full-word MOVQ load. So `s[1]` over a global str read 8 bytes - * at ptr+8 instead of the single byte at ptr+1 (cstage emits MOVZBQ). - * cstage `case N_INDEX:` (cmd/w6c/cgen.c) dispatches esz off the RESOLVED - * base TYPE (`idx_eff(lhs->type)->sub->size`), uniform across local/global/ - * str/slice/ptr — so it was already correct. LOCAL str/slice index was also - * already byte-id-clean (esz resolves off the local's tnode). - * - * THE FIX (#10): align cgindex's global-resolution arm UP to cstage's uniform - * type-driven dispatch — the same template the sister fn `cgslice` already - * uses (generic globaltn, esz = elemsizeofc(c, tn), base = N_TARRAY ? LEAQ : - * MOVQ name(SB)). A global str/slice now resolves esz=1 off the type table - * and routes through the existing isglobalptr emission (MOVQ name(SB),BX; - * ADDQ; MOVZBQ (BX),AX) — byte-identical to cstage. - * - * EACH ROW CARRIES BOTH DIMENSIONS (802 model): - * (a) cstage `ww build` + run, asserting the exit — pins that the converged - * asm reads the correct element value, not a ptr word. - * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10). A - * stride-8 regression of the fix re-diverges wwstage from cstage here. - * - * BYTE-ID SCOPE PER ROW: - * Most rows compare the FULL `.s`. The global-slice row compares the TEXT - * section only (lines before the first DATA/GLOBL directive): a bare - * module-level `let g: []u8;` decl emits a divergent zero-header DATAW in - * wwstage that cstage omits — a SEPARATE, pre-existing data-emission gap - * (module-level slice static-init, task #7/#18 family) orthogonal to the - * index read. The TEXT comparison still pins the index codegen exactly - * (where a stride-8 regression manifests: MOVZBQ vs IMULQ $8 + MOVQ), so - * the #10 fix is fully gated; only the unrelated DATA noise is excluded. - * (Same kind of orthogonal block 802 documents for its slice rows.) - * - * GATE POLARITY: must stay GREEN. A wrong exit means a global str/slice index - * regressed back to a ptr-word read; a byte-id FAIL means the stages diverged. - */ -#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; int text_only; }; - -static const struct row rows[] = { - /* the exact #10 repro: a global str "ABC", s[1] == 'B' == 66. - * Pre-fix this read an 8-byte word at ptr+8 (wide-header stride). */ - { "gstr_mid", - "package main;\n" - "let s: str = \"ABC\";\n" - "export fn main() i32 = { return s[1]: i32; };\n", 66, 0 }, - /* first element s[0] == 'A' == 65 — guards an off-by-one in the - * index scale (a wide stride would land elsewhere). */ - { "gstr_first", - "package main;\n" - "let s: str = \"ABC\";\n" - "export fn main() i32 = { return s[0]: i32; };\n", 65, 0 }, - /* last element s[2] == 'C' == 67. */ - { "gstr_last", - "package main;\n" - "let s: str = \"ABC\";\n" - "export fn main() i32 = { return s[2]: i32; };\n", 67, 0 }, - /* two global-str indices summed (65 + 66 == 131) — pins the byte - * load width: a full-word load would carry the high bytes. */ - { "gstr_sum", - "package main;\n" - "let s: str = \"ABC\";\n" - "export fn main() i32 = { return s[0]: i32 + s[1]: i32; };\n", 131, 0 }, - /* global slice index: g[1] == 20. The bare `let g: []u8;` decl emits - * a divergent zero-header DATAW (orthogonal #7/#18 data gap), so this - * row is TEXT-only byte-id; cstage runtime pins the value, TEXT-id - * pins the index read. g is assigned at runtime to dodge the SEPARATE - * module-level slice-literal static-init gap (which fails to link in - * BOTH stages identically). */ - { "gslice_mid", - "package main;\n" - "let g: []u8;\n" - "export fn main() i32 = {\n" - " let a: [3]u8 = [10u8, 20u8, 30u8];\n" - " g = a;\n" - " return g[1]: i32;\n" - "};\n", 20, 1 }, - /* global slice with a WIDTH>1, SIGNED element: g[1] - g[0] == 20 - (-5) - * == 25. Pins that the global path resolves esz=4 off the type table - * (not the default 8) AND sign-extends (MOVSXD) — a stride-8 regression - * re-diverges the TEXT (IMULQ $8 + MOVQ word vs IMULQ $4 + MOVSXD). Same - * bare-decl DATAW data-gap as gslice_mid, so TEXT-only. */ - { "gislice_mid", - "package main;\n" - "let g: []i32;\n" - "export fn main() i32 = {\n" - " let a: [3]i32 = [-5, 20, 30];\n" - " g = a;\n" - " return g[1] - g[0];\n" - "};\n", 25, 1 }, - /* #11 ADDR-OF, global str: &s[1] then read the byte through the - * pointer == 'B' == 66. Pre-#11 cgun materialised the wide {ptr,len, - * cap} header + an 8-byte stride (IMULQ $8 + LEAQ s(SB)) instead of - * MOVQ s(SB) (.ptr) + ADDQ; FULL byte-id (string-literal DATA is - * stage-identical, same as the gstr READ rows). */ - { "gstr_addr", - "package main;\n" - "let s: str = \"ABC\";\n" - "export fn main() i32 = { let p: *u8 = &s[1]; return (*p): i32; };\n", - 66, 0 }, - /* #11 ADDR-OF, global slice []u8: &g[1] read back == 20. Bare - * `let g: []u8;` decl → orthogonal zero-header DATAW, so TEXT-only - * (same data gap as gslice_mid; the addr codegen is all TEXT). */ - { "gslice_addr", - "package main;\n" - "let g: []u8;\n" - "export fn main() i32 = {\n" - " let a: [3]u8 = [10u8, 20u8, 30u8];\n" - " g = a;\n" - " let p: *u8 = &g[1];\n" - " return (*p): i32;\n" - "};\n", 20, 1 }, - /* #11 ADDR-OF, global slice WIDTH>1: &g[1] over []i32, *p == 20. - * Pins esz=4 stride on the addr path — a stride-8 regression lands - * &g[2] (*p == 30) and re-diverges the TEXT (IMULQ $4 vs IMULQ $8). - * Same bare-decl DATAW gap → TEXT-only. */ - { "gislice_addr", - "package main;\n" - "let g: []i32;\n" - "export fn main() i32 = {\n" - " let a: [3]i32 = [-5, 20, 30];\n" - " g = a;\n" - " let p: *i32 = &g[1];\n" - " return *p;\n" - "};\n", 20, 1 }, - /* #11 STORE, global slice []u8: g[1] = 99, then g[1]+g[0]+g[2] == - * 99 + 10 + 30 == 139. The g[0]/g[2] addends are the OOB-WRITE GUARD: - * pre-#11 the store was a full-word MOVQ at an 8-byte stride, writing - * 99 at ptr+8 (8-byte OOB) and leaving g[1] == 20 (sum 60, AND clobbered - * memory). The fix stores MOVB at ptr+1; adjacent bytes intact. TEXT- - * only (bare-decl DATAW gap). */ - { "gslice_store", - "package main;\n" - "let g: []u8;\n" - "export fn main() i32 = {\n" - " let a: [3]u8 = [10u8, 20u8, 30u8];\n" - " g = a;\n" - " g[1] = 99u8;\n" - " return g[1]: i32 + g[0]: i32 + g[2]: i32;\n" - "};\n", 139, 1 }, - /* #11 STORE, global slice WIDTH>1 SIGNED: g[1] = 42, then - * g[1]+g[0]+g[2] == 42 + (-5) + 30 == 67. Pins esz=4 store WIDTH + - * stride: a full-word/stride-8 regression writes 8 bytes at ptr+8 - * (OOB) and re-diverges the TEXT (IMULQ $4 + MOVL vs IMULQ $8 + MOVQ). - * g[0]/g[2] are the adjacency guard. TEXT-only. */ - { "gislice_store", - "package main;\n" - "let g: []i32;\n" - "export fn main() i32 = {\n" - " let a: [3]i32 = [-5, 20, 30];\n" - " g = a;\n" - " g[1] = 42;\n" - " return g[1] + g[0] + g[2];\n" - "};\n", 67, 1 }, - /* #11 COMPOUND STORE, global slice []u8: g[1] += 79 → 20+79 == 99, - * then g[1]+g[0]+g[2] == 99+10+30 == 139. Exercises the THIRD fixed - * arm (cgenexpr.ww cgassign n.op != TK_ASSIGN), a distinct code path - * from the plain-store rows above: it load-combines-stores in place. - * Pre-#11 it hit the same kind whitelist → esz=8 + a full-word RMW: an - * 8-byte OOB load AND store at ptr+8, leaving g[1] == 20 (sum 60) and - * clobbering adjacent memory. The g[0]/g[2] addends are the OOB guard. - * TEXT-only (bare-decl DATAW gap). */ - { "gslice_compound", - "package main;\n" - "let g: []u8;\n" - "export fn main() i32 = {\n" - " let a: [3]u8 = [10u8, 20u8, 30u8];\n" - " g = a;\n" - " g[1] += 79u8;\n" - " return g[1]: i32 + g[0]: i32 + g[2]: i32;\n" - "};\n", 139, 1 }, - /* #11 COMPOUND STORE, global slice WIDTH>1 SIGNED: g[1] += 47 → - * (-5)+47 == 42, then g[1]+g[0]+g[2] == 42+(-25)+30 == 47. Pins the - * compound arm's esz=4 load/store WIDTH + stride on the global path — - * a stride-8/full-word RMW regression reads+writes 8 bytes at ptr+8 - * (OOB) and re-diverges the TEXT (IMULQ $4 + MOVL/MOVSXD vs IMULQ $8 + - * MOVQ). g[0]/g[2] are the adjacency guard. TEXT-only. */ - { "gislice_compound", - "package main;\n" - "let g: []i32;\n" - "export fn main() i32 = {\n" - " let a: [3]i32 = [-25, -5, 30];\n" - " g = a;\n" - " g[1] += 47;\n" - " return g[1] + g[0] + g[2];\n" - "};\n", 47, 1 }, - /* REGRESSION PIN: local str ADDR-OF (already clean pre-#11) — the - * fix must not perturb the local path. &s[1] read back == 66. FULL. */ - { "lstr_addr", - "package main;\n" - "export fn main() i32 = { let s: str = \"ABC\"; let p: *u8 = &s[1]; return (*p): i32; };\n", - 66, 0 }, - /* REGRESSION PIN: local slice STORE (already clean) — g[1] = 99 then - * g[1]+g[0]+g[2] == 139, adjacency intact. FULL byte-id (a local slice - * carries no bare-decl data gap). */ - { "lslice_store", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [3]u8 = [10u8, 20u8, 30u8];\n" - " let g: []u8 = a;\n" - " g[1] = 99u8;\n" - " return g[1]: i32 + g[0]: i32 + g[2]: i32;\n" - "};\n", 139, 0 }, - /* REGRESSION PIN: local str index (already byte-id-clean pre-fix) — - * the fix must not perturb the local path. s[1] == 'B' == 66. */ - { "lstr_mid", - "package main;\n" - "export fn main() i32 = { let s: str = \"ABC\"; return s[1]: i32; };\n", - 66, 0 }, - /* REGRESSION PIN: local slice index (already clean). g[2] == 30. */ - { "lslice_last", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [3]u8 = [10u8, 20u8, 30u8];\n" - " let g: []u8 = a;\n" - " return g[2]: i32;\n" - "};\n", 30, 0 }, - /* REGRESSION PIN: local array index (already clean). a[1] == 20. */ - { "larr_mid", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [3]u8 = [10u8, 20u8, 30u8];\n" - " return a[1]: i32;\n" - "};\n", 20, 0 }, - { NULL, NULL, 0, 0 } -}; - -/* A directive line that begins the DATA/GLOBL section. The TEXT-only - * compare stops at the first such line (the index codegen lives entirely - * in the TEXT segment above it). */ -static int -isdataline(const char *ln) -{ - return strncmp(ln, "DATA", 4) == 0 || strncmp(ln, "GLOBL", 5) == 0; -} - -/* Byte-compare two .s files. With text_only, both files are truncated at - * the first DATA/GLOBL line before comparison (orthogonal data-section - * divergence excluded; see the BYTE-ID SCOPE note above). */ -static int -asm_eq(const char *a, const char *b, int text_only) -{ - 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; - char la[4096], lb[4096]; - for (;;) { - char *ra = fgets(la, sizeof la, fa); - char *rb = fgets(lb, sizeof lb, fb); - if (text_only && ra && isdataline(la)) ra = NULL; - if (text_only && rb && isdataline(lb)) rb = NULL; - if (ra == NULL && rb == NULL) break; - if (ra == NULL || rb == NULL) { rc = -1; break; } - if (strcmp(la, lb) != 0) { rc = -1; 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, "globalidx: 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/wwgi_%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/wwgi_%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/wwgi_%d_%d_cs.s", getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwgi_%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 (asm_eq(cs_s, ws_s, rows[i].text_only) != 0) { - fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER%s " - "(rule-10 byte-id violation)\n", rows[i].label, - rows[i].text_only ? " (TEXT section)" : ""); - fail++; - } - unlink(src); unlink(cs_s); unlink(ws_s); - } - - if (fail) { - fprintf(stderr, "%d/%d globalidx tests failed\n", fail, n); - return 1; - } - printf("globalidx: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); - return 0; -} diff --git a/test/wcc/903_tuple_elem_slice_len_run.c b/test/wcc/903_tuple_elem_slice_len_run.c deleted file mode 100644 index b05f3ee2..00000000 --- a/test/wcc/903_tuple_elem_slice_len_run.c +++ /dev/null @@ -1,252 +0,0 @@ -/* - * 903_tuple_elem_slice_len_run — project #235: `len(t.N)` where t is a tuple - * and t.N is a slice/str-typed element. - * - * The len() builtin special-cased only a PLAIN N_IDENT slice operand (load - * .len at BP+off+8) and an array operand (fold $alen); every other shape fell - * back to a bare `cgexpr(operand)`, which for a slice leaves AX=.ptr. The - * tuple-element read (`t.N`) loads only AX=.ptr (it has no slice-header - * sibling — that gap is the separate #238), so `len(t.N)` returned the slice's - * .ptr word AS the length — a SILENT miscompile, gate-blind because the - * bootstrap never does len() on a slice-typed tuple element. - * - * Fix (both stages, byte-identical per rule 10): for a slice/str tuple-element - * len() operand, load the element's .len word directly at - * BP + tuple_off + element_off + 8 — mirroring the N_IDENT slice arm and the - * tuple-field-offset walk. The +8 is the {ptr,len,cap}-header .len offset, the - * same constant the N_IDENT arm and the .len pseudo-field use; element_off sums - * preceding element sizes through the type table. - * - * Rows (every element a DISTINCT length so a dropped/wrong field is caught; - * each ww program self-asserts and returns 0 only when every len(t.N) is - * correct). Tuples are slice/str-ONLY — a leading scalar element exercises a - * SEPARATE pre-existing mixed-tuple sret-layout cs/ww divergence (filed apart - * from #235), out of scope here. - * two_slice ([]u8,[]u8) lens 3,5 - * three_slice ([]u8,[]u8,[]u8) lens 3,5,11 - * str_slice (str,[]u8) len("abcd")=4, slice 6 - * slice_str ([]u8,str) slice 7, len("hi")=2 - * - * All K_RUN: build+run exit 0 on BOTH drivers AND cs==ww byte-identical. - * NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling - * race does not apply (940/945/799 precedent). - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -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[] = { - { "two_slice", - "package main;\n" - "fn mk() ([]u8, []u8) = {\n" - " let a: []u8; a.len = 3; a.cap = 7;\n" - " let b: []u8; b.len = 5; b.cap = 9;\n" - " return (a, b);\n" - "};\n" - "export fn main() i32 = {\n" - " let t: ([]u8, []u8) = mk();\n" - " if (len(t.0) != 3) { return 1; };\n" - " if (len(t.1) != 5) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - { "three_slice", - "package main;\n" - "fn mk() ([]u8, []u8, []u8) = {\n" - " let a: []u8; a.len = 3; a.cap = 7;\n" - " let b: []u8; b.len = 5; b.cap = 9;\n" - " let c: []u8; c.len = 11; c.cap = 13;\n" - " return (a, b, c);\n" - "};\n" - "export fn main() i32 = {\n" - " let t: ([]u8, []u8, []u8) = mk();\n" - " if (len(t.0) != 3) { return 1; };\n" - " if (len(t.1) != 5) { return 2; };\n" - " if (len(t.2) != 11) { return 3; };\n" - " return 0;\n" - "};\n", 0 }, - { "str_slice", - "package main;\n" - "fn mk() (str, []u8) = {\n" - " let s: str = \"abcd\";\n" - " let b: []u8; b.len = 6; b.cap = 8;\n" - " return (s, b);\n" - "};\n" - "export fn main() i32 = {\n" - " let t: (str, []u8) = mk();\n" - " if (len(t.0) != 4) { return 1; };\n" - " if (len(t.1) != 6) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - { "slice_str", - "package main;\n" - "fn mk() ([]u8, str) = {\n" - " let b: []u8; b.len = 7; b.cap = 9;\n" - " let s: str = \"hi\";\n" - " return (b, s);\n" - "};\n" - "export fn main() i32 = {\n" - " let t: ([]u8, str) = mk();\n" - " if (len(t.0) != 7) { return 1; };\n" - " if (len(t.1) != 2) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, -}; - -/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */ -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/telen_%d_%d.ww", getpid(), i); - snprintf(tmpdir, sizeof tmpdir, "/tmp/telen_%d_d_%d", getpid(), i); - snprintf(errf, sizeof errf, "/tmp/telen_%d_e_%d", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - mkdir(tmpdir, 0755); - snprintf(cmd, sizeof cmd, "cd %s && %s build %s >/dev/null 2>%s", - tmpdir, driver, src, errf); - int brc = runwait(cmd); - if (brc != 0) { - fprintf(stderr, "row[%s]: build via %s failed\n", - r->label, driver); - unlink(src); unlink(errf); rmdir(tmpdir); - return -1; - } - - const char *base = strrchr(src, '/'); - base = base ? base + 1 : src; - char outbin[256]; - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - char *dot = strrchr(outbin, '.'); - if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; - int got = runwait(outbin); - - unlink(src); unlink(outbin); unlink(errf); rmdir(tmpdir); - if (got != r->want) { - fprintf(stderr, "row[%s]: %s exit %d, want %d\n", - r->label, driver, got, r->want); - return 1; - } - return 0; -} - -/* cs==ww .s byte-id (rule 10). */ -static int -byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i) -{ - char src[96], cs_s[96], ws_s[96], cmd[1024]; - snprintf(src, sizeof src, "/tmp/telen_bi_%d_%d.ww", getpid(), i); - snprintf(cs_s, sizeof cs_s, "/tmp/telen_bi_%d_%d_cs.s", getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/telen_bi_%d_%d_ww.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - int rc = 0; - 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", r->label); rc = 1; } - else { - 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", r->label); rc = 1; } - else if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER " - "(rule-10 byte-id)\n", r->label); - rc = 1; - } - } - unlink(src); unlink(cs_s); unlink(ws_s); - return rc; -} - -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], wdrv[640], w6c[640], w6c_ww[640]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_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, "tuple_elem_slice_len: skip %s (no %s)\n", - drivers[d].name, drivers[d].path); - continue; - } - for (int i = 0; i < n; i++) { - total++; - if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++; - } - } - - if (access(w6c_ww, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++; - } - } - - if (fail) { - fprintf(stderr, "tuple_elem_slice_len: %d/%d checks failed\n", - fail, total); - return 1; - } - printf("tuple_elem_slice_len: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/927_composite_call_arg_run.c b/test/wcc/927_composite_call_arg_run.c deleted file mode 100644 index 53d65602..00000000 --- a/test/wcc/927_composite_call_arg_run.c +++ /dev/null @@ -1,289 +0,0 @@ -/* - * 927_composite_call_arg_run — runtime sentinel for #24 (Class A - * wwstage cgen miscompile). When a 3-reg composite (`[]u8` slice) - * CALL result is passed inline as a composite arg to another call, - * pre-fix wwstage emitted a single `PUSHQ AX` (loses .len/.cap) and - * under-popped the receiver's arg-regs by two words. Net: arg-shift - * collision corrupts every subsequent arg; the receiver reads the - * caller's spilled `p.ptr` as its own `s.len`, etc. - * - * Fix-site: `nodeisslice` in selfhost/cmd/wcc/cgenutil.ww gained an - * N_CALL arm mirroring `nodeisstr`'s existing one. Both the push side - * (pushargsrev's 3-PUSH `CX, BX, AX` arm) and the pop side (cgcall's - * `extra = 2`) then fire for slice-returning CALLs as args. - * - * Rows pin the runtime semantics across the matrix rob-pike outlined: - * (a) canonical `f(g())` with g returning `[]u8`. - * (b) slice-CALL then let-slice `f(g(), p)` (strings.hasprefix shape). - * (c) two composite CALLs `f(g(in1), g(in2))` (arg-shift collision). - * (d) slice-CALL in middle arg position `f(p, g(), q)`. - * (e) slice-CALL as last arg `f(p, q, g())` (stack-spill region). - * (f) nested composite `f(g(h(s)))` (composite-in-composite). - * (g) tagged-CALL regression alongside slice-CALL — confirm #21 - * still holds and the two natural-push arms compose. - * (h) slice-CALL followed by a scalar `f(g(), 42)` (pop-count mix). - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want; }; - -static const struct row rows[] = { - /* (a) canonical: f(g()) with g returning `[]u8`. Pre-fix wwstage - * dispatches via fewer PUSHes; check.len reads .ptr-low (a tiny - * non-13 number) → return 11. */ - { "canonical_slice_call", - "fn view(s: str) []u8 = {\n" - " let r: []u8;\n" - " r.ptr = s.ptr;\n" - " r.len = s.len;\n" - " r.cap = s.len;\n" - " return r;\n" - "};\n" - "fn check(a: []u8) i32 = {\n" - " if (a.len == 13) { return 0; };\n" - " return 11;\n" - "};\n" - "export fn main() i32 = {\n" - " let s: str = \"hello, world!\";\n" - " return check(view(s));\n" - "};\n", - 0 }, - /* (b) f(g(), p) — strings.hasprefix shape: slice-CALL into 6-reg - * argpack, let-slice as second. Pre-fix p.ptr lands in DI/SI - * instead of R8/R9; check2 sees garbage for `b.len`. */ - { "slice_call_then_letslice", - "fn view(s: str) []u8 = {\n" - " let r: []u8;\n" - " r.ptr = s.ptr;\n" - " r.len = s.len;\n" - " r.cap = s.len;\n" - " return r;\n" - "};\n" - "fn check2(a: []u8, b: []u8) i32 = {\n" - " if (a.len != 13) { return 11; };\n" - " if (b.len != 5) { return 12; };\n" - " return 0;\n" - "};\n" - "export fn main() i32 = {\n" - " let s: str = \"hello, world!\";\n" - " let p: []u8;\n" - " p.ptr = s.ptr;\n" - " p.len = 5;\n" - " p.cap = 5;\n" - " return check2(view(s), p);\n" - "};\n", - 0 }, - /* (c) f(g(in1), g(in2)) — two composite CALLs back-to-back. The - * second call's pushargsrev runs first (right-to-left), so the - * stack must hold (a.cap,a.len,a.ptr,b.cap,b.len,b.ptr) before - * the 6-POP drain. Pre-fix only `a.ptr` and `b.ptr` made it. */ - { "two_composite_calls_args", - "fn view(s: str) []u8 = {\n" - " let r: []u8;\n" - " r.ptr = s.ptr;\n" - " r.len = s.len;\n" - " r.cap = s.len;\n" - " return r;\n" - "};\n" - "fn check2(a: []u8, b: []u8) i32 = {\n" - " if (a.len != 3) { return 11; };\n" - " if (b.len != 5) { return 12; };\n" - " return 0;\n" - "};\n" - "export fn main() i32 = {\n" - " let s1: str = \"foo\";\n" - " let s2: str = \"hello\";\n" - " return check2(view(s1), view(s2));\n" - "};\n", - 0 }, - /* (d) slice-CALL in middle of three args. Composite arg-shift - * collision pulls scalar arg2 into the wrong reg-class window. */ - { "slice_call_middle_arg", - "fn view(s: str) []u8 = {\n" - " let r: []u8;\n" - " r.ptr = s.ptr;\n" - " r.len = s.len;\n" - " r.cap = s.len;\n" - " return r;\n" - "};\n" - "fn check3(k0: i32, a: []u8, k1: i32) i32 = {\n" - " if (k0 != 7) { return 11; };\n" - " if (a.len != 4) { return 12; };\n" - " if (k1 != 9) { return 13; };\n" - " return 0;\n" - "};\n" - "export fn main() i32 = {\n" - " let s: str = \"abcd\";\n" - " return check3(7, view(s), 9);\n" - "};\n", - 0 }, - /* (e) f(g(h(s))) — composite-in-composite nesting. Both inner and - * outer calls must route slice CALL → slice arg through the same - * pushargsrev path. */ - { "nested_composite_calls", - "fn view(s: str) []u8 = {\n" - " let r: []u8;\n" - " r.ptr = s.ptr;\n" - " r.len = s.len;\n" - " r.cap = s.len;\n" - " return r;\n" - "};\n" - "fn passthrough(a: []u8) []u8 = {\n" - " return a;\n" - "};\n" - "fn check(a: []u8) i32 = {\n" - " if (a.len == 6) { return 0; };\n" - " return 11;\n" - "};\n" - "export fn main() i32 = {\n" - " let s: str = \"abcdef\";\n" - " return check(passthrough(view(s)));\n" - "};\n", - 0 }, - /* (f) slice-CALL followed by scalar — pop-count mix; pre-fix the - * scalar would land off-by-2 in the int-stream. */ - { "slice_call_then_scalar", - "fn view(s: str) []u8 = {\n" - " let r: []u8;\n" - " r.ptr = s.ptr;\n" - " r.len = s.len;\n" - " r.cap = s.len;\n" - " return r;\n" - "};\n" - "fn use2(a: []u8, k: i32) i32 = {\n" - " if (a.len != 13) { return 11; };\n" - " if (k != 99) { return 12; };\n" - " return 0;\n" - "};\n" - "export fn main() i32 = {\n" - " let s: str = \"hello, world!\";\n" - " return use2(view(s), 99);\n" - "};\n", - 0 }, - /* (g) tagged-CALL regression alongside the slice path. Confirms - * #21's natural-push arm composes with #24's. The dispatch shape - * mirrors lib/encoding/utf8.next's call-chain in selfhost. */ - { "tagged_call_regression", - "type oserror = !i32;\n" - "fn yield_ptr() (*u8 | oserror) = {\n" - " let p: *u8 = nil;\n" - " return p;\n" - "};\n" - "fn dispatch(v: (*u8 | oserror)) i32 = {\n" - " match (v) {\n" - " case let p: *u8 => return 7;\n" - " case let e: oserror => return e: i32;\n" - " };\n" - " return -99;\n" - "};\n" - "export fn main() i32 = {\n" - " if (dispatch(yield_ptr()) != 7) { return 11; };\n" - " return 0;\n" - "};\n", - 0 }, -}; - -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/cca_%d_%d.ww", getpid(), i); - snprintf(tmpdir, sizeof tmpdir, "/tmp/cca_%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[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; -} - -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, - "composite_call_arg_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, - "composite_call_arg_run[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - if (fail) { - fprintf(stderr, - "composite_call_arg_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("composite_call_arg_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/952_slicecopy_assign_run.c b/test/wcc/952_slicecopy_assign_run.c deleted file mode 100644 index a64e665e..00000000 --- a/test/wcc/952_slicecopy_assign_run.c +++ /dev/null @@ -1,262 +0,0 @@ -/* - * 952_slicecopy_assign_run — runtime + cs==ww byte-id net for #145 - * (c1.5a): bulk slice-copy-assign into a range place `s.arr[lo:hi] = bs` - * (the LHS is an N_SLICE). No legacy N_ASSIGN arm caught an N_SLICE LHS, - * so the statement fell through to the scalar tail and emitted NOTHING — - * a silent no-op, BOTH stages, byte-id-green (#263-class). It blocked - * the path c2 port's appendlit (ref/hare/path/stack.ha:72 - * `buf.buf[newend..newend+len(bs)] = bs`). - * - * The fix reuses the N_SLICE READ lowering (AX = base+lo*esz, BX = hi-lo, - * CX = cap), scales BX by the SAME read-path element width (esz; [N]u8 -> - * 1, [N]i32 -> 4), and emits a runtime-counted byte-granular copy loop - * from bs.ptr (the length is RUNTIME — w6a has no REP/MOVSB and the - * #265/#268 memcpy emitters are compile-time-sz unrolled). The Hare - * len(bs)==hi-lo assert is deferred to task #149 (documented, not a c2 - * blocker — appendlit's lengths are equal by construction). - * - * WRITE-twin rows assert: the copy lands the right bytes in [lo,hi) AND - * leaves bytes OUTSIDE the range untouched (every row sums members both - * inside and outside the range, so a mis-strided or over-wide copy - * fails). esz coverage: [N]u8 (esz=1) and [N]i32 (esz=4). Base coverage: - * a struct-field array (N_DOT, the appendlit shape), a struct field via a - * *struct param (via_ptr), and a bare-local array (N_IDENT). READ-twin - * rows (drew's #5, already clean) lock the reslice-consume of the field - * array. byteid=1 throughout: post-fix the emission is byte-identical - * (the loop labels share the mklabel prefix/count/order across stages). - */ -#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; int byteid; }; - -static const struct row rows[] = { - /* WRITE twin — struct-field [N]u8 base (the appendlit shape). */ - { "w_u8_lo0", - "package main;\n" - "type buffer = struct { buf: [8]u8, end: i32 };\n" - "export fn main() i32 = {\n" - " let s: buffer;\n" - " let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n" - " s.buf[0:3] = bs[0:3];\n" - " return (s.buf[0]+s.buf[1]+s.buf[2]+s.buf[3]\n" - " +s.buf[4]+s.buf[5]): i32;\n" - "};\n", 60, 1 }, - { "w_u8_lo2", - "package main;\n" - "type buffer = struct { buf: [8]u8, end: i32 };\n" - "export fn main() i32 = {\n" - " let s: buffer;\n" - " let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n" - " s.buf[2:5] = bs[0:3];\n" - " return (s.buf[0]+s.buf[1]+s.buf[2]+s.buf[3]\n" - " +s.buf[4]+s.buf[5]): i32;\n" - "};\n", 60, 1 }, - { "w_u8_single", - "package main;\n" - "type buffer = struct { buf: [8]u8, end: i32 };\n" - "export fn main() i32 = {\n" - " let s: buffer;\n" - " let bs: [1]u8; bs[0]=42u8;\n" - " s.buf[3:4] = bs[0:1];\n" - " return (s.buf[2]+s.buf[3]+s.buf[4]): i32;\n" - "};\n", 42, 1 }, - /* empty range hi==lo — count=0, the loop's CMPQ $0 guard must - * JLE-exit before the first MOVB (off-by-one underflow would - * clobber buf[lo] and run away). Nothing in buf may change. */ - { "w_empty", - "package main;\n" - "type buffer = struct { buf: [8]u8, end: i32 };\n" - "export fn main() i32 = {\n" - " let s: buffer; s.buf[2]=5u8; s.buf[3]=7u8;\n" - " let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n" - " s.buf[2:2] = bs[0:0];\n" - " return (s.buf[1]+s.buf[2]+s.buf[3]+s.buf[4]): i32;\n" - "};\n", 12, 1 }, - /* esz=4 — [N]i32 field strides by the element width, not 1. */ - { "w_i32", - "package main;\n" - "type box = struct { o: [8]i32 };\n" - "export fn main() i32 = {\n" - " let s: box;\n" - " let bs: [2]i32; bs[0]=40; bs[1]=88;\n" - " s.o[1:3] = bs[0:2];\n" - " return (s.o[0]+s.o[1]+s.o[2]+s.o[3]): i32;\n" - "};\n", 128, 1 }, - /* via_ptr — the field write through a *struct param (appendlit's - * `buf: *buffer` shape). */ - { "w_viaptr", - "package main;\n" - "type buffer = struct { buf: [8]u8, end: i32 };\n" - "fn wr(s: *buffer, src: []u8) void = { s.buf[1:4] = src; };\n" - "export fn main() i32 = {\n" - " let s: buffer;\n" - " let bs: [3]u8; bs[0]=11u8; bs[1]=22u8; bs[2]=33u8;\n" - " wr(&s, bs[0:3]);\n" - " return (s.buf[0]+s.buf[1]+s.buf[2]+s.buf[3]\n" - " +s.buf[4]): i32;\n" - "};\n", 66, 1 }, - /* N_IDENT base — a bare-local array place (slicebaseesz IDENT leg). */ - { "w_local", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [8]u8;\n" - " let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n" - " a[2:5] = bs[0:3];\n" - " return (a[0]+a[1]+a[2]+a[3]+a[4]+a[5]): i32;\n" - "};\n", 60, 1 }, - /* READ twin (drew's #5, already CLEAN) — reslice the field array with - * a runtime hi bound, consume as []u8. Locks the read companion. */ - { "rd_reslice", - "package main;\n" - "type buffer = struct { buf: [8]u8, end: i32 };\n" - "export fn main() i32 = {\n" - " let s: buffer;\n" - " let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n" - " s.buf[0:3] = bs[0:3];\n" - " s.end = 3;\n" - " let v: []u8 = s.buf[0:s.end];\n" - " return (v[0]: i32 + v[1]: i32 + v[2]: i32 + (v.len: i32)*100);\n" - "};\n", 104, 1 }, - { "rd_len", - "package main;\n" - "type buffer = struct { buf: [8]u8, end: i32 };\n" - "export fn main() i32 = {\n" - " let s: buffer;\n" - " s.end = 5;\n" - " let v: []u8 = s.buf[0:s.end];\n" - " return v.len: i32;\n" - "};\n", 5, 1 }, - { NULL, NULL, 0, 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, "slicecopy: 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/wwsca_%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/wwsca_%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); - - if (!rows[i].byteid) { unlink(src); continue; } - - char cs_s[64], ws_s[64]; - snprintf(cs_s, sizeof cs_s, "/tmp/wwsca_%d_%d_cs.s", - getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwsca_%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 slicecopy-assign tests failed\n", - fail, n); - return 1; - } - printf("slicecopy: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); - return 0; -} diff --git a/test/wcc/989_globslicefield_run.c b/test/wcc/989_globslicefield_run.c deleted file mode 100644 index aa489685..00000000 --- a/test/wcc/989_globslicefield_run.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * 989_globslicefield_run — F8-c1 (report-item #35): a SLICE field of a - * module-GLOBAL struct, assigned with `g.field = `, must store the - * full {ptr,len,cap} header — both stages, byte-identical and correct. - * - * THE BUG (cat-A silent miscompile, align-UP): in cgenexpr.ww the - * single-dot global-struct field-assign block (`g.f = v`, base is a direct - * global struct ident) had a str arm that staged the base in DX and stored - * all three header words, but it gated on isstrtype ONLY — a non-str slice - * field ([]i64, []int, ...) fell through to the generic 1-word scalar store - * (`LEAQ g(SB),BX; MOVQ AX,(BX)`), silently dropping .len and .cap. cstage - * (cmd/w6c/cgen.c:5055) gates the same arm on TY_STR||TY_SLICE, so it stored - * the full header and ran correct — the cat-A divergence. The bootstrap - * corpus never assigns a non-str slice to a global-struct field, so 990-997 - * stayed green; a runtime row is the net. THE FIX: the wwstage gate widens - * to isstrtype||isslicetype (aligning UP to cstage); a slice rides the - * existing, already-correct 3-word DX store. - * - * Rows (each builds+runs on cstage `ww` and, when present, wwstage `ww_ww`; - * rule-10 — both stages agree AND hit want_exit): - * row | shape | want - * ---------------+---------------------------------------------+----- - * glob_slice_len | g:{sl:[]i64}; g.sl=b[0:3]; len(g.sl) | 3 [#35] - * glob_slice_cap | same; g.sl.cap (b:[4]i64 → cap 4) | 4 [#35] - * glob_off_len | g:{pad:i64,sl:[]i64}; sl@foff 8; len(g.sl) | 2 [#35: - * | pins the DX+foff+8 header store] | - * glob_str_len | g:{name:str}; g.name="hello"; len(g.name) | 5 (str-arm - * | regression pin — the pre-existing path) | - * glob_scalar_n | g:{n:i64}; g.n=7; g.n (scalar field control) | 7 (the - * | 1-word store the slice arm must not steal) | - * - * Pre-fix wwstage: glob_slice_len/cap/off_len all read 0 for the dropped - * words (the 1-word store); cstage and the str/scalar rows were correct. - */ -#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[] = { - { "glob_slice_len", - "package main;\n" - "type box = struct { sl: []i64 };\n" - "let g: box;\n" - "export fn main() int = {\n" - " let b: [4]i64 = [10, 11, 12, 13];\n" - " g.sl = b[0:3];\n" - " return len(g.sl): int;\n" - "};\n", - 3 }, - - { "glob_slice_cap", - "package main;\n" - "type box = struct { sl: []i64 };\n" - "let g: box;\n" - "export fn main() int = {\n" - " let b: [4]i64 = [10, 11, 12, 13];\n" - " g.sl = b[0:3];\n" - " return g.sl.cap: int;\n" - "};\n", - 4 }, - - /* slice field at NON-ZERO field offset (pad: i64 @0, sl @8). Pins - * the DX+foff+8 / +16 header store. */ - { "glob_off_len", - "package main;\n" - "type box = struct { pad: i64, sl: []i64 };\n" - "let g: box;\n" - "export fn main() int = {\n" - " let b: [4]i64 = [10, 11, 12, 13];\n" - " g.pad = 99;\n" - " g.sl = b[0:2];\n" - " return len(g.sl): int;\n" - "};\n", - 2 }, - - /* str field on a global struct — the pre-existing isstrtype arm; a - * regression pin that the widened gate keeps it byte-identical. */ - { "glob_str_len", - "package main;\n" - "type box = struct { name: str };\n" - "let g: box;\n" - "export fn main() int = {\n" - " g.name = \"hello\";\n" - " return len(g.name): int;\n" - "};\n", - 5 }, - - /* scalar field on a global struct — the generic 1-word store the - * slice arm must not divert (control). */ - { "glob_scalar_n", - "package main;\n" - "type box = struct { n: i64 };\n" - "let g: box;\n" - "export fn main() int = {\n" - " g.n = 7;\n" - " return g.n: int;\n" - "};\n", - 7 }, -}; - -/* run_build — build+run `src` via `driver`; returns the binary's exit - * code, or -1 on a build failure. */ -static int -run_build(const char *driver, const struct row *r, int i) -{ - char src[64], tmpdir[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/gslf_%d_%d.ww", getpid(), i); - snprintf(tmpdir, sizeof tmpdir, "/tmp/gslf_%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 && %s build %s 2>/dev/null", - tmpdir, driver, src); - int brc = runwait(cmd); - - 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 = -1; - if (brc == 0) got = runwait(outbin); - - unlink(src); unlink(outbin); rmdir(tmpdir); - return brc == 0 ? got : -1; -} - -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 *drv; 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].drv, X_OK) != 0) { - fprintf(stderr, "globslicefield_run: skip %s (no %s)\n", - drivers[d].name, drivers[d].drv); - continue; - } - for (int i = 0; i < n; i++) { - total++; - int got = run_build(drivers[d].drv, &rows[i], i); - if (got != rows[i].want_exit) { - fprintf(stderr, "globslicefield_run[%s][%s]: exit=%d " - "want=%d\n", drivers[d].name, rows[i].label, - got, rows[i].want_exit); - fail++; - } - } - } - - if (fail) { - fprintf(stderr, "globslicefield_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("globslicefield_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/989_globstrslice_run.c b/test/wcc/989_globstrslice_run.c deleted file mode 100644 index 1bfba89e..00000000 --- a/test/wcc/989_globstrslice_run.c +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 989_globstrslice_run — F8-c2 (report-item #47): a module-GLOBAL str - * sliced with a DEFAULT high bound and passed as a call arg (`take(g[1:])`) - * must load its length word for the hi — both stages, byte-identical. - * - * THE BUG (cat-A silent miscompile, align-UP): in cgenutil.ww the N_SLICE - * call-arg pusharg builds {cap,len,ptr} on the stack. For a DEFAULT hi - * (`g[lo:]`) it picks the base length per base kind. The global-base branch - * handled N_TARRAY and N_TSLICE but had NO arm for a global str (N_TNAME - * "str"), so AX kept the base pointer (loaded for the base push) and got - * PUSHQ'd as the hi → len = ptr - lo, garbage. The LOCAL-str arm and the - * alias-NAMED (bu60) arm already loaded the +8 length word; only the plain - * global-str arm was missing. cstage pushargs reads the base type via - * type_chase_named uniformly (cmd/w6c/cgen.c N_SLICE), so it loaded the len - * and ran correct — the cat-A divergence (990-997 never slice a global str - * arg with a default hi, so they stayed green). - * - * THE FIX: the global default-hi branch grows an N_TNAME "str" arm emitting - * the same `LEAQ g(SB),CX; MOVQ 8(CX),AX` as the global N_TSLICE arm (str IS - * []u8 — the len word sits at +8). align ww UP; the .s is byte-identical. - * - * Rows (build+run on cstage `ww` and wwstage `ww_ww`; rule-10 — agree+hit): - * row | shape | want - * ----------------+-------------------------------+----- - * gstr_defhi | slen(g[1:]) g="hello" | 4 [#47 bug — len ptr-lo] - * gstr_defhi_full | slen(g[0:]) lo=0 default hi | 5 [#47 bug] - * gstr_defhi_ptr | sfirst(g[1:]) s[0]=='e' | 101 [ptr word still ok] - * gstr_explicit | slen(g[1:3]) hi explicit | 2 (control: hi!=nil path - * | | must not regress) - */ -#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; -}; - -#define PRELUDE \ - "package main;\n" \ - "let g: str = \"hello\";\n" \ - "fn slen(s: str) int = { return len(s): int; };\n" \ - "fn sfirst(s: str) int = { return s[0]: int; };\n" - -static const struct row rows[] = { - { "gstr_defhi", - PRELUDE - "export fn main() int = { return slen(g[1:]); };\n", - 4 }, - - { "gstr_defhi_full", - PRELUDE - "export fn main() int = { return slen(g[0:]); };\n", - 5 }, - - { "gstr_defhi_ptr", - PRELUDE - "export fn main() int = { return sfirst(g[1:]); };\n", - 101 }, - - { "gstr_explicit", - PRELUDE - "export fn main() int = { return slen(g[1:3]); };\n", - 2 }, -}; - -/* run_build — build+run `src` via `driver`; returns the binary's exit - * code, or -1 on a build failure. */ -static int -run_build(const char *driver, const struct row *r, int i) -{ - char src[64], tmpdir[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/gss_%d_%d.ww", getpid(), i); - snprintf(tmpdir, sizeof tmpdir, "/tmp/gss_%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 && %s build %s 2>/dev/null", - tmpdir, driver, src); - int brc = runwait(cmd); - - 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 = -1; - if (brc == 0) got = runwait(outbin); - - unlink(src); unlink(outbin); rmdir(tmpdir); - return brc == 0 ? got : -1; -} - -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 *drv; 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].drv, X_OK) != 0) { - fprintf(stderr, "globstrslice_run: skip %s (no %s)\n", - drivers[d].name, drivers[d].drv); - continue; - } - for (int i = 0; i < n; i++) { - total++; - int got = run_build(drivers[d].drv, &rows[i], i); - if (got != rows[i].want_exit) { - fprintf(stderr, "globstrslice_run[%s][%s]: exit=%d " - "want=%d\n", drivers[d].name, rows[i].label, - got, rows[i].want_exit); - fail++; - } - } - } - - if (fail) { - fprintf(stderr, "globstrslice_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("globstrslice_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/989_trystr_run.c b/test/wcc/989_trystr_run.c deleted file mode 100644 index 3caaaa46..00000000 --- a/test/wcc/989_trystr_run.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 989_trystr_run (#16) — the `?`/`!` unwrap of a str-success tagged union - * must shuffle the str header (DX,CX,R8) → (AX,BX,CX) for ANY operand shape - * and ANY variant order, keyed off the STAMPED operand type. - * - * THE BUG (wwstage only): cgtryprop/cgtryunw computed succisstr ONLY when the - * operand was an N_CALL, via a name-keyed fnretlookupmod on the callee leaf, - * and tested the FIRST variant (not the success variant). So an ident-source - * unwrap (`let r = mk(); r!`) and an error-first union (`(e|str)`) both - * dropped the `MOVQ CX,BX / MOVQ R8,CX` shuffle — the unwrapped str kept a - * stale len/cap. cstage reads success_is_str = type_isstr(succ_t) at - * cg_tagged_success_tag from the stamped operand type. THE FIX: read the - * stamped operand's success-variant type via successvariant()+typeisstr(). - * - * row | shape | exit (cs==ww) - * ----------------+----------------------------------------+-------------- - * ident_source | let r=mk(); junk; r! → s.len | 2 (was ww 40) - * errfirst_call | (e|str) mk2()! → s.len | 3 - * succfirst_call | (str|e) mk()! (control) | 4 - * A 40-byte `junk` str precedes the unwrap so a dropped shuffle leaks junk's - * len into s.len: pre-fix ident_source ran ww=40 vs cs=2 (silent overrun). - */ -#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[] = { - { "ident_source", - "package main;\n" - "type e = !i32;\n" - "fn mk() (str | e) = { return \"hi\"; };\n" - "export fn main() i32 = {\n" - " let r: (str | e) = mk();\n" - " let junk: str = \"0123456789012345678901234567890123456789\";\n" - " let s: str = r!;\n" - " return s.len: i32;\n" - "};\n", - 2 }, - - { "errfirst_call", - "package main;\n" - "type e = !i32;\n" - "fn mk2() (e | str) = { return \"abc\"; };\n" - "export fn main() i32 = {\n" - " let junk: str = \"0123456789012345678901234567890123456789\";\n" - " let s3: str = mk2()!;\n" - " return s3.len: i32;\n" - "};\n", - 3 }, - - { "succfirst_call", - "package main;\n" - "type e = !i32;\n" - "fn mk() (str | e) = { return \"wxyz\"; };\n" - "export fn main() i32 = {\n" - " let junk: str = \"0123456789012345678901234567890123456789\";\n" - " let s: str = mk()!;\n" - " return s.len: i32;\n" - "};\n", - 4 }, -}; - -static int -run_build(const char *driver, const struct row *r, int i) -{ - char src[64], tmpdir[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/trystr_%d_%d.ww", getpid(), i); - snprintf(tmpdir, sizeof tmpdir, "/tmp/trystr_%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 && %s build %s 2>/dev/null", - tmpdir, driver, src); - int brc = runwait(cmd); - - 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 = -1; - if (brc == 0) got = runwait(outbin); - - unlink(src); unlink(outbin); rmdir(tmpdir); - return brc == 0 ? got : -1; -} - -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); - int have_ww = (access(wdrv, X_OK) == 0); - - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int i = 0; i < n; i++) { - total++; - int gc = run_build(cdrv, &rows[i], i); - if (gc < 0) { - fprintf(stderr, "trystr[cstage][%s]: build/run failed " - "(got %d)\n", rows[i].label, gc); - fail++; - continue; - } - if (gc != rows[i].want_exit) { - fprintf(stderr, "trystr[cstage][%s]: exit=%d want=%d\n", - rows[i].label, gc, rows[i].want_exit); - fail++; - } - if (!have_ww) { - fprintf(stderr, "trystr: skip wwstage (no %s)\n", wdrv); - continue; - } - int gw = run_build(wdrv, &rows[i], i); - if (gw != gc) { - fprintf(stderr, "trystr[%s]: cs=%d != ww=%d " - "(str-success unwrap shuffle dropped — #16)\n", - rows[i].label, gc, gw); - fail++; - } - if (gw != rows[i].want_exit) { - fprintf(stderr, "trystr[wwstage][%s]: exit=%d want=%d\n", - rows[i].label, gw, rows[i].want_exit); - fail++; - } - } - - if (fail) { - fprintf(stderr, "trystr_run: %d/%d checks failed\n", - fail, total); - return 1; - } - printf("trystr_run: %d/%d ok\n", total, total); - return 0; -}