go/test/codegen/math.go
Michael Munday 320df537cc cmd/compile: emit classify instructions for infinity tests on riscv64
The 'classify' instruction on RISC-V sets a bit in a mask to indicate
the class a floating point value belongs to (e.g. whether the value is
an infinity, a normal number, a subnormal number and so on). There are
other places this instruction is useful but for now I've just used it
for infinity tests.

The gains are relatively small (~1-2 instructions per IsInf call) but
using FCLASSD does potentially unlock further optimizations. It also
reduces the number of loads from memory and the number of moves
between general purpose and floating point register files.

goos: linux
goarch: riscv64
pkg: math
cpu: Spacemit(R) X60
                    │        sec/op        │   sec/op     vs base                │
Acos                           159.9n ± 0%   173.7n ± 0%   +8.66% (p=0.000 n=10)
Acosh                          249.8n ± 0%   254.4n ± 0%   +1.86% (p=0.000 n=10)
Asin                           159.9n ± 0%   173.7n ± 0%   +8.66% (p=0.000 n=10)
Asinh                          292.2n ± 0%   283.0n ± 0%   -3.15% (p=0.000 n=10)
Atan                           119.1n ± 0%   119.0n ± 0%   -0.08% (p=0.036 n=10)
Atanh                          265.1n ± 0%   271.6n ± 0%   +2.43% (p=0.000 n=10)
Atan2                          194.9n ± 0%   186.7n ± 0%   -4.23% (p=0.000 n=10)
Cbrt                           216.3n ± 0%   203.1n ± 0%   -6.10% (p=0.000 n=10)
Ceil                           31.82n ± 0%   31.81n ± 0%        ~ (p=0.063 n=10)
Copysign                       4.897n ± 0%   4.893n ± 3%   -0.08% (p=0.038 n=10)
Cos                            123.9n ± 0%   107.7n ± 1%  -13.03% (p=0.000 n=10)
Cosh                           293.0n ± 0%   264.6n ± 0%   -9.68% (p=0.000 n=10)
Erf                            150.0n ± 0%   133.8n ± 0%  -10.80% (p=0.000 n=10)
Erfc                           151.8n ± 0%   137.9n ± 0%   -9.16% (p=0.000 n=10)
Erfinv                         173.8n ± 0%   173.8n ± 0%        ~ (p=0.820 n=10)
Erfcinv                        173.8n ± 0%   173.8n ± 0%        ~ (p=1.000 n=10)
Exp                            247.7n ± 0%   220.4n ± 0%  -11.04% (p=0.000 n=10)
ExpGo                          261.4n ± 0%   232.5n ± 0%  -11.04% (p=0.000 n=10)
Expm1                          176.2n ± 0%   164.9n ± 0%   -6.41% (p=0.000 n=10)
Exp2                           220.4n ± 0%   190.2n ± 0%  -13.70% (p=0.000 n=10)
Exp2Go                         232.5n ± 0%   204.0n ± 0%  -12.22% (p=0.000 n=10)
Abs                            4.897n ± 0%   4.897n ± 0%        ~ (p=0.726 n=10)
Dim                            16.32n ± 0%   16.31n ± 0%        ~ (p=0.770 n=10)
Floor                          31.84n ± 0%   31.83n ± 0%        ~ (p=0.677 n=10)
Max                            26.11n ± 0%   26.13n ± 0%        ~ (p=0.290 n=10)
Min                            26.10n ± 0%   26.11n ± 0%        ~ (p=0.424 n=10)
Mod                            416.2n ± 0%   337.8n ± 0%  -18.83% (p=0.000 n=10)
Frexp                          63.65n ± 0%   50.60n ± 0%  -20.50% (p=0.000 n=10)
Gamma                          218.8n ± 0%   206.4n ± 0%   -5.62% (p=0.000 n=10)
Hypot                          92.20n ± 0%   94.69n ± 0%   +2.70% (p=0.000 n=10)
HypotGo                        107.7n ± 0%   109.3n ± 0%   +1.49% (p=0.000 n=10)
Ilogb                          59.54n ± 0%   44.04n ± 0%  -26.04% (p=0.000 n=10)
J0                             708.9n ± 0%   674.5n ± 0%   -4.86% (p=0.000 n=10)
J1                             707.6n ± 0%   676.1n ± 0%   -4.44% (p=0.000 n=10)
Jn                             1.513µ ± 0%   1.427µ ± 0%   -5.68% (p=0.000 n=10)
Ldexp                          70.20n ± 0%   57.09n ± 0%  -18.68% (p=0.000 n=10)
Lgamma                         201.5n ± 0%   185.3n ± 1%   -8.01% (p=0.000 n=10)
Log                            201.5n ± 0%   182.7n ± 0%   -9.35% (p=0.000 n=10)
Logb                           59.54n ± 0%   46.53n ± 0%  -21.86% (p=0.000 n=10)
Log1p                          178.8n ± 0%   173.9n ± 6%   -2.74% (p=0.021 n=10)
Log10                          201.4n ± 0%   184.3n ± 0%   -8.49% (p=0.000 n=10)
Log2                           79.17n ± 0%   66.07n ± 0%  -16.54% (p=0.000 n=10)
Modf                           34.27n ± 0%   34.25n ± 0%        ~ (p=0.559 n=10)
Nextafter32                    49.34n ± 0%   49.37n ± 0%   +0.05% (p=0.040 n=10)
Nextafter64                    43.66n ± 0%   43.66n ± 0%        ~ (p=0.869 n=10)
PowInt                         309.1n ± 0%   267.4n ± 0%  -13.49% (p=0.000 n=10)
PowFrac                        769.6n ± 0%   677.3n ± 0%  -11.98% (p=0.000 n=10)
Pow10Pos                       13.88n ± 0%   13.88n ± 0%        ~ (p=0.811 n=10)
Pow10Neg                       19.58n ± 0%   19.57n ± 0%        ~ (p=0.993 n=10)
Round                          23.65n ± 0%   23.66n ± 0%        ~ (p=0.354 n=10)
RoundToEven                    27.75n ± 0%   27.75n ± 0%        ~ (p=0.971 n=10)
Remainder                      380.0n ± 0%   309.9n ± 0%  -18.45% (p=0.000 n=10)
Signbit                        13.06n ± 0%   13.06n ± 0%        ~ (p=1.000 n=10)
Sin                            133.8n ± 0%   120.8n ± 0%   -9.75% (p=0.000 n=10)
Sincos                         160.7n ± 0%   147.7n ± 0%   -8.12% (p=0.000 n=10)
Sinh                           305.9n ± 0%   277.9n ± 0%   -9.17% (p=0.000 n=10)
SqrtIndirect                   3.265n ± 0%   3.264n ± 0%        ~ (p=0.546 n=10)
SqrtLatency                    19.58n ± 0%   19.58n ± 0%        ~ (p=0.973 n=10)
SqrtIndirectLatency            19.59n ± 0%   19.58n ± 0%        ~ (p=0.370 n=10)
SqrtGoLatency                  205.7n ± 0%   202.7n ± 0%   -1.46% (p=0.000 n=10)
SqrtPrime                      4.953µ ± 0%   4.954µ ± 0%        ~ (p=0.477 n=10)
Tan                            163.2n ± 0%   150.2n ± 0%   -7.99% (p=0.000 n=10)
Tanh                           312.4n ± 0%   284.2n ± 0%   -9.01% (p=0.000 n=10)
Trunc                          31.83n ± 0%   31.83n ± 0%        ~ (p=0.663 n=10)
Y0                             701.0n ± 0%   669.2n ± 0%   -4.54% (p=0.000 n=10)
Y1                             704.5n ± 0%   672.4n ± 0%   -4.55% (p=0.000 n=10)
Yn                             1.490µ ± 0%   1.422µ ± 0%   -4.60% (p=0.000 n=10)
Float64bits                    5.713n ± 0%   5.710n ± 0%        ~ (p=0.926 n=10)
Float64frombits                4.896n ± 0%   4.896n ± 0%        ~ (p=0.663 n=10)
Float32bits                    12.25n ± 0%   12.25n ± 0%        ~ (p=0.571 n=10)
Float32frombits                4.898n ± 0%   4.896n ± 0%        ~ (p=0.754 n=10)
FMA                            4.895n ± 0%   4.895n ± 0%        ~ (p=0.745 n=10)
geomean                        94.40n        89.43n        -5.27%

Change-Id: I4fe0f2e9f609e38d79463f9ba2519a3f9427432e
Reviewed-on: https://go-review.googlesource.com/c/go/+/348389
Reviewed-by: Keith Randall <khr@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Meng Zhuo <mengzhuo1203@gmail.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Keith Randall <khr@google.com>
2025-08-13 20:33:56 -07:00

326 lines
7.6 KiB
Go

// asmcheck
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package codegen
import "math"
var sink64 [8]float64
func approx(x float64) {
// amd64/v2:-".*x86HasSSE41" amd64/v3:-".*x86HasSSE41"
// amd64:"ROUNDSD\t[$]2"
// s390x:"FIDBR\t[$]6"
// arm64:"FRINTPD"
// ppc64x:"FRIP"
// wasm:"F64Ceil"
sink64[0] = math.Ceil(x)
// amd64/v2:-".*x86HasSSE41" amd64/v3:-".*x86HasSSE41"
// amd64:"ROUNDSD\t[$]1"
// s390x:"FIDBR\t[$]7"
// arm64:"FRINTMD"
// ppc64x:"FRIM"
// wasm:"F64Floor"
sink64[1] = math.Floor(x)
// s390x:"FIDBR\t[$]1"
// arm64:"FRINTAD"
// ppc64x:"FRIN"
sink64[2] = math.Round(x)
// amd64/v2:-".*x86HasSSE41" amd64/v3:-".*x86HasSSE41"
// amd64:"ROUNDSD\t[$]3"
// s390x:"FIDBR\t[$]5"
// arm64:"FRINTZD"
// ppc64x:"FRIZ"
// wasm:"F64Trunc"
sink64[3] = math.Trunc(x)
// amd64/v2:-".*x86HasSSE41" amd64/v3:-".*x86HasSSE41"
// amd64:"ROUNDSD\t[$]0"
// s390x:"FIDBR\t[$]4"
// arm64:"FRINTND"
// wasm:"F64Nearest"
sink64[4] = math.RoundToEven(x)
}
func sqrt(x float64) float64 {
// amd64:"SQRTSD"
// 386/sse2:"SQRTSD" 386/softfloat:-"SQRTD"
// arm64:"FSQRTD"
// arm/7:"SQRTD"
// mips/hardfloat:"SQRTD" mips/softfloat:-"SQRTD"
// mips64/hardfloat:"SQRTD" mips64/softfloat:-"SQRTD"
// wasm:"F64Sqrt"
// ppc64x:"FSQRT"
// riscv64: "FSQRTD"
return math.Sqrt(x)
}
func sqrt32(x float32) float32 {
// amd64:"SQRTSS"
// 386/sse2:"SQRTSS" 386/softfloat:-"SQRTS"
// arm64:"FSQRTS"
// arm/7:"SQRTF"
// mips/hardfloat:"SQRTF" mips/softfloat:-"SQRTF"
// mips64/hardfloat:"SQRTF" mips64/softfloat:-"SQRTF"
// wasm:"F32Sqrt"
// ppc64x:"FSQRTS"
// riscv64: "FSQRTS"
return float32(math.Sqrt(float64(x)))
}
// Check that it's using integer registers
func abs(x, y float64) {
// amd64:"BTRQ\t[$]63"
// arm64:"FABSD\t"
// s390x:"LPDFR\t",-"MOVD\t" (no integer load/store)
// ppc64x:"FABS\t"
// riscv64:"FABSD\t"
// wasm:"F64Abs"
// arm/6:"ABSD\t"
// mips64/hardfloat:"ABSD\t"
// mips/hardfloat:"ABSD\t"
sink64[0] = math.Abs(x)
// amd64:"BTRQ\t[$]63","PXOR" (TODO: this should be BTSQ)
// s390x:"LNDFR\t",-"MOVD\t" (no integer load/store)
// ppc64x:"FNABS\t"
sink64[1] = -math.Abs(y)
}
// Check that it's using integer registers
func abs32(x float32) float32 {
// s390x:"LPDFR",-"LDEBR",-"LEDBR" (no float64 conversion)
return float32(math.Abs(float64(x)))
}
// Check that it's using integer registers
func copysign(a, b, c float64) {
// amd64:"BTRQ\t[$]63","ANDQ","ORQ"
// s390x:"CPSDR",-"MOVD" (no integer load/store)
// ppc64x:"FCPSGN"
// riscv64:"FSGNJD"
// wasm:"F64Copysign"
sink64[0] = math.Copysign(a, b)
// amd64:"BTSQ\t[$]63"
// s390x:"LNDFR\t",-"MOVD\t" (no integer load/store)
// ppc64x:"FCPSGN"
// riscv64:"FSGNJD"
// arm64:"ORR", -"AND"
sink64[1] = math.Copysign(c, -1)
// Like math.Copysign(c, -1), but with integer operations. Useful
// for platforms that have a copysign opcode to see if it's detected.
// s390x:"LNDFR\t",-"MOVD\t" (no integer load/store)
sink64[2] = math.Float64frombits(math.Float64bits(a) | 1<<63)
// amd64:"ANDQ","ORQ"
// s390x:"CPSDR\t",-"MOVD\t" (no integer load/store)
// ppc64x:"FCPSGN"
// riscv64:"FSGNJD"
sink64[3] = math.Copysign(-1, c)
}
func fma(x, y, z float64) float64 {
// amd64/v3:-".*x86HasFMA"
// amd64:"VFMADD231SD"
// arm/6:"FMULAD"
// arm64:"FMADDD"
// loong64:"FMADDD"
// s390x:"FMADD"
// ppc64x:"FMADD"
// riscv64:"FMADDD"
return math.FMA(x, y, z)
}
func fms(x, y, z float64) float64 {
// riscv64:"FMSUBD"
return math.FMA(x, y, -z)
}
func fnms(x, y, z float64) float64 {
// riscv64:"FNMSUBD",-"FNMADDD"
return math.FMA(-x, y, z)
}
func fnma(x, y, z float64) float64 {
// riscv64:"FNMADDD",-"FNMSUBD"
return math.FMA(x, -y, -z)
}
func isPosInf(x float64) bool {
// riscv64:"FCLASSD"
return math.IsInf(x, 1)
}
func isPosInfEq(x float64) bool {
// riscv64:"FCLASSD"
return x == math.Inf(1)
}
func isPosInfCmp(x float64) bool {
// riscv64:"FCLASSD"
return x > math.MaxFloat64
}
func isNotPosInf(x float64) bool {
// riscv64:"FCLASSD"
return !math.IsInf(x, 1)
}
func isNotPosInfEq(x float64) bool {
// riscv64:"FCLASSD"
return x != math.Inf(1)
}
func isNotPosInfCmp(x float64) bool {
// riscv64:"FCLASSD"
return x <= math.MaxFloat64
}
func isNegInf(x float64) bool {
// riscv64:"FCLASSD"
return math.IsInf(x, -1)
}
func isNegInfEq(x float64) bool {
// riscv64:"FCLASSD"
return x == math.Inf(-1)
}
func isNegInfCmp(x float64) bool {
// riscv64:"FCLASSD"
return x < -math.MaxFloat64
}
func isNotNegInf(x float64) bool {
// riscv64:"FCLASSD"
return !math.IsInf(x, -1)
}
func isNotNegInfEq(x float64) bool {
// riscv64:"FCLASSD"
return x != math.Inf(-1)
}
func isNotNegInfCmp(x float64) bool {
// riscv64:"FCLASSD"
return x >= -math.MaxFloat64
}
func fromFloat64(f64 float64) uint64 {
// amd64:"MOVQ\tX.*, [^X].*"
// arm64:"FMOVD\tF.*, R.*"
// loong64:"MOVV\tF.*, R.*"
// ppc64x:"MFVSRD"
// mips64/hardfloat:"MOVV\tF.*, R.*"
// riscv64:"FMVXD"
return math.Float64bits(f64+1) + 1
}
func fromFloat32(f32 float32) uint32 {
// amd64:"MOVL\tX.*, [^X].*"
// arm64:"FMOVS\tF.*, R.*"
// loong64:"MOVW\tF.*, R.*"
// mips64/hardfloat:"MOVW\tF.*, R.*"
// riscv64:"FMVXW"
return math.Float32bits(f32+1) + 1
}
func toFloat64(u64 uint64) float64 {
// amd64:"MOVQ\t[^X].*, X.*"
// arm64:"FMOVD\tR.*, F.*"
// loong64:"MOVV\tR.*, F.*"
// ppc64x:"MTVSRD"
// mips64/hardfloat:"MOVV\tR.*, F.*"
// riscv64:"FMVDX"
return math.Float64frombits(u64+1) + 1
}
func toFloat32(u32 uint32) float32 {
// amd64:"MOVL\t[^X].*, X.*"
// arm64:"FMOVS\tR.*, F.*"
// loong64:"MOVW\tR.*, F.*"
// mips64/hardfloat:"MOVW\tR.*, F.*"
// riscv64:"FMVWX"
return math.Float32frombits(u32+1) + 1
}
// Test that comparisons with constants converted to float
// are evaluated at compile-time
func constantCheck64() bool {
// amd64:"(MOVB\t[$]0)|(XORL\t[A-Z][A-Z0-9]+, [A-Z][A-Z0-9]+)",-"FCMP",-"MOVB\t[$]1"
// s390x:"MOV(B|BZ|D)\t[$]0,",-"FCMPU",-"MOV(B|BZ|D)\t[$]1,"
return 0.5 == float64(uint32(1)) || 1.5 > float64(uint64(1<<63))
}
func constantCheck32() bool {
// amd64:"MOV(B|L)\t[$]1",-"FCMP",-"MOV(B|L)\t[$]0"
// s390x:"MOV(B|BZ|D)\t[$]1,",-"FCMPU",-"MOV(B|BZ|D)\t[$]0,"
return float32(0.5) <= float32(int64(1)) && float32(1.5) >= float32(int32(-1<<31))
}
// Test that integer constants are converted to floating point constants
// at compile-time
func constantConvert32(x float32) float32 {
// amd64:"MOVSS\t[$]f32.3f800000\\(SB\\)"
// s390x:"FMOVS\t[$]f32.3f800000\\(SB\\)"
// ppc64x/power8:"FMOVS\t[$]f32.3f800000\\(SB\\)"
// ppc64x/power9:"FMOVS\t[$]f32.3f800000\\(SB\\)"
// ppc64x/power10:"XXSPLTIDP\t[$]1065353216, VS0"
// arm64:"FMOVS\t[$]\\(1.0\\)"
if x > math.Float32frombits(0x3f800000) {
return -x
}
return x
}
func constantConvertInt32(x uint32) uint32 {
// amd64:-"MOVSS"
// s390x:-"FMOVS"
// ppc64x:-"FMOVS"
// arm64:-"FMOVS"
if x > math.Float32bits(1) {
return -x
}
return x
}
func nanGenerate64() float64 {
// Test to make sure we don't generate a NaN while constant propagating.
// See issue 36400.
zero := 0.0
// amd64:-"DIVSD"
inf := 1 / zero // +inf. We can constant propagate this one.
negone := -1.0
// amd64:"DIVSD"
z0 := zero / zero
// amd64/v1,amd64/v2:"MULSD"
z1 := zero * inf
// amd64:"SQRTSD"
z2 := math.Sqrt(negone)
// amd64/v3:"VFMADD231SD"
return z0 + z1 + z2
}
func nanGenerate32() float32 {
zero := float32(0.0)
// amd64:-"DIVSS"
inf := 1 / zero // +inf. We can constant propagate this one.
// amd64:"DIVSS"
z0 := zero / zero
// amd64/v1,amd64/v2:"MULSS"
z1 := zero * inf
// amd64/v3:"VFMADD231SS"
return z0 + z1
}