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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user