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 at7f320d3) can drop the `let nv: f64 = -2.5;` indirection and use the direct literal — sibling cleanup.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -117,6 +117,44 @@ static const struct row rows[] = {
|
||||
/* -1.0 == 0xBFF0000000000000 == 13830554455654793216 */
|
||||
"13830554455654793216", "1"),
|
||||
0 },
|
||||
/* #40: N_UN(MINUS, N_FLOATLIT) where the inner literal is untyped.
|
||||
* cunop returns the operand type for TK_MINUS, so the expression's
|
||||
* type is ty_untyped_float; the pre-fix fld_isfloat predicate
|
||||
* accepted only TY_F32/TY_F64 and the variant-widen float arm
|
||||
* silently fell through to MOVQ AX, dropping the X0 result onto
|
||||
* a trashed AX. -2.5 == 0xC004000000000000.
|
||||
*
|
||||
* Hex u64 literal pins the bit pattern directly. Decimal literals
|
||||
* above 2^63 round-trip cleanly through the lexer (cgexpr_int sees
|
||||
* the right bits) but the surrounding comparison ladder has an
|
||||
* unrelated latent miscompile on huge-decimal-u64 RHS — orthogonal
|
||||
* to this fix; do not entangle. */
|
||||
{ "unary_neg_floatlit_direct",
|
||||
BITCHECK_F64(
|
||||
"",
|
||||
"let a: (i64 | f64) = -2.5;\n",
|
||||
"0xC004000000000000", "1"),
|
||||
0 },
|
||||
/* Same expression shape with explicit parens around the literal.
|
||||
* Parser builds N_UN(MINUS, N_FLOATLIT) for both forms; pinning
|
||||
* both rows guards against a future parse change altering that. */
|
||||
{ "unary_neg_floatlit_paren",
|
||||
BITCHECK_F64(
|
||||
"",
|
||||
"let a: (i64 | f64) = -(2.5);\n",
|
||||
"0xC004000000000000", "1"),
|
||||
0 },
|
||||
/* Binop of two untyped-float literals — N_BIN type is
|
||||
* ty_untyped_float (untyped+untyped → untyped, see check.c cbinop).
|
||||
* Same predicate gate as the unary case; pins that the extended
|
||||
* fld_isfloat covers the binop arm too. 2.5 + 1.0 == 3.5 ==
|
||||
* 0x400C000000000000. */
|
||||
{ "binop_floatlit_sum",
|
||||
BITCHECK_F64(
|
||||
"",
|
||||
"let a: (i64 | f64) = (2.5 + 1.0);\n",
|
||||
"0x400C000000000000", "1"),
|
||||
0 },
|
||||
/* Concrete f64 ident — cgexpr emits MOVSD load to X0; AX is
|
||||
* never touched. */
|
||||
{ "ident_f64",
|
||||
|
||||
Reference in New Issue
Block a user