wcc: widen-push spills the float payload from X0, both stages

Widening a runtime f64 into a tagged slot pushed a stale AX as the
payload while the value sat in X0 — both stages shared the push bug
(float literals dodged it because TK_FLOAT loads AX too); the
divergent pop sides then produced different garbage. Spill the
payload from X0 (MOVSD) with the variant tag. Review item #49.

Both stages move in one commit: one emission contract; splitting
would leave the byte-id gates red between the halves.
This commit is contained in:
2026-06-12 21:14:57 +09:00
parent a4a4cd7c16
commit ef7c0c1675
5 changed files with 94 additions and 4 deletions

View File

@@ -18215,7 +18215,24 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
emitline("\tPUSHQ\tDX\n");
pp -= 8;
};
emitline("\tPUSHQ\tAX\n");
// #49: an f64/f32 payload sits in X0 (cgexpr left it
// there), not AX — spill it through the stack so the
// callee reads the real bits. A plain PUSHQ AX pushed
// whatever AX last held (stale for a runtime float
// producer; only a const folder leaves the bits in AX
// — why #48 with a no-payload-read arm passed but #49
// reading `d == 2.5` did not). Both stages (#263);
// cstage cg_widen_tagged_push twin.
if (isfloattype(c, arg)) {
emitline("\tSUBQ\t$8, SP\n");
let fmov: str = "MOVSD";
if (isf32type(c, arg)) { fmov = "MOVSS"; };
emitline("\t");
emitline(fmov);
emitline("\tX0, (SP)\n");
} else {
emitline("\tPUSHQ\tAX\n");
};
emitline("\tMOVQ\t$");
emitint(widentag: i64);
emitline(", AX\n");