wcc: struct ABI size maxalign-rounded via structabisize (#169)
wwstage struct-return RECV and RETURN used unrounded / round-to-8 sizes where
cstage uses the maxalign-rounded lu->size / rt->size, so a struct with maxalign
8 and a sub-8 tail (e.g. struct{i64,i32}) — or a maxalign<8 struct on the
return path — unpacked with a different trailing-word width (MOVL vs MOVQ)
between stages. Value-correct either way, but a cs!=ww asm divergence.
Add a dedicated structabisize = round(natural, maxalign) used only at the two
register-ABI sites. structnaturalsize stays unrounded: cstage's >24B sret and
memory-move path (cgen.c:8150, Task #33) genuinely uses the unrounded natural
size, so the two are different sizes — rounding the shared metric breaks 995.
maxalign derives from each field's tinfo.align (mirrors cstage check.c:708),
not an fsz ladder (a ladder over-rounds composite [N]u8 fields).
Gate-blind (no bootstrap struct hits the maxalign-8+tail shape) — the
discriminator is the cs==ww .s byte-cmp; covered by probe 698.
This commit is contained in:
@@ -133,6 +133,49 @@ static const struct row rows[] = {
|
||||
"};\n"
|
||||
"fn main() i32 = { let f: five = mk(); return 0; };\n",
|
||||
0 },
|
||||
/* #169 RETURN, maxalign<8: struct{i32,i32,i32} natural extent 12,
|
||||
* maxalign 4 → cstage rt->size = round(12,4) = 12. The N_IDENT
|
||||
* return word-copy loop sizes on rsz; pre-fix wwstage used the
|
||||
* slot-padded totsize (round(12,8) = 16) → emitted MOVQ+MOVQ
|
||||
* where cstage emits MOVQ+MOVL. Byte-id catches it; value
|
||||
* readback (a+b+c) is correct in both stages either way. */
|
||||
{ "trip_i32_ident_return",
|
||||
"type t3 = struct { a: i32, b: i32, c: i32 };\n"
|
||||
"fn mk() t3 = {\n"
|
||||
" let s: t3 = t3 { a = 1, b = 2, c = 3 };\n"
|
||||
" return s;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = { let p: t3 = mk(); return p.a + p.b + p.c - 6; };\n",
|
||||
0 },
|
||||
/* #169 RECV, maxalign==8 + sub-8 tail: struct{i64,i32} natural
|
||||
* extent 12, maxalign 8 → cstage lu->size = round(12,8) = 16.
|
||||
* The `let p = mk()` receive sizes on structnaturalsize; pre-fix
|
||||
* it returned the unrounded 12 → emitted MOVQ+MOVL where cstage
|
||||
* emits MOVQ+MOVQ. mk returns via structlit (no RETURN-side
|
||||
* divergence), isolating the RECV split. */
|
||||
{ "pair_i64_i32_recv",
|
||||
"type pt = struct { a: i64, b: i32 };\n"
|
||||
"fn mk() pt = { return pt { a = 7i64, b = 9i32 }; };\n"
|
||||
"fn main() i32 = { let p: pt = mk(); return p.b - 9; };\n",
|
||||
0 },
|
||||
/* #169 RECV, composite [N]u8 field: struct{i32,[6]u8} natural
|
||||
* extent 10, maxalign 4 → cstage lu->size = round(10,4) = 12.
|
||||
* Guards that a [N]u8 field's natural extent (10, NOT slot-
|
||||
* rounded) feeds structabisize and rounds to maxalign, not 8:
|
||||
* pre-fix the RECV used the unrounded structnaturalsize (10) →
|
||||
* MOVW tail; the slot-padded totsize (16) would give MOVQ; only
|
||||
* the maxalign-rounded 12 gives the MOVL cstage emits. The [6]u8
|
||||
* align must come from tinfo.align (1), not an fsz ladder (which
|
||||
* would read 4 off the 6-byte slot) — here the i32 sets maxalign
|
||||
* anyway, but the row pins the composite-field path byte-id.
|
||||
* mk returns via struct literal so the bare-let local zero-init
|
||||
* (a separate same-class latent) stays out of the fixture. */
|
||||
{ "recv_i32_arr6_u8",
|
||||
"type s6 = struct { n: i32, a: [6]u8 };\n"
|
||||
"fn mk() s6 = "
|
||||
"{ return s6 { n = 4i32, a = [1u8, 2u8, 3u8, 4u8, 5u8, 6u8] }; };\n"
|
||||
"fn main() i32 = { let p: s6 = mk(); return p.n - 4; };\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
|
||||
Reference in New Issue
Block a user