mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-14 07:53:40 +00:00
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.
This commit is contained in:
@@ -52,3 +52,130 @@ func float64bits(f float64) uint64 {
|
|||||||
func float64frombits(b uint64) float64 {
|
func float64frombits(b uint64) float64 {
|
||||||
return *(*float64)(unsafe.Pointer(&b))
|
return *(*float64)(unsafe.Pointer(&b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The fmimimum/fmaximum are missing from most libm implementations.
|
||||||
|
// Just define them ourselves.
|
||||||
|
|
||||||
|
//export fminimum
|
||||||
|
func fminimum(x, y float64) float64 {
|
||||||
|
return minimumFloat64(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export fminimumf
|
||||||
|
func fminimumf(x, y float32) float32 {
|
||||||
|
return minimumFloat32(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export fmaximum
|
||||||
|
func fmaximum(x, y float64) float64 {
|
||||||
|
return maximumFloat64(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export fmaximumf
|
||||||
|
func fmaximumf(x, y float32) float32 {
|
||||||
|
return maximumFloat32(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create seperate copies of the function that are not exported.
|
||||||
|
// This is necessary so that LLVM does not recognize them as builtins.
|
||||||
|
// If tests called the builtins, LLVM would just override them on most platforms.
|
||||||
|
|
||||||
|
func minimumFloat32(x, y float32) float32 {
|
||||||
|
return minimumFloat[float32, int32](x, y, minPosNaN32, magMask32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func minimumFloat64(x, y float64) float64 {
|
||||||
|
return minimumFloat[float64, int64](x, y, minPosNaN64, magMask64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func maximumFloat32(x, y float32) float32 {
|
||||||
|
return maximumFloat[float32, int32](x, y, minPosNaN32, magMask32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func maximumFloat64(x, y float64) float64 {
|
||||||
|
return maximumFloat[float64, int64](x, y, minPosNaN64, magMask64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// minimumFloat is a generic implementation of the floating-point minimum operation.
|
||||||
|
// This implementation uses integer operations because this is mainly used for platforms without an FPU.
|
||||||
|
func minimumFloat[T float, I floatInt](x, y T, minPosNaN, magMask I) T {
|
||||||
|
xBits := *(*I)(unsafe.Pointer(&x))
|
||||||
|
yBits := *(*I)(unsafe.Pointer(&y))
|
||||||
|
|
||||||
|
// Handle the special case of a positive NaN value.
|
||||||
|
switch {
|
||||||
|
case xBits >= minPosNaN:
|
||||||
|
return x
|
||||||
|
case yBits >= minPosNaN:
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
// The exponent-mantissa portion of the float is comparable via unsigned comparison (excluding the NaN case).
|
||||||
|
// We can turn a float into a signed-comparable value by reversing the comparison order of negative values.
|
||||||
|
// We can reverse the order by inverting the bits.
|
||||||
|
// This also ensures that positive zero compares greater than negative zero (as required by the spec).
|
||||||
|
// Negative NaN values will compare less than any other value, so they require no special handling to propogate.
|
||||||
|
if xBits < 0 {
|
||||||
|
xBits ^= magMask
|
||||||
|
}
|
||||||
|
if yBits < 0 {
|
||||||
|
yBits ^= magMask
|
||||||
|
}
|
||||||
|
if xBits <= yBits {
|
||||||
|
return x
|
||||||
|
} else {
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// maximumFloat is a generic implementation of the floating-point maximum operation.
|
||||||
|
// This implementation uses integer operations because this is mainly used for platforms without an FPU.
|
||||||
|
func maximumFloat[T float, I floatInt](x, y T, minPosNaN, magMask I) T {
|
||||||
|
xBits := *(*I)(unsafe.Pointer(&x))
|
||||||
|
yBits := *(*I)(unsafe.Pointer(&y))
|
||||||
|
|
||||||
|
// The exponent-mantissa portion of the float is comparable via unsigned comparison (excluding the NaN case).
|
||||||
|
// We can turn a float into a signed-comparable value by reversing the comparison order of negative values.
|
||||||
|
// We can reverse the order by inverting the bits.
|
||||||
|
// This also ensures that positive zero compares greater than negative zero (as required by the spec).
|
||||||
|
// Positive NaN values will compare greater than any other value, so they require no special handling to propogate.
|
||||||
|
if xBits < 0 {
|
||||||
|
xBits ^= magMask
|
||||||
|
}
|
||||||
|
if yBits < 0 {
|
||||||
|
yBits ^= magMask
|
||||||
|
}
|
||||||
|
// Handle the special case of a negative NaN value.
|
||||||
|
maxNegNaN := ^minPosNaN
|
||||||
|
switch {
|
||||||
|
case xBits <= maxNegNaN:
|
||||||
|
return x
|
||||||
|
case yBits <= maxNegNaN:
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
if xBits >= yBits {
|
||||||
|
return x
|
||||||
|
} else {
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
signPos64 = 63
|
||||||
|
exponentPos64 = 52
|
||||||
|
minPosNaN64 = ((1 << signPos64) - (1 << exponentPos64)) + 1
|
||||||
|
magMask64 = 1<<signPos64 - 1
|
||||||
|
|
||||||
|
signPos32 = 31
|
||||||
|
exponentPos32 = 23
|
||||||
|
minPosNaN32 = ((1 << signPos32) - (1 << exponentPos32)) + 1
|
||||||
|
magMask32 = 1<<signPos32 - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type float interface {
|
||||||
|
float32 | float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type floatInt interface {
|
||||||
|
int32 | int64
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
package runtime_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
_ "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFloatMinMax32(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, c := range []struct {
|
||||||
|
x float32
|
||||||
|
y float32
|
||||||
|
min float32
|
||||||
|
max float32
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -12,
|
||||||
|
y: 2,
|
||||||
|
min: -12,
|
||||||
|
max: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2,
|
||||||
|
y: -12,
|
||||||
|
min: -12,
|
||||||
|
max: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: float32(math.Copysign(0, -1)),
|
||||||
|
y: 0,
|
||||||
|
min: float32(math.Copysign(0, -1)),
|
||||||
|
max: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: float32(math.Copysign(0, -1)),
|
||||||
|
min: float32(math.Copysign(0, -1)),
|
||||||
|
max: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: float32(math.Inf(-1)),
|
||||||
|
y: float32(math.Inf(1)),
|
||||||
|
min: float32(math.Inf(-1)),
|
||||||
|
max: float32(math.Inf(1)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.MaxFloat32,
|
||||||
|
y: math.SmallestNonzeroFloat32,
|
||||||
|
min: math.SmallestNonzeroFloat32,
|
||||||
|
max: math.MaxFloat32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.Float32frombits(float32PositiveNaN),
|
||||||
|
y: 0,
|
||||||
|
min: math.Float32frombits(float32PositiveNaN),
|
||||||
|
max: math.Float32frombits(float32PositiveNaN),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: math.Float32frombits(float32PositiveNaN),
|
||||||
|
min: math.Float32frombits(float32PositiveNaN),
|
||||||
|
max: math.Float32frombits(float32PositiveNaN),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.Float32frombits(float32PositiveNaN),
|
||||||
|
y: math.Float32frombits(float32PositiveNaN),
|
||||||
|
min: math.Float32frombits(float32PositiveNaN),
|
||||||
|
max: math.Float32frombits(float32PositiveNaN),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.Float32frombits(float32NegativeNaN),
|
||||||
|
y: 0,
|
||||||
|
min: math.Float32frombits(float32NegativeNaN),
|
||||||
|
max: math.Float32frombits(float32NegativeNaN),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: math.Float32frombits(float32NegativeNaN),
|
||||||
|
min: math.Float32frombits(float32NegativeNaN),
|
||||||
|
max: math.Float32frombits(float32NegativeNaN),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.Float32frombits(float32NegativeNaN),
|
||||||
|
y: math.Float32frombits(float32NegativeNaN),
|
||||||
|
min: math.Float32frombits(float32NegativeNaN),
|
||||||
|
max: math.Float32frombits(float32NegativeNaN),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if min := minimumFloat32(c.x, c.y); math.Float32bits(min) != math.Float32bits(c.min) {
|
||||||
|
t.Errorf("minimumFloat32(%f, %f) = %f (expected %f)", c.x, c.y, min, c.min)
|
||||||
|
}
|
||||||
|
if max := maximumFloat32(c.x, c.y); math.Float32bits(max) != math.Float32bits(c.max) {
|
||||||
|
t.Errorf("maximumFloat32(%f, %f) = %f (expected %f)", c.x, c.y, max, c.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// float32PositiveNaN is the smallest positive NaN value for a float32.
|
||||||
|
float32PositiveNaN = 0x7FC00001
|
||||||
|
// float32NegativeNaN is the smallest negative NaN value for a float32.
|
||||||
|
float32NegativeNaN = 0xFFC00001
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname minimumFloat32 runtime.minimumFloat32
|
||||||
|
func minimumFloat32(x, y float32) float32
|
||||||
|
|
||||||
|
//go:linkname maximumFloat32 runtime.maximumFloat32
|
||||||
|
func maximumFloat32(x, y float32) float32
|
||||||
|
|
||||||
|
func TestFloatMinMax64(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, c := range []struct {
|
||||||
|
x float64
|
||||||
|
y float64
|
||||||
|
min float64
|
||||||
|
max float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -12,
|
||||||
|
y: 2,
|
||||||
|
min: -12,
|
||||||
|
max: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 2,
|
||||||
|
y: -12,
|
||||||
|
min: -12,
|
||||||
|
max: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.Copysign(0, -1),
|
||||||
|
y: 0,
|
||||||
|
min: math.Copysign(0, -1),
|
||||||
|
max: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: math.Copysign(0, -1),
|
||||||
|
min: math.Copysign(0, -1),
|
||||||
|
max: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.Inf(-1),
|
||||||
|
y: math.Inf(1),
|
||||||
|
min: math.Inf(-1),
|
||||||
|
max: math.Inf(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.MaxFloat64,
|
||||||
|
y: math.SmallestNonzeroFloat64,
|
||||||
|
min: math.SmallestNonzeroFloat64,
|
||||||
|
max: math.MaxFloat64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.Float64frombits(float64PositiveNaN),
|
||||||
|
y: 0,
|
||||||
|
min: math.Float64frombits(float64PositiveNaN),
|
||||||
|
max: math.Float64frombits(float64PositiveNaN),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: math.Float64frombits(float64PositiveNaN),
|
||||||
|
min: math.Float64frombits(float64PositiveNaN),
|
||||||
|
max: math.Float64frombits(float64PositiveNaN),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.Float64frombits(float64PositiveNaN),
|
||||||
|
y: math.Float64frombits(float64PositiveNaN),
|
||||||
|
min: math.Float64frombits(float64PositiveNaN),
|
||||||
|
max: math.Float64frombits(float64PositiveNaN),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.Float64frombits(float64NegativeNaN),
|
||||||
|
y: 0,
|
||||||
|
min: math.Float64frombits(float64NegativeNaN),
|
||||||
|
max: math.Float64frombits(float64NegativeNaN),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: math.Float64frombits(float64NegativeNaN),
|
||||||
|
min: math.Float64frombits(float64NegativeNaN),
|
||||||
|
max: math.Float64frombits(float64NegativeNaN),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: math.Float64frombits(float64NegativeNaN),
|
||||||
|
y: 0,
|
||||||
|
min: math.Float64frombits(float64NegativeNaN),
|
||||||
|
max: math.Float64frombits(float64NegativeNaN),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if min := minimumFloat64(c.x, c.y); math.Float64bits(min) != math.Float64bits(c.min) {
|
||||||
|
t.Errorf("minimumFloat64(%f, %f) = %f (expected %f)", c.x, c.y, min, c.min)
|
||||||
|
}
|
||||||
|
if max := maximumFloat64(c.x, c.y); math.Float64bits(max) != math.Float64bits(c.max) {
|
||||||
|
t.Errorf("maximumFloat64(%f, %f) = %f (expected %f)", c.x, c.y, max, c.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// float64PositiveNaN is the smallest positive NaN value for a float64.
|
||||||
|
float64PositiveNaN = 0x7FF8000000000001
|
||||||
|
// float64NegativeNaN is the smallest negative NaN value for a float64.
|
||||||
|
float64NegativeNaN = 0xFFF8000000000001
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname minimumFloat64 runtime.minimumFloat64
|
||||||
|
func minimumFloat64(x, y float64) float64
|
||||||
|
|
||||||
|
//go:linkname maximumFloat64 runtime.maximumFloat64
|
||||||
|
func maximumFloat64(x, y float64) float64
|
||||||
Reference in New Issue
Block a user