cstage+test: variant-widen f64 arm accepts TY_UNTYPED_FLOAT (#40)

#30 (82be8b9) shipped the f64 variant-widen MOVSD path, but
cg_widen_tagged_store's float-arm gate `fld_isfloat` only accepted
declared f64/f32 — not TY_UNTYPED_FLOAT. cunop on TK_MINUS over an
N_FLOATLIT returns the operand's type (ty_untyped_float), and
cbinop on two untyped-floats returns ty_untyped_float too. So
`let a: (i64 | f64) = -2.5;` and `(2.5 + 1.0)` fell through to the
scalar fallback and stored AX residue at payload+8 (tag still set
correctly, payload = 0).

Wwstage post-#30 was already correct via exprfloatkind's AST walk.

Extend fld_isfloat to accept TY_UNTYPED_FLOAT (defaults to f64, no
TY_UNTYPED_F32 exists). Acceptance set now matches cg_isfloat
exactly. All 15 other fld_isfloat call sites pass declared field /
element / pointee types that never carry TY_UNTYPED_* post-check —
no over-trigger.

Test 715 grows from 7 → 10 rows: unary_neg_floatlit_direct (`-2.5`),
unary_neg_floatlit_paren (`-(2.5)`), binop_floatlit_sum (`2.5+1.0`).
All pin payload bits via hex-u64 punning through *u8 — direct/paren
land 0xC004000000000000 (sign=1, exp=0x400, mant=0x4000000000000);
sum lands 0x400C000000000000 (3.5). Hex literal use is documented
inline pointing at #41 (orthogonal comparison-ladder bug surfaced
during test development; decimal-u64 RHS of != miscompiles).

After this lands, lib/fmt/fmttest.ww's 3 routed-around rows (cited
at 7f320d3) can drop the `let nv: f64 = -2.5;` indirection and use
the direct literal — sibling cleanup.
This commit is contained in:
2026-05-16 14:54:11 +09:00
parent 7f320d3e75
commit 4d6a19fc8a
2 changed files with 57 additions and 6 deletions

View File

@@ -113,12 +113,24 @@ node_isf32(Node *n)
return n && type_isf32(n->type);
}
/* fld_isfloat — true iff f's underlying type is f32 or f64. The cgen
* passes float values in X0 (via MOVSD/MOVSS), integer/ptr values in
* AX (via MOVQ). Without this check, a field store/load on an f64 slot
* runs through AX and the bits never reach the SSE side — see the
* vfloat / L.curfval traps documented in examples/lisp/CLAUDE.md.
* Sets *isf32 to 1 for f32, 0 for f64. */
/* fld_isfloat — true iff f's underlying type is f32, f64, or
* untyped_float. The cgen passes float values in X0 (via MOVSD/MOVSS),
* integer/ptr values in AX (via MOVQ). Without this check, a field
* store/load on an f64 slot runs through AX and the bits never reach
* the SSE side — see the vfloat / L.curfval traps documented in
* examples/lisp/CLAUDE.md.
*
* TY_UNTYPED_FLOAT defaults to f64 (no TY_UNTYPED_F32 exists). Every
* field/element/pointee caller passes a declared type that is never
* UNTYPED — adding the case is a no-op for them. The variant-widen
* call site (cg_widen_tagged_store) is the only one passing an
* expression type, where `let _: (i64|f64) = -2.5;` arrives with
* src->type = ty_untyped_float (cunop returns the operand type for
* TK_MINUS, untyped_float for an untyped float literal). The earlier
* narrow predicate dropped the payload via the AX scalar fallback —
* matches cg_isfloat's acceptance set now.
*
* Sets *isf32 to 1 for f32, 0 for f64 / untyped_float. */
static int
fld_isfloat(Type *t, int *isf32)
{
@@ -127,6 +139,7 @@ fld_isfloat(Type *t, int *isf32)
if (t->kind == TY_NAMED) t = t->under;
if (t == NULL) return 0;
if (t->kind == TY_F64) return 1;
if (t->kind == TY_UNTYPED_FLOAT) return 1;
if (t->kind == TY_F32) { if (isf32) *isf32 = 1; return 1; }
return 0;
}