test: migrate str/slice global family-1 batch to test/lang @test, retire C twins (fold-2)
Continue fold-2 (after 374e97b): migrate the remaining Family-1 str/slice
global + literal + index + call-arg group from bespoke build+run C twins to
test/lang @test, retiring each twin in the same commit. Runtime coverage MOVES
from $(TESTS) to test-lang (T1 runs+asserts via `ww test`) + test-lang-byteid
(T2 keeps cs==ww .s byte-id); coverage is preserved, the $(TESTS) headline
drops 9 (434->425). All asserts are primitive int/u8/bool comparisons (no
fmt/strconv in the assert path); the slice-store/index rows reset the global
each fn and sum ADJACENT elements so a dropped/mis-strided/over-wide word FAILS.
989_globslicefield_run.c -> glob_slice_field_test.ww (slice field of a global struct: g.f=<slice> stores full 24B header; len/cap/non-zero-offset + str/scalar controls)
989_globstrslice_run.c -> glob_str_slice_arg_test.ww (global str sliced with default hi passed as call arg loads its len word; explicit-hi control)
989_trystr_run.c -> try_str_unwrap_test.ww (`!` unwrap of str-success tagged union shuffles the str header for ident-source/error-first/success-first)
797_len_strglobal_run.c -> len_str_global_test.ww (len(str-global) loads .len via name(SB); local-str control)
801_litstr_pseudo_run.c -> lit_str_pseudo_test.ww (string-literal .len/.ptr pseudo-field; empty/multibyte + arg-passthrough)
803_globalidx_run.c -> global_index_test.ww (global str/slice index read/addr-of/store/compound, esz 1/4; local regression pins)
903_tuple_elem_slice_len.c -> tuple_elem_slice_len_test.ww (len(t.N) of a slice/str tuple element loads .len at +8; 2/3-slice, str-slice both orders)
927_composite_call_arg_run.c-> composite_call_arg_test.ww (slice-returning CALL passed inline as a composite arg; canonical/letslice/two-call/middle/nested/scalar/tagged)
952_slicecopy_assign_run.c -> slice_copy_assign_test.ww (bulk slice-copy-assign `arr[lo:hi]=bs`, esz 1/4, field/via-ptr/local bases; reslice-read companion)
rd_reslice asserts the TRUE value 360 (the .c twin's want=104 was 360 & 0xFF,
an exit-code truncation). 723_composite_call_arg.c's comment repointed to the
new test/lang location. 802_lenidx_run.c is DEFERRED (it carries //ww:error
reject rows — needs a value-rows-only split + a slim reject carrier, a fold-3
pass). Bump LANGBYTEID_EXPECTED_MIN 22->31 to ratchet the new corpus floor.
This commit is contained in:
122
test/lang/composite_call_arg_test.ww
Normal file
122
test/lang/composite_call_arg_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
58
test/lang/glob_slice_field_test.ww
Normal file
58
test/lang/glob_slice_field_test.ww
Normal file
@@ -0,0 +1,58 @@
|
||||
// glob_slice_field_test — a SLICE field of a module-GLOBAL struct, assigned
|
||||
// with `g.field = <slice>`, 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);
|
||||
};
|
||||
34
test/lang/glob_str_slice_arg_test.ww
Normal file
34
test/lang/glob_str_slice_arg_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
143
test/lang/global_index_test.ww
Normal file
143
test/lang/global_index_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
22
test/lang/len_str_global_test.ww
Normal file
22
test/lang/len_str_global_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
39
test/lang/lit_str_pseudo_test.ww
Normal file
39
test/lang/lit_str_pseudo_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
98
test/lang/slice_copy_assign_test.ww
Normal file
98
test/lang/slice_copy_assign_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
41
test/lang/try_str_unwrap_test.ww
Normal file
41
test/lang/try_str_unwrap_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
64
test/lang/tuple_elem_slice_len_test.ww
Normal file
64
test/lang/tuple_elem_slice_len_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
Reference in New Issue
Block a user