Skip to content

Commit

Permalink
Add big-endian support
Browse files Browse the repository at this point in the history
Add to_little_endian()/to_big_endian() helpers converting internal
representations to/from requested byte order.
Also fix store()/load() functions to work correctly on big-endian
architectures.
  • Loading branch information
chfast committed Mar 11, 2022
1 parent d12472a commit bc15a64
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 9 deletions.
63 changes: 55 additions & 8 deletions include/intx/intx.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ using builtin_uint128 = unsigned __int128;
#pragma GCC diagnostic pop
#endif

constexpr bool byte_order_is_little_endian =
#if defined(__BYTE_ORDER__)
(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__);
#elif defined(_WIN32)
true; // On Windows assume little endian.
#else
#error "Unknown endianness"
#endif

template <unsigned N>
struct uint;

Expand Down Expand Up @@ -2016,6 +2025,43 @@ inline constexpr uint512 operator"" _u512(const char* s)
return from_string<uint512>(s);
}


/// Convert native representation to/from little-endian byte order.
/// intx and built-in integral types are supported.
template <typename T>
inline constexpr T to_little_endian(const T& x) noexcept
{
if constexpr (byte_order_is_little_endian)
return x;
else if constexpr (std::is_integral_v<T>)
return bswap(x);
else // Wordwise bswap.
{
T r;
for (size_t i = 0; i < T::num_words; ++i)
r[i] = bswap(x[i]);
return r;
}
}

/// Convert native representation to/from big-endian byte order.
/// intx and built-in integral types are supported.
template <typename T>
inline constexpr T to_big_endian(const T& x) noexcept
{
if constexpr (byte_order_is_little_endian)
return bswap(x);
else if constexpr (std::is_integral_v<T>)
return x;
else // Swap words.
{
T r;
for (size_t i = 0; i < T::num_words; ++i)
r[T::num_words - 1 - i] = x[i];
return r;
}
}

namespace le // Conversions to/from LE bytes.
{
template <typename IntT, unsigned M>
Expand All @@ -2025,13 +2071,15 @@ inline IntT load(const uint8_t (&src)[M]) noexcept
"the size of source bytes must match the size of the destination uint");
IntT x;
std::memcpy(&x, src, sizeof(x));
x = to_little_endian(x);
return x;
}

template <unsigned N>
inline void store(uint8_t (&dst)[N / 8], const uint<N>& x) noexcept
{
std::memcpy(dst, &x, sizeof(x));
const auto d = to_little_endian(x);
std::memcpy(dst, &d, sizeof(d));
}

} // namespace le
Expand All @@ -2048,7 +2096,7 @@ inline IntT load(const uint8_t (&src)[M]) noexcept
"the size of source bytes must not exceed the size of the destination uint");
IntT x;
std::memcpy(&as_bytes(x)[IntT::num_bits / 8 - M], src, M);
x = bswap(x);
x = to_big_endian(x);
return x;
}

Expand All @@ -2062,7 +2110,7 @@ inline IntT load(const T& t) noexcept
template <unsigned N>
inline void store(uint8_t (&dst)[N / 8], const uint<N>& x) noexcept
{
const auto d = bswap(x);
const auto d = to_big_endian(x);
std::memcpy(dst, &d, sizeof(d));
}

Expand All @@ -2083,9 +2131,8 @@ template <unsigned M, unsigned N>
inline void trunc(uint8_t (&dst)[M], const uint<N>& x) noexcept
{
static_assert(M < N / 8, "destination must be smaller than the source value");
const auto d = bswap(x);
const auto b = as_bytes(d);
std::memcpy(dst, &b[sizeof(d) - M], M);
const auto d = to_big_endian(x);
std::memcpy(dst, &as_bytes(d)[sizeof(d) - M], M);
}

/// Stores the truncated value of an uint in the .bytes field of an object of type T.
Expand All @@ -2106,7 +2153,7 @@ inline IntT load(const uint8_t* src) noexcept
{
IntT x;
std::memcpy(&x, src, sizeof(x));
x = bswap(x);
x = to_big_endian(x);
return x;
}

Expand All @@ -2115,7 +2162,7 @@ inline IntT load(const uint8_t* src) noexcept
template <unsigned N>
inline void store(uint8_t* dst, const uint<N>& x) noexcept
{
const auto d = bswap(x);
const auto d = to_big_endian(x);
std::memcpy(dst, &d, sizeof(d));
}
} // namespace unsafe
Expand Down
12 changes: 12 additions & 0 deletions test/unittests/test_builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ static_assert(clz_generic(uint64_t{1}) == 63);
static_assert(clz_generic(uint64_t{3}) == 62);
static_assert(clz_generic(uint64_t{9}) == 60);

static constexpr auto is_le = byte_order_is_little_endian;
static_assert(to_little_endian(uint8_t{0x0a}) == 0x0a);
static_assert(to_little_endian(uint16_t{0x0b0a}) == (is_le ? 0x0b0a : 0x0a0b));
static_assert(to_little_endian(uint32_t{0x0d0c0b0a}) == (is_le ? 0x0d0c0b0a : 0x0a0b0c0d));
static_assert(to_little_endian(uint64_t{0x02010f0e0d0c0b0a}) ==
(is_le ? 0x02010f0e0d0c0b0a : 0x0a0b0c0d0e0f0102));
static_assert(to_big_endian(uint8_t{0x0a}) == 0x0a);
static_assert(to_big_endian(uint16_t{0x0b0a}) == (is_le ? 0x0a0b : 0x0b0a));
static_assert(to_big_endian(uint32_t{0x0d0c0b0a}) == (is_le ? 0x0a0b0c0d : 0x0d0c0b0a));
static_assert(to_big_endian(uint64_t{0x02010f0e0d0c0b0a}) ==
(is_le ? 0x0a0b0c0d0e0f0102 : 0x02010f0e0d0c0b0a));


TEST(builtins, clz64_single_one)
{
Expand Down
3 changes: 2 additions & 1 deletion test/unittests/test_intx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ TYPED_TEST(uint_test, to_string_base)

TYPED_TEST(uint_test, as_bytes)
{
constexpr auto x = TypeParam{0xa05};
constexpr auto x = to_little_endian(TypeParam{0xa05});
const auto b = as_bytes(x);
EXPECT_EQ(b[0], 5);
EXPECT_EQ(b[1], 0xa);
Expand All @@ -281,5 +281,6 @@ TYPED_TEST(uint_test, as_bytes)
auto d = as_bytes(y);
d[0] = 3;
d[1] = 0xc;
y = to_little_endian(y);
EXPECT_EQ(y, 0xc03);
}

0 comments on commit bc15a64

Please sign in to comment.