cgen: tuple receive spills f64 word from XMM, not integer reg (both stages, #105)

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.
This commit is contained in:
2026-05-25 16:23:47 +09:00
parent 4c4006d854
commit ec19d0ad20
2 changed files with 112 additions and 28 deletions

View File

@@ -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);