Skip to content

Commit ba4d369

Browse files
authored
[libc] Make BigInt bit_cast-able to compatible types (#75063)
This is a second take on #74837 to fix #74258
1 parent c0931d4 commit ba4d369

File tree

4 files changed

+102
-14
lines changed

4 files changed

+102
-14
lines changed

libc/src/__support/CPP/bit.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ namespace LIBC_NAMESPACE::cpp {
2929
// UB in the implementation.
3030
template <
3131
typename To, typename From,
32-
typename = cpp::enable_if_t<sizeof(To) == sizeof(From)>,
33-
typename = cpp::enable_if_t<cpp::is_trivially_constructible<To>::value>,
34-
typename = cpp::enable_if_t<cpp::is_trivially_copyable<To>::value>,
35-
typename = cpp::enable_if_t<cpp::is_trivially_copyable<From>::value>>
32+
typename = cpp::enable_if_t<sizeof(To) == sizeof(From) &&
33+
cpp::is_trivially_constructible<To>::value &&
34+
cpp::is_trivially_copyable<To>::value &&
35+
cpp::is_trivially_copyable<From>::value>>
3636
LIBC_INLINE constexpr To bit_cast(const From &from) {
3737
MSAN_UNPOISON(&from, sizeof(From));
3838
#if LIBC_HAS_BUILTIN(__builtin_bit_cast)

libc/src/__support/FPUtil/FPBits.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "src/__support/CPP/bit.h"
1313
#include "src/__support/CPP/type_traits.h"
14+
#include "src/__support/UInt128.h"
1415
#include "src/__support/common.h"
1516
#include "src/__support/macros/attributes.h" // LIBC_INLINE
1617

libc/src/__support/UInt.h

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ template <size_t Bits, bool Signed> struct BigInt {
3030
static_assert(Bits > 0 && Bits % 64 == 0,
3131
"Number of bits in BigInt should be a multiple of 64.");
3232
LIBC_INLINE_VAR static constexpr size_t WORDCOUNT = Bits / 64;
33-
uint64_t val[WORDCOUNT]{};
33+
cpp::array<uint64_t, WORDCOUNT> val{};
3434

3535
LIBC_INLINE_VAR static constexpr uint64_t MASK32 = 0xFFFFFFFFu;
3636

@@ -954,6 +954,48 @@ struct make_signed<UInt<Bits>> : type_identity<Int<Bits>> {
954954
"Number of bits in Int should be a multiple of 64.");
955955
};
956956

957+
namespace internal {
958+
template <typename T> struct is_custom_uint : cpp::false_type {};
959+
template <size_t Bits> struct is_custom_uint<UInt<Bits>> : cpp::true_type {};
960+
} // namespace internal
961+
962+
// bit_cast to UInt
963+
// Note: The standard scheme for SFINAE selection is to have exactly one
964+
// function instanciation valid at a time. This is usually done by having a
965+
// predicate in one function and the negated predicate in the other one.
966+
// e.g.
967+
// template<typename = cpp::enable_if_t< is_custom_uint<To>::value == true> ...
968+
// template<typename = cpp::enable_if_t< is_custom_uint<To>::value == false> ...
969+
//
970+
// Unfortunately this would make the default 'cpp::bit_cast' aware of
971+
// 'is_custom_uint' (or any other customization). To prevent exposing all
972+
// customizations in the original function, we create a different function with
973+
// four 'typename's instead of three - otherwise it would be considered as a
974+
// redeclaration of the same function leading to "error: template parameter
975+
// redefines default argument".
976+
template <typename To, typename From,
977+
typename = cpp::enable_if_t<sizeof(To) == sizeof(From) &&
978+
cpp::is_trivially_copyable<To>::value &&
979+
cpp::is_trivially_copyable<From>::value>,
980+
typename = cpp::enable_if_t<internal::is_custom_uint<To>::value>>
981+
LIBC_INLINE constexpr To bit_cast(const From &from) {
982+
To out;
983+
using Storage = decltype(out.val);
984+
out.val = cpp::bit_cast<Storage>(from);
985+
return out;
986+
}
987+
988+
// bit_cast from UInt
989+
template <
990+
typename To, size_t Bits,
991+
typename = cpp::enable_if_t<sizeof(To) == sizeof(UInt<Bits>) &&
992+
cpp::is_trivially_constructible<To>::value &&
993+
cpp::is_trivially_copyable<To>::value &&
994+
cpp::is_trivially_copyable<UInt<Bits>>::value>>
995+
LIBC_INLINE constexpr To bit_cast(const UInt<Bits> &from) {
996+
return cpp::bit_cast<To>(from.val);
997+
}
998+
957999
} // namespace LIBC_NAMESPACE::cpp
9581000

9591001
#endif // LLVM_LIBC_SRC___SUPPORT_UINT_H

libc/test/src/__support/uint_test.cpp

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,62 @@
1010
#include "src/__support/UInt.h"
1111

1212
#include "test/UnitTest/Test.h"
13+
#include <math.h> // HUGE_VALF, HUGE_VALF
1314

14-
// We want to test LIBC_NAMESPACE::cpp::UInt<128> explicitly. So, for
15+
namespace LIBC_NAMESPACE {
16+
17+
using LL_UInt64 = cpp::UInt<64>;
18+
// We want to test cpp::UInt<128> explicitly. So, for
1519
// convenience, we use a sugar which does not conflict with the UInt128 type
1620
// which can resolve to __uint128_t if the platform has it.
17-
using LL_UInt128 = LIBC_NAMESPACE::cpp::UInt<128>;
18-
using LL_UInt192 = LIBC_NAMESPACE::cpp::UInt<192>;
19-
using LL_UInt256 = LIBC_NAMESPACE::cpp::UInt<256>;
20-
using LL_UInt320 = LIBC_NAMESPACE::cpp::UInt<320>;
21-
using LL_UInt512 = LIBC_NAMESPACE::cpp::UInt<512>;
22-
using LL_UInt1024 = LIBC_NAMESPACE::cpp::UInt<1024>;
21+
using LL_UInt128 = cpp::UInt<128>;
22+
using LL_UInt192 = cpp::UInt<192>;
23+
using LL_UInt256 = cpp::UInt<256>;
24+
using LL_UInt320 = cpp::UInt<320>;
25+
using LL_UInt512 = cpp::UInt<512>;
26+
using LL_UInt1024 = cpp::UInt<1024>;
27+
28+
using LL_Int128 = cpp::Int<128>;
29+
using LL_Int192 = cpp::Int<192>;
30+
31+
TEST(LlvmLibcUIntClassTest, BitCastToFromDouble) {
32+
static_assert(cpp::is_trivially_copyable<LL_UInt64>::value);
33+
static_assert(sizeof(LL_UInt64) == sizeof(double));
34+
const double inf = HUGE_VAL;
35+
const double max = DBL_MAX;
36+
const double array[] = {0.0, 0.1, 1.0, max, inf};
37+
for (double value : array) {
38+
LL_UInt64 back = cpp::bit_cast<LL_UInt64>(value);
39+
double forth = cpp::bit_cast<double>(back);
40+
EXPECT_TRUE(value == forth);
41+
}
42+
}
2343

24-
using LL_Int128 = LIBC_NAMESPACE::cpp::Int<128>;
25-
using LL_Int192 = LIBC_NAMESPACE::cpp::Int<192>;
44+
#ifdef __SIZEOF_INT128__
45+
TEST(LlvmLibcUIntClassTest, BitCastToFromNativeUint128) {
46+
static_assert(cpp::is_trivially_copyable<LL_UInt128>::value);
47+
static_assert(sizeof(LL_UInt128) == sizeof(__uint128_t));
48+
const __uint128_t array[] = {0, 1, ~__uint128_t(0)};
49+
for (__uint128_t value : array) {
50+
LL_UInt128 back = cpp::bit_cast<LL_UInt128>(value);
51+
__uint128_t forth = cpp::bit_cast<__uint128_t>(back);
52+
EXPECT_TRUE(value == forth);
53+
}
54+
}
55+
#endif
56+
57+
#ifdef LIBC_COMPILER_HAS_FLOAT128
58+
TEST(LlvmLibcUIntClassTest, BitCastToFromNativeFloat128) {
59+
static_assert(cpp::is_trivially_copyable<LL_UInt128>::value);
60+
static_assert(sizeof(LL_UInt128) == sizeof(float128));
61+
const float128 array[] = {0, 0.1, 1};
62+
for (float128 value : array) {
63+
LL_UInt128 back = cpp::bit_cast<LL_UInt128>(value);
64+
float128 forth = cpp::bit_cast<float128>(back);
65+
EXPECT_TRUE(value == forth);
66+
}
67+
}
68+
#endif
2669

2770
TEST(LlvmLibcUIntClassTest, BasicInit) {
2871
LL_UInt128 half_val(12345);
@@ -634,3 +677,5 @@ TEST(LlvmLibcUIntClassTest, ConstructorFromUInt128Tests) {
634677
}
635678

636679
#endif // __SIZEOF_INT128__
680+
681+
} // namespace LIBC_NAMESPACE

0 commit comments

Comments
 (0)