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

@@ -37317,7 +37317,28 @@ fn cgletbody(c: *cgen, n: *node, off: i32) void = {
emitline("\tMOVQ\t$0, ");
emitoff(off: i64);
emitline("(BP)\n");
} else { if (zsz > 8) {
} else { if (zsz == 8) {
// #213: an 8B composite (single-field struct / tagged) is
// neither an 8B primitive nor zsz>8, so it fell through
// un-zeroed while cstage emits MOVQ $0 (cgen.c N_LET
// `else if (sz == 8)`); a read-before-init then saw stack
// garbage (cs!=ww byte-id + a latent garbage-read). Match
// cstage's immediate MOVQ $0, checked BEFORE the run arm
// below so an 8B slot stays one immediate store, not
// XORQ+MOVQ (rule-10 byte-id).
emitline("\tMOVQ\t$0, ");
emitoff(off: i64);
emitline("(BP)\n");
} else { if (zsz > 0) {
// #16: the run arm was gated `zsz > 8`, so a SUB-8
// aggregate (`let c: [3]u8;` = 3, a 3-byte struct, etc.)
// matched no arm and fell through un-zeroed — the exact
// stack-garbage read ken's bytes verdict pinpointed
// (ltrim_cases' `let c: [3]u8;`), BOTH stages, gate-blind
// (#263). cstage widened its `!n->rhs && sz > 8` gate to
// `sz > 0` symmetrically; the MOVL/MOVB tail already sizes
// the run to any 1..7-byte extent. (`[0]T`, zsz == 0, needs
// no stores — the lone XORQ is skipped, matching cstage.)
emitline("\tXORQ\tAX, AX\n");
let zi: i32 = 0;
for (zi + 8 <= zsz) {
@@ -37338,19 +37359,6 @@ fn cgletbody(c: *cgen, n: *node, off: i32) void = {
emitline("(BP)\n");
zi += 1;
};
} else { if (zsz == 8) {
// #213: an 8B composite (single-field struct / tagged) is
// neither an 8B primitive nor zsz>8, so it fell through
// un-zeroed while cstage emits MOVQ $0 (cgen.c N_LET
// `else if (sz == 8)`); a read-before-init then saw stack
// garbage (cs!=ww byte-id + a latent garbage-read). Match
// cstage. Sub-8 (4B/1B) composites stay un-zeroed — cstage
// doesn't zero them either, so zeroing here would re-
// diverge; that sub-8 read-before-init garbage is a SHARED
// latent, out of this slice's scope.
emitline("\tMOVQ\t$0, ");
emitoff(off: i64);
emitline("(BP)\n");
}; }; };
};
c.lastwasreturn = 0;