Skip to content

Commit cd9fd64

Browse files
committed
cmd/compile: don't allow NaNs in floating-point constant ops
Trying this CL again, with a fixed test that allows platforms to disagree on the exact behavior of converting NaNs. 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. Update #36400 Change-Id: Idf203b688a15abceabbd66ba290d4e9f63619ecb Reviewed-on: https://go-review.googlesource.com/c/go/+/221790 Run-TryBot: Keith Randall <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Josh Bleecher Snyder <[email protected]>
1 parent 24343cb commit cd9fd64

File tree

11 files changed

+198
-26
lines changed

11 files changed

+198
-26
lines changed

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,64 @@ 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, we get a NaN.
518+
// (Ideally we want a quiet NaN, but some platforms don't agree.)
519+
// See issue 36399.
520+
s32 := math.Float32frombits(snan32bitsVar)
521+
if s32 == s32 {
522+
t.Errorf("converting a NaN did not result in a NaN")
523+
}
524+
s64 := math.Float64frombits(snan64bitsVar)
525+
if s64 == s64 {
526+
t.Errorf("converting a NaN did not result in a NaN")
527+
}
528+
}
529+
530+
func TestFloatSignalingNaNConversionConst(t *testing.T) {
531+
// Test to make sure when we convert a signaling NaN, it converts to a NaN.
532+
// (Ideally we want a quiet NaN, but some platforms don't agree.)
533+
// See issue 36399 and 36400.
534+
s32 := math.Float32frombits(snan32bits)
535+
if s32 == s32 {
536+
t.Errorf("converting a NaN did not result in a NaN")
537+
}
538+
s64 := math.Float64frombits(snan64bits)
539+
if s64 == s64 {
540+
t.Errorf("converting a NaN did not result in a NaN")
541+
}
542+
}
543+
486544
var sinkFloat float64
487545

488546
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, auxARM64BitField:
144+
case auxInt64, auxARM64BitField:
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
@@ -80,7 +80,7 @@
8080

8181
// Constant folding
8282
(FABS (FMOVDconst [x])) -> (FMOVDconst [auxFrom64F(math.Abs(auxTo64F(x)))])
83-
(FSQRT (FMOVDconst [x])) -> (FMOVDconst [auxFrom64F(math.Sqrt(auxTo64F(x)))])
83+
(FSQRT (FMOVDconst [x])) && auxTo64F(x) >= 0 -> (FMOVDconst [auxFrom64F(math.Sqrt(auxTo64F(x)))])
8484
(FFLOOR (FMOVDconst [x])) -> (FMOVDconst [auxFrom64F(math.Floor(auxTo64F(x)))])
8585
(FCEIL (FMOVDconst [x])) -> (FMOVDconst [auxFrom64F(math.Ceil(auxTo64F(x)))])
8686
(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
@@ -357,7 +357,7 @@
357357
(I64Or (I64Const [x]) (I64Const [y])) -> (I64Const [x | y])
358358
(I64Xor (I64Const [x]) (I64Const [y])) -> (I64Const [x ^ y])
359359
(F64Add (F64Const [x]) (F64Const [y])) -> (F64Const [auxFrom64F(auxTo64F(x) + auxTo64F(y))])
360-
(F64Mul (F64Const [x]) (F64Const [y])) -> (F64Const [auxFrom64F(auxTo64F(x) * auxTo64F(y))])
360+
(F64Mul (F64Const [x]) (F64Const [y])) && !math.IsNaN(auxTo64F(x) * auxTo64F(y)) -> (F64Const [auxFrom64F(auxTo64F(x) * auxTo64F(y))])
361361
(I64Eq (I64Const [x]) (I64Const [y])) && x == y -> (I64Const [1])
362362
(I64Eq (I64Const [x]) (I64Const [y])) && x != y -> (I64Const [0])
363363
(I64Ne (I64Const [x]) (I64Const [y])) && x == y -> (I64Const [0])
@@ -367,15 +367,16 @@
367367
(I64ShrU (I64Const [x]) (I64Const [y])) -> (I64Const [int64(uint64(x) >> uint64(y))])
368368
(I64ShrS (I64Const [x]) (I64Const [y])) -> (I64Const [x >> uint64(y)])
369369

370-
(I64Add (I64Const [x]) y) -> (I64Add y (I64Const [x]))
371-
(I64Mul (I64Const [x]) y) -> (I64Mul y (I64Const [x]))
372-
(I64And (I64Const [x]) y) -> (I64And y (I64Const [x]))
373-
(I64Or (I64Const [x]) y) -> (I64Or y (I64Const [x]))
374-
(I64Xor (I64Const [x]) y) -> (I64Xor y (I64Const [x]))
375-
(F64Add (F64Const [x]) y) -> (F64Add y (F64Const [x]))
376-
(F64Mul (F64Const [x]) y) -> (F64Mul y (F64Const [x]))
377-
(I64Eq (I64Const [x]) y) -> (I64Eq y (I64Const [x]))
378-
(I64Ne (I64Const [x]) y) -> (I64Ne y (I64Const [x]))
370+
// TODO: declare these operations as commutative and get rid of these rules?
371+
(I64Add (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Add y (I64Const [x]))
372+
(I64Mul (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Mul y (I64Const [x]))
373+
(I64And (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64And y (I64Const [x]))
374+
(I64Or (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Or y (I64Const [x]))
375+
(I64Xor (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Xor y (I64Const [x]))
376+
(F64Add (F64Const [x]) y) && y.Op != OpWasmF64Const -> (F64Add y (F64Const [x]))
377+
(F64Mul (F64Const [x]) y) && y.Op != OpWasmF64Const -> (F64Mul y (F64Const [x]))
378+
(I64Eq (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Eq y (I64Const [x]))
379+
(I64Ne (I64Const [x]) y) && y.Op != OpWasmI64Const -> (I64Ne y (I64Const [x]))
379380

380381
(I64Eq x (I64Const [0])) -> (I64Eqz x)
381382
(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
@@ -119,8 +119,8 @@
119119
(Mul16 (Const16 [c]) (Const16 [d])) -> (Const16 [int64(int16(c*d))])
120120
(Mul32 (Const32 [c]) (Const32 [d])) -> (Const32 [int64(int32(c*d))])
121121
(Mul64 (Const64 [c]) (Const64 [d])) -> (Const64 [c*d])
122-
(Mul32F (Const32F [c]) (Const32F [d])) -> (Const32F [auxFrom32F(auxTo32F(c) * auxTo32F(d))])
123-
(Mul64F (Const64F [c]) (Const64F [d])) -> (Const64F [auxFrom64F(auxTo64F(c) * auxTo64F(d))])
122+
(Mul32F (Const32F [c]) (Const32F [d])) && !math.IsNaN(float64(auxTo32F(c) * auxTo32F(d))) -> (Const32F [auxFrom32F(auxTo32F(c) * auxTo32F(d))])
123+
(Mul64F (Const64F [c]) (Const64F [d])) && !math.IsNaN(auxTo64F(c) * auxTo64F(d)) -> (Const64F [auxFrom64F(auxTo64F(c) * auxTo64F(d))])
124124

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

@@ -623,8 +623,8 @@
623623
-> x
624624

625625
// Pass constants through math.Float{32,64}bits and math.Float{32,64}frombits
626-
(Load <t1> p1 (Store {t2} p2 (Const64 [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 8 && is64BitFloat(t1) -> (Const64F [x])
627-
(Load <t1> p1 (Store {t2} p2 (Const32 [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 4 && is32BitFloat(t1) -> (Const32F [auxFrom32F(math.Float32frombits(uint32(x)))])
626+
(Load <t1> p1 (Store {t2} p2 (Const64 [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 8 && is64BitFloat(t1) && !math.IsNaN(math.Float64frombits(uint64(x))) -> (Const64F [x])
627+
(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)))])
628628
(Load <t1> p1 (Store {t2} p2 (Const64F [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 8 && is64BitInt(t1) -> (Const64 [x])
629629
(Load <t1> p1 (Store {t2} p2 (Const32F [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 4 && is32BitInt(t1) -> (Const32 [int64(int32(math.Float32bits(auxTo32F(x))))])
630630

@@ -1893,7 +1893,7 @@
18931893
(Div32F x (Const32F <t> [c])) && reciprocalExact32(auxTo32F(c)) -> (Mul32F x (Const32F <t> [auxFrom32F(1/auxTo32F(c))]))
18941894
(Div64F x (Const64F <t> [c])) && reciprocalExact64(auxTo64F(c)) -> (Mul64F x (Const64F <t> [auxFrom64F(1/auxTo64F(c))]))
18951895

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

18981898
// recognize runtime.newobject and don't Zero/Nilcheck it
18991899
(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
@@ -323,7 +323,12 @@ var genericOps = []opData{
323323
{name: "Const32", aux: "Int32"}, // auxint is sign-extended 32 bits
324324
// Note: ConstX are sign-extended even when the type of the value is unsigned.
325325
// For instance, uint8(0xaa) is stored as auxint=0xffffffffffffffaa.
326-
{name: "Const64", aux: "Int64"}, // value is auxint
326+
{name: "Const64", aux: "Int64"}, // value is auxint
327+
// Note: for both Const32F and Const64F, we disallow encoding NaNs.
328+
// Signaling NaNs are tricky because if you do anything with them, they become quiet.
329+
// Particularly, converting a 32 bit sNaN to 64 bit and back converts it to a qNaN.
330+
// See issue 36399 and 36400.
331+
// Encodings of +inf, -inf, and -0 are fine.
327332
{name: "Const32F", aux: "Float32"}, // value is math.Float64frombits(uint64(auxint)) and is exactly representable as float 32
328333
{name: "Const64F", aux: "Float64"}, // value is math.Float64frombits(uint64(auxint))
329334
{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)