Skip to content

Commit 48af3a8

Browse files
committed
cmd/compile: fix store-to-load forwarding of 32-bit sNaNs
Signalling NaNs were being converted to quiet NaNs during constant propagation through integer <-> float store-to-load forwarding. This occurs because we store float32 constants as float64 values and CPU hardware 'quietens' NaNs during conversion between the two. Eventually we want to move to using float32 values to store float32 constants, however this will be a big change since both the compiler and the assembler expect float64 values. So for now this is a small change that will fix the immediate issue. Fixes #27193. Change-Id: Iac54bd8c13abe26f9396712bc71f9b396f842724 Reviewed-on: https://go-review.googlesource.com/132956 Run-TryBot: Michael Munday <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 067dfce commit 48af3a8

File tree

5 files changed

+159
-11
lines changed

5 files changed

+159
-11
lines changed

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

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,117 @@ func TestFloatConvertFolded(t *testing.T) {
362362
}
363363
}
364364

365+
func TestFloat32StoreToLoadConstantFold(t *testing.T) {
366+
// Test that math.Float32{,from}bits constant fold correctly.
367+
// In particular we need to be careful that signalling NaN (sNaN) values
368+
// are not converted to quiet NaN (qNaN) values during compilation.
369+
// See issue #27193 for more information.
370+
371+
// signalling NaNs
372+
{
373+
const nan = uint32(0x7f800001) // sNaN
374+
if x := math.Float32bits(math.Float32frombits(nan)); x != nan {
375+
t.Errorf("got %#x, want %#x", x, nan)
376+
}
377+
}
378+
{
379+
const nan = uint32(0x7fbfffff) // sNaN
380+
if x := math.Float32bits(math.Float32frombits(nan)); x != nan {
381+
t.Errorf("got %#x, want %#x", x, nan)
382+
}
383+
}
384+
{
385+
const nan = uint32(0xff800001) // sNaN
386+
if x := math.Float32bits(math.Float32frombits(nan)); x != nan {
387+
t.Errorf("got %#x, want %#x", x, nan)
388+
}
389+
}
390+
{
391+
const nan = uint32(0xffbfffff) // sNaN
392+
if x := math.Float32bits(math.Float32frombits(nan)); x != nan {
393+
t.Errorf("got %#x, want %#x", x, nan)
394+
}
395+
}
396+
397+
// quiet NaNs
398+
{
399+
const nan = uint32(0x7fc00000) // qNaN
400+
if x := math.Float32bits(math.Float32frombits(nan)); x != nan {
401+
t.Errorf("got %#x, want %#x", x, nan)
402+
}
403+
}
404+
{
405+
const nan = uint32(0x7fffffff) // qNaN
406+
if x := math.Float32bits(math.Float32frombits(nan)); x != nan {
407+
t.Errorf("got %#x, want %#x", x, nan)
408+
}
409+
}
410+
{
411+
const nan = uint32(0x8fc00000) // qNaN
412+
if x := math.Float32bits(math.Float32frombits(nan)); x != nan {
413+
t.Errorf("got %#x, want %#x", x, nan)
414+
}
415+
}
416+
{
417+
const nan = uint32(0x8fffffff) // qNaN
418+
if x := math.Float32bits(math.Float32frombits(nan)); x != nan {
419+
t.Errorf("got %#x, want %#x", x, nan)
420+
}
421+
}
422+
423+
// infinities
424+
{
425+
const inf = uint32(0x7f800000) // +∞
426+
if x := math.Float32bits(math.Float32frombits(inf)); x != inf {
427+
t.Errorf("got %#x, want %#x", x, inf)
428+
}
429+
}
430+
{
431+
const negInf = uint32(0xff800000) // -∞
432+
if x := math.Float32bits(math.Float32frombits(negInf)); x != negInf {
433+
t.Errorf("got %#x, want %#x", x, negInf)
434+
}
435+
}
436+
437+
// numbers
438+
{
439+
const zero = uint32(0) // +0.0
440+
if x := math.Float32bits(math.Float32frombits(zero)); x != zero {
441+
t.Errorf("got %#x, want %#x", x, zero)
442+
}
443+
}
444+
{
445+
const negZero = uint32(1 << 31) // -0.0
446+
if x := math.Float32bits(math.Float32frombits(negZero)); x != negZero {
447+
t.Errorf("got %#x, want %#x", x, negZero)
448+
}
449+
}
450+
{
451+
const one = uint32(0x3f800000) // 1.0
452+
if x := math.Float32bits(math.Float32frombits(one)); x != one {
453+
t.Errorf("got %#x, want %#x", x, one)
454+
}
455+
}
456+
{
457+
const negOne = uint32(0xbf800000) // -1.0
458+
if x := math.Float32bits(math.Float32frombits(negOne)); x != negOne {
459+
t.Errorf("got %#x, want %#x", x, negOne)
460+
}
461+
}
462+
{
463+
const frac = uint32(0x3fc00000) // +1.5
464+
if x := math.Float32bits(math.Float32frombits(frac)); x != frac {
465+
t.Errorf("got %#x, want %#x", x, frac)
466+
}
467+
}
468+
{
469+
const negFrac = uint32(0xbfc00000) // -1.5
470+
if x := math.Float32bits(math.Float32frombits(negFrac)); x != negFrac {
471+
t.Errorf("got %#x, want %#x", x, negFrac)
472+
}
473+
}
474+
}
475+
365476
var sinkFloat float64
366477

367478
func BenchmarkMul2(b *testing.B) {

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package ssa
66

77
import (
88
"math"
9+
"math/bits"
910
)
1011

1112
// checkFunc checks invariants of f.
@@ -146,7 +147,7 @@ func checkFunc(f *Func) {
146147
// AuxInt must be zero, so leave canHaveAuxInt set to false.
147148
case auxFloat32:
148149
canHaveAuxInt = true
149-
if !isExactFloat32(v) {
150+
if !isExactFloat32(v.AuxFloat()) {
150151
f.Fatalf("value %v has an AuxInt value that is not an exact float32", v)
151152
}
152153
case auxString, auxSym, auxTyp:
@@ -508,8 +509,12 @@ func domCheck(f *Func, sdom SparseTree, x, y *Block) bool {
508509
return sdom.isAncestorEq(x, y)
509510
}
510511

511-
// isExactFloat32 reports whether v has an AuxInt that can be exactly represented as a float32.
512-
func isExactFloat32(v *Value) bool {
513-
x := v.AuxFloat()
514-
return math.Float64bits(x) == math.Float64bits(float64(float32(x)))
512+
// isExactFloat32 reports whether x can be exactly represented as a float32.
513+
func isExactFloat32(x float64) bool {
514+
// Check the mantissa is in range.
515+
if bits.TrailingZeros64(math.Float64bits(x)) < 52-23 {
516+
return false
517+
}
518+
// Check the exponent is in range. The mantissa check above is sufficient for NaN values.
519+
return math.IsNaN(x) || x == float64(float32(x))
515520
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -572,9 +572,9 @@
572572

573573
// Pass constants through math.Float{32,64}bits and math.Float{32,64}frombits
574574
(Load <t1> p1 (Store {t2} p2 (Const64 [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 8 && is64BitFloat(t1) -> (Const64F [x])
575-
(Load <t1> p1 (Store {t2} p2 (Const32 [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 4 && is32BitFloat(t1) -> (Const32F [f2i(float64(math.Float32frombits(uint32(x))))])
575+
(Load <t1> p1 (Store {t2} p2 (Const32 [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 4 && is32BitFloat(t1) -> (Const32F [f2i(extend32Fto64F(math.Float32frombits(uint32(x))))])
576576
(Load <t1> p1 (Store {t2} p2 (Const64F [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 8 && is64BitInt(t1) -> (Const64 [x])
577-
(Load <t1> p1 (Store {t2} p2 (Const32F [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 4 && is32BitInt(t1) -> (Const32 [int64(int32(math.Float32bits(float32(i2f(x)))))])
577+
(Load <t1> p1 (Store {t2} p2 (Const32F [x]) _)) && isSamePtr(p1,p2) && sizeof(t2) == 4 && is32BitInt(t1) -> (Const32 [int64(int32(math.Float32bits(truncate64Fto32F(i2f(x)))))])
578578

579579
// Float Loads up to Zeros so they can be constant folded.
580580
(Load <t1> op:(OffPtr [o1] p1)

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,38 @@ func shiftIsBounded(v *Value) bool {
418418
return v.AuxInt != 0
419419
}
420420

421+
// truncate64Fto32F converts a float64 value to a float32 preserving the bit pattern
422+
// of the mantissa. It will panic if the truncation results in lost information.
423+
func truncate64Fto32F(f float64) float32 {
424+
if !isExactFloat32(f) {
425+
panic("truncate64Fto32F: truncation is not exact")
426+
}
427+
if !math.IsNaN(f) {
428+
return float32(f)
429+
}
430+
// NaN bit patterns aren't necessarily preserved across conversion
431+
// instructions so we need to do the conversion manually.
432+
b := math.Float64bits(f)
433+
m := b & ((1 << 52) - 1) // mantissa (a.k.a. significand)
434+
// | sign | exponent | mantissa |
435+
r := uint32(((b >> 32) & (1 << 31)) | 0x7f800000 | (m >> (52 - 23)))
436+
return math.Float32frombits(r)
437+
}
438+
439+
// extend32Fto64F converts a float32 value to a float64 value preserving the bit
440+
// pattern of the mantissa.
441+
func extend32Fto64F(f float32) float64 {
442+
if !math.IsNaN(float64(f)) {
443+
return float64(f)
444+
}
445+
// NaN bit patterns aren't necessarily preserved across conversion
446+
// instructions so we need to do the conversion manually.
447+
b := uint64(math.Float32bits(f))
448+
// | sign | exponent | mantissa |
449+
r := ((b << 32) & (1 << 63)) | (0x7ff << 52) | ((b & 0x7fffff) << (52 - 23))
450+
return math.Float64frombits(r)
451+
}
452+
421453
// i2f is used in rules for converting from an AuxInt to a float.
422454
func i2f(i int64) float64 {
423455
return math.Float64frombits(uint64(i))

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

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

0 commit comments

Comments
 (0)