diff --git a/test/lang/chainidx_test.ww b/test/lang/chainidx_test.ww new file mode 100644 index 00000000..5b7e4cfd --- /dev/null +++ b/test/lang/chainidx_test.ww @@ -0,0 +1,52 @@ +// chainidx_test — a CHAINED index `m[i][k]` whose element is a str/slice must +// load the full 24B/16B header, both stages, migrated from +// test/wcc/989_chainidx_run.c (#22). The chained-index arm in cgindex read +// the element tinfo for esz/signedness but NOT elemisstr/elemisslice, so a +// chained index over [N][M]str / [N][M][]T loaded only the .ptr word and left +// .len/.cap stale — a cat-A divergence the bootstrap corpus never hits. +// +// Each "row" is a distinct chained-index SHAPE (str read, str as call-arg, +// slice read, scalar control), not data, so this file uses per-shape asserts +// rather than the uniesc row-loop idiom. The [N][M] arrays are built by +// per-element store — the nested array literal `[[..],[..]]` is independently +// #270-1c-blocked (orthogonal). + +package chainidx_test; + +fn takelen(s: str) int = { return len(s): int; }; + +@test fn chain_str() void = { + let m: [2][2]str; + m[0][0] = "aa"; m[0][1] = "bbb"; + m[1][0] = "c"; m[1][1] = "ddddd"; + + // chain_str_read: m[1][1] = "ddddd", len 5 (bug dropped the .len load). + let str0: str = m[1][1]; + assert(len(str0) == 5); + + // chain_str_call: the chained str element passed as a CALL ARG — needs + // both the c2 push-side recognizer and this c3 read-side header load. + assert(takelen(m[1][1]) == 5); +}; + +@test fn chain_slice() void = { + let a: []int = [1, 2]; + let b: []int = [9, 9, 9, 9, 9, 9, 9]; + let m: [2][2][]int; + m[0][0] = a; m[0][1] = a; + m[1][0] = a; m[1][1] = b; + + // chain_slice_read: m[1][1] = b, len 7 (bug dropped the .len/.cap load). + let xs: []int = m[1][1]; + assert(len(xs) == 7); +}; + +@test fn chain_scalar() void = { + let m: [2][2]int; + m[0][0] = 1; m[0][1] = 2; + m[1][0] = 3; m[1][1] = 42; + + // chain_scalar_read: control — the scalar chained index the arm already + // handled; c3 must not regress it. m[1][1] == 42. + assert(m[1][1] == 42); +}; diff --git a/test/lang/gunsigned_test.ww b/test/lang/gunsigned_test.ww new file mode 100644 index 00000000..668353f5 --- /dev/null +++ b/test/lang/gunsigned_test.ww @@ -0,0 +1,40 @@ +// gunsigned_test — a module-GLOBAL unsigned ident on the divide / shift / +// relational path must select the UNSIGNED opcode (DIVQ / SHRQ / JA), migrated +// from test/wcc/989_gunsigned_run.c (#25). nodeisunsigned read the LOCAL's +// declared tnode and fell to signed (false) for a module-global ident, so a +// u64 global fed to `/ % >> >= >` got the signed opcode and diverged from +// cstage on a high-bit-set value — the cat-A signature. +// +// The subject IS the module-global IDENT, so the rows MUST stay package-scope +// globals exercised directly: a struct-array row-table would route the values +// through N_INDEX/N_DOT and never hit the N_IDENT-global codegen path the bug +// lives on (so this file does not use the uniesc row-loop idiom — the shapes, +// not the data, are what vary). + +package gunsigned_test; + +let g: u64 = 0; +let s: i64 = 0; + +@test fn global_unsigned_path() void = { + // global_ushr: (1<<63) >> 1 unsigned == 1<<62 (bug: signed SARQ). + g = 9223372036854775808u64; + let r0: u64 = g >> 1; + assert(r0 == 4611686018427387904u64); + + // global_udiv: (1<<63) / (1<<62) unsigned == 2 (bug: signed IDIVQ). + g = 9223372036854775808u64; + let r1: u64 = g / 4611686018427387904u64; + assert(r1 == 2u64); + + // global_ucmp: (1<<63) > 1 is true unsigned, false signed (bug: JG). + g = 9223372036854775808u64; + assert(g > 1u64); + + // signed_global_ctl: a SIGNED i64 global must KEEP the signed shift — + // -8 >> 1 == -4 (control: c5 reads the stamp, no blanket + // global -> unsigned over-conversion). + s = -8; + let r3: i64 = s >> 1; + assert(r3 == -4); +}; diff --git a/test/lang/idxarg_test.ww b/test/lang/idxarg_test.ww new file mode 100644 index 00000000..5ddc098c --- /dev/null +++ b/test/lang/idxarg_test.ww @@ -0,0 +1,50 @@ +// idxarg_test — an INDEXED slice/str element passed as a call argument must +// push its full multi-word header, both stages, migrated from +// test/wcc/989_idxarg_run.c (#45/#46). pushargsrev sizes a call arg via +// nodeisslice/nodeisstr; pre-fix nodeisslice had no N_INDEX arm (#45, slice +// element fell to a 1-word scalar push) and nodeisstr's N_INDEX arm was a +// base-kind whitelist that only knew N_IDENT/N_DOT bases (#46, a non-ident/ +// non-dot base fell through to a 1-word push). Either way the callee read +// garbage .len/.cap — a cat-A divergence invisible to byte-id. +// +// Each "row" is a distinct index-base SHAPE (slice element, N_CALL base, +// N_IDENT base, plain local control), not data, so this file uses per-shape +// asserts rather than the uniesc row-loop idiom. + +package idxarg_test; + +fn takeintlen(xs: []int) int = { return len(xs): int; }; +fn takestrlen(s: str) int = { return len(s): int; }; + +fn getarr() []str = { + let a: []str = ["x", "ddddd", "zz"]; + return a; +}; + +@test fn idx_slice_arg() void = { + // slice_elem_arg (#45): a []int slice element `rows[1]` as a call arg — + // pre-fix nodeisslice had no N_INDEX arm, so a 1-word push gave the + // callee garbage len. len(rows[1]) == len(b) == 2. + let a: []int = [10, 20, 30, 40]; + let b: []int = [1, 2]; + let rows: [2][]int = [a, b]; + assert(takeintlen(rows[1]) == 2); +}; + +@test fn idx_str_arg() void = { + // call_str_elem (#46): an index whose base is an N_CALL (`getarr()[1]`) + // — the non-ident/non-dot base the old whitelist missed. len == 5. + assert(takestrlen(getarr()[1]) == 5); + + // ident_str_elem (#46 control): an N_IDENT base `arr[1]` — the case the + // old whitelist DID handle; the stamp read must not regress it. + let arr: [3]str = ["x", "ddddd", "zz"]; + assert(takestrlen(arr[1]) == 5); +}; + +@test fn idx_local_arg() void = { + // slice_local_arg (control): a plain non-indexed str/slice local arg + // (the c1 N_IDENT-local arm) — c2 must leave it alone. len == 3. + let xs: []int = [7, 8, 9]; + assert(takeintlen(xs) == 3); +};