Skip to content
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

Rework and extend endian-specific conversion API #107

Merged
merged 14 commits into from
Aug 20, 2019
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
29 changes: 23 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,34 @@ The format is based on [Keep a Changelog],
and this project adheres to [Semantic Versioning].


## [0.4.0] - unreleased

### Added

- Added the `as_bytes()` casting helper.
[[#106](https://github.com/chfast/intx/pull/106)]

### Changed

- The endian-specific API for converting intx types to/from bytes has been reworked.
[[#107](https://github.com/chfast/intx/pull/107)]


## [0.3.0] - 2019-06-20

### Added
- New `addmod()` and `mulmod()` procedures have been added for the `uint256` type
([#101](https://github.com/chfast/intx/pull/101)).
- New `addmod()` and `mulmod()` procedures have been added for the `uint256` type.
[[#101](https://github.com/chfast/intx/pull/101)]

### Changed
- Pedantic compiler warnings have been fixed
([#98](https://github.com/chfast/intx/pull/98)).
- Pedantic compiler warnings have been fixed.
[[#98](https://github.com/chfast/intx/pull/98)]
- Performance of the division algorithm increased up to 40%
when dividing 256-bit values by 128-bit and 64-bit ones
([#99](https://github.com/chfast/intx/pull/99)).
when dividing 256-bit values by 128-bit and 64-bit ones.
[[#99](https://github.com/chfast/intx/pull/99)]


[0.4.0]: https://github.com/chfast/intx/compare/v0.3.0...master
[0.3.0]: https://github.com/chfast/intx/releases/v0.2.0

[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
Expand Down
2 changes: 2 additions & 0 deletions include/intx/int128.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ struct uint;
template <>
struct uint<128>
{
static constexpr unsigned num_bits = 128;

uint64_t lo = 0;
uint64_t hi = 0;

Expand Down
84 changes: 73 additions & 11 deletions include/intx/intx.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -969,18 +969,18 @@ constexpr uint512 operator"" _u512(const char* s) noexcept

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

template <unsigned N>
inline void store(uint8_t* dst, const intx::uint<N>& x) noexcept
inline void store(uint8_t (&dst)[N / 8], const intx::uint<N>& x) noexcept
{
std::memcpy(dst, &x, sizeof(x));
}
Expand All @@ -990,22 +990,84 @@ inline void store(uint8_t* dst, const intx::uint<N>& x) noexcept

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 (&bytes)[M]) noexcept
{
static_assert(M <= IntT::num_bits / 8,
"the size of source bytes must not exceed the size of the destination uint");
auto x = IntT{};
std::memcpy(&as_bytes(x)[IntT::num_bits / 8 - M], bytes, M);
return bswap(x);
}

template <typename IntT, typename T>
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 intx::uint<N> uint(const uint8_t bytes[sizeof(intx::uint<N>)]) noexcept
inline void store(uint8_t (&dst)[N / 8], const intx::uint<N>& x) noexcept
{
const auto d = bswap(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
/// of the size matching the size of uint.
template <typename T, unsigned N>
inline T store(const intx::uint<N>& x) noexcept
{
T r{};
store(r.bytes, x);
return r;
}

/// Stores the truncated value of an uint in a bytes array.
/// Only the least significant bytes from big-endian representation of the uint
/// are stored in the result bytes array up to array's size.
template <unsigned M, unsigned N>
inline void trunc(uint8_t (&dst)[M], const intx::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);
}

/// Stores the truncated value of an uint in the .bytes field of an object of type T.
template <typename T, unsigned N>
inline T trunc(const intx::uint<N>& x) noexcept
{
T r{};
trunc(r.bytes, x);
return r;
}

namespace unsafe
{
/// Loads an uint value from a buffer. The user must make sure
/// that the provided buffer is big enough. Therefore marked "unsafe".
template <typename IntT>
inline IntT load(const uint8_t* bytes) noexcept
{
auto x = intx::uint<N>{};
auto x = IntT{};
std::memcpy(&x, bytes, sizeof(x));
return bswap(x);
}
constexpr auto uint256 = uint<256>;
constexpr auto uint512 = uint<512>;

/// Stores an uint 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 intx::uint<N>& x) noexcept
{
auto d = bswap(x);
const auto d = bswap(x);
std::memcpy(dst, &d, sizeof(d));
}
} // namespace unsafe

} // namespace be

Expand Down
84 changes: 82 additions & 2 deletions test/unittests/test_intx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -442,14 +442,94 @@ TYPED_TEST(uint_test, endianness)
le::store(data, x);
EXPECT_EQ(data[0], 1);
EXPECT_EQ(data[s - 1], 0);
EXPECT_EQ(le::uint<s * 8>(data), x);
EXPECT_EQ(le::load<TypeParam>(data), x);

be::store(data, x);
EXPECT_EQ(data[0], 0);
EXPECT_EQ(data[s - 1], 1);
EXPECT_EQ(be::uint<s * 8>(data), x);
EXPECT_EQ(be::load<TypeParam>(data), x);

be::unsafe::store(data, x);
EXPECT_EQ(data[0], 0);
EXPECT_EQ(data[s - 1], 1);
EXPECT_EQ(be::unsafe::load<TypeParam>(data), x);
}

TYPED_TEST(uint_test, be_zext)
{
uint8_t data[] = {0x01, 0x02, 0x03};
const auto x = be::load<TypeParam>(data);
EXPECT_EQ(x, 0x010203);
}

TYPED_TEST(uint_test, be_load)
{
constexpr auto size = sizeof(TypeParam);
uint8_t data[size]{};
data[0] = 0x80;
data[size - 1] = 1;
const auto x = be::load<TypeParam>(data);
EXPECT_EQ(x, (TypeParam{1} << (TypeParam::num_bits - 1)) | 1);
}

TYPED_TEST(uint_test, be_store)
{
const auto x = TypeParam{0x0201};
uint8_t data[sizeof(x)];
be::store(data, x);
EXPECT_EQ(data[sizeof(x) - 1], 1);
EXPECT_EQ(data[sizeof(x) - 2], 2);
EXPECT_EQ(data[sizeof(x) - 3], 0);
EXPECT_EQ(data[0], 0);
}

TYPED_TEST(uint_test, be_trunc)
{
constexpr auto x = TypeParam{0xee48656c6c6f20536f6c617269732121_u128};
uint8_t out[15];
be::trunc(out, x);
const auto str = std::string{reinterpret_cast<char*>(out), sizeof(out)};
EXPECT_EQ(str, "Hello Solaris!!");
}

template <unsigned M>
struct storage
{
uint8_t bytes[M];
};

TYPED_TEST(uint_test, typed_store)
{
const auto x = TypeParam{2};
const auto s = be::store<storage<sizeof(x)>>(x);
EXPECT_EQ(s.bytes[sizeof(x) - 1], 2);
}

TYPED_TEST(uint_test, typed_trunc)
{
const auto x = TypeParam{0xaabb};
const auto s = be::trunc<storage<9>>(x);
EXPECT_EQ(s.bytes[8], 0xbb);
EXPECT_EQ(s.bytes[7], 0xaa);
EXPECT_EQ(s.bytes[6], 0);
EXPECT_EQ(s.bytes[0], 0);
}

TYPED_TEST(uint_test, typed_load_zext)
{
const auto s = storage<1>({0xed});
const auto x = be::load<TypeParam>(s);
EXPECT_EQ(x, 0xed);
}

TYPED_TEST(uint_test, typed_load)
{
const auto s = storage<sizeof(TypeParam)>({0x88});
const auto x = be::load<TypeParam>(s);
EXPECT_EQ(x, TypeParam{0x88} << (TypeParam::num_bits - 8));
}


TYPED_TEST(uint_test, convert_to_bool)
{
EXPECT_TRUE((TypeParam{1, 0}));
Expand Down
5 changes: 0 additions & 5 deletions test/unittests/test_intx_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@

using namespace intx;

static_assert(&be::uint<256> == be::uint256, "wrong alias: be::uint256");
static_assert(&be::uint<512> == be::uint512, "wrong alias: be::uint512");
static_assert(&le::uint<256> == le::uint256, "wrong alias: le::uint256");
static_assert(&le::uint<512> == le::uint512, "wrong alias: le::uint512");

static_assert(uint128{2} + uint128{2} == 4, "");
static_assert(uint256{2} + uint256{2} == 4, "");
static_assert(uint512{2} + uint512{2} == 4, "");
Expand Down