From f54208ee781fde878a42dcb573afa92293ffabe3 Mon Sep 17 00:00:00 2001 From: Shreeyash Pandey Date: Fri, 22 Aug 2025 14:16:57 +0530 Subject: [PATCH 1/2] [libc][stdfix] Implement fxdivi functions Signed-off-by: Shreeyash Pandey --- libc/config/linux/riscv/entrypoints.txt | 1 + libc/config/linux/x86_64/entrypoints.txt | 1 + libc/include/stdfix.yaml | 8 ++++ libc/src/__support/fixed_point/fx_bits.h | 53 ++++++++++++++++++++ libc/src/stdfix/CMakeLists.txt | 14 ++++++ libc/src/stdfix/rdivi.cpp | 21 ++++++++ libc/src/stdfix/rdivi.h | 21 ++++++++ libc/test/src/stdfix/CMakeLists.txt | 16 +++++++ libc/test/src/stdfix/DivITest.h | 61 ++++++++++++++++++++++++ libc/test/src/stdfix/rdivi_test.cpp | 14 ++++++ 10 files changed, 210 insertions(+) create mode 100644 libc/src/stdfix/rdivi.cpp create mode 100644 libc/src/stdfix/rdivi.h create mode 100644 libc/test/src/stdfix/DivITest.h create mode 100644 libc/test/src/stdfix/rdivi_test.cpp diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt index 1a03683d72e61..b783dd4b04c74 100644 --- a/libc/config/linux/riscv/entrypoints.txt +++ b/libc/config/linux/riscv/entrypoints.txt @@ -986,6 +986,7 @@ if(LIBC_COMPILER_HAS_FIXED_POINT) libc.src.stdfix.idivulr libc.src.stdfix.idivuk libc.src.stdfix.idivulk + libc.src.stdfix.rdivi ) endif() diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index 5590f1a15ac57..f6beccc7229dd 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -1019,6 +1019,7 @@ if(LIBC_COMPILER_HAS_FIXED_POINT) libc.src.stdfix.idivulr libc.src.stdfix.idivuk libc.src.stdfix.idivulk + libc.src.stdfix.rdivi ) endif() diff --git a/libc/include/stdfix.yaml b/libc/include/stdfix.yaml index 5b385124eb63d..451330c3478d2 100644 --- a/libc/include/stdfix.yaml +++ b/libc/include/stdfix.yaml @@ -544,3 +544,11 @@ functions: arguments: - type: unsigned long accum guard: LIBC_COMPILER_HAS_FIXED_POINT + - name: rdivi + standards: + - stdc_ext + return_type: fract + arguments: + - type: int + - type: int + guard: LIBC_COMPILER_HAS_FIXED_POINT diff --git a/libc/src/__support/fixed_point/fx_bits.h b/libc/src/__support/fixed_point/fx_bits.h index 00c6119b4f353..9069031786b0a 100644 --- a/libc/src/__support/fixed_point/fx_bits.h +++ b/libc/src/__support/fixed_point/fx_bits.h @@ -224,6 +224,59 @@ idiv(T x, T y) { return static_cast(result); } +LIBC_INLINE long accum nrstep(long accum d, long accum x0) { + auto v = x0 * (2.lk - (d * x0)); + return v; +} + +/* Divide the two integers and return a fixed_point value + * + * For reference, see: + * https://en.wikipedia.org/wiki/Division_algorithm#Newton%E2%80%93Raphson_division + * https://stackoverflow.com/a/9231996 + */ +template LIBC_INLINE constexpr XType divi(int n, int d) { + // If the value of the second operand of the / operator is zero, the + // behavior is undefined. Ref: ISO/IEC TR 18037:2008(E) p.g. 16 + LIBC_CRASH_ON_VALUE(d, 0); + + if (LIBC_UNLIKELY(n == 0)) { + return FXRep::ZERO(); + } + bool d_is_signed = false; + if (d < 0) { + d = (d * -1); + d_is_signed = true; + } + + unsigned int nv = static_cast(n); + unsigned int dv = static_cast(d); + unsigned int clz = cpp::countl_zero(dv) - 1; + unsigned long int scaled_val = dv << clz; + /* Scale denominator to be in the range of [0.5,1] */ + FXBits d_scaled{scaled_val}; + unsigned long int scaled_val_n = nv << clz; + /* Scale the numerator as much as the denominator to maintain correctness of + * the original equation + */ + FXBits n_scaled{scaled_val_n}; + long accum n_scaled_val = n_scaled.get_val(); + long accum d_scaled_val = d_scaled.get_val(); + /* x0 = (48/17) - (32/17) * d_n */ + long accum a = 2.8235lk; /* 48/17 */ + long accum b = 1.8823lk; /* 32/17 */ + long accum initial_approx = a - (b * d_scaled_val); + long accum val = nrstep(d_scaled_val, initial_approx); + val = nrstep(d_scaled_val, val); + val = nrstep(d_scaled_val, val); + val = nrstep(d_scaled_val, val); + long accum res = n_scaled_val * val; + if (d_is_signed) { + res *= static_cast(-1); + } + return static_cast(res); +} + } // namespace fixed_point } // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdfix/CMakeLists.txt b/libc/src/stdfix/CMakeLists.txt index 843111e3f80b1..3cbabd17ad34c 100644 --- a/libc/src/stdfix/CMakeLists.txt +++ b/libc/src/stdfix/CMakeLists.txt @@ -89,6 +89,20 @@ foreach(suffix IN ITEMS r lr k lk ur ulr uk ulk) ) endforeach() +foreach(suffix IN ITEMS r) + add_entrypoint_object( + ${suffix}divi + HDRS + ${suffix}divi.h + SRCS + ${suffix}divi.cpp + COMPILE_OPTIONS + ${libc_opt_high_flag} + DEPENDS + libc.src.__support.fixed_point.fx_bits + ) +endforeach() + add_entrypoint_object( uhksqrtus HDRS diff --git a/libc/src/stdfix/rdivi.cpp b/libc/src/stdfix/rdivi.cpp new file mode 100644 index 0000000000000..227b412879a38 --- /dev/null +++ b/libc/src/stdfix/rdivi.cpp @@ -0,0 +1,21 @@ +//===-- Implementation of rdivi 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 +// +//===----------------------------------------------------------------------===// + +#include "rdivi.h" +#include "include/llvm-libc-macros/stdfix-macros.h" // fract +#include "src/__support/common.h" // LLVM_LIBC_FUNCTION +#include "src/__support/fixed_point/fx_bits.h" // fixed_point +#include "src/__support/macros/config.h" // LIBC_NAMESPACE_DECL + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(fract, rdivi, (int a, int b)) { + return fixed_point::divi(a,b); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdfix/rdivi.h b/libc/src/stdfix/rdivi.h new file mode 100644 index 0000000000000..aeda1ee9d40f0 --- /dev/null +++ b/libc/src/stdfix/rdivi.h @@ -0,0 +1,21 @@ +//===-- Implementation header for rdivi ------------------------*- 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_SRC_STDFIX_RDIVI_H +#define LLVM_LIBC_SRC_STDFIX_RDIVI_H + +#include "include/llvm-libc-macros/stdfix-macros.h" +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +fract rdivi(int a, int b); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_STDFIX_RDIVI_H diff --git a/libc/test/src/stdfix/CMakeLists.txt b/libc/test/src/stdfix/CMakeLists.txt index e2b4bc1805f7c..741522276feaa 100644 --- a/libc/test/src/stdfix/CMakeLists.txt +++ b/libc/test/src/stdfix/CMakeLists.txt @@ -120,6 +120,22 @@ foreach(suffix IN ITEMS r lr k lk ur ulr uk ulk) ) endforeach() +foreach(suffix IN ITEMS r) + add_libc_test( + ${suffix}divi_test + SUITE + libc-stdfix-tests + HDRS + DivITest.h + SRCS + ${suffix}divi_test.cpp + DEPENDS + libc.src.stdfix.${suffix}divi + libc.src.__support.fixed_point.fx_bits + libc.hdr.signal_macros + ) +endforeach() + add_libc_test( uhksqrtus_test SUITE diff --git a/libc/test/src/stdfix/DivITest.h b/libc/test/src/stdfix/DivITest.h new file mode 100644 index 0000000000000..6642a5a4c93ab --- /dev/null +++ b/libc/test/src/stdfix/DivITest.h @@ -0,0 +1,61 @@ +//===-- Utility class to test fxdivi functions ------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "src/__support/CPP/type_traits.h" +#include "src/__support/fixed_point/fx_bits.h" +#include "src/__support/fixed_point/fx_rep.h" +#include "test/UnitTest/Test.h" + +template XType get_epsilon() { + // TODO: raise error + return 0; +} +template <> fract get_epsilon() { return FRACT_EPSILON; } +template <> unsigned fract get_epsilon() { return UFRACT_EPSILON; } +template <> long fract get_epsilon() { return LFRACT_EPSILON; } + +template +class DivITest : public LIBC_NAMESPACE::testing::Test { + using FXRep = LIBC_NAMESPACE::fixed_point::FXRep; + using FXBits = LIBC_NAMESPACE::fixed_point::FXBits; + +public: + typedef XType (*DivIFunc)(int, int); + + void testBasic(DivIFunc func) { + XType epsilon = get_epsilon(); + EXPECT_LT((func(2, 3) - 0.666666r), epsilon); + EXPECT_LT((func(3, 4) - 0.75r), epsilon); + EXPECT_LT((func(1043, 2764) - 0.37735r), epsilon); + EXPECT_LT((func(60000, 720293) - 0.083299r), epsilon); + + EXPECT_EQ(func(128, 256), 0.5r); + EXPECT_EQ(func(1, 2), 0.5r); + EXPECT_EQ(func(1, 4), 0.25r); + EXPECT_EQ(func(1, 8), 0.125r); + EXPECT_EQ(func(1, 16), 0.0625r); + + EXPECT_EQ(func(-1, 2), -0.5r); + EXPECT_EQ(func(1, -4), -0.25r); + EXPECT_EQ(func(-1, 8), -0.125r); + EXPECT_EQ(func(1, -16), -0.0625r); + } + + void testSpecial(DivIFunc func) { + EXPECT_EQ(func(0,10), 0.r); + EXPECT_DEATH([func] { func(10, 0); }, WITH_SIGNAL(-1)); + EXPECT_EQ(func(-32768,32768), FRACT_MIN); + EXPECT_EQ(func(32767,32768), FRACT_MAX); + } +}; + +#define LIST_DIVI_TESTS(Name, XType, func) \ + using LlvmLibc##Name##diviTest = DivITest; \ + TEST_F(LlvmLibc##Name##diviTest, Basic) { testBasic(&func); } \ + TEST_F(LlvmLibc##Name##diviTest, Special) { testSpecial(&func); } \ + static_assert(true, "Require semicolon.") diff --git a/libc/test/src/stdfix/rdivi_test.cpp b/libc/test/src/stdfix/rdivi_test.cpp new file mode 100644 index 0000000000000..10ab366679a36 --- /dev/null +++ b/libc/test/src/stdfix/rdivi_test.cpp @@ -0,0 +1,14 @@ +//===-- Unittests for rdivi -----------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "DivITest.h" + +#include "llvm-libc-macros/stdfix-macros.h" // fract +#include "src/stdfix/rdivi.h" + +LIST_DIVI_TESTS(r, fract, LIBC_NAMESPACE::rdivi); From 040e83342ab4bd051b584e7cdbad91158ce06426 Mon Sep 17 00:00:00 2001 From: Shreeyash Pandey Date: Sun, 31 Aug 2025 01:01:23 +0530 Subject: [PATCH 2/2] [libc][stdfix] add a couple more tests and error info for divi Signed-off-by: Shreeyash Pandey --- libc/src/__support/fixed_point/fx_bits.h | 11 +++++++++++ libc/test/src/stdfix/DivITest.h | 20 ++++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/libc/src/__support/fixed_point/fx_bits.h b/libc/src/__support/fixed_point/fx_bits.h index 9069031786b0a..b6c88998d2bd4 100644 --- a/libc/src/__support/fixed_point/fx_bits.h +++ b/libc/src/__support/fixed_point/fx_bits.h @@ -265,10 +265,21 @@ template LIBC_INLINE constexpr XType divi(int n, int d) { /* x0 = (48/17) - (32/17) * d_n */ long accum a = 2.8235lk; /* 48/17 */ long accum b = 1.8823lk; /* 32/17 */ + /* Error of the initial approximation, as derived + * from the wikipedia article is + * E0 = 1/17 = 0.059 (5.9%) + */ long accum initial_approx = a - (b * d_scaled_val); + /* Each newton-raphson iteration will square the error, due + * to quadratic convergence. So, + * E1 = (0.059)^2 = 0.0034 + */ long accum val = nrstep(d_scaled_val, initial_approx); + /* E2 = 0.0000121 */ val = nrstep(d_scaled_val, val); + /* E3 = 1.468e−10 */ val = nrstep(d_scaled_val, val); + /* E4 = 2.155e−20 */ val = nrstep(d_scaled_val, val); long accum res = n_scaled_val * val; if (d_is_signed) { diff --git a/libc/test/src/stdfix/DivITest.h b/libc/test/src/stdfix/DivITest.h index 6642a5a4c93ab..f3ac145ad0af8 100644 --- a/libc/test/src/stdfix/DivITest.h +++ b/libc/test/src/stdfix/DivITest.h @@ -11,10 +11,7 @@ #include "src/__support/fixed_point/fx_rep.h" #include "test/UnitTest/Test.h" -template XType get_epsilon() { - // TODO: raise error - return 0; -} +template XType get_epsilon() = delete; template <> fract get_epsilon() { return FRACT_EPSILON; } template <> unsigned fract get_epsilon() { return UFRACT_EPSILON; } template <> long fract get_epsilon() { return LFRACT_EPSILON; } @@ -29,10 +26,10 @@ class DivITest : public LIBC_NAMESPACE::testing::Test { void testBasic(DivIFunc func) { XType epsilon = get_epsilon(); - EXPECT_LT((func(2, 3) - 0.666666r), epsilon); + EXPECT_LT((func(2, 3) - 0.666656494140625r), epsilon); EXPECT_LT((func(3, 4) - 0.75r), epsilon); - EXPECT_LT((func(1043, 2764) - 0.37735r), epsilon); - EXPECT_LT((func(60000, 720293) - 0.083299r), epsilon); + EXPECT_LT((func(1043, 2764) - 0.3773516643r), epsilon); + EXPECT_LT((func(60000, 720293) - 0.08329943509r), epsilon); EXPECT_EQ(func(128, 256), 0.5r); EXPECT_EQ(func(1, 2), 0.5r); @@ -48,9 +45,16 @@ class DivITest : public LIBC_NAMESPACE::testing::Test { void testSpecial(DivIFunc func) { EXPECT_EQ(func(0,10), 0.r); - EXPECT_DEATH([func] { func(10, 0); }, WITH_SIGNAL(-1)); + EXPECT_EQ(func(0,-10), 0.r); EXPECT_EQ(func(-32768,32768), FRACT_MIN); EXPECT_EQ(func(32767,32768), FRACT_MAX); + EXPECT_EQ(func(INT_MAX,INT_MAX), 1.0r); + EXPECT_EQ(func(INT_MAX-1,INT_MAX), 0.99999999r); + EXPECT_EQ(func(INT_MIN,INT_MAX), FRACT_MIN); + /* Expecting 0 here as fract is not precise enough to + * handle 1/INT_MAX + */ + EXPECT_EQ(func(1, INT_MAX), 0.r); } };