cgen: store a 2-byte struct-literal field with MOVW, not an over-wide MOVQ (#15)

cg_structlit_fill/cgstructlitfill dispatched scalar field stores as {1->MOVB, 4->MOVL, else->MOVQ} with no fsz==2 case, so a 2-byte field was stored with an 8-byte MOVQ. Interior over-stores were harmlessly overwritten by the next field, but the LAST field at the frame edge corrupted the saved base pointer: an (S|e) union success variant places the struct payload after the 8B tag, landing the last field at -4(BP), so MOVQ AX,-4(BP) wrote into saved [BP] and POPQ BP restored garbage — a silent both-stage caller-frame clobber. Route the scalar store through the existing fldstoreop/fieldstoreop helper ({1->MOVB,2->MOVW,4->MOVL,else->MOVQ}), both stages; the #13 graduation comments already pre-documented this resolution. Pure width fix, no loud-stop (scalar widths are always {1,2,4,8} and narrowing is always correct). Value-asserting pin: an i64 sentinel live across the union-maker call (detects the clobber directly) + all members, with a non-union control.
This commit is contained in:
2026-06-28 01:59:21 +09:00
parent b691a6d8c3
commit 8fa59f9d45
3 changed files with 72 additions and 21 deletions

View File

@@ -5189,11 +5189,10 @@ export fn dotchainresolve(c: *cgen, n: *syntax.node,
// preserved because the existing inline code's reload-before-each-
// store pattern matches the helper's per-store reload exactly.
//
// Graduation note (task #13): the scalar store currently uses the
// explicit {1→MOVB, 4→MOVL, else MOVQ} dispatch to match cstage
// byte-identically — cstage hasn't yet learned MOVW for fsz==2. Once
// #13 aligns both stages, the dispatch can switch to fieldstoreop
// which already returns MOVW where appropriate.
// The scalar store routes through fieldstoreop (full field-width
// {1→MOVB, 2→MOVW, 4→MOVL, else MOVQ}); the missing MOVW for fsz==2
// over-stored a 2-byte tail field past its slot into saved BP on a
// union-return success variant (#15). Symmetric with cstage fldstoreop.
fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
mode: i32, srcoff: i32, srcname: str,
disp: i32) void = {
@@ -5704,16 +5703,13 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
};
fi = nil;
} else {
// Explicit {1→MOVB, 4→MOVL, else MOVQ}
// dispatch (not fieldstoreop) to match
// cstage byte-identically. wwstage's
// fieldstoreop would return MOVW for
// fsz==2 which cstage doesn't emit —
// tracked as task #13.
let fsz: i32 = fi.fsz;
let op: str = "MOVQ";
if (fsz == 1) { op = "MOVB"; };
if (fsz == 4) { op = "MOVL"; };
// Full field-width store via fieldstoreop
// {1→MOVB, 2→MOVW, 4→MOVL, else MOVQ}: the
// missing MOVW for fsz==2 over-stored a
// 2-byte tail field past its slot into
// saved BP on a union-return success
// variant (#15). Symmetric with cstage.
let op: str = fieldstoreop(c, fi);
emitline("\t");
emitline(op);
emitline("\tAX, ");