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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions libc/src/__support/CPP/bit.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ namespace LIBC_NAMESPACE::cpp {
// UB in the implementation.
template <
typename To, typename From,
typename = cpp::enable_if_t<sizeof(To) == sizeof(From)>,
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<From>::value>>
typename = cpp::enable_if_t<sizeof(To) == sizeof(From) &&
cpp::is_trivially_constructible<To>::value &&
cpp::is_trivially_copyable<To>::value &&
cpp::is_trivially_copyable<From>::value>>
LIBC_INLINE constexpr To bit_cast(const From &from) {
MSAN_UNPOISON(&from, sizeof(From));
#if LIBC_HAS_BUILTIN(__builtin_bit_cast)
Expand Down
1 change: 1 addition & 0 deletions libc/src/__support/FPUtil/FPBits.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
44 changes: 43 additions & 1 deletion libc/src/__support/UInt.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ template <size_t Bits, bool Signed> struct BigInt {
static_assert(Bits > 0 && Bits % 64 == 0,
"Number of bits in BigInt should be a multiple of 64.");
LIBC_INLINE_VAR static constexpr size_t WORDCOUNT = Bits / 64;
uint64_t val[WORDCOUNT]{};
cpp::array<uint64_t, WORDCOUNT> val{};

LIBC_INLINE_VAR static constexpr uint64_t MASK32 = 0xFFFFFFFFu;

Expand Down Expand Up @@ -952,6 +952,48 @@ 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
// Note: The standard scheme for SFINAE selection is to have exactly one
// function instanciation valid at a time. This is usually done by having a
// predicate in one function and the negated predicate in the other one.
// e.g.
// template<typename = cpp::enable_if_t< is_custom_uint<To>::value == true> ...
// template<typename = cpp::enable_if_t< is_custom_uint<To>::value == false> ...
//
// Unfortunately this would make the default 'cpp::bit_cast' aware of
// 'is_custom_uint' (or any other customization). To prevent exposing all
// customizations in the original function, we create a different function with
// four 'typename's instead of three - otherwise it would be considered as a
// redeclaration of the same function leading to "error: template parameter
// redefines default argument".
template <typename To, typename From,
typename = cpp::enable_if_t<sizeof(To) == sizeof(From) &&
cpp::is_trivially_copyable<To>::value &&
cpp::is_trivially_copyable<From>::value>,
typename = cpp::enable_if_t<internal::is_custom_uint<To>::value>>
LIBC_INLINE constexpr To bit_cast(const From &from) {
To out;
using Storage = decltype(out.val);
out.val = cpp::bit_cast<Storage>(from);
return out;
}

// bit_cast from UInt
template <
typename To, size_t Bits,
typename = cpp::enable_if_t<sizeof(To) == sizeof(UInt<Bits>) &&
cpp::is_trivially_constructible<To>::value &&
cpp::is_trivially_copyable<To>::value &&
cpp::is_trivially_copyable<UInt<Bits>>::value>>
LIBC_INLINE constexpr To bit_cast(const UInt<Bits> &from) {
return cpp::bit_cast<To>(from.val);
}

} // namespace LIBC_NAMESPACE::cpp

#endif // LLVM_LIBC_SRC___SUPPORT_UINT_H
63 changes: 54 additions & 9 deletions libc/test/src/__support/uint_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -634,3 +677,5 @@ TEST(LlvmLibcUIntClassTest, ConstructorFromUInt128Tests) {
}

#endif // __SIZEOF_INT128__

} // namespace LIBC_NAMESPACE