From ec19d0ad20ec6bb3d494ad61384090db8119e1cf Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 25 May 2026 16:23:47 +0900 Subject: [PATCH] cgen: tuple receive spills f64 word from XMM, not integer reg (both stages, #105) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A (f64,i64)/(i64,f64) tuple returns its f64 word in X0 (the SSE return reg) and its integer word in an integer reg (tuple_rseq AX/DX). All three tuple-from-call receive forms — single-var (cglet), destructure (N_MLET), reassign (N_MASSIGN) — share the #83 tuple_rseq cursor and all spilled the f64 word via MOVQ from the integer cursor; that reg holds garbage (the float is in X0), and #103-FACE-Z's field read (MOVSD slot,X0) then reads it. A single-return callee masked it (a float-literal return leaves the f64 bits in AX, and X0 stays live); a branched callee with a non- literal f64 word has an inner CALL clobber AX, exposing the corruption. Make every receive spill class-aware: an f64/f32 word spills MOVSD/MOVSS from X0 (the single SSE return reg, which survives the reg->mem stores regardless of the word's position), an integer word spills MOVQ from its tuple_rseq reg as before. cstage applies this at all three inline sites (cglet, N_MLET, N_MASSIGN); wwstage at the cglet branch and in the shared tupstore helper (covering cgmlet and cgmassign). The integer/str/slice path is byte-identical to before, so bootstrap codegen is unperturbed. Multi-float tuples collide on X0 at the RETURN (#107), out of scope here. --- cmd/w6c/cgen.c | 56 ++++++++++++++++++++---- selfhost/cmd/wcc/cgenstmt.ww | 84 ++++++++++++++++++++++++++++-------- 2 files changed, 112 insertions(+), 28 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index bafc4c36..5c550307 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -6672,13 +6672,33 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16)); break; } - /* 2-tuple initialiser from a function call: SysV returns - * a 16-byte aggregate in (AX, DX). Store both into the - * tuple slot. */ + /* 2-tuple initialiser from a function call. An integer word + * rides its tuple_rseq[] reg (AX, DX); a single f64/f32 word + * rides X0, the SSE return reg — the RETURN leaves the float + * in X0 and pushes garbage through that word's integer slot, + * so a blanket MOVQ-from-integer spill stores garbage and the + * #103-FACE-Z field read (MOVSD-from-slot) reads it (#105). + * Spill each word from its real class. Multi-float tuples + * collide on X0 at the RETURN (#107), out of scope here. */ if (n->rhs && lu && lu->kind == TY_TUPLE && sz == 16) { + Tparam *p0 = lu->params; + Tparam *p1 = p0 ? p0->next : NULL; + int f0_f32 = 0, f1_f32 = 0; + int e0_f = p0 && fld_isfloat(p0->type, &f0_f32); + int e1_f = p1 && fld_isfloat(p1->type, &f1_f32); cgexpr(c, n->rhs, *locals); - ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0)); - ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8)); + if (e0_f) + ins2(c, f0_f32 ? A_MOVSS : A_MOVSD, + areg(D_X0), amem(D_BP, off + 0)); + else + ins2(c, A_MOVQ, areg(tuple_rseq[0]), + amem(D_BP, off + 0)); + if (e1_f) + ins2(c, f1_f32 ? A_MOVSS : A_MOVSD, + areg(D_X0), amem(D_BP, off + 8)); + else + ins2(c, A_MOVQ, areg(tuple_rseq[1]), + amem(D_BP, off + 8)); break; } /* 32B tuple initialiser for `(scalar, str)` / `(str, scalar)`. @@ -7563,8 +7583,18 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) ins2(c, A_MOVQ, areg(tuple_rseq[cur + 2]), amem(D_BP, off + 16)); /* .cap */ } else { - ins2(c, A_MOVQ, areg(tuple_rseq[cur]), - amem(D_BP, off)); + /* #105: an f64/f32 element rides X0, not its + * integer cursor reg — MOVSD/MOVSS it, else + * the slot gets garbage and the FACE-Z field + * read sees it. X0 survives the reg->mem + * stores. Single-float scope; #107 is multi. */ + int e_f32 = 0; + if (fld_isfloat(t, &e_f32)) + ins2(c, e_f32 ? A_MOVSS : A_MOVSD, + areg(D_X0), amem(D_BP, off)); + else + ins2(c, A_MOVQ, areg(tuple_rseq[cur]), + amem(D_BP, off)); } cur += tuple_ebytes(wide); } @@ -7620,8 +7650,16 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) ins2(c, A_MOVQ, areg(tuple_rseq[cur + 2]), amem(D_BP, off + 16)); /* .cap */ } else { - ins2(c, A_MOVQ, areg(tuple_rseq[cur]), - amem(D_BP, off)); + /* #105: f64/f32 element rides X0 (SSE), + * not its integer cursor reg — see N_MLET. */ + int e_f32 = 0; + if (fld_isfloat(et, &e_f32)) + ins2(c, e_f32 ? A_MOVSS : A_MOVSD, + areg(D_X0), amem(D_BP, off)); + else + ins2(c, A_MOVQ, + areg(tuple_rseq[cur]), + amem(D_BP, off)); } } cur += tuple_ebytes(wide); diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 34b01c40..69ffebe5 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -163,7 +163,7 @@ fn rettupleof(c: *cgen, rhs: *node) *node = { // header (ref/hare/rt/ensure.ha:4-8) at off/+8/+16 from consecutive // cursor registers; a scalar stores 1 word. Byte-identical to the cstage // N_MLET/N_MASSIGN store (cmd/w6c/cgen.c). -fn tupstore(cur: i32, off: i32, wide: bool) void = { +fn tupstore(c: *cgen, cur: i32, off: i32, wide: bool, tn: *node) void = { if (wide) { emitline("\tMOVQ\t"); emitline(tupreg(cur + 0)); @@ -182,6 +182,20 @@ fn tupstore(cur: i32, off: i32, wide: bool) void = { emitline("(BP)\n"); return; }; + // #105: an f64/f32 element rides X0 (the SSE return reg), not its + // integer cursor reg — MOVSD/MOVSS it, else the slot gets garbage and + // the FACE-Z field read sees it. X0 survives the reg->mem stores. + // Single-float scope; multi-float collides on X0 at RETURN (#107). + if (isfloattype(c, tn)) { + let mov: str = "MOVSD"; + if (isf32type(c, tn)) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; emitline("\tMOVQ\t"); emitline(tupreg(cur)); emitline(", "); @@ -864,23 +878,55 @@ fn cglet(c: *cgen, n: *node) void = { }; }; }; - // 16B tuple init from a function call: SysV returns a - // 16-byte aggregate across the integer cursor (AX, DX). Spill - // BOTH eightbytes over the integer registers — an f64 element - // also rides its eightbyte in AX/DX here and is re-read from - // X0/XMM at field-read time, so no SSE cursor is needed (the - // f64 read is already byte-id). Mirror of cstage cgen.c:6652. - // Without this branch a 16B tuple receive (any element mix, - // incl. all-integer) fell to the generic single-word store - // below and dropped word1 — silent loss of t.1 (#102). - if (rettupleof(c, rhs) != nil && sz == 16) { + // 16B tuple init from a function call. An integer word rides + // its integer cursor reg (AX, DX); a single f64/f32 word rides + // X0, the SSE return reg — the RETURN leaves the float in X0 + // and pushes garbage through that word's integer slot, so a + // blanket MOVQ-from-integer spill stores garbage and the #103- + // FACE-Z field read (MOVSD-from-slot) reads it (#105). Spill + // each word from its real class. Multi-float tuples collide on + // X0 at the RETURN (#107), out of scope. Mirror of cstage + // cgen.c. Without this branch a 16B tuple receive (any element + // mix) fell to the generic single-word store below and dropped + // word1 — silent loss of t.1 (#102). + let rt16: *node = rettupleof(c, rhs); + if (rt16 != nil && sz == 16) { + let q0: *node = rt16.list; + let q1: *node = nil; + if (q0 != nil) { q1 = q0.next; }; + let q0t: *node = nil; + let q1t: *node = nil; + if (q0 != nil) { q0t = q0.lhs; }; + if (q1 != nil) { q1t = q1.lhs; }; + let e0f: bool = isfloattype(c, q0t); + let e1f: bool = isfloattype(c, q1t); cgexpr(c, rhs); - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tDX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); + if (e0f) { + let mov: str = "MOVSD"; + if (isf32type(c, q0t)) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitoff(off: i64); + emitline("(BP)\n"); + } else { + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + }; + if (e1f) { + let mov: str = "MOVSD"; + if (isf32type(c, q1t)) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + } else { + emitline("\tMOVQ\tDX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + }; c.lastwasreturn = 0; return; }; @@ -1351,7 +1397,7 @@ fn cgmassign(c: *cgen, n: *node) void = { let off: i32 = 0; if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); }; if (off != 0) { - tupstore(cur, off, wide); + tupstore(c, cur, off, wide, tn); }; cur = cur + tupebytes(wide); l = l.next; @@ -1423,7 +1469,7 @@ fn cgmlet(c: *cgen, n: *node) void = { let sz: i32 = 8; if (wide) { sz = tyslicesize(): i32; }; let off: i32 = localadd(c, l.str, sz, tn); - tupstore(cur, off, wide); + tupstore(c, cur, off, wide, tn); cur = cur + tupebytes(wide); l = l.next; if (pt != nil) { pt = pt.next; };