diff --git a/libc/shared/math.h b/libc/shared/math.h index bad2b07ecb993..9c85838145530 100644 --- a/libc/shared/math.h +++ b/libc/shared/math.h @@ -79,6 +79,7 @@ #include "math/logbf.h" #include "math/logbf128.h" #include "math/logbf16.h" +#include "math/logf.h" #include "math/rsqrtf.h" #include "math/rsqrtf16.h" #include "math/sin.h" diff --git a/libc/shared/math/logf.h b/libc/shared/math/logf.h new file mode 100644 index 0000000000000..6f10d6357e70b --- /dev/null +++ b/libc/shared/math/logf.h @@ -0,0 +1,23 @@ +//===-- Shared logf function ------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SHARED_MATH_LOGF_H +#define LLVM_LIBC_SHARED_MATH_LOGF_H + +#include "shared/libc_common.h" +#include "src/__support/math/logf.h" + +namespace LIBC_NAMESPACE_DECL { +namespace shared { + +using math::logf; + +} // namespace shared +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SHARED_MATH_LOGF_H diff --git a/libc/src/__support/math/CMakeLists.txt b/libc/src/__support/math/CMakeLists.txt index bcc5b1024b8da..7e96da4808587 100644 --- a/libc/src/__support/math/CMakeLists.txt +++ b/libc/src/__support/math/CMakeLists.txt @@ -1198,6 +1198,23 @@ add_header_library( libc.src.__support.FPUtil.manipulation_functions ) +add_header_library( + logf + HDRS + logf.h + DEPENDS + .common_constants + libc.src.__support.FPUtil.fenv_impl + libc.src.__support.FPUtil.fp_bits + libc.src.__support.FPUtil.polyeval + libc.src.__support.FPUtil.except_value_utils + libc.src.__support.FPUtil.multiply_add + libc.src.__support.common + libc.src.__support.macros.config + libc.src.__support.macros.optimization + libc.src.__support.macros.properties.cpu_features +) + add_header_library( log_range_reduction HDRS diff --git a/libc/src/__support/math/logf.h b/libc/src/__support/math/logf.h new file mode 100644 index 0000000000000..e96fe28fc020d --- /dev/null +++ b/libc/src/__support/math/logf.h @@ -0,0 +1,192 @@ +//===-- Single-precision log(x) function ----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC___SUPPORT_MATH_LOGF_H +#define LLVM_LIBC_SRC___SUPPORT_MATH_LOGF_H + +#include "common_constants.h" // Lookup table for (1/f) and log(f) +#include "src/__support/FPUtil/FEnvImpl.h" +#include "src/__support/FPUtil/FPBits.h" +#include "src/__support/FPUtil/PolyEval.h" +#include "src/__support/FPUtil/except_value_utils.h" +#include "src/__support/FPUtil/multiply_add.h" +#include "src/__support/common.h" +#include "src/__support/macros/config.h" +#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY +#include "src/__support/macros/properties/cpu_features.h" + +// This is an algorithm for log(x) in single precision which is correctly +// rounded for all rounding modes, based on the implementation of log(x) from +// the RLIBM project at: +// https://people.cs.rutgers.edu/~sn349/rlibm + +// Step 1 - Range reduction: +// For x = 2^m * 1.mant, log(x) = m * log(2) + log(1.m) +// If x is denormal, we normalize it by multiplying x by 2^23 and subtracting +// m by 23. + +// Step 2 - Another range reduction: +// To compute log(1.mant), let f be the highest 8 bits including the hidden +// bit, and d be the difference (1.mant - f), i.e. the remaining 16 bits of the +// mantissa. Then we have the following approximation formula: +// log(1.mant) = log(f) + log(1.mant / f) +// = log(f) + log(1 + d/f) +// ~ log(f) + P(d/f) +// since d/f is sufficiently small. +// log(f) and 1/f are then stored in two 2^7 = 128 entries look-up tables. + +// Step 3 - Polynomial approximation: +// To compute P(d/f), we use a single degree-5 polynomial in double precision +// which provides correct rounding for all but few exception values. +// For more detail about how this polynomial is obtained, please refer to the +// paper: +// Lim, J. and Nagarakatte, S., "One Polynomial Approximation to Produce +// Correctly Rounded Results of an Elementary Function for Multiple +// Representations and Rounding Modes", Proceedings of the 49th ACM SIGPLAN +// Symposium on Principles of Programming Languages (POPL-2022), Philadelphia, +// USA, January 16-22, 2022. +// https://people.cs.rutgers.edu/~sn349/papers/rlibmall-popl-2022.pdf + +namespace LIBC_NAMESPACE_DECL { + +namespace math { + +LIBC_INLINE static float logf(float x) { + using namespace common_constants_internal; + constexpr double LOG_2 = 0x1.62e42fefa39efp-1; + using FPBits = typename fputil::FPBits; + + FPBits xbits(x); + uint32_t x_u = xbits.uintval(); + + int m = -FPBits::EXP_BIAS; + + using fputil::round_result_slightly_down; + using fputil::round_result_slightly_up; + + // Small inputs + if (x_u < 0x4c5d65a5U) { +#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS + // Hard-to-round cases. + switch (x_u) { + case 0x3f7f4d6fU: // x = 0x1.fe9adep-1f + return round_result_slightly_up(-0x1.659ec8p-9f); + case 0x41178febU: // x = 0x1.2f1fd6p+3f + return round_result_slightly_up(0x1.1fcbcep+1f); +#ifdef LIBC_TARGET_CPU_HAS_FMA + case 0x3f800000U: // x = 1.0f + return 0.0f; +#else + case 0x1e88452dU: // x = 0x1.108a5ap-66f + return round_result_slightly_up(-0x1.6d7b18p+5f); +#endif // LIBC_TARGET_CPU_HAS_FMA + } +#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS + // Subnormal inputs. + if (LIBC_UNLIKELY(x_u < FPBits::min_normal().uintval())) { + if (x == 0.0f) { + // Return -inf and raise FE_DIVBYZERO + fputil::set_errno_if_required(ERANGE); + fputil::raise_except_if_required(FE_DIVBYZERO); + return FPBits::inf(Sign::NEG).get_val(); + } + // Normalize denormal inputs. + xbits = FPBits(xbits.get_val() * 0x1.0p23f); + m -= 23; + x_u = xbits.uintval(); + } + } else { +#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS + // Hard-to-round cases. + switch (x_u) { + case 0x4c5d65a5U: // x = 0x1.bacb4ap+25f + return round_result_slightly_down(0x1.1e0696p+4f); + case 0x65d890d3U: // x = 0x1.b121a6p+76f + return round_result_slightly_down(0x1.a9a3f2p+5f); + case 0x6f31a8ecU: // x = 0x1.6351d8p+95f + return round_result_slightly_down(0x1.08b512p+6f); + case 0x7a17f30aU: // x = 0x1.2fe614p+117f + return round_result_slightly_up(0x1.451436p+6f); +#ifndef LIBC_TARGET_CPU_HAS_FMA_DOUBLE + case 0x500ffb03U: // x = 0x1.1ff606p+33f + return round_result_slightly_up(0x1.6fdd34p+4f); + case 0x5cd69e88U: // x = 0x1.ad3d1p+58f + return round_result_slightly_up(0x1.45c146p+5f); + case 0x5ee8984eU: // x = 0x1.d1309cp+62f; + return round_result_slightly_up(0x1.5c9442p+5f); +#endif // LIBC_TARGET_CPU_HAS_FMA_DOUBLE + } +#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS + // Exceptional inputs. + if (LIBC_UNLIKELY(x_u > FPBits::max_normal().uintval())) { + if (x_u == 0x8000'0000U) { + // Return -inf and raise FE_DIVBYZERO + fputil::set_errno_if_required(ERANGE); + fputil::raise_except_if_required(FE_DIVBYZERO); + return FPBits::inf(Sign::NEG).get_val(); + } + if (xbits.is_neg() && !xbits.is_nan()) { + // Return NaN and raise FE_INVALID + fputil::set_errno_if_required(EDOM); + fputil::raise_except_if_required(FE_INVALID); + return FPBits::quiet_nan().get_val(); + } + // x is +inf or nan + if (xbits.is_signaling_nan()) { + fputil::raise_except_if_required(FE_INVALID); + return FPBits::quiet_nan().get_val(); + } + + return x; + } + } + +#ifndef LIBC_TARGET_CPU_HAS_FMA + // Returning the correct +0 when x = 1.0 for non-FMA targets with FE_DOWNWARD + // rounding mode. + if (LIBC_UNLIKELY((x_u & 0x007f'ffffU) == 0)) + return static_cast( + static_cast(m + xbits.get_biased_exponent()) * LOG_2); +#endif // LIBC_TARGET_CPU_HAS_FMA + + uint32_t mant = xbits.get_mantissa(); + // Extract 7 leading fractional bits of the mantissa + int index = mant >> 16; + // Add unbiased exponent. Add an extra 1 if the 7 leading fractional bits are + // all 1's. + m += static_cast((x_u + (1 << 16)) >> 23); + + // Set bits to 1.m + xbits.set_biased_exponent(0x7F); + + float u = xbits.get_val(); + double v = 0.0; +#ifdef LIBC_TARGET_CPU_HAS_FMA_FLOAT + v = static_cast(fputil::multiply_add(u, R[index], -1.0f)); // Exact. +#else + v = fputil::multiply_add(static_cast(u), RD[index], -1.0); // Exact +#endif // LIBC_TARGET_CPU_HAS_FMA_FLOAT + + // Degree-5 polynomial approximation of log generated by Sollya with: + // > P = fpminimax(log(1 + x)/x, 4, [|1, D...|], [-2^-8, 2^-7]); + constexpr double COEFFS[4] = {-0x1.000000000fe63p-1, 0x1.555556e963c16p-2, + -0x1.000028dedf986p-2, 0x1.966681bfda7f7p-3}; + double v2 = v * v; // Exact + double p2 = fputil::multiply_add(v, COEFFS[3], COEFFS[2]); + double p1 = fputil::multiply_add(v, COEFFS[1], COEFFS[0]); + double p0 = LOG_R[index] + v; + double r = fputil::multiply_add(static_cast(m), LOG_2, + fputil::polyeval(v2, p0, p1, p2)); + return static_cast(r); +} + +} // namespace math + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC___SUPPORT_MATH_LOGF_H diff --git a/libc/src/math/generic/CMakeLists.txt b/libc/src/math/generic/CMakeLists.txt index 6e58434415ead..7e9ed721f336c 100644 --- a/libc/src/math/generic/CMakeLists.txt +++ b/libc/src/math/generic/CMakeLists.txt @@ -2086,14 +2086,7 @@ add_entrypoint_object( HDRS ../logf.h DEPENDS - libc.src.__support.FPUtil.except_value_utils - libc.src.__support.FPUtil.fenv_impl - libc.src.__support.FPUtil.fp_bits - libc.src.__support.FPUtil.multiply_add - libc.src.__support.FPUtil.polyeval - libc.src.__support.macros.optimization - libc.src.__support.macros.properties.cpu_features - libc.src.__support.math.common_constants + libc.src.__support.math.logf ) add_entrypoint_object( diff --git a/libc/src/math/generic/logf.cpp b/libc/src/math/generic/logf.cpp index 4d2947dc14db0..863a9378d3135 100644 --- a/libc/src/math/generic/logf.cpp +++ b/libc/src/math/generic/logf.cpp @@ -7,16 +7,7 @@ //===----------------------------------------------------------------------===// #include "src/math/logf.h" -#include "src/__support/FPUtil/FEnvImpl.h" -#include "src/__support/FPUtil/FPBits.h" -#include "src/__support/FPUtil/PolyEval.h" -#include "src/__support/FPUtil/except_value_utils.h" -#include "src/__support/FPUtil/multiply_add.h" -#include "src/__support/common.h" -#include "src/__support/macros/config.h" -#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY -#include "src/__support/macros/properties/cpu_features.h" -#include "src/__support/math/common_constants.h" // Lookup table for (1/f) and log(f) +#include "src/__support/math/logf.h" // Lookup table for (1/f) and log(f) // This is an algorithm for log(x) in single precision which is correctly // rounded for all rounding modes, based on the implementation of log(x) from @@ -52,133 +43,6 @@ namespace LIBC_NAMESPACE_DECL { -LLVM_LIBC_FUNCTION(float, logf, (float x)) { - using namespace common_constants_internal; - constexpr double LOG_2 = 0x1.62e42fefa39efp-1; - using FPBits = typename fputil::FPBits; - - FPBits xbits(x); - uint32_t x_u = xbits.uintval(); - - int m = -FPBits::EXP_BIAS; - - using fputil::round_result_slightly_down; - using fputil::round_result_slightly_up; - - // Small inputs - if (x_u < 0x4c5d65a5U) { -#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS - // Hard-to-round cases. - switch (x_u) { - case 0x3f7f4d6fU: // x = 0x1.fe9adep-1f - return round_result_slightly_up(-0x1.659ec8p-9f); - case 0x41178febU: // x = 0x1.2f1fd6p+3f - return round_result_slightly_up(0x1.1fcbcep+1f); -#ifdef LIBC_TARGET_CPU_HAS_FMA - case 0x3f800000U: // x = 1.0f - return 0.0f; -#else - case 0x1e88452dU: // x = 0x1.108a5ap-66f - return round_result_slightly_up(-0x1.6d7b18p+5f); -#endif // LIBC_TARGET_CPU_HAS_FMA - } -#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS - // Subnormal inputs. - if (LIBC_UNLIKELY(x_u < FPBits::min_normal().uintval())) { - if (x == 0.0f) { - // Return -inf and raise FE_DIVBYZERO - fputil::set_errno_if_required(ERANGE); - fputil::raise_except_if_required(FE_DIVBYZERO); - return FPBits::inf(Sign::NEG).get_val(); - } - // Normalize denormal inputs. - xbits = FPBits(xbits.get_val() * 0x1.0p23f); - m -= 23; - x_u = xbits.uintval(); - } - } else { -#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS - // Hard-to-round cases. - switch (x_u) { - case 0x4c5d65a5U: // x = 0x1.bacb4ap+25f - return round_result_slightly_down(0x1.1e0696p+4f); - case 0x65d890d3U: // x = 0x1.b121a6p+76f - return round_result_slightly_down(0x1.a9a3f2p+5f); - case 0x6f31a8ecU: // x = 0x1.6351d8p+95f - return round_result_slightly_down(0x1.08b512p+6f); - case 0x7a17f30aU: // x = 0x1.2fe614p+117f - return round_result_slightly_up(0x1.451436p+6f); -#ifndef LIBC_TARGET_CPU_HAS_FMA_DOUBLE - case 0x500ffb03U: // x = 0x1.1ff606p+33f - return round_result_slightly_up(0x1.6fdd34p+4f); - case 0x5cd69e88U: // x = 0x1.ad3d1p+58f - return round_result_slightly_up(0x1.45c146p+5f); - case 0x5ee8984eU: // x = 0x1.d1309cp+62f; - return round_result_slightly_up(0x1.5c9442p+5f); -#endif // LIBC_TARGET_CPU_HAS_FMA_DOUBLE - } -#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS - // Exceptional inputs. - if (LIBC_UNLIKELY(x_u > FPBits::max_normal().uintval())) { - if (x_u == 0x8000'0000U) { - // Return -inf and raise FE_DIVBYZERO - fputil::set_errno_if_required(ERANGE); - fputil::raise_except_if_required(FE_DIVBYZERO); - return FPBits::inf(Sign::NEG).get_val(); - } - if (xbits.is_neg() && !xbits.is_nan()) { - // Return NaN and raise FE_INVALID - fputil::set_errno_if_required(EDOM); - fputil::raise_except_if_required(FE_INVALID); - return FPBits::quiet_nan().get_val(); - } - // x is +inf or nan - if (xbits.is_signaling_nan()) { - fputil::raise_except_if_required(FE_INVALID); - return FPBits::quiet_nan().get_val(); - } - - return x; - } - } - -#ifndef LIBC_TARGET_CPU_HAS_FMA - // Returning the correct +0 when x = 1.0 for non-FMA targets with FE_DOWNWARD - // rounding mode. - if (LIBC_UNLIKELY((x_u & 0x007f'ffffU) == 0)) - return static_cast( - static_cast(m + xbits.get_biased_exponent()) * LOG_2); -#endif // LIBC_TARGET_CPU_HAS_FMA - - uint32_t mant = xbits.get_mantissa(); - // Extract 7 leading fractional bits of the mantissa - int index = mant >> 16; - // Add unbiased exponent. Add an extra 1 if the 7 leading fractional bits are - // all 1's. - m += static_cast((x_u + (1 << 16)) >> 23); - - // Set bits to 1.m - xbits.set_biased_exponent(0x7F); - - float u = xbits.get_val(); - double v; -#ifdef LIBC_TARGET_CPU_HAS_FMA_FLOAT - v = static_cast(fputil::multiply_add(u, R[index], -1.0f)); // Exact. -#else - v = fputil::multiply_add(static_cast(u), RD[index], -1.0); // Exact -#endif // LIBC_TARGET_CPU_HAS_FMA_FLOAT - - // Degree-5 polynomial approximation of log generated by Sollya with: - // > P = fpminimax(log(1 + x)/x, 4, [|1, D...|], [-2^-8, 2^-7]); - constexpr double COEFFS[4] = {-0x1.000000000fe63p-1, 0x1.555556e963c16p-2, - -0x1.000028dedf986p-2, 0x1.966681bfda7f7p-3}; - double v2 = v * v; // Exact - double p2 = fputil::multiply_add(v, COEFFS[3], COEFFS[2]); - double p1 = fputil::multiply_add(v, COEFFS[1], COEFFS[0]); - double p0 = LOG_R[index] + v; - double r = fputil::multiply_add(static_cast(m), LOG_2, - fputil::polyeval(v2, p0, p1, p2)); - return static_cast(r); -} +LLVM_LIBC_FUNCTION(float, logf, (float x)) { return math::logf(x); } } // namespace LIBC_NAMESPACE_DECL diff --git a/libc/test/shared/CMakeLists.txt b/libc/test/shared/CMakeLists.txt index 3fc36ed0c2198..ced4ca3572cb0 100644 --- a/libc/test/shared/CMakeLists.txt +++ b/libc/test/shared/CMakeLists.txt @@ -72,6 +72,8 @@ add_fp_unittest( libc.src.__support.math.logbf libc.src.__support.math.logbf128 libc.src.__support.math.logbf16 + libc.src.__support.math.logf + libc.src.__support.math.ldexpf libc.src.__support.math.ldexpf128 libc.src.__support.math.ldexpf16 libc.src.__support.math.llogbf diff --git a/libc/test/shared/shared_math_test.cpp b/libc/test/shared/shared_math_test.cpp index 45ba79de87226..0939780a2662d 100644 --- a/libc/test/shared/shared_math_test.cpp +++ b/libc/test/shared/shared_math_test.cpp @@ -73,6 +73,7 @@ TEST(LlvmLibcSharedMathTest, AllFloat) { EXPECT_FP_EQ(0x1p+0f, LIBC_NAMESPACE::shared::exp2f(0.0f)); EXPECT_FP_EQ(0x0p+0f, LIBC_NAMESPACE::shared::expm1f(0.0f)); EXPECT_FP_EQ(0x0p+0f, LIBC_NAMESPACE::shared::hypotf(0.0f, 0.0f)); + EXPECT_FP_EQ(0x0p+0f, LIBC_NAMESPACE::shared::logf(1.0f)); EXPECT_FP_EQ_ALL_ROUNDING(0.75f, LIBC_NAMESPACE::shared::frexpf(24.0f, &exponent)); diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel index 2819824a11857..507e968657d0e 100644 --- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel +++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel @@ -3090,6 +3090,23 @@ libc_support_library( ], ) +libc_support_library( + name = "__support_math_logf", + hdrs = ["src/__support/math/logf.h"], + deps = [ + ":__support_common", + ":__support_fputil_except_value_utils", + ":__support_fputil_fenv_impl", + ":__support_fputil_fp_bits", + ":__support_fputil_multiply_add", + ":__support_fputil_polyeval", + ":__support_macros_config", + ":__support_macros_optimization", + ":__support_macros_properties_cpu_features", + ":__support_math_common_constants", + ], +) + libc_support_library( name = "__support_math_exp_constants", hdrs = ["src/__support/math/exp_constants.h"], @@ -4639,12 +4656,7 @@ libc_math_function( libc_math_function( name = "logf", additional_deps = [ - ":__support_fputil_fma", - ":__support_fputil_multiply_add", - ":__support_fputil_polyeval", - ":__support_macros_optimization", - ":__support_macros_properties_cpu_features", - ":__support_math_common_constants", + ":__support_math_logf", ], )