Skip to content

Commit 9aa75d1

Browse files
committed
runtime: implement fminimum/fmaximum
The compiler may generate calls to fminimum/fmaximum on some platforms. Neither of the libm implementations we statically link against have these functions yet. Implement them ourselves.
1 parent 66d7099 commit 9aa75d1

2 files changed

Lines changed: 340 additions & 0 deletions

File tree

src/runtime/float.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,130 @@ func float64bits(f float64) uint64 {
5252
func float64frombits(b uint64) float64 {
5353
return *(*float64)(unsafe.Pointer(&b))
5454
}
55+
56+
// The fmimimum/fmaximum are missing from most libm implementations.
57+
// Just define them ourselves.
58+
59+
//export fminimum
60+
func fminimum(x, y float64) float64 {
61+
return minimumFloat64(x, y)
62+
}
63+
64+
//export fminimumf
65+
func fminimumf(x, y float32) float32 {
66+
return minimumFloat32(x, y)
67+
}
68+
69+
//export fmaximum
70+
func fmaximum(x, y float64) float64 {
71+
return maximumFloat64(x, y)
72+
}
73+
74+
//export fmaximumf
75+
func fmaximumf(x, y float32) float32 {
76+
return maximumFloat32(x, y)
77+
}
78+
79+
// Create seperate copies of the function that are not exported.
80+
// This is necessary so that LLVM does not recognize them as builtins.
81+
// If tests called the builtins, LLVM would just override them on most platforms.
82+
83+
func minimumFloat32(x, y float32) float32 {
84+
return minimumFloat[float32, int32](x, y, minPosNaN32, magMask32)
85+
}
86+
87+
func minimumFloat64(x, y float64) float64 {
88+
return minimumFloat[float64, int64](x, y, minPosNaN64, magMask64)
89+
}
90+
91+
func maximumFloat32(x, y float32) float32 {
92+
return maximumFloat[float32, int32](x, y, minPosNaN32, magMask32)
93+
}
94+
95+
func maximumFloat64(x, y float64) float64 {
96+
return maximumFloat[float64, int64](x, y, minPosNaN64, magMask64)
97+
}
98+
99+
// minimumFloat is a generic implementation of the floating-point minimum operation.
100+
// This implementation uses integer operations because this is mainly used for platforms without an FPU.
101+
func minimumFloat[T float, I floatInt](x, y T, minPosNaN, magMask I) T {
102+
xBits := *(*I)(unsafe.Pointer(&x))
103+
yBits := *(*I)(unsafe.Pointer(&y))
104+
105+
// Handle the special case of a positive NaN value.
106+
switch {
107+
case xBits >= minPosNaN:
108+
return x
109+
case yBits >= minPosNaN:
110+
return y
111+
}
112+
113+
// The exponent-mantissa portion of the float is comparable via unsigned comparison (excluding the NaN case).
114+
// We can turn a float into a signed-comparable value by reversing the comparison order of negative values.
115+
// We can reverse the order by inverting the bits.
116+
// This also ensures that positive zero compares greater than negative zero (as required by the spec).
117+
// Negative NaN values will compare less than any other value, so they require no special handling to propogate.
118+
if xBits < 0 {
119+
xBits ^= magMask
120+
}
121+
if yBits < 0 {
122+
yBits ^= magMask
123+
}
124+
if xBits <= yBits {
125+
return x
126+
} else {
127+
return y
128+
}
129+
}
130+
131+
// maximumFloat is a generic implementation of the floating-point maximum operation.
132+
// This implementation uses integer operations because this is mainly used for platforms without an FPU.
133+
func maximumFloat[T float, I floatInt](x, y T, minPosNaN, magMask I) T {
134+
xBits := *(*I)(unsafe.Pointer(&x))
135+
yBits := *(*I)(unsafe.Pointer(&y))
136+
137+
// The exponent-mantissa portion of the float is comparable via unsigned comparison (excluding the NaN case).
138+
// We can turn a float into a signed-comparable value by reversing the comparison order of negative values.
139+
// We can reverse the order by inverting the bits.
140+
// This also ensures that positive zero compares greater than negative zero (as required by the spec).
141+
// Positive NaN values will compare greater than any other value, so they require no special handling to propogate.
142+
if xBits < 0 {
143+
xBits ^= magMask
144+
}
145+
if yBits < 0 {
146+
yBits ^= magMask
147+
}
148+
// Handle the special case of a negative NaN value.
149+
maxNegNaN := ^minPosNaN
150+
switch {
151+
case xBits <= maxNegNaN:
152+
return x
153+
case yBits <= maxNegNaN:
154+
return y
155+
}
156+
if xBits >= yBits {
157+
return x
158+
} else {
159+
return y
160+
}
161+
}
162+
163+
const (
164+
signPos64 = 63
165+
exponentPos64 = 52
166+
minPosNaN64 = ((1 << signPos64) - (1 << exponentPos64)) + 1
167+
magMask64 = 1<<signPos64 - 1
168+
169+
signPos32 = 31
170+
exponentPos32 = 23
171+
minPosNaN32 = ((1 << signPos32) - (1 << exponentPos32)) + 1
172+
magMask32 = 1<<signPos32 - 1
173+
)
174+
175+
type float interface {
176+
float32 | float64
177+
}
178+
179+
type floatInt interface {
180+
int32 | int64
181+
}

src/runtime/float_test.go

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package runtime_test
2+
3+
import (
4+
"math"
5+
"testing"
6+
_ "unsafe"
7+
)
8+
9+
func TestFloatMinMax32(t *testing.T) {
10+
t.Parallel()
11+
12+
for _, c := range []struct {
13+
x float32
14+
y float32
15+
min float32
16+
max float32
17+
}{
18+
{
19+
x: 0,
20+
y: 0,
21+
min: 0,
22+
max: 0,
23+
},
24+
{
25+
x: -12,
26+
y: 2,
27+
min: -12,
28+
max: 2,
29+
},
30+
{
31+
x: 2,
32+
y: -12,
33+
min: -12,
34+
max: 2,
35+
},
36+
{
37+
x: float32(math.Copysign(0, -1)),
38+
y: 0,
39+
min: float32(math.Copysign(0, -1)),
40+
max: 0,
41+
},
42+
{
43+
x: 0,
44+
y: float32(math.Copysign(0, -1)),
45+
min: float32(math.Copysign(0, -1)),
46+
max: 0,
47+
},
48+
{
49+
x: float32(math.Inf(-1)),
50+
y: float32(math.Inf(1)),
51+
min: float32(math.Inf(-1)),
52+
max: float32(math.Inf(1)),
53+
},
54+
{
55+
x: math.MaxFloat32,
56+
y: math.SmallestNonzeroFloat32,
57+
min: math.SmallestNonzeroFloat32,
58+
max: math.MaxFloat32,
59+
},
60+
{
61+
x: math.Float32frombits(0x7FC00001),
62+
y: 0,
63+
min: math.Float32frombits(0x7FC00001),
64+
max: math.Float32frombits(0x7FC00001),
65+
},
66+
{
67+
x: 0,
68+
y: math.Float32frombits(0x7FC00001),
69+
min: math.Float32frombits(0x7FC00001),
70+
max: math.Float32frombits(0x7FC00001),
71+
},
72+
{
73+
x: math.Float32frombits(0x7FC00001),
74+
y: math.Float32frombits(0x7FC00001),
75+
min: math.Float32frombits(0x7FC00001),
76+
max: math.Float32frombits(0x7FC00001),
77+
},
78+
{
79+
x: math.Float32frombits(0xFFC00001),
80+
y: 0,
81+
min: math.Float32frombits(0xFFC00001),
82+
max: math.Float32frombits(0xFFC00001),
83+
},
84+
{
85+
x: 0,
86+
y: math.Float32frombits(0xFFC00001),
87+
min: math.Float32frombits(0xFFC00001),
88+
max: math.Float32frombits(0xFFC00001),
89+
},
90+
{
91+
x: math.Float32frombits(0xFFC00001),
92+
y: math.Float32frombits(0xFFC00001),
93+
min: math.Float32frombits(0xFFC00001),
94+
max: math.Float32frombits(0xFFC00001),
95+
},
96+
} {
97+
if min := minimumFloat32(c.x, c.y); math.Float32bits(min) != math.Float32bits(c.min) {
98+
t.Errorf("minimumFloat32(%f, %f) = %f (expected %f)", c.x, c.y, min, c.min)
99+
}
100+
if max := maximumFloat32(c.x, c.y); math.Float32bits(max) != math.Float32bits(c.max) {
101+
t.Errorf("maximumFloat32(%f, %f) = %f (expected %f)", c.x, c.y, max, c.max)
102+
}
103+
}
104+
}
105+
106+
//go:linkname minimumFloat32 runtime.minimumFloat32
107+
func minimumFloat32(x, y float32) float32
108+
109+
//go:linkname maximumFloat32 runtime.maximumFloat32
110+
func maximumFloat32(x, y float32) float32
111+
112+
func TestFloatMinMax64(t *testing.T) {
113+
t.Parallel()
114+
115+
for _, c := range []struct {
116+
x float64
117+
y float64
118+
min float64
119+
max float64
120+
}{
121+
{
122+
x: 0,
123+
y: 0,
124+
min: 0,
125+
max: 0,
126+
},
127+
{
128+
x: -12,
129+
y: 2,
130+
min: -12,
131+
max: 2,
132+
},
133+
{
134+
x: 2,
135+
y: -12,
136+
min: -12,
137+
max: 2,
138+
},
139+
{
140+
x: math.Copysign(0, -1),
141+
y: 0,
142+
min: math.Copysign(0, -1),
143+
max: 0,
144+
},
145+
{
146+
x: 0,
147+
y: math.Copysign(0, -1),
148+
min: math.Copysign(0, -1),
149+
max: 0,
150+
},
151+
{
152+
x: math.Inf(-1),
153+
y: math.Inf(1),
154+
min: math.Inf(-1),
155+
max: math.Inf(1),
156+
},
157+
{
158+
x: math.MaxFloat32,
159+
y: math.SmallestNonzeroFloat32,
160+
min: math.SmallestNonzeroFloat32,
161+
max: math.MaxFloat32,
162+
},
163+
{
164+
x: math.Float64frombits(0x7FF8000000000001),
165+
y: 0,
166+
min: math.Float64frombits(0x7FF8000000000001),
167+
max: math.Float64frombits(0x7FF8000000000001),
168+
},
169+
{
170+
x: 0,
171+
y: math.Float64frombits(0x7FF8000000000001),
172+
min: math.Float64frombits(0x7FF8000000000001),
173+
max: math.Float64frombits(0x7FF8000000000001),
174+
},
175+
{
176+
x: math.Float64frombits(0x7FF8000000000001),
177+
y: math.Float64frombits(0x7FF8000000000001),
178+
min: math.Float64frombits(0x7FF8000000000001),
179+
max: math.Float64frombits(0x7FF8000000000001),
180+
},
181+
{
182+
x: math.Float64frombits(0xFFF8000000000001),
183+
y: 0,
184+
min: math.Float64frombits(0xFFF8000000000001),
185+
max: math.Float64frombits(0xFFF8000000000001),
186+
},
187+
{
188+
x: 0,
189+
y: math.Float64frombits(0xFFF8000000000001),
190+
min: math.Float64frombits(0xFFF8000000000001),
191+
max: math.Float64frombits(0xFFF8000000000001),
192+
},
193+
{
194+
x: math.Float64frombits(0xFFF8000000000001),
195+
y: 0,
196+
min: math.Float64frombits(0xFFF8000000000001),
197+
max: math.Float64frombits(0xFFF8000000000001),
198+
},
199+
} {
200+
if min := minimumFloat64(c.x, c.y); math.Float64bits(min) != math.Float64bits(c.min) {
201+
t.Errorf("minimumFloat64(%f, %f) = %f (expected %f)", c.x, c.y, min, c.min)
202+
}
203+
if max := maximumFloat64(c.x, c.y); math.Float64bits(max) != math.Float64bits(c.max) {
204+
t.Errorf("maximumFloat64(%f, %f) = %f (expected %f)", c.x, c.y, max, c.max)
205+
}
206+
}
207+
}
208+
209+
//go:linkname minimumFloat64 runtime.minimumFloat64
210+
func minimumFloat64(x, y float64) float64
211+
212+
//go:linkname maximumFloat64 runtime.maximumFloat64
213+
func maximumFloat64(x, y float64) float64

0 commit comments

Comments
 (0)