Skip to content

[libc] Make BigInt bit_cast-able to compatible types #75063

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 21, 2023

Conversation

gchatelet
Copy link
Contributor

This is a second take on #74837 to fix #74258

@llvmbot llvmbot added the libc label Dec 11, 2023
@llvmbot
Copy link
Member

llvmbot commented Dec 11, 2023

@llvm/pr-subscribers-libc

Author: Guillaume Chatelet (gchatelet)

Changes

This is a second take on #74837 to fix #74258


Full diff: https://github.com/llvm/llvm-project/pull/75063.diff

5 Files Affected:

  • (modified) libc/src/__support/CPP/bit.h (+22-8)
  • (modified) libc/src/__support/FPUtil/FPBits.h (+1)
  • (modified) libc/src/__support/UInt.h (+29)
  • (modified) libc/src/string/memory_utils/utils.h (+2-25)
  • (modified) libc/test/src/__support/uint_test.cpp (+54-9)
diff --git a/libc/src/__support/CPP/bit.h b/libc/src/__support/CPP/bit.h
index 2091c3662d507..f9bb2ef9f8332 100644
--- a/libc/src/__support/CPP/bit.h
+++ b/libc/src/__support/CPP/bit.h
@@ -25,6 +25,27 @@ namespace LIBC_NAMESPACE::cpp {
 #define LLVM_LIBC_HAS_BUILTIN_MEMCPY_INLINE
 #endif
 
+// Performs a copy from 'src' to 'dst' of 'size' bytes.
+// The semantics is valid if 'src' and 'dst' are equal but undefined if the
+// regions defined by [src, src + size] and [dst, dst + size] overlap.
+template <size_t size, typename DstT, typename SrcT>
+LIBC_INLINE constexpr void memcpy_inline(DstT *__restrict dst,
+                                         const SrcT *__restrict src) {
+#ifdef LLVM_LIBC_HAS_BUILTIN_MEMCPY_INLINE
+  __builtin_memcpy(dst, src, size);
+#else
+  // `memcpy_inline` is instantiated several times with different value of the
+  // size parameter. This doesn't play well with GCC's Value Range Analysis that
+  // wrongly detects out of bounds accesses. We disable the 'array-bounds'
+  // warning for the purpose of this function.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+  for (size_t i = 0; i < size; ++i)
+    static_cast<char *>(dst)[i] = static_cast<const char *>(src)[i];
+#pragma GCC diagnostic pop
+#endif
+}
+
 // This implementation of bit_cast requires trivially-constructible To, to avoid
 // UB in the implementation.
 template <
@@ -39,14 +60,7 @@ LIBC_INLINE constexpr To bit_cast(const From &from) {
   return __builtin_bit_cast(To, from);
 #else
   To to;
-  char *dst = reinterpret_cast<char *>(&to);
-  const char *src = reinterpret_cast<const char *>(&from);
-#if LIBC_HAS_BUILTIN(__builtin_memcpy_inline)
-  __builtin_memcpy_inline(dst, src, sizeof(To));
-#else
-  for (unsigned i = 0; i < sizeof(To); ++i)
-    dst[i] = src[i];
-#endif // LIBC_HAS_BUILTIN(__builtin_memcpy_inline)
+  memcpy_inline<sizeof(To)>(&to, &from);
   return to;
 #endif // LIBC_HAS_BUILTIN(__builtin_bit_cast)
 }
diff --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h
index ca98aa7126249..eb75abc379a03 100644
--- a/libc/src/__support/FPUtil/FPBits.h
+++ b/libc/src/__support/FPUtil/FPBits.h
@@ -11,6 +11,7 @@
 
 #include "src/__support/CPP/bit.h"
 #include "src/__support/CPP/type_traits.h"
+#include "src/__support/UInt128.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/attributes.h" // LIBC_INLINE
 
diff --git a/libc/src/__support/UInt.h b/libc/src/__support/UInt.h
index f72b995f8788d..78d8213cabcf9 100644
--- a/libc/src/__support/UInt.h
+++ b/libc/src/__support/UInt.h
@@ -952,6 +952,35 @@ struct make_signed<UInt<Bits>> : type_identity<Int<Bits>> {
                 "Number of bits in Int should be a multiple of 64.");
 };
 
+namespace internal {
+template <typename T> struct is_custom_uint : cpp::false_type {};
+template <size_t Bits> struct is_custom_uint<UInt<Bits>> : cpp::true_type {};
+} // namespace internal
+
+// bit_cast to UInt
+template <typename To, typename From,
+          typename = cpp::enable_if_t<internal::is_custom_uint<To>::value>,
+          typename = cpp::enable_if_t<sizeof(To) == sizeof(From)>,
+          typename = cpp::enable_if_t<cpp::is_trivially_copyable<From>::value>>
+LIBC_INLINE constexpr To bit_cast(const From &from) {
+  To out;
+  cpp::memcpy_inline<sizeof(out)>(out.val, &from);
+  return out;
+}
+
+// bit_cast from UInt
+template <
+    typename To, size_t Bits,
+    typename = cpp::enable_if_t<sizeof(To) == sizeof(UInt<Bits>)>,
+    typename = cpp::enable_if_t<cpp::is_trivially_constructible<To>::value>,
+    typename = cpp::enable_if_t<cpp::is_trivially_copyable<To>::value>,
+    typename = cpp::enable_if_t<cpp::is_trivially_copyable<UInt<Bits>>::value>>
+LIBC_INLINE constexpr To bit_cast(const UInt<Bits> &from) {
+  To out;
+  cpp::memcpy_inline<sizeof(out)>(&out, from.val);
+  return out;
+}
+
 } // namespace LIBC_NAMESPACE::cpp
 
 #endif // LLVM_LIBC_SRC___SUPPORT_UINT_H
diff --git a/libc/src/string/memory_utils/utils.h b/libc/src/string/memory_utils/utils.h
index 5cd716e033d6a..bbc9f68edb4bd 100644
--- a/libc/src/string/memory_utils/utils.h
+++ b/libc/src/string/memory_utils/utils.h
@@ -71,33 +71,10 @@ LIBC_INLINE bool is_disjoint(const void *p1, const void *p2, size_t size) {
   return sdiff >= 0 ? size <= udiff : size <= neg_udiff;
 }
 
-#if LIBC_HAS_BUILTIN(__builtin_memcpy_inline)
-#define LLVM_LIBC_HAS_BUILTIN_MEMCPY_INLINE
-#endif
-
 #if LIBC_HAS_BUILTIN(__builtin_memset_inline)
 #define LLVM_LIBC_HAS_BUILTIN_MEMSET_INLINE
 #endif
 
-// Performs a constant count copy.
-template <size_t Size>
-LIBC_INLINE void memcpy_inline(void *__restrict dst,
-                               const void *__restrict src) {
-#ifdef LLVM_LIBC_HAS_BUILTIN_MEMCPY_INLINE
-  __builtin_memcpy_inline(dst, src, Size);
-#else
-  // In memory functions `memcpy_inline` is instantiated several times with
-  // different value of the Size parameter. This doesn't play well with GCC's
-  // Value Range Analysis that wrongly detects out of bounds accesses. We
-  // disable the 'array-bounds' warning for the purpose of this function.
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Warray-bounds"
-  for (size_t i = 0; i < Size; ++i)
-    static_cast<char *>(dst)[i] = static_cast<const char *>(src)[i];
-#pragma GCC diagnostic pop
-#endif
-}
-
 using Ptr = cpp::byte *;        // Pointer to raw data.
 using CPtr = const cpp::byte *; // Const pointer to raw data.
 
@@ -204,13 +181,13 @@ LIBC_INLINE MemcmpReturnType cmp_neq_uint64_t(uint64_t a, uint64_t b) {
 // type.
 template <typename T> LIBC_INLINE T load(CPtr ptr) {
   T Out;
-  memcpy_inline<sizeof(T)>(&Out, ptr);
+  cpp::memcpy_inline<sizeof(T)>(&Out, ptr);
   return Out;
 }
 
 // Stores a value of type T in memory (possibly unaligned).
 template <typename T> LIBC_INLINE void store(Ptr ptr, T value) {
-  memcpy_inline<sizeof(T)>(ptr, &value);
+  cpp::memcpy_inline<sizeof(T)>(ptr, &value);
 }
 
 // On architectures that do not allow for unaligned access we perform several
diff --git a/libc/test/src/__support/uint_test.cpp b/libc/test/src/__support/uint_test.cpp
index 971bac55bd9d3..0ad72c35645c4 100644
--- a/libc/test/src/__support/uint_test.cpp
+++ b/libc/test/src/__support/uint_test.cpp
@@ -10,19 +10,62 @@
 #include "src/__support/UInt.h"
 
 #include "test/UnitTest/Test.h"
+#include <math.h> // HUGE_VALF, HUGE_VALF
 
-// We want to test LIBC_NAMESPACE::cpp::UInt<128> explicitly. So, for
+namespace LIBC_NAMESPACE {
+
+using LL_UInt64 = cpp::UInt<64>;
+// We want to test cpp::UInt<128> explicitly. So, for
 // convenience, we use a sugar which does not conflict with the UInt128 type
 // which can resolve to __uint128_t if the platform has it.
-using LL_UInt128 = LIBC_NAMESPACE::cpp::UInt<128>;
-using LL_UInt192 = LIBC_NAMESPACE::cpp::UInt<192>;
-using LL_UInt256 = LIBC_NAMESPACE::cpp::UInt<256>;
-using LL_UInt320 = LIBC_NAMESPACE::cpp::UInt<320>;
-using LL_UInt512 = LIBC_NAMESPACE::cpp::UInt<512>;
-using LL_UInt1024 = LIBC_NAMESPACE::cpp::UInt<1024>;
+using LL_UInt128 = cpp::UInt<128>;
+using LL_UInt192 = cpp::UInt<192>;
+using LL_UInt256 = cpp::UInt<256>;
+using LL_UInt320 = cpp::UInt<320>;
+using LL_UInt512 = cpp::UInt<512>;
+using LL_UInt1024 = cpp::UInt<1024>;
+
+using LL_Int128 = cpp::Int<128>;
+using LL_Int192 = cpp::Int<192>;
+
+TEST(LlvmLibcUIntClassTest, BitCastToFromDouble) {
+  static_assert(cpp::is_trivially_copyable<LL_UInt64>::value);
+  static_assert(sizeof(LL_UInt64) == sizeof(double));
+  const double inf = HUGE_VAL;
+  const double max = DBL_MAX;
+  const double array[] = {0.0, 0.1, 1.0, max, inf};
+  for (double value : array) {
+    LL_UInt64 back = cpp::bit_cast<LL_UInt64>(value);
+    double forth = cpp::bit_cast<double>(back);
+    EXPECT_TRUE(value == forth);
+  }
+}
 
-using LL_Int128 = LIBC_NAMESPACE::cpp::Int<128>;
-using LL_Int192 = LIBC_NAMESPACE::cpp::Int<192>;
+#ifdef __SIZEOF_INT128__
+TEST(LlvmLibcUIntClassTest, BitCastToFromNativeUint128) {
+  static_assert(cpp::is_trivially_copyable<LL_UInt128>::value);
+  static_assert(sizeof(LL_UInt128) == sizeof(__uint128_t));
+  const __uint128_t array[] = {0, 1, ~__uint128_t(0)};
+  for (__uint128_t value : array) {
+    LL_UInt128 back = cpp::bit_cast<LL_UInt128>(value);
+    __uint128_t forth = cpp::bit_cast<__uint128_t>(back);
+    EXPECT_TRUE(value == forth);
+  }
+}
+#endif
+
+#ifdef LIBC_COMPILER_HAS_FLOAT128
+TEST(LlvmLibcUIntClassTest, BitCastToFromNativeFloat128) {
+  static_assert(cpp::is_trivially_copyable<LL_UInt128>::value);
+  static_assert(sizeof(LL_UInt128) == sizeof(float128));
+  const float128 array[] = {0, 0.1, 1};
+  for (float128 value : array) {
+    LL_UInt128 back = cpp::bit_cast<LL_UInt128>(value);
+    float128 forth = cpp::bit_cast<float128>(back);
+    EXPECT_TRUE(value == forth);
+  }
+}
+#endif
 
 TEST(LlvmLibcUIntClassTest, BasicInit) {
   LL_UInt128 half_val(12345);
@@ -634,3 +677,5 @@ TEST(LlvmLibcUIntClassTest, ConstructorFromUInt128Tests) {
 }
 
 #endif // __SIZEOF_INT128__
+
+} // namespace LIBC_NAMESPACE

@gchatelet
Copy link
Contributor Author

Contrary to #74837 this patch does not try to make the type bit_cast-able directly but adds bit_cast implementations that know how to interact with the type.

@gchatelet
Copy link
Contributor Author

@michaelrj-google @lntue @petrhosek

Now even with this patch in, when we disable native support for 128 bit uint types in

#if !defined(__SIZEOF_INT128__)
using UInt128 = LIBC_NAMESPACE::cpp::UInt<128>;
using Int128 = LIBC_NAMESPACE::cpp::Int<128>;
#else
using UInt128 = __uint128_t;
using Int128 = __int128_t;
#endif

and use UInt128 = cpp::UInt<128>, we have the following error:

[ RUN      ] LlvmLibcStrToLDTest.SimpleTest
external/llvm-project/libc/test/src/stdlib/strtold_test.cpp:90: FAILURE
      Expected: actual_fp.bits
      Which is: 0x7fffc000000000000000
To be equal to: expected_fp.bits
      Which is: 0x4005f600000000000000
external/llvm-project/libc/test/src/stdlib/strtold_test.cpp:92: FAILURE
      Expected: actual_fp.get_exponent()
      Which is: 16384
To be equal to: expected_fp.get_exponent()
      Which is: 6
external/llvm-project/libc/test/src/stdlib/strtold_test.cpp:93: FAILURE
      Expected: actual_fp.get_mantissa()
      Which is: 0x4000000000000000
To be equal to: expected_fp.get_mantissa()
      Which is: 0x7600000000000000
[  FAILED  ] LlvmLibcStrToLDTest.SimpleTest

I tried to trace it down but it feels like an issue with the BigInt implementation. strtold seems to exercise paths where BigInt does not quite have the same semantics as __uint128_t.

@gchatelet
Copy link
Contributor Author

gchatelet commented Dec 11, 2023

The recent breakage on the arm32 build bot might also be related to how BigInt<128, false> is implemented.

https://lab.llvm.org/buildbot/#/builders/229/builds/20953/steps/6/logs/stdio

/llvm/libc_worker/worker/libc-arm32-debian/libc-arm32-debian-dbg/llvm-project/libc/src/__support/FPUtil/FloatProperties.h:134:3: error: static_assert failed due to requirement '(SIG_MASK | EXP_MASK | SIGN_MASK_) == FP_MASK' "masks covers"
  static_assert((SIG_MASK | EXP_MASK | SIGN_MASK_) == FP_MASK, "masks covers");
  ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This fails during Intel Extended Precision instantiation which requires 128 bit types. ARM32 does not have __uint128_t and uses cpp::UInt<128>.

edit: I can reproduce the error on x86 by manually forcing UInt128 = cpp::UInt<128>.

edit: Initializing an unsigned bigint with -1 is probably a bad idea

return count == 0 ? 0 : (T(-1) >> (t_bits - count));

edit: Fixed in #75084 although the failure in #75063 (comment) is still here.

Copy link
Contributor

@michaelrj-google michaelrj-google left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fine to me, though I'd say wait for other reviewers to approve.

@gchatelet gchatelet requested a review from lntue December 18, 2023 13:11
@gchatelet gchatelet requested a review from lntue December 19, 2023 09:23
@gchatelet gchatelet requested review from legrosbuffle and removed request for legrosbuffle December 20, 2023 09:31
@gchatelet gchatelet merged commit ba4d369 into llvm:main Dec 21, 2023
@gchatelet gchatelet deleted the bigint_bitcastable branch December 21, 2023 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

libc/src/__support/FPUtil/FPBits.h:111:14: error: no matching function for call to bit_cast
5 participants