Skip to content

Commit

Permalink
Merge pull request #265 from chfast/builtin_load_store
Browse files Browse the repository at this point in the history
Generalize be::{load,store} for built-in integer types
  • Loading branch information
chfast authored Mar 15, 2022
2 parents 50f6f3f + 9850ab7 commit 66042eb
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 40 deletions.
82 changes: 50 additions & 32 deletions include/intx/intx.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1408,16 +1408,18 @@ inline constexpr const uint64_t* as_words(const uint<N>& x) noexcept
return &x[0];
}

template <unsigned N>
inline uint8_t* as_bytes(uint<N>& x) noexcept
template <typename T>
inline uint8_t* as_bytes(T& x) noexcept
{
return reinterpret_cast<uint8_t*>(as_words(x));
static_assert(std::is_trivially_copyable_v<T>); // As in bit_cast.
return reinterpret_cast<uint8_t*>(&x);
}

template <unsigned N>
inline const uint8_t* as_bytes(const uint<N>& x) noexcept
template <typename T>
inline const uint8_t* as_bytes(const T& x) noexcept
{
return reinterpret_cast<const uint8_t*>(as_words(x));
static_assert(std::is_trivially_copyable_v<T>); // As in bit_cast.
return reinterpret_cast<const uint8_t*>(&x);
}

template <unsigned N>
Expand Down Expand Up @@ -2069,38 +2071,54 @@ inline constexpr T to_big_endian(const T& x) noexcept

namespace le // Conversions to/from LE bytes.
{
template <typename IntT, unsigned M>
inline IntT load(const uint8_t (&src)[M]) noexcept
template <typename T, unsigned M>
inline T load(const uint8_t (&src)[M]) noexcept
{
static_assert(M == IntT::num_bits / 8,
"the size of source bytes must match the size of the destination uint");
IntT x;
static_assert(
M == sizeof(T), "the size of source bytes must match the size of the destination uint");
T x;
std::memcpy(&x, src, sizeof(x));
x = to_little_endian(x);
return x;
return to_little_endian(x);
}

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

namespace unsafe
{
template <typename T>
inline T load(const uint8_t* src) noexcept
{
T x;
std::memcpy(&x, src, sizeof(x));
return to_little_endian(x);
}

template <typename T>
inline void store(uint8_t* dst, const T& x) noexcept
{
const auto d = to_little_endian(x);
std::memcpy(dst, &d, sizeof(d));
}
} // namespace unsafe
} // namespace le


namespace be // Conversions to/from BE bytes.
{
/// Loads an uint value from bytes of big-endian order.
/// If the size of bytes is smaller than the result uint, the value is zero-extended.
template <typename IntT, unsigned M>
inline IntT load(const uint8_t (&src)[M]) noexcept
/// Loads an integer value from bytes of big-endian order.
/// If the size of bytes is smaller than the result, the value is zero-extended.
template <typename T, unsigned M>
inline T load(const uint8_t (&src)[M]) noexcept
{
static_assert(M <= IntT::num_bits / 8,
static_assert(M <= sizeof(T),
"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);
T x{};
std::memcpy(&as_bytes(x)[sizeof(T) - M], src, M);
x = to_big_endian(x);
return x;
}
Expand All @@ -2111,20 +2129,20 @@ inline IntT load(const T& t) noexcept
return load<IntT>(t.bytes);
}

/// Stores an uint value in a bytes array in big-endian order.
template <unsigned N>
inline void store(uint8_t (&dst)[N / 8], const uint<N>& x) noexcept
/// Stores an integer value in a bytes array in big-endian order.
template <typename T>
inline void store(uint8_t (&dst)[sizeof(T)], const T& x) noexcept
{
const auto d = to_big_endian(x);
std::memcpy(dst, &d, sizeof(d));
}

/// Stores an uint value in .bytes field of type T. The .bytes must be an array of uint8_t
/// Stores an SrcT value in .bytes field of type DstT. The .bytes must be an array of uint8_t
/// of the size matching the size of uint.
template <typename T, unsigned N>
inline T store(const uint<N>& x) noexcept
template <typename DstT, typename SrcT>
inline DstT store(const SrcT& x) noexcept
{
T r{};
DstT r{};
store(r.bytes, x);
return r;
}
Expand Down Expand Up @@ -2162,10 +2180,10 @@ inline IntT load(const uint8_t* src) noexcept
return x;
}

/// Stores an uint value at the provided pointer in big-endian order. The user must make sure
/// Stores an integer value at the provided pointer in big-endian order. The user must make sure
/// that the provided buffer is big enough to fit the value. Therefore marked "unsafe".
template <unsigned N>
inline void store(uint8_t* dst, const uint<N>& x) noexcept
template <typename T>
inline void store(uint8_t* dst, const T& x) noexcept
{
const auto d = to_big_endian(x);
std::memcpy(dst, &d, sizeof(d));
Expand Down
58 changes: 50 additions & 8 deletions test/unittests/test_builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,9 @@ TEST(builtins, be_load_uint8_t)
constexpr auto size = sizeof(uint8_t);
uint8_t data[size]{};
data[0] = 0x81;
const auto x = be::unsafe::load<uint8_t>(data);
EXPECT_EQ(x, 0x81);
const auto expected = 0x81;
EXPECT_EQ(be::unsafe::load<uint8_t>(data), expected);
EXPECT_EQ(be::load<uint8_t>(data), expected);
}

TEST(builtins, be_load_uint16_t)
Expand All @@ -187,8 +188,9 @@ TEST(builtins, be_load_uint16_t)
uint8_t data[size]{};
data[0] = 0x80;
data[size - 1] = 1;
const auto x = be::unsafe::load<uint16_t>(data);
EXPECT_EQ(x, (uint16_t{1} << (sizeof(uint16_t) * 8 - 1)) | 1);
const auto expected = (uint16_t{1} << (sizeof(uint16_t) * 8 - 1)) | 1;
EXPECT_EQ(be::unsafe::load<uint16_t>(data), expected);
EXPECT_EQ(be::load<uint16_t>(data), expected);
}

TEST(builtins, be_load_uint32_t)
Expand All @@ -197,8 +199,9 @@ TEST(builtins, be_load_uint32_t)
uint8_t data[size]{};
data[0] = 0x80;
data[size - 1] = 1;
const auto x = be::unsafe::load<uint32_t>(data);
EXPECT_EQ(x, (uint32_t{1} << (sizeof(uint32_t) * 8 - 1)) | 1);
const auto expected = (uint32_t{1} << (sizeof(uint32_t) * 8 - 1)) | 1;
EXPECT_EQ(be::unsafe::load<uint32_t>(data), expected);
EXPECT_EQ(be::load<uint32_t>(data), expected);
}

TEST(builtins, be_load_uint64_t)
Expand All @@ -207,6 +210,45 @@ TEST(builtins, be_load_uint64_t)
uint8_t data[size]{};
data[0] = 0x80;
data[size - 1] = 1;
const auto x = be::unsafe::load<uint64_t>(data);
EXPECT_EQ(x, (uint64_t{1} << (sizeof(uint64_t) * 8 - 1)) | 1);
const auto expected = (uint64_t{1} << (sizeof(uint64_t) * 8 - 1)) | 1;
EXPECT_EQ(be::unsafe::load<uint64_t>(data), expected);
EXPECT_EQ(be::load<uint64_t>(data), expected);
}

TEST(builtins, be_load_partial)
{
uint8_t data[1]{0xec};
EXPECT_EQ(be::load<uint64_t>(data), uint64_t{0xec});
EXPECT_EQ(be::load<uint32_t>(data), uint32_t{0xec});
EXPECT_EQ(be::load<uint16_t>(data), uint16_t{0xec});
}

TEST(builtins, be_store_uint64_t)
{
constexpr auto size = sizeof(uint64_t);
uint8_t data[size]{};
std::string_view view{reinterpret_cast<const char*>(data), std::size(data)};
be::store(data, uint64_t{0x0102030405060708});
EXPECT_EQ(view, "\x01\x02\x03\x04\x05\x06\x07\x08");
std::fill_n(data, std::size(data), uint8_t{0});
be::unsafe::store(data, uint64_t{0x0102030405060708});
EXPECT_EQ(view, "\x01\x02\x03\x04\x05\x06\x07\x08");
}

TEST(builtins, le_load_uint32_t)
{
const uint8_t data[] = {0xb1, 0xb2, 0xb3, 0xb4};
EXPECT_EQ(le::load<uint32_t>(data), 0xb4b3b2b1);
EXPECT_EQ(le::unsafe::load<uint32_t>(data), 0xb4b3b2b1);
}

TEST(builtins, le_store_uint32_t)
{
uint8_t data[] = {0xb1, 0xb2, 0xb3, 0xb4};
std::string_view view{reinterpret_cast<const char*>(data), std::size(data)};
le::store(data, uint32_t{0xa1a2a3a4});
EXPECT_EQ(view, "\xa4\xa3\xa2\xa1");
std::fill_n(data, std::size(data), uint8_t{0xff});
le::unsafe::store(data, uint32_t{0xc1c2c3c4});
EXPECT_EQ(view, "\xc4\xc3\xc2\xc1");
}

0 comments on commit 66042eb

Please sign in to comment.