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:
@@ -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));
|
ins2(c, A_XORQ, areg(D_DX), areg(D_DX));
|
||||||
ins1(c, A_PUSHQ, 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));
|
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||||
ins1(c, A_PUSHQ, areg(D_AX)); /* tag at +0 */
|
ins1(c, A_PUSHQ, areg(D_AX)); /* tag at +0 */
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -18215,7 +18215,24 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
|||||||
emitline("\tPUSHQ\tDX\n");
|
emitline("\tPUSHQ\tDX\n");
|
||||||
pp -= 8;
|
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$");
|
emitline("\tMOVQ\t$");
|
||||||
emitint(widentag: i64);
|
emitint(widentag: i64);
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
|
|||||||
@@ -501,7 +501,24 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
|||||||
emitline("\tPUSHQ\tDX\n");
|
emitline("\tPUSHQ\tDX\n");
|
||||||
pp -= 8;
|
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$");
|
emitline("\tMOVQ\t$");
|
||||||
emitint(widentag: i64);
|
emitint(widentag: i64);
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
|
|||||||
@@ -18215,7 +18215,24 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
|||||||
emitline("\tPUSHQ\tDX\n");
|
emitline("\tPUSHQ\tDX\n");
|
||||||
pp -= 8;
|
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$");
|
emitline("\tMOVQ\t$");
|
||||||
emitint(widentag: i64);
|
emitint(widentag: i64);
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
|
|||||||
@@ -97,6 +97,32 @@ static const struct row rows[] = {
|
|||||||
" let d: f64 = 3.5;\n"
|
" let d: f64 = 3.5;\n"
|
||||||
" return g(d);\n"
|
" return g(d);\n"
|
||||||
"};\n", 2, K_RUN, NULL },
|
"};\n", 2, K_RUN, NULL },
|
||||||
|
/* #49 (#263 both-stages): a RUNTIME f64 producer (mk) feeds a value
|
||||||
|
* widened into (f64|void), and the f64 arm READS the payload
|
||||||
|
* (d == 2.5). Pre-fix the widen-PUSH did `PUSHQ AX` for the payload
|
||||||
|
* while the f64 sat in X0, so d read stale bits → 1 (cs) / 3 (ww,
|
||||||
|
* the #48 pop divergence compounding). #48's arm dodged this by not
|
||||||
|
* reading the payload; this one catches the stale-AX push. Both
|
||||||
|
* stages now spill X0 → 0. (After the c4 drain fix the two stages
|
||||||
|
* already agree on the pop; this push fix closes the shared bug.) */
|
||||||
|
{ "runtime_float_widen_payload",
|
||||||
|
"package main;\n"
|
||||||
|
"type fv = (f64 | void);\n"
|
||||||
|
"fn mk(x: f64) f64 = { return x + 1.5; };\n"
|
||||||
|
"fn take(v: fv) i32 = {\n"
|
||||||
|
" match (v) {\n"
|
||||||
|
" case let d: f64 => {\n"
|
||||||
|
" if (d == 2.5) { return 0; };\n"
|
||||||
|
" return 1;\n"
|
||||||
|
" };\n"
|
||||||
|
" case void => { return 2; };\n"
|
||||||
|
" };\n"
|
||||||
|
" return 3;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let d: f64 = mk(1.0);\n"
|
||||||
|
" return take(d);\n"
|
||||||
|
"};\n", 0, K_RUN, NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|||||||
Reference in New Issue
Block a user