w6c+wwstage: full-size aggregate copy for deref-rhs let-init (#265 fold-1)

A `let c: T = *p` (T a struct or array, >8B) copied no full aggregate:
cstage dropped the init entirely (c read garbage); wwstage emitted only
the scalar `MOVQ AX,off(BP)` tail (first 8 bytes). Both wrong, differently
— converge BOTH stages on a size-driven slot-to-slot memcpy: cgexpr the
deref operand to the source address in AX, MOVQ AX,SI, then a MOVQ run
plus a sized MOVL/MOVW/MOVB tail over the #254 non-slot-padded ABI extent
(lu->size / structabisize for a struct, tinfo.size for an array). Mirror
arms in cgen.c N_LET and cgenstmt.ww cglet, byte-identical (rule-10).

Unblocks sha256's faithful `let copy = *h`. The by-value aggregate RETURN
ABI (array/struct return truncates to AX) is fold-2 (#267, deferred).

949 gains 6 full-readback rows (every member written distinct + summed,
so a truncated copy fails): struct{[4]u32} 16B, struct{[8]u32} 32B via
both *(&s) and *p (sha256 shape), bare [4]u32, and non-8-mult tails
([3]u32 12B → MOVL, [11]u8 11B → MOVW+MOVB). w6c+wwdump combined.ww regen
(#110). 61/61 949, test-unit 240, sizelint, smoke green.
This commit is contained in:
2026-06-02 09:31:44 +09:00
parent 0afe4225cd
commit 4d3f8467a8
5 changed files with 331 additions and 0 deletions

View File

@@ -750,6 +750,78 @@ static const struct row rows[] = {
" case void => yield 1: i32;\n"
" };\n"
"};\n", 63, 1 },
/* #265 fold-1 aggregate deref-rhs let-init `let c: T = *p` (T a
* struct or array, >8B). Pre-fix NEITHER stage copied the whole
* aggregate: cstage DROPPED the copy entirely (c read garbage);
* wwstage copied only the FIRST 8 bytes (the scalar `MOVQ AX,off`
* tail). Fix: both stages memcpy the ABI-size aggregate slot→slot
* via SI — a MOVQ run plus a sized MOVL/MOVW/MOVB tail. Converged
* (rule-10, byteid=1). Each row writes DISTINCT values to ALL
* members and reads back EVERY member (sum), so a partial/zero copy
* fails — a c[0]-only readback would pass a truncated copy. Covers:
* struct{[4]u32} 16B + struct{[8]u32} 32B (sha256 `*h` shape, both
* `*(&s)` and `*p` pointer-ident) + a bare [4]u32 array + non-8-mult
* tails ([3]u32 12B → MOVL tail; [11]u8 11B → MOVW+MOVB tail). The
* by-value aggregate RETURN ABI is fold-2 (#267, deferred). */
{ "deref_struct16",
"package main;\n"
"type t = struct { h: [4]u32 };\n"
"export fn main() i32 = {\n"
" let s: t;\n"
" s.h[0]=10u32; s.h[1]=20u32; s.h[2]=30u32; s.h[3]=40u32;\n"
" let c: t = *(&s);\n"
" return (c.h[0]+c.h[1]+c.h[2]+c.h[3]): i32;\n"
"};\n", 100, 1 },
{ "deref_struct32",
"package main;\n"
"type t = struct { h: [8]u32 };\n"
"export fn main() i32 = {\n"
" let s: t;\n"
" s.h[0]=1u32; s.h[1]=2u32; s.h[2]=3u32; s.h[3]=4u32;\n"
" s.h[4]=5u32; s.h[5]=6u32; s.h[6]=7u32; s.h[7]=8u32;\n"
" let c: t = *(&s);\n"
" return (c.h[0]+c.h[1]+c.h[2]+c.h[3]\n"
" +c.h[4]+c.h[5]+c.h[6]+c.h[7]): i32;\n"
"};\n", 36, 1 },
{ "deref_ptr32",
"package main;\n"
"type t = struct { h: [8]u32 };\n"
"export fn main() i32 = {\n"
" let s: t;\n"
" s.h[0]=1u32; s.h[1]=2u32; s.h[2]=3u32; s.h[3]=4u32;\n"
" s.h[4]=5u32; s.h[5]=6u32; s.h[6]=7u32; s.h[7]=8u32;\n"
" let p: *t = &s;\n"
" let c: t = *p;\n"
" return (c.h[0]+c.h[1]+c.h[2]+c.h[3]\n"
" +c.h[4]+c.h[5]+c.h[6]+c.h[7]): i32;\n"
"};\n", 36, 1 },
{ "deref_array16",
"package main;\n"
"export fn main() i32 = {\n"
" let s: [4]u32;\n"
" s[0]=5u32; s[1]=6u32; s[2]=7u32; s[3]=8u32;\n"
" let c: [4]u32 = *(&s);\n"
" return (c[0]+c[1]+c[2]+c[3]): i32;\n"
"};\n", 26, 1 },
{ "deref_tail12",
"package main;\n"
"export fn main() i32 = {\n"
" let s: [3]u32;\n"
" s[0]=7u32; s[1]=8u32; s[2]=9u32;\n"
" let c: [3]u32 = *(&s);\n"
" return (c[0]+c[1]+c[2]): i32;\n"
"};\n", 24, 1 },
{ "deref_tail11",
"package main;\n"
"export fn main() i32 = {\n"
" let s: [11]u8;\n"
" s[0]=1u8; s[1]=2u8; s[2]=3u8; s[3]=4u8; s[4]=5u8;\n"
" s[5]=6u8; s[6]=7u8; s[7]=8u8; s[8]=9u8; s[9]=10u8;\n"
" s[10]=11u8;\n"
" let c: [11]u8 = *(&s);\n"
" return (c[0]+c[1]+c[2]+c[3]+c[4]+c[5]\n"
" +c[6]+c[7]+c[8]+c[9]+c[10]): i32;\n"
"};\n", 66, 1 },
{ NULL, NULL, 0, 0 }
};