Skip to content

Commit 2aa7c6c

Browse files
committed
cmd/compile: don't allow NaNs in floating-point constant ops
We store 32-bit floating point constants in a 64-bit field, by converting that 32-bit float to 64-bit float to store it, and convert it back to use it. That works for *almost* all floating-point constants. The exception is signaling NaNs. The round trip described above means we can't represent a 32-bit signaling NaN, because conversions strip the signaling bit. To fix this issue, just forbid NaNs as floating-point constants in SSA form. This shouldn't affect any real-world code, as people seldom constant-propagate NaNs (except in test code). Additionally, NaNs are somewhat underspecified (which of the many NaNs do you get when dividing 0/0?), so when cross-compiling there's a danger of using the compiler machine's NaN regime for some math, and the target machine's NaN regime for other math. Better to use the target machine's NaN regime always. This has been a bug since 1.10, and there's an easy workaround (declare a global varaible containing the signaling NaN pattern, and use that as the argument to math.Float32frombits) so we'll fix it in 1.15. Fixes #36400 Update #36399 Change-Id: Icf155e743281560eda2eed953d19a829552ccfda Reviewed-on: https://go-review.googlesource.com/c/go/+/213477 Run-TryBot: Keith Randall <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Josh Bleecher Snyder <[email protected]>
1 parent 0fb1a49 commit 2aa7c6c

File tree

11 files changed

+200
-26
lines changed

11 files changed

+200
-26
lines changed

src/cmd/compile/internal/gc/float_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,66 @@ func TestFloat32StoreToLoadConstantFold(t *testing.T) {
483483
}
484484
}
485485

486+
// Signaling NaN values as constants.
487+
const (
488+
snan32bits uint32 = 0x7f800001
489+
snan64bits uint64 = 0x7ff0000000000001
490+
)
491+
492+
// Signaling NaNs as variables.
493+
var snan32bitsVar uint32 = snan32bits
494+
var snan64bitsVar uint64 = snan64bits
495+
496+
func TestFloatSignalingNaN(t *testing.T) {
497+
// Make sure we generate a signaling NaN from a constant properly.
498+
// See issue 36400.
499+
f32 := math.Float32frombits(snan32bits)
500+
g32 := math.Float32frombits(snan32bitsVar)
501+
x32 := math.Float32bits(f32)
502+
y32 := math.Float32bits(g32)
503+
if x32 != y32 {
504+
t.Errorf("got %x, want %x (diff=%x)", x32, y32, x32^y32)
505+
}
506+
507+
f64 := math.Float64frombits(snan64bits)
508+
g64 := math.Float64frombits(snan64bitsVar)
509+
x64 := math.Float64bits(f64)
510+
y64 := math.Float64bits(g64)
511+
if x64 != y64 {
512+
t.Errorf("got %x, want %x (diff=%x)", x64, y64, x64^y64)
513+
}
514+
}
515+
516+
func TestFloatSignalingNaNConversion(t *testing.T) {
517+
// Test to make sure when we convert a signaling NaN, it converts to a quiet NaN.
518+
// See issue 36399.
519+
s32 := math.Float32frombits(snan32bitsVar)
520+
q64 := float64(s32)
521+
if math.Float64bits(q64)>>52&1 == 0 {
522+
t.Errorf("got signaling NaN, want quiet NaN")
523+
}
524+
s64 := math.Float64frombits(snan64bitsVar)
525+
q32 := float32(s64)
526+
if math.Float32bits(q32)>>22&1 == 0 {
527+
t.Errorf("got signaling NaN, want quiet NaN")
528+
}
529+
}
530+
531+
func TestFloatSignalingNaNConversionConst(t *testing.T) {
532+
// Test to make sure when we convert a signaling NaN, it converts to a quiet NaN.
533+
// See issue 36399 and 36400.
534+
s32 := math.Float32frombits(snan32bits)
535+
q64 := float64(s32)
536+
if math.Float64bits(q64)>>52&1 == 0 {
537+
t.Errorf("got signaling NaN, want quiet NaN")
538+
}
539+
s64 := math.Float64frombits(snan64bits)
540+
q32 := float32(s64)
541+
if math.Float32bits(q32)>>22&1 == 0 {
542+
t.Errorf("got signaling NaN, want quiet NaN")
543+
}
544+
}
545+
486546
var sinkFloat float64
487547

488548
func BenchmarkMul2(b *testing.B) {

src/cmd/compile/internal/ssa/check.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,23 @@ func checkFunc(f *Func) {
141141
f.Fatalf("bad int32 AuxInt value for %v", v)
142142
}
143143
canHaveAuxInt = true
144-
case auxInt64, auxFloat64:
144+
case auxInt64:
145145
canHaveAuxInt = true
146146
case auxInt128:
147147
// AuxInt must be zero, so leave canHaveAuxInt set to false.
148148
case auxFloat32:
149149
canHaveAuxInt = true
150+
if math.IsNaN(v.AuxFloat()) {
151+
f.Fatalf("value %v has an AuxInt that encodes a NaN", v)
152+
}
150153
if !isExactFloat32(v.AuxFloat()) {
151154
f.Fatalf("value %v has an AuxInt value that is not an exact float32", v)
152155
}
156+
case auxFloat64:
157+
canHaveAuxInt = true
158+
if math.IsNaN(v.AuxFloat()) {
159+
f.Fatalf("value %v has an AuxInt that encodes a NaN", v)
160+
}
153161
case auxString, auxSym, auxTyp, auxArchSpecific:
154162
canHaveAux = true
155163
case auxSymOff, auxSymValAndOff, auxTypSize:

src/cmd/compile/internal/ssa/gen/PPC64.rules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878

7979
// Constant folding
8080
(FABS (FMOVDconst [x])) -> (FMOVDconst [auxFrom64F(math.Abs(auxTo64F(x)))])
81-
(FSQRT (FMOVDconst [x])) -> (FMOVDconst [auxFrom64F(math.Sqrt(auxTo64F(x)))])
81+
(FSQRT (FMOVDconst [x])) && auxTo64F(x) >= 0 -> (FMOVDconst [auxFrom64F(math.Sqrt(auxTo64F(x)))])
8282
(FFLOOR (FMOVDconst [x])) -> (FMOVDconst [auxFrom64F(math.Floor(auxTo64F(x)))])
8383
(FCEIL (FMOVDconst [x])) -> (FMOVDconst [auxFrom64F(math.Ceil(auxTo64F(x)))])
8484
(FTRUNC (FMOVDconst [x])) -> (FMOVDconst [auxFrom64F(math.Trunc(auxTo64F(x)))])

src/cmd/compile/internal/ssa/gen/Wasm.rules

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@
372372
(I64Or (I64Const [x]) (I64Const [y])) -> (I64Const [x | y])
373373
(I64Xor (I64Const [x]) (I64Const [y])) -> (I64Const [x ^ y])
374374
(F64Add (F64Const [x]) (F64Const [y])) -> (F64Const [auxFrom64F(auxTo64F(x) + auxTo64F(y))])
375-
(F64Mul (F64Const [x]) (F64Const [y])) -> (F64Const [auxFrom64F(auxTo64F(x) * auxTo64F(y))])
375+
(F64Mul (F64Const [x]) (F64Const [y])) && !math.IsNaN(auxTo64F(x) * auxTo64F(y)) -> (F64Const [auxFrom64F(auxTo64F(x) * auxTo64F(y))])
376376
(I64Eq (I64Const [x]) (I64Const [y])) && x == y -> (I64Const [1])
377377
(I64Eq (I64Const [x]) (I64Const [y])) && x != y -> (I64Const [0])
378378
(I64Ne (I64Const [x]) (I64Const [y])) && x == y -> (I64Const [0])
@@ -382,15 +382,16 @@
382382
(I64ShrU (I64Const [x]) (I64Const [y])) -> (I64Const [int64(uint64(x) >> uint64(y))])
383383
(I64ShrS (I64Const [x]) (I64Const [y])) -> (I64Const [x >> uint64(y)])
384384

385-
(I64Add (I64Const [x]) y) -> (I64Add y (I64Const [x]))
386-
(I64Mul (I64Const [x]) y) -> (I64Mul y (I64Const [x]))
387-
(I64And (I64Const [x]) y) -> (I64And y (I64Const [x]))
388-
(I64Or (I64Const [x]) y) -> (I64Or y (I64Const [x]))
389-
(I64Xor (I64Const [x]) y) -> (I64Xor y (I64Const [x]))
390-
(F64Add (F64Const [x]) y) -> (F64Add y (F64Const [x]))
391-
(F64Mul (F64Const [x]) y) -> (F64Mul y (F64Const [x]))
392-
(I64Eq (I64Const [x]) y) -> (I64Eq y (I64Const [x]))
393-
(I64Ne (I64Const [x]) y) -> (I64Ne y (I64Const [x]))
385+
// TODO: declare these operations as commutative and get rid of these rules?
386+
(I64Add (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Add y (I64Const [x]))
387+
(I64Mul (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Mul y (I64Const [x]))
388+
(I64And (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64And y (I64Const [x]))
389+
(I64Or (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Or y (I64Const [x]))
390+
(I64Xor (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Xor y (I64Const [x]))
391+
(F64Add (F64Const [x]) y) && y.Op != OpWasmF64Const -> (F64Add y (F64Const [x]))
392+
(F64Mul (F64Const [x]) y) && y.Op != OpWasmF64Const -> (F64Mul y (F64Const [x]))
393+
(I64Eq (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Eq y (I64Const [x]))
394+
(I64Ne (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Ne y (I64Const [x]))
394395

395396
(I64Eq x (I64Const [0])) -> (I64Eqz x)
396397
(I64Ne x (I64Const [0])) -> (I64Eqz (I64Eqz x))

src/cmd/compile/internal/ssa/gen/generic.rules

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@
118118
(Mul16 (Const16 [c]) (Const16 [d])) -> (Const16 [int64(int16(c*d))])
119119
(Mul32 (Const32 [c]) (Const32 [d])) -> (Const32 [int64(int32(c*d))])
120120
(Mul64 (Const64 [c]) (Const64 [d])) -> (Const64 [c*d])
121-
(Mul32F (Const32F [c]) (Const32F [d])) -> (Const32F [auxFrom32F(auxTo32F(c) * auxTo32F(d))])
122-
(Mul64F (Const64F [c]) (Const64F [d])) -> (Const64F [auxFrom64F(auxTo64F(c) * auxTo64F(d))])
121+
(Mul32F (Const32F [c]) (Const32F [d])) && !math.IsNaN(float64(auxTo32F(c) * auxTo32F(d))) -> (Const32F [auxFrom32F(auxTo32F(c) * auxTo32F(d))])
122+
(Mul64F (Const64F [c]) (Const64F [d])) && !math.IsNaN(auxTo64F(c) * auxTo64F(d)) -> (Const64F [auxFrom64F(auxTo64F(c) * auxTo64F(d))])
123123

124124
(And8 (Const8 [c]) (Const8 [d])) -> (Const8 [int64(int8(c&d))])
125125
(And16 (Const16 [c]) (Const16 [d])) -> (Const16 [int64(int16(c&d))])
@@ -144,8 +144,8 @@
144144
(Div16u (Const16 [c]) (Const16 [d])) && d != 0 -> (Const16 [int64(int16(uint16(c)/uint16(d)))])
145145
(Div32u (Const32 [c]) (Const32 [d])) && d != 0 -> (Const32 [int64(int32(uint32(c)/uint32(d)))])
146146
(Div64u (Const64 [c]) (Const64 [d])) && d != 0 -> (Const64 [int64(uint64(c)/uint64(d))])
147-
(Div32F (Const32F [c]) (Const32F [d])) -> (Const32F [auxFrom32F(auxTo32F(c) / auxTo32F(d))])
148-
(Div64F (Const64F [c]) (Const64F [d])) -> (Const64F [auxFrom64F(auxTo64F(c) / auxTo64F(d))])
147+
(Div32F (Const32F [c]) (Const32F [d])) && !math.IsNaN(float64(auxTo32F(c) / auxTo32F(d))) -> (Const32F [auxFrom32F(auxTo32F(c) / auxTo32F(d))])
148+
(Div64F (Const64F [c]) (Const64F [d])) && !math.IsNaN(auxTo64F(c) / auxTo64F(d)) -> (Const64F [auxFrom64F(auxTo64F(c) / auxTo64F(d))])
149149
(Select0 (Div128u (Const64 [0]) lo y)) -> (Div64u lo y)
150150
(Select1 (Div128u (Const64 [0]) lo y)) -> (Mod64u lo y)
151151

@@ -588,8 +588,8 @@
588588
-> x
589589

590590
// Pass constants through math.Float{32,64}bits and math.Float{32,64}frombits
591-
(Load <t1> p1 (Store {t2} p2 (Const64 [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 8 && is64BitFloat(t1) -> (Const64F [x])
592-
(Load <t1> p1 (Store {t2} p2 (Const32 [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 4 && is32BitFloat(t1) -> (Const32F [auxFrom32F(math.Float32frombits(uint32(x)))])
591+
(Load <t1> p1 (Store {t2} p2 (Const64 [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 8 && is64BitFloat(t1) && !math.IsNaN(math.Float64frombits(uint64(x))) -> (Const64F [x])
592+
(Load <t1> p1 (Store {t2} p2 (Const32 [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 4 && is32BitFloat(t1) && !math.IsNaN(float64(math.Float32frombits(uint32(x)))) -> (Const32F [auxFrom32F(math.Float32frombits(uint32(x)))])
593593
(Load <t1> p1 (Store {t2} p2 (Const64F [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 8 && is64BitInt(t1) -> (Const64 [x])
594594
(Load <t1> p1 (Store {t2} p2 (Const32F [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 4 && is32BitInt(t1) -> (Const32 [int64(int32(math.Float32bits(auxTo32F(x))))])
595595

@@ -1858,7 +1858,7 @@
18581858
(Div32F x (Const32F <t> [c])) && reciprocalExact32(auxTo32F(c)) -> (Mul32F x (Const32F <t> [auxFrom32F(1/auxTo32F(c))]))
18591859
(Div64F x (Const64F <t> [c])) && reciprocalExact64(auxTo64F(c)) -> (Mul64F x (Const64F <t> [auxFrom64F(1/auxTo64F(c))]))
18601860

1861-
(Sqrt (Const64F [c])) -> (Const64F [auxFrom64F(math.Sqrt(auxTo64F(c)))])
1861+
(Sqrt (Const64F [c])) && !math.IsNaN(math.Sqrt(auxTo64F(c))) -> (Const64F [auxFrom64F(math.Sqrt(auxTo64F(c)))])
18621862

18631863
// recognize runtime.newobject and don't Zero/Nilcheck it
18641864
(Zero (Load (OffPtr [c] (SP)) mem) mem)

src/cmd/compile/internal/ssa/gen/genericOps.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,12 @@ var genericOps = []opData{
339339
{name: "Const32", aux: "Int32"}, // auxint is sign-extended 32 bits
340340
// Note: ConstX are sign-extended even when the type of the value is unsigned.
341341
// For instance, uint8(0xaa) is stored as auxint=0xffffffffffffffaa.
342-
{name: "Const64", aux: "Int64"}, // value is auxint
342+
{name: "Const64", aux: "Int64"}, // value is auxint
343+
// Note: for both Const32F and Const64F, we disallow encoding NaNs.
344+
// Signaling NaNs are tricky because if you do anything with them, they become quiet.
345+
// Particularly, converting a 32 bit sNaN to 64 bit and back converts it to a qNaN.
346+
// See issue 36399 and 36400.
347+
// Encodings of +inf, -inf, and -0 are fine.
343348
{name: "Const32F", aux: "Float32"}, // value is math.Float64frombits(uint64(auxint)) and is exactly representable as float 32
344349
{name: "Const64F", aux: "Float64"}, // value is math.Float64frombits(uint64(auxint))
345350
{name: "ConstInterface"}, // nil interface

src/cmd/compile/internal/ssa/rewrite.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,11 +487,17 @@ func DivisionNeedsFixUp(v *Value) bool {
487487

488488
// auxFrom64F encodes a float64 value so it can be stored in an AuxInt.
489489
func auxFrom64F(f float64) int64 {
490+
if f != f {
491+
panic("can't encode a NaN in AuxInt field")
492+
}
490493
return int64(math.Float64bits(f))
491494
}
492495

493496
// auxFrom32F encodes a float32 value so it can be stored in an AuxInt.
494497
func auxFrom32F(f float32) int64 {
498+
if f != f {
499+
panic("can't encode a NaN in AuxInt field")
500+
}
495501
return int64(math.Float64bits(extend32Fto64F(f)))
496502
}
497503

src/cmd/compile/internal/ssa/rewritePPC64.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)