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

@@ -56,16 +56,15 @@ runwait(const char *cmd)
return -1;
}
/* beid — include this row in the cstage-vs-wwstage byte-id sweep. The
* empty_zero row is EXCLUDED (beid=0): an empty `[0]int = []` module global
* has a PRE-EXISTING cgen divergence unrelated to #9 (a checker-only fix) —
* wwstage emits an extra zero-width `DATAW main.X(SB),""` row that cstage
* omits; both run identically (return 7). Filed as task #15 (empty-array-
* global DATA emission, NOT folded into #9). A LOCAL `[0]int = []` is no
* escape — it diverges differently (wwstage allocates a $16 frame, cstage
* $0), so there is no byte-id-clean spelling of an empty zero-length array
* to restructure toward. The infer rows DO converge and pin that the
* accept-path asm stays byte-identical. */
/* beid — include this row in the cstage-vs-wwstage byte-id sweep. Both the
* empty_zero global (`let X:[0]int=[]`) and the empty_zero_local row now
* converge byte-identically (beid=1): #15 closed the empty-`[0]T` cgen
* divergence — wwstage's spurious zero-width `DATAW main.X(SB),""` (cstage
* omits a zero-byte global) and its over-allocated `$16` local frame (cstage
* `$0` — a zero-length array reserves no slot) are both gated on the array
* being non-empty. See .ai/rob-15-spec.md: cgen.ww emitletdataw gates the
* array DATAW on `sz > 0`; cgenutil.ww slotsize returns 0 for a TY_ARRAY of
* alen==0. Both forms still run 7. */
struct row { const char *label; const char *src; int want; int beid; };
static const struct row rows[] = {
@@ -79,14 +78,39 @@ static const struct row rows[] = {
20, 1 },
/* empty_zero — a real zero-length array (`[0]int = []`) stays VALID.
* beid=0: pre-existing empty-array-global cgen divergence (see beid). */
* beid=1: #15 closed the empty-array-global DATAW divergence. */
{ "empty_zero",
"package main;\n"
"let X: [0]int = [];\n"
"export fn main() i32 = {\n"
"\treturn 7;\n"
"};\n",
7, 0 },
7, 1 },
/* empty_zero_local — a LOCAL zero-length array (`[0]int = []`) reserves
* no frame slot (#15: cstage `$0`, wwstage was `$16`). beid=1. */
{ "empty_zero_local",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet x: [0]int = [];\n"
"\treturn 7;\n"
"};\n",
7, 1 },
/* void_local — a zero-SIZE (not zero-length) local: `done = void` sizes
* 0 via slotsize, but cstage cglet defaults a non-composite local to an
* 8B slot. #15 dropped localreserve's sub-8 floor, which had masked this
* — letslotsize now floors a void local to 8 (cstage parity). beid=1: a
* regression here (void slot 0) collides with the spilled param. */
{ "void_local",
"package main;\n"
"type done = void;\n"
"export fn main() i32 = {\n"
"\tlet d: done;\n"
"\tlet a: i32 = 7;\n"
"\treturn a;\n"
"};\n",
7, 1 },
/* infer_len — `[_]` infer is unaffected, `.len` reads the real count. */
{ "infer_len",