cgen: f64-typed int-literal + tuple-field materialize in X0 (both stages, #103)

Two sites, same class: an f64 value failing to reach XMM (X0) before an
SSE op. Both gate-blind — cstage and wwstage emitted the same wrong asm —
so the fix touches both stages identically.

FACE X — a no-decimal float-typed integer literal (`0f64`, `8f64`) is an
N_INTLIT carrying float TYPE. The integer-immediate path stranded it in
AX, so `n == 0f64` compared a stale X0 (true for all n) and
`(8f64 * 10.0): i32` read garbage. Route the float-typed N_INTLIT through
the float-constant-in-X0 emit (cgen.c cgexpr_float, factored from
N_FLOATLIT; cgenexpr.ww cgfloatbits). The wwstage also needs the
exprfloatkind N_INTLIT arm so the downstream f64->i32 cast emits
CVTTSD2SI not MOVSXD — cstage reads the checker-stamped type directly,
so this is the same #101 structural-vs-stamped asymmetry.

FACE Z — a tuple positional f64 field read (`r.0`, r:(f64,i64)) loaded
via the integer op into AX, so `r.0 == 0.0` was wrongly true. Add a
fld_isfloat branch -> MOVSD/MOVSS into X0 (cgen.c:5910 tuple arm;
cgenexpr.ww tuple arm), mirroring the struct-field float load at
cgen.c:1462,1838 (the #96 pattern).
This commit is contained in:
2026-05-25 15:26:43 +09:00
parent e784968dd8
commit c9e39c6782
3 changed files with 92 additions and 21 deletions

View File

@@ -1230,6 +1230,23 @@ cgexpr_int(Cg *c, long long v)
ins2(c, A_MOVQ, aimm(v), areg(D_AX));
}
/* Materialise a float constant in X0: MOVQ the IEEE bits into AX, PUSH,
* MOVSD off the stack into X0. Shared by N_FLOATLIT and the f64/f32-typed
* N_INTLIT arm (#103 FACE X): a no-decimal `0f64`/`8f64` is an N_INTLIT
* carrying float TYPE, so it must reach X0 like a true float literal does
* — the integer-immediate path left the value stranded in AX, so an SSE
* compare/mul read a stale X0. */
static void
cgexpr_float(Cg *c, double val)
{
union { double d; u64 u; } x;
x.d = val;
ins2(c, A_MOVQ, aimm((long long)x.u), areg(D_AX));
ins1(c, A_PUSHQ, areg(D_AX));
ins2(c, A_MOVSD, amem(D_SP, 0), areg(D_X0));
ins2(c, A_ADDQ, aimm(8), areg(D_SP));
}
/* cg_widen_tag_remap — when widening from one tagged union to another,
* rewrite the source's variant tag at BP+slot_off+0 to use the dst
* union's variant indices. No-op when src and dst index orders coincide.
@@ -1868,17 +1885,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
switch (n->kind) {
case N_INTLIT:
case N_RUNELIT:
if (node_isfloat(n)) {
cgexpr_float(c, (double)(long long)n->uval);
break;
}
cgexpr_int(c, (long long)n->uval);
break;
case N_FLOATLIT: {
union { double d; u64 u; } x;
x.d = n->fval;
ins2(c, A_MOVQ, aimm((long long)x.u), areg(D_AX));
ins1(c, A_PUSHQ, areg(D_AX));
ins2(c, A_MOVSD, amem(D_SP, 0), areg(D_X0));
ins2(c, A_ADDQ, aimm(8), areg(D_SP));
case N_FLOATLIT:
cgexpr_float(c, n->fval);
break;
}
case N_STRLIT: {
/* str IS []u8: the (ptr, len, cap) triple — ptr in AX, len in
* BX, cap in CX. A static literal has no spare storage, so
@@ -5924,6 +5939,17 @@ cgexpr(Cg *c, Node *n, Local *locals)
? tp->type->under : tp->type;
int op = fldloadop(tp->type, fsz);
int off = localfind(locals, n->lhs->str);
/* f64/f32 tuple field must ride X0 via MOVSD/MOVSS;
* the integer fldloadop left it in AX (#103 FACE Z).
* Mirrors the struct-field float load at cgen.c:1462,
* 1838 (the #96 pattern). */
int tup_isf32 = 0;
if (fld_isfloat(tp->type, &tup_isf32)) {
int mov = tup_isf32 ? A_MOVSS : A_MOVSD;
ins2(c, mov, amem(D_BP, off + foff),
areg(D_X0));
break;
}
/* str IS []u8 — load (ptr, len, cap) into
* (AX, BX, CX), the canonical slice-header ABI,
* so chains like `t.1.len` propagate through the