Updated the endian stuff in Util.h

- Added a config test for the __builtin_bswap* functions. And use them if
  available.
- Added templates for: beToH, leToH, hToLe, hToBe.
- Added some static assertions to test the endian swap functions
This commit is contained in:
Calin Culianu 2025-09-04 19:39:35 -05:00
parent 310e0acc20
commit 852734e1a6
No known key found for this signature in database
GPG key ID: 21810A542031C02C
4 changed files with 72 additions and 4 deletions

View file

@ -141,9 +141,15 @@ contains(CONFIG, config_bswap_64) {
# htole32_and_friends
qtCompileTest(htole32_and_friends)
contains(CONFIG, config_htole32_and_friends) {
DEFINES += HAVE_DECL_HTOBE16 HAVE_DECL_HTOLE16 HAVE_DECL_BE16TOH HAVE_DECL_LE16TOH \
HAVE_DECL_HTOBE32 HAVE_DECL_HTOLE32 HAVE_DECL_BE32TOH HAVE_DECL_LE32TOH \
HAVE_DECL_HTOBE64 HAVE_DECL_HTOLE64 HAVE_DECL_BE64TOH HAVE_DECL_LE64TOH
DEFINES += HAVE_DECL_HTOBE16 HAVE_DECL_HTOLE16 HAVE_DECL_BE16TOH HAVE_DECL_LE16TOH \
HAVE_DECL_HTOBE32 HAVE_DECL_HTOLE32 HAVE_DECL_BE32TOH HAVE_DECL_LE32TOH \
HAVE_DECL_HTOBE64 HAVE_DECL_HTOLE64 HAVE_DECL_BE64TOH HAVE_DECL_LE64TOH
}
# __builtin_bswap{16,32,64}
qtCompileTest(builtin_bswap)
contains(CONFIG, config_builtin_bswap) {
DEFINES += HAVE_BUILTIN_BSWAP
}
# Handle or add GIT_COMMIT=

View file

@ -0,0 +1,9 @@
#include <cstdint>
int main(int argc, char *[])
{
const uint16_t u16 = static_cast<uint16_t>(argc);
const uint32_t u32 = static_cast<uint32_t>(argc);
const uint64_t u64 = static_cast<uint64_t>(argc);
return __builtin_bswap16(u16) + __builtin_bswap32(u32) + __builtin_bswap64(u64);
}

View file

@ -0,0 +1,7 @@
SOURCES = main.cpp
versionAtLeast(QT_VERSION, 6.5.0) {
CONFIG += c++20
} else {
# Old alias for C++20 was "c++2a"
CONFIG += c++2a
}

View file

@ -1033,13 +1033,18 @@ namespace Util {
/* ---- Endian swap ops ----
*
* Note we reproduce functionality from "bitcoin/crypto/endian.h" here in order to not depend on the bitcoin lib
* everywhere in this codebase.
* everywhere in this codebase. Also we add a templatized set of hToBe, beToH, etc.
*/
inline bool constexpr isBigEndian() noexcept { return std::endian::native == std::endian::big; }
inline bool constexpr isLittleEndian() noexcept { return std::endian::native == std::endian::little; }
static_assert(isBigEndian() + isLittleEndian() == 1, "Assumption: Endianness must be one of these two");
#if HAVE_BUILTIN_BSWAP
[[nodiscard]] inline constexpr uint16_t byteSwap16(uint16_t const x) noexcept { return __builtin_bswap16(x); }
[[nodiscard]] inline constexpr uint32_t byteSwap32(uint32_t const x) noexcept { return __builtin_bswap32(x); }
[[nodiscard]] inline constexpr uint64_t byteSwap64(uint64_t const x) noexcept { return __builtin_bswap64(x); }
#else
[[nodiscard]] inline constexpr uint16_t byteSwap16(uint16_t const x) noexcept {
uint32_t const x32 = static_cast<uint32_t>(x); // to keep everything within an unsigned int and prevent promotion to `int`
return static_cast<uint16_t>( ((x32 & uint32_t{0xff00u}) >> 8u)
@ -1061,6 +1066,7 @@ namespace Util {
| ((x & uint64_t{0x000000000000ff00ull}) << 40ull)
| ((x & uint64_t{0x00000000000000ffull}) << 56ull);
}
#endif // HAVE_BUILTIN_BSWAP
[[nodiscard]] inline constexpr uint16_t hToLe16(uint16_t x) noexcept { if constexpr (isBigEndian()) return byteSwap16(x); else return x; }
[[nodiscard]] inline constexpr uint16_t le16ToH(uint16_t x) noexcept { if constexpr (isBigEndian()) return byteSwap16(x); else return x; }
[[nodiscard]] inline constexpr uint32_t hToLe32(uint32_t x) noexcept { if constexpr (isBigEndian()) return byteSwap32(x); else return x; }
@ -1073,6 +1079,46 @@ namespace Util {
[[nodiscard]] inline constexpr uint32_t be32ToH(uint32_t x) noexcept { if constexpr (isLittleEndian()) return byteSwap32(x); else return x; }
[[nodiscard]] inline constexpr uint64_t hToBe64(uint64_t x) noexcept { if constexpr (isLittleEndian()) return byteSwap64(x); else return x; }
[[nodiscard]] inline constexpr uint64_t be64ToH(uint64_t x) noexcept { if constexpr (isLittleEndian()) return byteSwap64(x); else return x; }
template<typename T>
concept U16_or_32_or_64 = std::is_same_v<uint64_t, T> || std::is_same_v<uint32_t, T> || std::is_same_v<uint16_t, T>;
template <U16_or_32_or_64 U> [[nodiscard]] inline constexpr U beToH(U x) noexcept {
if constexpr (std::is_same_v<U, uint16_t>) return be16ToH(x);
else if constexpr (std::is_same_v<U, uint32_t>) return be32ToH(x);
else if constexpr (std::is_same_v<U, uint64_t>) return be64ToH(x);
else throw std::domain_error("Impossible state in beToH<U>");
}
template <U16_or_32_or_64 U> [[nodiscard]] inline constexpr U leToH(U x) noexcept {
if constexpr (std::is_same_v<U, uint16_t>) return le16ToH(x);
else if constexpr (std::is_same_v<U, uint32_t>) return le32ToH(x);
else if constexpr (std::is_same_v<U, uint64_t>) return le64ToH(x);
else throw std::domain_error("Impossible state in leToH<U>");
}
template <U16_or_32_or_64 U> [[nodiscard]] inline constexpr U hToBe(U x) noexcept { return beToH(x); }
template <U16_or_32_or_64 U> [[nodiscard]] inline constexpr U hToLe(U x) noexcept { return leToH(x); }
// Some static assertions to sanity check the above templates
static_assert(beToH(hToBe(static_cast<uint16_t>(0x1))) == 0x1u);
static_assert(beToH(hToBe(static_cast<uint32_t>(0x1))) == 0x1u);
static_assert(beToH(hToBe(static_cast<uint64_t>(0x1))) == 0x1u);
static_assert(leToH(hToLe(static_cast<uint16_t>(0x1))) == 0x1u);
static_assert(leToH(hToLe(static_cast<uint32_t>(0x1))) == 0x1u);
static_assert(leToH(hToLe(static_cast<uint64_t>(0x1))) == 0x1u);
static_assert(hToBe(static_cast<uint16_t>(0x1)) == (isLittleEndian() ? 0x100u : 0x1u));
static_assert(hToBe(static_cast<uint32_t>(0x1)) == (isLittleEndian() ? 0x1000000u : 0x1u));
static_assert(hToBe(static_cast<uint64_t>(0x1)) == (isLittleEndian() ? 0x100000000000000ull : 0x1ull));
static_assert(hToLe(static_cast<uint16_t>(0x1)) == (isBigEndian() ? 0x100u : 0x1u));
static_assert(hToLe(static_cast<uint32_t>(0x1)) == (isBigEndian() ? 0x1000000u : 0x1u));
static_assert(hToLe(static_cast<uint64_t>(0x1)) == (isBigEndian() ? 0x100000000000000ull : 0x1ull));
static_assert(beToH(static_cast<uint16_t>(0x1)) == (isLittleEndian() ? 0x100u : 0x1u));
static_assert(beToH(static_cast<uint32_t>(0x1)) == (isLittleEndian() ? 0x1000000u : 0x1u));
static_assert(beToH(static_cast<uint64_t>(0x1)) == (isLittleEndian() ? 0x100000000000000ull : 0x1ull));
static_assert(leToH(static_cast<uint16_t>(0x1)) == (isBigEndian() ? 0x100u : 0x1u));
static_assert(leToH(static_cast<uint32_t>(0x1)) == (isBigEndian() ? 0x1000000u : 0x1u));
static_assert(leToH(static_cast<uint64_t>(0x1)) == (isBigEndian() ? 0x100000000000000ull : 0x1ull));
// End: ---- Endian swap ops ----
} // end namespace Util