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

@@ -3108,7 +3108,20 @@ cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
ins2(c, A_XORQ, areg(D_DX), areg(D_DX));
ins1(c, A_PUSHQ, areg(D_DX));
}
ins1(c, A_PUSHQ, areg(D_AX)); /* value at +8 */
/* #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, which is why #48's no-payload-read arm
* passed but #49's `d == 2.5` read did not). Both stages
* (#263); wwstage cgenutil.ww scalar-variant twin. */
if (node_isfloat(src)) {
int fmov = node_isf32(src) ? A_MOVSS : A_MOVSD;
ins2(c, A_SUBQ, aimm(8), areg(D_SP));
ins2(c, fmov, areg(D_X0), amem(D_SP, 0));
} else {
ins1(c, A_PUSHQ, areg(D_AX)); /* value at +8 */
}
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
ins1(c, A_PUSHQ, areg(D_AX)); /* tag at +0 */
return;