test: migrate deref/narrow/stride family-6 to test/lang @test, retire C twins (fold-2)

Continue fold-2: migrate the pointer-deref / narrow-load / pointer-array-stride
family 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); the
$(TESTS) headline drops 5. Every assert is a primitive int/bool comparison with
a width-preserving (`==`) sink so a stale high half or wrong stride FAILS; the
narrow-signed rows route through an `: i32` cast against a 64B-widened literal
to force sign-extension onto the load. Each .c row maps to one inline @test fn
(50 cases total, strict superset of the C rows):

  947_deref_narrow_run.c  -> deref_narrow_test.ww  (#116, 10 rows: *p reads pointee width not 8B MOVQ; i32/u32/u8/i8/i16/u16 + !i32-alias + enum-i8 + bool/i64 controls)
  949_ptrarr_index_run.c  -> ptrarr_index_test.ww  (#61, 23 rows: p[i]/(&p[i])/(*p)[i] stride by size(T); {1,2,4,8}B, const+var idx, param/local/cast bases, neighbor guards, nested *[2][3], *[3]str header, siphash round())
  949_dotbase_arr_run.c   -> dotbase_arr_test.ww   (#135, 3 rows: (*struct).arrayfield[i] read/write/compound addresses the field)
  944_def_amp_idx_run.c   -> def_amp_idx_test.ww   (#94, 6 rows: &D[i] over a def-array; +plain/read/2D controls)
  944_alias_amp_idx_run.c -> alias_amp_idx_test.ww (#5, 8 rows: &a[i] over an alias-typed base classifies off the chased type; local/global, narrow, fwd-ref, plain+str controls)

The two sibling .c (949_dotbase_addr_slice_run, 944_alias_def_addr_run) stay in
$(TESTS): the first is run-only byteid=0 (#254 cs!=ww rows), the second carries
a LOUD rule-7 reject row — both routed to fold-3 (task #7). Bump
LANGBYTEID_EXPECTED_MIN 38->43.
This commit is contained in:
2026-06-22 13:34:28 +09:00
parent a6b74e46c4
commit c9c5f6406f
11 changed files with 534 additions and 1543 deletions

View File

@@ -0,0 +1,96 @@
// deref_narrow_test — an integer pointer deref (`*p`) must read its pointee's
// ACTUAL width, not a fixed 8B MOVQ, migrated from test/wcc/947_deref_narrow_run.c
// (#116). Before #116 the TK_STAR integer arm emitted a raw `MOVQ (AX),AX`
// regardless of pointee size, so `*p` over a packed `*i32`/`*u32`/`*iN` slot
// pulled the next 47 bytes into the high half of RAX. Truncating sinks (an i32
// store) dropped the garbage; WIDTH-PRESERVING sinks (a `==` compare that
// widens the literal to 8B → CMPQ) saw it. The fix routes the load through
// localloadop(n->type), the MOVSXD/MOVSWQ/MOVSBQ + MOVL/MOVZWQ/MOVZBQ dispatch
// already shared by IDENT/DOT/index loads. The .s is byte-identical cs==ww, so
// these behavioral @tests are the runtime net the byte-id gate can't be.
//
// Each @test deliberately uses a `==` (CMPQ-width) sink so a stale high half
// would make the assert FAIL; the narrow-signed rows route through an `: i32`
// cast whose compare against a 64B-widened literal forces the sign-extension
// onto the load. alias/enum rows exercise the TY_NAMED/TBANG/TY_ENUM peel in
// localloadop's tinfo lookup; the struct-field base on the i16/u16/alias/enum
// rows dodges the sibling [N]alias array-init store-width concern (out of #116
// scope). i64_ctrl is the regression guard that a width-correct case keeps MOVQ.
package deref_narrow_test;
type err = !i32;
type abox = struct { v: err };
type ibox = struct { v: i16, m: u16 };
type ubox = struct { v: u16, m: u16 };
type flag = enum i8 { A = -2i8, B = 1i8 };
type fbox = struct { v: flag, m: u8 };
@test fn i32_cmpq() void = {
let buf: [4]i32 = [10, 20, 30, 40];
let p: *i32 = &buf[1];
assert(*p == 20);
};
@test fn u32_zeroext() void = {
let buf: [3]u32 = [10u32, 20u32, 0xFFFFFFFFu32];
let p: *u32 = &buf[2];
assert(*p == 0xFFFFFFFFu32);
};
@test fn u8_zeroext() void = {
let buf: [3]u8 = [10u8, 20u8, 0xFFu8];
let p: *u8 = &buf[2];
assert(*p == 0xFFu8);
};
@test fn i8_signext() void = {
let buf: [3]i8 = [-1i8, -2i8, -3i8];
let p: *i8 = &buf[1];
let v: i32 = (*p): i32;
assert(v == -2i32);
};
@test fn alias_signext() void = {
// `type err = !i32; *err` — the TY_NAMED/TBANG peel must reach the
// underlying i32 width for MOVSXD to fire.
let b: abox = abox { v = -7i32: err };
let p: *err = &b.v;
let r: i32 = (*p): i32;
assert(r == -7i32);
};
@test fn bool_ctrl() void = {
let buf: [3]bool = [true, false, true];
let p: *bool = &buf[1];
assert(*p == false);
};
@test fn i16_signext() void = {
let b: ibox = ibox { v = -200i16, m = 0xABCDu16 };
let p: *i16 = &b.v;
let r: i32 = (*p): i32;
assert(r == -200i32);
};
@test fn u16_zeroext() void = {
let b: ubox = ubox { v = 0xFFFFu16, m = 0xABCDu16 };
let p: *u16 = &b.v;
assert(*p == 0xFFFFu16);
};
@test fn enum_signext() void = {
// `type flag = enum i8 { ... }; *flag` — TY_NAMED/TY_ENUM peel in
// typeissigned + tinfo.size on the NAMED wrapper must land on MOVSBQ.
let b: fbox = fbox { v = flag.A, m = 0xABu8 };
let p: *flag = &b.v;
let r: i32 = ((*p): i8): i32;
assert(r == -2i32);
};
@test fn i64_ctrl() void = {
// width-correct case — MUST still emit MOVQ; regression guard.
let buf: [3]i64 = [100i64, 200i64, 300i64];
let p: *i64 = &buf[1];
assert(*p == 200i64);
};