From 0c1ad05f5062ac6a51280cb157cb11bd21b6745c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?kleines=20Filmr=C3=B6llchen?= Date: Sat, 6 Jan 2024 19:08:39 +0100 Subject: [PATCH] LibC: Add rounding specializations for RISC-V Whenever the floating-point values are in integer range, we can use the various FCVT functions with static rounding mode to perform fast rounding. I took this opportunity to clean up the architecture differentiation for these functions, which allows us to use the software rounding implementation for all extreme and unimplemented cases, including AArch64. Also adds more round & trunc tests. --- Tests/LibC/TestMath.cpp | 89 +++++-- Userland/Libraries/LibC/math.cpp | 420 +++++++++++++++++-------------- 2 files changed, 301 insertions(+), 208 deletions(-) diff --git a/Tests/LibC/TestMath.cpp b/Tests/LibC/TestMath.cpp index bd6453472a9..4af5b05a2ec 100644 --- a/Tests/LibC/TestMath.cpp +++ b/Tests/LibC/TestMath.cpp @@ -56,12 +56,6 @@ TEST_CASE(trig) EXPECT_APPROXIMATE(atan(555.5), 1.568996); } -TEST_CASE(other) -{ - EXPECT_EQ(trunc(9999999999999.5), 9999999999999.0); - EXPECT_EQ(trunc(-9999999999999.5), -9999999999999.0); -} - TEST_CASE(exponents) { struct values { @@ -272,22 +266,77 @@ TEST_CASE(acos) TEST_CASE(floor) { - EXPECT_EQ(floor(0.125), 0); - EXPECT_EQ(floor(-0.125), -1.0); - EXPECT_EQ(floor(0.5), 0); - EXPECT_EQ(floor(-0.5), -1.0); - EXPECT_EQ(floor(0.25), 0); - EXPECT_EQ(floor(-0.25), -1.0); - EXPECT_EQ(floor(-3.0 / 2.0), -2.0); + // NOTE: We run tests for all three float types since architecture-specific code may vary significantly between types. +#define TEST_FLOOR_FOR(suffix) \ + EXPECT_EQ(floor##suffix(0.125f), 0.f); \ + EXPECT_EQ(floor##suffix(-0.125f), -1.0f); \ + EXPECT_EQ(floor##suffix(0.5f), 0.f); \ + EXPECT_EQ(floor##suffix(-0.5f), -1.0f); \ + EXPECT_EQ(floor##suffix(0.25f), 0.f); \ + EXPECT_EQ(floor##suffix(-0.25f), -1.0f); \ + EXPECT_EQ(floor##suffix(-3.0f / 2.0f), -2.0f); + + TEST_FLOOR_FOR(); + TEST_FLOOR_FOR(f); + TEST_FLOOR_FOR(l); + + EXPECT_EQ(floor(-9999999999999.5), -10000000000000.0); + EXPECT_EQ(floor(9999999999999.5), 9999999999999.0); } TEST_CASE(ceil) { - EXPECT_EQ(ceil(0.125), 1.0); - EXPECT_EQ(ceil(-0.125), 0); - EXPECT_EQ(ceil(0.5), 1.0); - EXPECT_EQ(ceil(-0.5), 0); - EXPECT_EQ(ceil(0.25), 1.0); - EXPECT_EQ(ceil(-0.25), 0); - EXPECT_EQ(ceil(-3.0 / 2.0), -1.0); +#define TEST_CEIL_FOR(suffix) \ + EXPECT_EQ(ceil##suffix(0.125##suffix), 1.0##suffix); \ + EXPECT_EQ(ceil##suffix(-0.125##suffix), 0.##suffix); \ + EXPECT_EQ(ceil##suffix(0.5##suffix), 1.0##suffix); \ + EXPECT_EQ(ceil##suffix(-0.5##suffix), 0.##suffix); \ + EXPECT_EQ(ceil##suffix(0.25##suffix), 1.0##suffix); \ + EXPECT_EQ(ceil##suffix(-0.25##suffix), 0.##suffix); \ + EXPECT_EQ(ceil##suffix(-3.0##suffix / 2.0##suffix), -1.0##suffix); + + TEST_CEIL_FOR(); + TEST_CEIL_FOR(f); + TEST_CEIL_FOR(l); + + EXPECT_EQ(ceil(9999999999999.5), 10000000000000.0); + EXPECT_EQ(ceil(-9999999999999.5), -9999999999999.0); +} + +TEST_CASE(trunc) +{ +#define TEST_TRUNC_FOR(suffix) \ + EXPECT_EQ(trunc##suffix(0.125##suffix), 0.##suffix); \ + EXPECT_EQ(trunc##suffix(-0.125##suffix), 0.##suffix); \ + EXPECT_EQ(trunc##suffix(0.5##suffix), 0.##suffix); \ + EXPECT_EQ(trunc##suffix(-0.5##suffix), 0.##suffix); \ + EXPECT_EQ(trunc##suffix(0.25##suffix), 0.##suffix); \ + EXPECT_EQ(trunc##suffix(-0.25##suffix), 0.##suffix); \ + EXPECT_EQ(trunc##suffix(-3.0##suffix / 2.0##suffix), -1.0##suffix); + + TEST_TRUNC_FOR(); + TEST_TRUNC_FOR(f); + TEST_TRUNC_FOR(l); + + EXPECT_EQ(trunc(9999999999999.5), 9999999999999.0); + EXPECT_EQ(trunc(-9999999999999.5), -9999999999999.0); +} + +TEST_CASE(round) +{ +#define TEST_ROUND_FOR(suffix) \ + EXPECT_EQ(round##suffix(0.125##suffix), 0.##suffix); \ + EXPECT_EQ(round##suffix(-0.125##suffix), 0.##suffix); \ + EXPECT_EQ(round##suffix(0.5##suffix), 1.0##suffix); \ + EXPECT_EQ(round##suffix(-0.5##suffix), -1.0##suffix); \ + EXPECT_EQ(round##suffix(0.25##suffix), 0.##suffix); \ + EXPECT_EQ(round##suffix(-0.25##suffix), 0.##suffix); \ + EXPECT_EQ(round##suffix(-3.0##suffix / 2.0##suffix), -2.0##suffix); + + TEST_ROUND_FOR(); + TEST_ROUND_FOR(f); + TEST_ROUND_FOR(l); + + EXPECT_EQ(round(9999999999999.5), 10000000000000.0); + EXPECT_EQ(round(-9999999999999.5), -10000000000000.0); } diff --git a/Userland/Libraries/LibC/math.cpp b/Userland/Libraries/LibC/math.cpp index ee8ea956657..6b0568ddc03 100644 --- a/Userland/Libraries/LibC/math.cpp +++ b/Userland/Libraries/LibC/math.cpp @@ -57,7 +57,9 @@ enum class RoundingMode { ToZero = FE_TOWARDZERO, Up = FE_UPWARD, Down = FE_DOWNWARD, - ToEven = FE_TONEAREST + ToEven = FE_TONEAREST, + // Round to nearest, ties away from zero. + ToMaxMagnitude = FE_TOMAXMAGNITUDE, }; // This is much branchier than it really needs to be @@ -118,6 +120,9 @@ static FloatType internal_to_integer(FloatType x, RoundingMode rounding_mode) break; case RoundingMode::ToZero: break; + case RoundingMode::ToMaxMagnitude: + should_round = true; + break; } if (should_round) { @@ -397,6 +402,14 @@ double trunc(double x) NOEXCEPT : [temp] "m"(temp)); return x; } +#elif ARCH(RISCV64) + if (fabs(x) < LONG_LONG_MAX) { + i64 output; + asm("fcvt.l.d %0, %1, rtz" + : "=r"(output) + : "f"(x)); + return static_cast(output); + } #endif return internal_to_integer(x, RoundingMode::ToZero); @@ -414,6 +427,14 @@ float truncf(float x) NOEXCEPT : [temp] "m"(temp)); return x; } +#elif ARCH(RISCV64) + if (fabsf(x) < LONG_LONG_MAX) { + i64 output; + asm("fcvt.l.s %0, %1, rtz" + : "=r"(output) + : "f"(x)); + return static_cast(output); + } #endif return internal_to_integer(x, RoundingMode::ToZero); @@ -444,8 +465,12 @@ double rint(double value) (void)value; TODO_AARCH64(); #elif ARCH(RISCV64) - (void)value; - TODO_RISCV64(); + i64 output; + // FIXME: This saturates at 64-bit integer boundaries; see Table 11.4 (RISC-V Unprivileged ISA V20191213) + asm("fcvt.l.d %0, %1, dyn" + : "=r"(output) + : "f"(value)); + return static_cast(output); #elif ARCH(X86_64) double res; asm( @@ -463,8 +488,12 @@ float rintf(float value) (void)value; TODO_AARCH64(); #elif ARCH(RISCV64) - (void)value; - TODO_RISCV64(); + i64 output; + // FIXME: This saturates at 64-bit integer boundaries; see Table 11.4 (RISC-V Unprivileged ISA V20191213) + asm("fcvt.l.s %0, %1, dyn" + : "=r"(output) + : "f"(value)); + return static_cast(output); #elif ARCH(X86_64) float res; asm( @@ -503,8 +532,12 @@ long lrint(double value) (void)value; TODO_AARCH64(); #elif ARCH(RISCV64) - (void)value; - TODO_RISCV64(); + i64 output; + // FIXME: This saturates at 64-bit integer boundaries; see Table 11.4 (RISC-V Unprivileged ISA V20191213) + asm("fcvt.l.d %0, %1, dyn" + : "=r"(output) + : "f"(value)); + return output; #elif ARCH(X86_64) long res; asm( @@ -523,8 +556,12 @@ long lrintf(float value) (void)value; TODO_AARCH64(); #elif ARCH(RISCV64) - (void)value; - TODO_RISCV64(); + i64 output; + // FIXME: This saturates at 64-bit integer boundaries; see Table 11.4 (RISC-V Unprivileged ISA V20191213) + asm("fcvt.l.s %0, %1, dyn" + : "=r"(output) + : "f"(value)); + return output; #elif ARCH(X86_64) long res; asm( @@ -544,8 +581,8 @@ long long llrintl(long double value) (void)value; TODO_AARCH64(); #elif ARCH(RISCV64) - (void)value; - TODO_RISCV64(); + // NOTE: RISC-V LP64 specifies long long == long. + return static_cast(lrintl(value)); #elif ARCH(X86_64) long long res; asm( @@ -564,8 +601,8 @@ long long llrint(double value) (void)value; TODO_AARCH64(); #elif ARCH(RISCV64) - (void)value; - TODO_RISCV64(); + // NOTE: RISC-V LP64 specifies long long == long. + return static_cast(lrint(value)); #elif ARCH(X86_64) long long res; asm( @@ -584,8 +621,8 @@ long long llrintf(float value) (void)value; TODO_AARCH64(); #elif ARCH(RISCV64) - (void)value; - TODO_RISCV64(); + // NOTE: RISC-V LP64 specifies long long == long. + return static_cast(lrintf(value)); #elif ARCH(X86_64) long long res; asm( @@ -680,16 +717,34 @@ long double frexpl(long double x, int* exp) NOEXCEPT return scalbnl(x, -(*exp)); } -#if !(ARCH(X86_64)) - -double round(double value) NOEXCEPT +double round(double x) NOEXCEPT { - return internal_to_integer(value, RoundingMode::ToEven); +#if ARCH(RISCV64) + if (fabs(x) < LONG_LONG_MAX) { + i64 output; + asm("fcvt.l.d %0, %1, rmm" + : "=r"(output) + : "f"(x)); + return static_cast(output); + } +#endif + + return internal_to_integer(x, RoundingMode::ToEven); } -float roundf(float value) NOEXCEPT +float roundf(float x) NOEXCEPT { - return internal_to_integer(value, RoundingMode::ToEven); +#if ARCH(RISCV64) + if (fabsf(x) < LONG_LONG_MAX) { + i64 output; + asm("fcvt.l.s %0, %1, rmm" + : "=r"(output) + : "f"(x)); + return static_cast(output); + } +#endif + + return internal_to_integer(x, RoundingMode::ToEven); } long double roundl(long double value) NOEXCEPT @@ -699,175 +754,164 @@ long double roundl(long double value) NOEXCEPT long lroundf(float value) NOEXCEPT { - return internal_to_integer(value, RoundingMode::ToEven); -} - -long lround(double value) NOEXCEPT -{ - return internal_to_integer(value, RoundingMode::ToEven); -} - -long lroundl(long double value) NOEXCEPT -{ - return internal_to_integer(value, RoundingMode::ToEven); -} - -long long llroundf(float value) NOEXCEPT -{ - return internal_to_integer(value, RoundingMode::ToEven); -} - -long long llround(double value) NOEXCEPT -{ - return internal_to_integer(value, RoundingMode::ToEven); -} - -long long llroundd(long double value) NOEXCEPT -{ - return internal_to_integer(value, RoundingMode::ToEven); -} - -float floorf(float value) NOEXCEPT -{ - return internal_to_integer(value, RoundingMode::Down); -} - -double floor(double value) NOEXCEPT -{ - return internal_to_integer(value, RoundingMode::Down); -} - -long double floorl(long double value) NOEXCEPT -{ - return internal_to_integer(value, RoundingMode::Down); -} - -float ceilf(float value) NOEXCEPT -{ - return internal_to_integer(value, RoundingMode::Up); -} - -double ceil(double value) NOEXCEPT -{ - return internal_to_integer(value, RoundingMode::Up); -} - -long double ceill(long double value) NOEXCEPT -{ - return internal_to_integer(value, RoundingMode::Up); -} - -#else - -double round(double x) NOEXCEPT -{ - // Note: This is break-tie-away-from-zero, so not the hw's understanding of - // "nearest", which would be towards even. - if (x == 0.) - return x; - if (x > 0.) - return floor(x + .5); - return ceil(x - .5); -} - -float roundf(float x) NOEXCEPT -{ - if (x == 0.f) - return x; - if (x > 0.f) - return floorf(x + .5f); - return ceilf(x - .5f); -} - -long double roundl(long double x) NOEXCEPT -{ - if (x == 0.L) - return x; - if (x > 0.L) - return floorl(x + .5L); - return ceill(x - .5L); -} - -long lroundf(float value) NOEXCEPT -{ - return static_cast(roundf(value)); -} - -long lround(double value) NOEXCEPT -{ - return static_cast(round(value)); -} - -long lroundl(long double value) NOEXCEPT -{ - return static_cast(roundl(value)); -} - -long long llroundf(float value) NOEXCEPT -{ - return static_cast(roundf(value)); -} - -long long llround(double value) NOEXCEPT -{ - return static_cast(round(value)); -} - -long long llroundd(long double value) NOEXCEPT -{ - return static_cast(roundl(value)); -} - -float floorf(float value) NOEXCEPT -{ - AK::X87RoundingModeScope scope { AK::RoundingMode::DOWN }; - asm("frndint" - : "+t"(value)); - return value; -} - -double floor(double value) NOEXCEPT -{ - AK::X87RoundingModeScope scope { AK::RoundingMode::DOWN }; - asm("frndint" - : "+t"(value)); - return value; -} - -long double floorl(long double value) NOEXCEPT -{ - AK::X87RoundingModeScope scope { AK::RoundingMode::DOWN }; - asm("frndint" - : "+t"(value)); - return value; -} - -float ceilf(float value) NOEXCEPT -{ - AK::X87RoundingModeScope scope { AK::RoundingMode::UP }; - asm("frndint" - : "+t"(value)); - return value; -} - -double ceil(double value) NOEXCEPT -{ - AK::X87RoundingModeScope scope { AK::RoundingMode::UP }; - asm("frndint" - : "+t"(value)); - return value; -} - -long double ceill(long double value) NOEXCEPT -{ - AK::X87RoundingModeScope scope { AK::RoundingMode::UP }; - asm("frndint" - : "+t"(value)); - return value; -} - +#if ARCH(RISCV64) + i64 output; + asm("fcvt.l.s %0, %1, rmm" + : "=r"(output) + : "f"(value)); + return output; #endif + return internal_to_integer(value, RoundingMode::ToEven); +} + +long lround(double value) NOEXCEPT +{ +#if ARCH(RISCV64) + i64 output; + asm("fcvt.l.d %0, %1, rmm" + : "=r"(output) + : "f"(value)); + return output; +#endif + + return internal_to_integer(value, RoundingMode::ToEven); +} + +long lroundl(long double value) NOEXCEPT +{ + return internal_to_integer(value, RoundingMode::ToEven); +} + +long long llroundf(float value) NOEXCEPT +{ +#if ARCH(RISCV64) + i64 output; + asm("fcvt.l.s %0, %1, rmm" + : "=r"(output) + : "f"(value)); + return output; +#endif + + return internal_to_integer(value, RoundingMode::ToEven); +} + +long long llround(double value) NOEXCEPT +{ +#if ARCH(RISCV64) + i64 output; + asm("fcvt.l.d %0, %1, rmm" + : "=r"(output) + : "f"(value)); + return output; +#endif + + return internal_to_integer(value, RoundingMode::ToEven); +} + +long long llroundd(long double value) NOEXCEPT +{ + return internal_to_integer(value, RoundingMode::ToEven); +} + +float floorf(float value) NOEXCEPT +{ +#if ARCH(RISCV64) + if (fabsf(value) < LONG_LONG_MAX) { + i64 output; + asm("fcvt.l.s %0, %1, rdn" + : "=r"(output) + : "f"(value)); + return static_cast(output); + } +#elif ARCH(X86_64) + AK::X87RoundingModeScope scope { AK::RoundingMode::DOWN }; + asm("frndint" + : "+t"(value)); + return value; +#endif + return internal_to_integer(value, RoundingMode::Down); +} + +double floor(double value) NOEXCEPT +{ +#if ARCH(RISCV64) + if (fabs(value) < LONG_LONG_MAX) { + i64 output; + asm("fcvt.l.d %0, %1, rdn" + : "=r"(output) + : "f"(value)); + return static_cast(output); + } +#elif ARCH(X86_64) + AK::X87RoundingModeScope scope { AK::RoundingMode::DOWN }; + asm("frndint" + : "+t"(value)); + return value; +#endif + return internal_to_integer(value, RoundingMode::Down); +} + +long double floorl(long double value) NOEXCEPT +{ +#if ARCH(X86_64) + AK::X87RoundingModeScope scope { AK::RoundingMode::DOWN }; + asm("frndint" + : "+t"(value)); + return value; +#endif + return internal_to_integer(value, RoundingMode::Down); +} + +float ceilf(float value) NOEXCEPT +{ +#if ARCH(RISCV64) + if (fabsf(value) < LONG_LONG_MAX) { + i64 output; + asm("fcvt.l.s %0, %1, rup" + : "=r"(output) + : "f"(value)); + return static_cast(output); + } +#elif ARCH(X86_64) + AK::X87RoundingModeScope scope { AK::RoundingMode::UP }; + asm("frndint" + : "+t"(value)); + return value; +#endif + return internal_to_integer(value, RoundingMode::Up); +} + +double ceil(double value) NOEXCEPT +{ +#if ARCH(RISCV64) + if (fabs(value) < LONG_LONG_MAX) { + i64 output; + asm("fcvt.l.d %0, %1, rup" + : "=r"(output) + : "f"(value)); + return static_cast(output); + } +#elif ARCH(X86_64) + AK::X87RoundingModeScope scope { AK::RoundingMode::UP }; + asm("frndint" + : "+t"(value)); + return value; +#endif + return internal_to_integer(value, RoundingMode::Up); +} + +long double ceill(long double value) NOEXCEPT +{ +#if ARCH(X86_64) + AK::X87RoundingModeScope scope { AK::RoundingMode::UP }; + asm("frndint" + : "+t"(value)); + return value; +#endif + return internal_to_integer(value, RoundingMode::Up); +} + long double modfl(long double x, long double* intpart) NOEXCEPT { return internal_modf(x, intpart);