wwstage: size struct tuple-field slot via fieldsize (#237)

The wwstage checker `fieldslotsize` (check.ww) summed each struct field's
SLOT width to stamp the enclosing struct's tinfo.slotsize, but had no
TY_TUPLE arm — a tuple-typed field fell through to the 8B default. So
`struct { f: ([]u8,[]u8) }` stamped slotsize=8 while size=48 (the natural
element sum, correct). A `let s: S` slot is allocated off ti.slotsize
(cgenutil.ww slotsize), so wwstage reserved an 8-byte frame slot for a
48-byte struct: a SILENT stack-corrupting miscompile.

cstage has no size/slotsize split — it sizes the field at f->type->size=48
throughout — so the stages diverged on the emitted frame ($16 wwstage vs
$64 cstage), invisible to a cstage-only check and caught only by cs==ww
byte-id (rule 10).

Add the TY_TUPLE arm (return the tuple's own slotsize, the per-element slot
sum already stamped at the N_TTUPLE arm with slices at 24 each). This
aligns the checker's field-slotsize with cgenutil.ww fieldsize, which
already returns the tuple's natural size (48). The stale comment claiming
"TY_TUPLE inside a struct currently defaults to 8 in cgenutil" is removed —
fieldsize stopped defaulting to 8 at the 2026-05-23 review.

Test 930 pins cs==ww .s byte-id for a struct with a tuple field (with and
without a leading scalar field, foff 0 and !=0); pure frame-size gate, no
runtime — the divergence is fully visible in the emitted assembly. No
selfhost source has a tuple-typed struct field, so the w6c/wwdump combined
amalgams regen with no asm change (byte-id-neutral bootstrap).
This commit is contained in:
2026-06-01 17:29:32 +09:00
parent 38a906cd9b
commit 93d8ece740
5 changed files with 174 additions and 9 deletions

View File

@@ -11762,10 +11762,15 @@ fn fieldslotsize(ft: *tinfo) u64 = {
if (fk == tykind.TY_PTR || fk == tykind.TY_FN ||
fk == tykind.TY_CHAN) { return 8u64; };
if (fk == tykind.TY_STR) { return t.size; };
// #237: a tuple-typed struct field carries its own slot total (the
// per-element slot sum stamped at the N_TTUPLE arm above — slices at
// 24 each). Without this it fell to the 8B default below, undersizing
// the enclosing struct's slotsize (size stayed correct), so a `let s:S`
// slot was too small — a silent stack-corrupting miscompile. Aligns
// with cgenutil.ww fieldsize, which already returns the tuple's size.
if (fk == tykind.TY_TUPLE) { return t.slotsize; };
// Primitives keep natural width inside structs (matches
// cgenutil fieldsize: primsize, not pad-to-8). TY_TUPLE inside a
// struct currently defaults to 8 in cgenutil — preserve that
// shape until a future graduation aligns the two.
// cgenutil fieldsize: primsize, not pad-to-8).
if (fk == tykind.TY_BOOL || fk == tykind.TY_RUNE ||
fk == tykind.TY_I8 || fk == tykind.TY_I16 ||
fk == tykind.TY_I32 || fk == tykind.TY_I64 ||

View File

@@ -1512,10 +1512,15 @@ fn fieldslotsize(ft: *tinfo) u64 = {
if (fk == tykind.TY_PTR || fk == tykind.TY_FN ||
fk == tykind.TY_CHAN) { return 8u64; };
if (fk == tykind.TY_STR) { return t.size; };
// #237: a tuple-typed struct field carries its own slot total (the
// per-element slot sum stamped at the N_TTUPLE arm above — slices at
// 24 each). Without this it fell to the 8B default below, undersizing
// the enclosing struct's slotsize (size stayed correct), so a `let s:S`
// slot was too small — a silent stack-corrupting miscompile. Aligns
// with cgenutil.ww fieldsize, which already returns the tuple's size.
if (fk == tykind.TY_TUPLE) { return t.slotsize; };
// Primitives keep natural width inside structs (matches
// cgenutil fieldsize: primsize, not pad-to-8). TY_TUPLE inside a
// struct currently defaults to 8 in cgenutil — preserve that
// shape until a future graduation aligns the two.
// cgenutil fieldsize: primsize, not pad-to-8).
if (fk == tykind.TY_BOOL || fk == tykind.TY_RUNE ||
fk == tykind.TY_I8 || fk == tykind.TY_I16 ||
fk == tykind.TY_I32 || fk == tykind.TY_I64 ||

View File

@@ -11762,10 +11762,15 @@ fn fieldslotsize(ft: *tinfo) u64 = {
if (fk == tykind.TY_PTR || fk == tykind.TY_FN ||
fk == tykind.TY_CHAN) { return 8u64; };
if (fk == tykind.TY_STR) { return t.size; };
// #237: a tuple-typed struct field carries its own slot total (the
// per-element slot sum stamped at the N_TTUPLE arm above — slices at
// 24 each). Without this it fell to the 8B default below, undersizing
// the enclosing struct's slotsize (size stayed correct), so a `let s:S`
// slot was too small — a silent stack-corrupting miscompile. Aligns
// with cgenutil.ww fieldsize, which already returns the tuple's size.
if (fk == tykind.TY_TUPLE) { return t.slotsize; };
// Primitives keep natural width inside structs (matches
// cgenutil fieldsize: primsize, not pad-to-8). TY_TUPLE inside a
// struct currently defaults to 8 in cgenutil — preserve that
// shape until a future graduation aligns the two.
// cgenutil fieldsize: primsize, not pad-to-8).
if (fk == tykind.TY_BOOL || fk == tykind.TY_RUNE ||
fk == tykind.TY_I8 || fk == tykind.TY_I16 ||
fk == tykind.TY_I32 || fk == tykind.TY_I64 ||