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

@@ -590,12 +590,17 @@ fn localalloc(c: *cgen, name: str, sz: i32, tnode: *node) i32 = {
// (`let x = f(x)`) resolves x in the OUTER scope (Hare evals the init in
// the outer scope: harec check.c clet runs cexpr before scope_define).
fn localreserve(c: *cgen, name: str, sz: i32, tnode: *node) *local = {
let asz: i32 = sz;
if (asz < 8) { asz = 8; };
if ((asz & 7) != 0) { asz = (asz + 7) & ~7; };
c.frame += asz;
// #15: mirror cstage localslot (cmd/w6c/cgen.c:1900) —
// `frame = (frame + size + 7) & ~7`, NO sub-8 floor. Identical to
// the old `max(8, round8(sz))` accumulation for every sz>0 (frame
// stays 8-aligned, so a 1..8B slot still costs 8); the only change
// is a zero-size slot (`[0]T`, void) adds 0, matching cstage's $0
// frame instead of over-reserving 8. local.sz is read only by the
// @-prefix grow-check in localadd, never for user lets, so storing
// the raw sz here is inert.
c.frame = (c.frame + sz + 7) & ~7;
let off: i32 = 0 - c.frame;
let l: *local = alloc(local{name=name, off=off, sz=asz, tnode=tnode, lnext=nil})!;
let l: *local = alloc(local{name=name, off=off, sz=sz, tnode=tnode, lnext=nil})!;
return l;
};
@@ -3055,7 +3060,14 @@ fn emitletdataw(c: *cgen, file: *node) void = {
route = true;
};
};
if (route) {
// #15: a zero-length array (`[0]T`)
// has no bytes — cstage emits no DATA
// row; wwstage's unguarded emit produced
// a spurious `DATAW name(SB),""`. sz
// (letemitsize, cgen.ww:2758) is 0 for
// [0]T → skip. (Non-empty [N>0] arrays
// keep sz>0.)
if (route && sz > 0) {
emitarraydata(c, "DATAW", nm,
d.nmod, dti, rh);
};