diff --git a/Fulcrum.pro b/Fulcrum.pro index 67b38ae..d546f61 100644 --- a/Fulcrum.pro +++ b/Fulcrum.pro @@ -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= diff --git a/config.tests/builtin_bswap/main.cpp b/config.tests/builtin_bswap/main.cpp new file mode 100644 index 0000000..776dd81 --- /dev/null +++ b/config.tests/builtin_bswap/main.cpp @@ -0,0 +1,9 @@ +#include + +int main(int argc, char *[]) +{ + const uint16_t u16 = static_cast(argc); + const uint32_t u32 = static_cast(argc); + const uint64_t u64 = static_cast(argc); + return __builtin_bswap16(u16) + __builtin_bswap32(u32) + __builtin_bswap64(u64); +} diff --git a/config.tests/builtin_bswap/test.pro b/config.tests/builtin_bswap/test.pro new file mode 100644 index 0000000..440a4da --- /dev/null +++ b/config.tests/builtin_bswap/test.pro @@ -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 +} diff --git a/src/Util.h b/src/Util.h index 244a8a6..42bd108 100644 --- a/src/Util.h +++ b/src/Util.h @@ -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(x); // to keep everything within an unsigned int and prevent promotion to `int` return static_cast( ((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 + concept U16_or_32_or_64 = std::is_same_v || std::is_same_v || std::is_same_v; + + template [[nodiscard]] inline constexpr U beToH(U x) noexcept { + if constexpr (std::is_same_v) return be16ToH(x); + else if constexpr (std::is_same_v) return be32ToH(x); + else if constexpr (std::is_same_v) return be64ToH(x); + else throw std::domain_error("Impossible state in beToH"); + } + + template [[nodiscard]] inline constexpr U leToH(U x) noexcept { + if constexpr (std::is_same_v) return le16ToH(x); + else if constexpr (std::is_same_v) return le32ToH(x); + else if constexpr (std::is_same_v) return le64ToH(x); + else throw std::domain_error("Impossible state in leToH"); + } + + template [[nodiscard]] inline constexpr U hToBe(U x) noexcept { return beToH(x); } + template [[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(0x1))) == 0x1u); + static_assert(beToH(hToBe(static_cast(0x1))) == 0x1u); + static_assert(beToH(hToBe(static_cast(0x1))) == 0x1u); + static_assert(leToH(hToLe(static_cast(0x1))) == 0x1u); + static_assert(leToH(hToLe(static_cast(0x1))) == 0x1u); + static_assert(leToH(hToLe(static_cast(0x1))) == 0x1u); + static_assert(hToBe(static_cast(0x1)) == (isLittleEndian() ? 0x100u : 0x1u)); + static_assert(hToBe(static_cast(0x1)) == (isLittleEndian() ? 0x1000000u : 0x1u)); + static_assert(hToBe(static_cast(0x1)) == (isLittleEndian() ? 0x100000000000000ull : 0x1ull)); + static_assert(hToLe(static_cast(0x1)) == (isBigEndian() ? 0x100u : 0x1u)); + static_assert(hToLe(static_cast(0x1)) == (isBigEndian() ? 0x1000000u : 0x1u)); + static_assert(hToLe(static_cast(0x1)) == (isBigEndian() ? 0x100000000000000ull : 0x1ull)); + static_assert(beToH(static_cast(0x1)) == (isLittleEndian() ? 0x100u : 0x1u)); + static_assert(beToH(static_cast(0x1)) == (isLittleEndian() ? 0x1000000u : 0x1u)); + static_assert(beToH(static_cast(0x1)) == (isLittleEndian() ? 0x100000000000000ull : 0x1ull)); + static_assert(leToH(static_cast(0x1)) == (isBigEndian() ? 0x100u : 0x1u)); + static_assert(leToH(static_cast(0x1)) == (isBigEndian() ? 0x1000000u : 0x1u)); + static_assert(leToH(static_cast(0x1)) == (isBigEndian() ? 0x100000000000000ull : 0x1ull)); // End: ---- Endian swap ops ---- } // end namespace Util