From bc15a642499a011d16077640097b4f3657e01d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 9 Mar 2022 23:42:49 +0100 Subject: [PATCH] Add big-endian support 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. --- include/intx/intx.hpp | 63 ++++++++++++++++++++++++++++---- test/unittests/test_builtins.cpp | 12 ++++++ test/unittests/test_intx.cpp | 3 +- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/include/intx/intx.hpp b/include/intx/intx.hpp index 502e12e8..c174c0d2 100644 --- a/include/intx/intx.hpp +++ b/include/intx/intx.hpp @@ -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 struct uint; @@ -2016,6 +2025,43 @@ inline constexpr uint512 operator"" _u512(const char* s) return from_string(s); } + +/// Convert native representation to/from little-endian byte order. +/// intx and built-in integral types are supported. +template +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) + 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 +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) + 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 @@ -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 inline void store(uint8_t (&dst)[N / 8], const uint& x) noexcept { - std::memcpy(dst, &x, sizeof(x)); + const auto d = to_little_endian(x); + std::memcpy(dst, &d, sizeof(d)); } } // namespace le @@ -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; } @@ -2062,7 +2110,7 @@ inline IntT load(const T& t) noexcept template inline void store(uint8_t (&dst)[N / 8], const uint& x) noexcept { - const auto d = bswap(x); + const auto d = to_big_endian(x); std::memcpy(dst, &d, sizeof(d)); } @@ -2083,9 +2131,8 @@ template inline void trunc(uint8_t (&dst)[M], const uint& 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. @@ -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; } @@ -2115,7 +2162,7 @@ inline IntT load(const uint8_t* src) noexcept template inline void store(uint8_t* dst, const uint& x) noexcept { - const auto d = bswap(x); + const auto d = to_big_endian(x); std::memcpy(dst, &d, sizeof(d)); } } // namespace unsafe diff --git a/test/unittests/test_builtins.cpp b/test/unittests/test_builtins.cpp index 6fd210e3..accece55 100644 --- a/test/unittests/test_builtins.cpp +++ b/test/unittests/test_builtins.cpp @@ -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) { diff --git a/test/unittests/test_intx.cpp b/test/unittests/test_intx.cpp index e775b7e6..a0912950 100644 --- a/test/unittests/test_intx.cpp +++ b/test/unittests/test_intx.cpp @@ -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); @@ -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); }