wcc/cgen: zero-init sub-8-byte bare lets, both stages; bytes test honest (#16-team)

A bare 'let x: T;' with 1 <= size(T) <= 7 matched no zero-fill arm in
either stage (8B and >8B were already zeroed) - 'let c: [3]u8;' read
stack garbage. User-ruled zero-value semantics: cstage gate sz>8 ->
sz>0; wwstage zsz==8 arm hoisted above the fill-run arm (required -
8B would otherwise route into the run and diverge) and run gate
zsz>0. New 840 pin: dirty-frame probe rows, dual-dim (run + cs/ww
byte-id); discriminators fail exit-154 on pre-fix binaries.

Fused with the lib/bytes test conversion (rule 11): either half alone
turns 967 red. The old exit(signalled+10) wrapped a real 1782-count
ltrim failure to exit 0 - green depended on the garbage. Converted to
assert form (completes the 35/35 @test conversion); ltrim rows keep
the bare 'let c: [3]u8;' as the consumer proof of the fix.
This commit is contained in:
2026-06-10 20:08:58 +09:00
parent 3e2bf0612e
commit 5b212e51cf
7 changed files with 527 additions and 276 deletions

View File

@@ -12896,11 +12896,11 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
}
} else if (sz == 8) {
ins2(c, A_MOVQ, aimm(0), amem(D_BP, off));
} else if (!n->rhs && sz > 8) {
/* `let x: T;` with no rhs for a multi-word composite
* (str/slice/tuple/struct/tagged/ARRAY). Zero the slot so
* reads after the bare let see {0...} rather than
* whatever the stack already held.
} else if (!n->rhs && sz > 0) {
/* `let x: T;` with no rhs for a composite (str/slice/
* tuple/struct/tagged/ARRAY). Zero the slot so reads after
* the bare let see {0...} rather than whatever the stack
* already held.
*
* #84: arrays were excluded here (`!TY_ARRAY`), so a
* dirtied-stack `let a: [3]int;` read garbage — BOTH
@@ -12909,7 +12909,17 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
* composite. Extent is lu->size (chased ABI size, rule-13
* — never a hardcoded count×elemsize). The unrolled
* word/dword/byte run mirrors the composite path; the
* largest real local array ([256]u8) is 32 MOVQs. */
* largest real local array ([256]u8) is 32 MOVQs.
*
* #16: the gate was `sz > 8`, so a SUB-8 aggregate
* (`let c: [3]u8;` = 3, a 3-byte struct, etc.) matched
* neither this arm nor the `sz == 8` MOVQ-$0 arm above and
* fell through to NOTHING — the exact stack-garbage read
* ken's bytes verdict pinpointed (ltrim_cases' `let c:
* [3]u8;`). Widening to `sz > 0` routes 1..7-byte slots
* through the same MOVL/MOVB tail; the run already sizes
* itself to any extent. (sz == 8 stays on the immediate
* MOVQ $0 above; sz == 0 — `[0]T` — needs no stores.) */
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
int zi = 0;
while (zi + 8 <= sz) {
@@ -12928,7 +12938,6 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
zi += 1;
}
}
/* arrays left uninitialised — caller writes via index */
letlink:
/* #152: link the binding into the lookup chain AFTER its
* initializer emits, so a self-shadowing init (`let x =