From c9e39c678202299c1c9df57ae12152a0d055e73c Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 25 May 2026 15:26:43 +0900 Subject: [PATCH] cgen: f64-typed int-literal + tuple-field materialize in X0 (both stages, #103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- cmd/w6c/cgen.c | 42 ++++++++++++++++++++----- selfhost/cmd/wcc/cgenexpr.ww | 59 ++++++++++++++++++++++++++++-------- selfhost/cmd/wcc/cgenutil.ww | 12 ++++++++ 3 files changed, 92 insertions(+), 21 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index f3953b91..bafc4c36 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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 diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 1ed7cfbc..00d05171 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -21,11 +21,37 @@ import typ; import sym; import strconv; +// cgfloatbits — materialise a float constant in X0: MOVQ the IEEE bits +// into AX, PUSH, MOVSD off the stack into X0. Shared by N_FLOATLIT (bits +// already in n.uval from the lexer's bitcast) and the f64/f32-typed +// N_INTLIT arm (#103 FACE X). +fn cgfloatbits(c: *cgen, bits: u64) void = { + emitline("\tMOVQ\t$"); + emitint(bits: i64); + emitline(", AX\n"); + emitline("\tPUSHQ\tAX\n"); + emitline("\tMOVSD\t(SP), X0\n"); + emitline("\tADDQ\t$8, SP\n"); +}; + fn cgexpr(c: *cgen, n: *node) void = { if (n == nil) { return; }; let k: nkind = n.kind; if (k == nkind.N_INTLIT) { + // A no-decimal `0f64`/`8f64` is an N_INTLIT carrying float + // TYPE; it must reach X0 like a true float literal, not the + // integer-immediate path (which strands it in AX and an SSE + // compare/mul reads a stale X0 — #103 FACE X). The bits are + // the IEEE pattern of the integer value, mirroring cstage's + // `(double)(long long)n->uval`; the (&fv):*u64 bitcast is the + // lex.ww idiom (lib/ww/lex/lex.ww). + if (isfloattype(c, n)) { + let fv: f64 = (n.uval: i64): f64; + let pu: *u64 = (&fv): *u64; + cgfloatbits(c, *pu); + return; + }; // Print signed (i64), not unsigned (u64). C cgen uses // `$%lld` so 64-bit constants with bit 63 set show up as // negative — e.g. FNV-1a's offset basis prints as @@ -36,19 +62,11 @@ fn cgexpr(c: *cgen, n: *node) void = { return; }; if (k == nkind.N_FLOATLIT) { - // Materialise the f64 bit pattern in AX, push, then MOVSD it - // into X0. The bits come from n.uval — the parser populates - // it from the lexer's bitcast of t.fval, so this path stays - // integer-only (no SSE in the cgen source). The f32 - // narrowing is handled at the consumer site, not here — the - // literal always carries the full double precision until - // typed by context. - emitline("\tMOVQ\t$"); - emitint(n.uval: i64); - emitline(", AX\n"); - emitline("\tPUSHQ\tAX\n"); - emitline("\tMOVSD\t(SP), X0\n"); - emitline("\tADDQ\t$8, SP\n"); + // The bits come from n.uval — the parser populates it from + // the lexer's bitcast of t.fval. The f32 narrowing is handled + // at the consumer site, not here — the literal always carries + // the full double precision until typed by context. + cgfloatbits(c, n.uval); return; }; if (k == nkind.N_RUNELIT) { @@ -1652,6 +1670,21 @@ fn cgdot(c: *cgen, n: *node) void = { emitline("(BP), CX\n"); return; }; + // f64/f32 tuple field must ride X0 via + // MOVSD/MOVSS; the integer load op left it + // in AX (#103 FACE Z). Mirrors the float + // local load above and cstage cgen.c:1462, + // 1838 (the #96 pattern). + if (isfloattype(c, tpt)) { + let mov: str = "MOVSD"; + if (isf32type(c, tpt)) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\t"); + emitoff((lc.off + foff): i64); + emitline("(BP), X0\n"); + return; + }; let sz: i32 = slotsize(c, tpt); let op: str = tnodeloadop(c, tpt, sz); emitline("\t"); diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 67ef2aca..c5f51d21 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -1847,6 +1847,18 @@ export fn exprfloatkind(c: *cgen, n: *node) i32 = { if (n == nil) { return 0; }; let k: nkind = n.kind; if (k == nkind.N_FLOATLIT) { return 2; }; + if (k == nkind.N_INTLIT) { + // A float-typed integer literal (`8f64`/`0f32`) now + // materialises in X0 (#103 FACE X), so the cast / spill / + // arith-recursion sides must classify it as float — else + // `(8f64 * 10.0): i32` recurses to this N_INTLIT lhs and + // falls through to integer, emitting MOVSXD not CVTTSD2SI + // (the #101 structural-vs-stamped asymmetry). cstage reads + // the checker-stamped type via node_isfloat directly. + if (isf32type(c, n)) { return 1; }; + if (isfloattype(c, n)) { return 2; }; + return 0; + }; if (k == nkind.N_CAST) { if (isf32type(c, n.rhs)) { return 1; }; if (isfloattype(c, n.rhs)) { return 2; };