diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index f26c8cc5..ee2f4702 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -7716,6 +7716,35 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) areg(D_CX)); ins2(c, A_MOVQ, areg(D_AX), areg(D_DX)); + } else if (type_isfloat(vt)) { + /* #157: float variant. cgexpr left + * the value in X0, not AX; there is + * no MOVQ-xmm->gp encoding, so bridge + * X0->DX through a stack slot (same + * SUBQ/MOVSD/ADDQ idiom as the arg- + * push at cgen.c:5367). Zero the slot + * first so the f32 case (MOVSS writes + * only the low 4 bytes) leaves a + * deterministic high-4 — cs==ww byte- + * id, matching f64's MOVSD which fills + * all 8. The AX-independent spill also + * removes the stale-AX cs!=ww on + * multi-variant returns. */ + int isf32 = type_isf32(vt); + ins2(c, A_SUBQ, aimm(8), areg(D_SP)); + ins2(c, A_MOVQ, aimm(0), + amem(D_SP, 0)); + ins2(c, isf32 ? A_MOVSS : A_MOVSD, + areg(D_X0), amem(D_SP, 0)); + ins2(c, A_MOVQ, amem(D_SP, 0), + areg(D_DX)); + ins2(c, A_ADDQ, aimm(8), areg(D_SP)); + if (rsz > 16) + ins2(c, A_MOVQ, aimm(0), + areg(D_CX)); + if (rsz > 24) + ins2(c, A_MOVQ, aimm(0), + areg(D_R8)); } else { ins2(c, A_MOVQ, areg(D_AX), areg(D_DX)); diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 7f0d1e21..84396180 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -22060,6 +22060,31 @@ fn cgreturn(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, R8\n"); emitline("\tMOVQ\tBX, CX\n"); emitline("\tMOVQ\tAX, DX\n"); + } else { if (exprfloatkind(c, rhs) != 0) { + // #157: float variant — cgexpr left the value + // in X0, not AX. No MOVQ-xmm->gp encoding, so + // bridge X0->DX through a stack slot (same arg- + // push idiom). Zero the slot first so the f32 + // case (MOVSS writes only the low 4 bytes) + // leaves a deterministic high-4 — cs==ww byte- + // id, matching f64's MOVSD which fills all 8. + // The AX-independent spill also removes the + // stale-AX cs!=ww on multi-variant returns. + emitline("\tSUBQ\t$8, SP\n"); + emitline("\tMOVQ\t$0, (SP)\n"); + let mov: str = "MOVSD"; + if (exprfloatkind(c, rhs) == 1) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\tX0, (SP)\n"); + emitline("\tMOVQ\t(SP), DX\n"); + emitline("\tADDQ\t$8, SP\n"); + if (rsz > 16) { + emitline("\tMOVQ\t$0, CX\n"); + }; + if (rsz > 24) { + emitline("\tMOVQ\t$0, R8\n"); + }; } else { emitline("\tMOVQ\tAX, DX\n"); // scalar fills DX only. Zero CX / R8 if dst @@ -22070,7 +22095,7 @@ fn cgreturn(c: *cgen, n: *node) void = { if (rsz > 24) { emitline("\tMOVQ\t$0, R8\n"); }; - };}; + };};}; emitline("\tMOVQ\t$"); if (idx < 0) { idx = 0; }; emitint(idx: i64); diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 3a4a8961..80ce9173 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -422,6 +422,31 @@ fn cgreturn(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, R8\n"); emitline("\tMOVQ\tBX, CX\n"); emitline("\tMOVQ\tAX, DX\n"); + } else { if (exprfloatkind(c, rhs) != 0) { + // #157: float variant — cgexpr left the value + // in X0, not AX. No MOVQ-xmm->gp encoding, so + // bridge X0->DX through a stack slot (same arg- + // push idiom). Zero the slot first so the f32 + // case (MOVSS writes only the low 4 bytes) + // leaves a deterministic high-4 — cs==ww byte- + // id, matching f64's MOVSD which fills all 8. + // The AX-independent spill also removes the + // stale-AX cs!=ww on multi-variant returns. + emitline("\tSUBQ\t$8, SP\n"); + emitline("\tMOVQ\t$0, (SP)\n"); + let mov: str = "MOVSD"; + if (exprfloatkind(c, rhs) == 1) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\tX0, (SP)\n"); + emitline("\tMOVQ\t(SP), DX\n"); + emitline("\tADDQ\t$8, SP\n"); + if (rsz > 16) { + emitline("\tMOVQ\t$0, CX\n"); + }; + if (rsz > 24) { + emitline("\tMOVQ\t$0, R8\n"); + }; } else { emitline("\tMOVQ\tAX, DX\n"); // scalar fills DX only. Zero CX / R8 if dst @@ -432,7 +457,7 @@ fn cgreturn(c: *cgen, n: *node) void = { if (rsz > 24) { emitline("\tMOVQ\t$0, R8\n"); }; - };}; + };};}; emitline("\tMOVQ\t$"); if (idx < 0) { idx = 0; }; emitint(idx: i64); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 3f937d68..71f8dc46 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -22060,6 +22060,31 @@ fn cgreturn(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, R8\n"); emitline("\tMOVQ\tBX, CX\n"); emitline("\tMOVQ\tAX, DX\n"); + } else { if (exprfloatkind(c, rhs) != 0) { + // #157: float variant — cgexpr left the value + // in X0, not AX. No MOVQ-xmm->gp encoding, so + // bridge X0->DX through a stack slot (same arg- + // push idiom). Zero the slot first so the f32 + // case (MOVSS writes only the low 4 bytes) + // leaves a deterministic high-4 — cs==ww byte- + // id, matching f64's MOVSD which fills all 8. + // The AX-independent spill also removes the + // stale-AX cs!=ww on multi-variant returns. + emitline("\tSUBQ\t$8, SP\n"); + emitline("\tMOVQ\t$0, (SP)\n"); + let mov: str = "MOVSD"; + if (exprfloatkind(c, rhs) == 1) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\tX0, (SP)\n"); + emitline("\tMOVQ\t(SP), DX\n"); + emitline("\tADDQ\t$8, SP\n"); + if (rsz > 16) { + emitline("\tMOVQ\t$0, CX\n"); + }; + if (rsz > 24) { + emitline("\tMOVQ\t$0, R8\n"); + }; } else { emitline("\tMOVQ\tAX, DX\n"); // scalar fills DX only. Zero CX / R8 if dst @@ -22070,7 +22095,7 @@ fn cgreturn(c: *cgen, n: *node) void = { if (rsz > 24) { emitline("\tMOVQ\t$0, R8\n"); }; - };}; + };};}; emitline("\tMOVQ\t$"); if (idx < 0) { idx = 0; }; emitint(idx: i64); diff --git a/test/wcc/707_cgreturn_variant_zero.c b/test/wcc/707_cgreturn_variant_zero.c index e2ebf208..65a33957 100644 --- a/test/wcc/707_cgreturn_variant_zero.c +++ b/test/wcc/707_cgreturn_variant_zero.c @@ -1,7 +1,10 @@ /* * 707_cgreturn_variant_zero — tagged-return ABI variant-widen zero-pad * for unused AX/DX/CX/R8 words (task #18) plus aliased-tagged variant- - * index lookup (task #20). + * index lookup (task #20) plus float-variant payload-from-X0 pack + * (#157, rows 10-12): the same scalar-variant arm packed stale AX into + * the payload word for an f64/f32 value (which lives in X0), not the + * float bits — fixed by an X0->stack->DX spill bridge. * * Pre-fix (#18): cgreturn's `!istagged && !isstruct` variant-widen arm * only filled the registers a given variant actually uses (scalar → DX; @@ -247,6 +250,53 @@ static const struct row rows[] = { " return (*pt): i32;\n" "};\n", 2 }, + /* 10-12: #157 — float variant of the same `!istagged && !isstruct` + * return arm. Pre-fix it did `MOVQ AX, DX` even when the value was + * a float in X0 (no MOVQ-xmm->gp form), so the payload word held + * stale AX, not the float bits. Fix spills X0 through a stack slot + * (zero-slot-first so the f32 MOVSS low-4 write leaves a determin- + * istic high-4). Probe slot+8 as the float directly (bit-exact + * f64/f32 equality) — isolates the return-pack from the match- + * receive. The f32 row avoids f32 ARGS (pre-existing #143 f32-arg + * MOVSD/MOVSS divergence is unrelated; f32 here comes from a global + * with an int arg, so the byte-id check pins THIS fix). */ + { "f64_variant_return_payload", + "type ft = (f64 | i32);\n" + "fn addf(a: f64, b: f64) ft = { return a + b; };\n" + "fn main() i32 = {\n" + " let a: ft = addf(1.5, 2.0);\n" + " let p: u64 = (&a): u64;\n" + " let pf: *f64 = (p + 8u64): *f64;\n" + " let fv: f64 = *pf;\n" + " if (fv == 3.5) { return 1; };\n" + " return 0;\n" + "};\n", + 1 }, + { "f32_variant_return_payload", + "let g: f32 = 1.5f32;\n" + "type ft = (f32 | i32);\n" + "fn mkf(k: i32) ft = { if (k > 0) { return g + 2.0f32; }; return k; };\n" + "fn main() i32 = {\n" + " let a: ft = mkf(1);\n" + " let p: u64 = (&a): u64;\n" + " let pf: *f32 = (p + 8u64): *f32;\n" + " let fv: f32 = *pf;\n" + " if (fv == 3.5f32) { return 1; };\n" + " return 0;\n" + "};\n", + 1 }, + { "f64_variant_return_multivariant", + "type ft = (f64 | i32 | bool);\n" + "fn mulf(a: f64, b: f64) ft = { return a * b; };\n" + "fn main() i32 = {\n" + " let a: ft = mulf(2.5, 4.0);\n" + " let p: u64 = (&a): u64;\n" + " let pf: *f64 = (p + 8u64): *f64;\n" + " let fv: f64 = *pf;\n" + " if (fv == 10.0) { return 1; };\n" + " return 0;\n" + "};\n", + 1 }, }; static int