wcc/cgen: #15 empty zero-length array emit — no spurious DATAW, cstage frame formula (wwstage)

An empty zero-length array diverged cs!=ww in asm (both ran correct=7):
a [0]int global emitted a spurious DATAW main.X(SB),"", and a [0]int local
reserved a $16 frame slot. cstage emits neither. wwstage-only, byte-id-only.

The global DATAW emit is now gated on sz > 0 (skips the empty array).
The local frame: localreserve dropped its sub-8 floor (if asz<8 asz=8) to
mirror cstage's localslot formula (frame+sz+7)&~7 -- but that floor was
MASKING slotsize(TY_VOID)=0 (a void local), which cstage defaults to 8B;
removing the floor alone collided the zero-size void slot with a spilled
param (a real miscompile -- 1132 self-compile hunks). So letslotsize now
returns 8 for a void local, while empty-struct / [0]-array stay genuine 0.
The frame formula is byte-id-neutral for every sz>=1 local (round8 already
>= 8); only true zero-size cases change.

cstage unchanged (w6c md5 unchanged). byte-id 990-997 8/8 (the full
self-compile is what caught the void-local class); test/wcc/820 un-carves
the #9 empty-[0] byte-id exclusion + adds void-local/local-[0] rows.
This commit is contained in:
2026-06-09 01:57:46 +09:00
parent 606c16a28f
commit d8e2a0692c
5 changed files with 150 additions and 51 deletions

View File

@@ -2417,13 +2417,26 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
// intercept here: the checker (inferarraylen, check.ww) stamps the
// real element count onto the array type's length child before cgen
// runs, so slotsize reads it like any explicit `[N]T` (#7).
if (n.lhs != nil) { return slotsize(c, n.lhs); };
// Annotation-less init: defer to the call's return type if we
// can infer it. Tagged-union returns need 24B; everything else
// matches slotsize on the inferred type.
let inferred: *node = inferletcalltype(c, n.rhs);
if (inferred != nil) { return slotsize(c, inferred); };
return 8;
// Annotation-less init: defer to the call's return type if we can
// infer it. Tagged-union returns need 24B; everything else matches
// slotsize on the inferred type.
let tn: *node = n.lhs;
if (tn == nil) { tn = inferletcalltype(c, n.rhs); };
if (tn == nil) { return 8; };
let s: i32 = slotsize(c, tn);
// #15: cstage cglet (cmd/w6c/cgen.c:12321) defaults a local's slot
// to 8 — only ARRAY/SLICE/STR/STRUCT/TUPLE/TAGGED take the real
// type size. slotsize returns 0 for a void/`done`-aliased scalar
// local; localreserve no longer applies a sub-8 floor (it mirrors
// cstage's no-floor localslot), so a void slot must be floored here
// instead, else its zero width collides with the next local. A
// genuine empty struct or [0]T array (also slotsize 0) keeps its 0.
if (s == 0) {
let ti: *tinfo = tn.type_: *tinfo;
if (ti != nil) { ti = tichase(ti); };
if (ti != nil && ti.kind == tykind.TY_VOID) { return 8; };
};
return s;
};
// #48 A.6.3d: AST walker retired. resolvewalk (check.ww:426-436) stamps