// 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 4–7 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); };