From 07e95468555501837b7798c38b188fd20d57e89e Mon Sep 17 00:00:00 2001 From: Martchus Date: Sun, 5 Feb 2023 21:29:22 +0100 Subject: [PATCH] Avoid relying on compiler optimizations for binary conversions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add/update binary conversion functions to use `std::memcopy` * Only GCC could actually optimize the custom code using bit operations * Clang could only optimize the `getBytes()` functions * MSVC could not optimize any of the functions * The to…Int…() functions cannot be updated as they are `constexpr` (and thus `std::memcopy` and `reinterpret_cast` cannot be used). So they stay as-is and `toInt()` has been added instead * The `BinaryReader` class has been updated to use the new `toInt()` function --- conversion/binaryconversion.h | 1 + conversion/binaryconversionprivate.h | 121 +++++---------------------- io/binaryreader.h | 52 ++++++------ 3 files changed, 46 insertions(+), 128 deletions(-) diff --git a/conversion/binaryconversion.h b/conversion/binaryconversion.h index 66ca7b7..318eeea 100644 --- a/conversion/binaryconversion.h +++ b/conversion/binaryconversion.h @@ -5,6 +5,7 @@ #include "../misc/traits.h" #include +#include // use helpers from bits header if available instead of custom code using bit operations #if __cplusplus >= 202002L diff --git a/conversion/binaryconversionprivate.h b/conversion/binaryconversionprivate.h index f3bf118..0b92001 100644 --- a/conversion/binaryconversionprivate.h +++ b/conversion/binaryconversionprivate.h @@ -133,31 +133,21 @@ CPP_UTILITIES_EXPORT inline double toFloat64(const char *value) } /*! - * \brief Stores the specified 16-bit signed integer value at a specified position in a char array. + * \brief Returns the specified (unsigned) integer converted from the specified char array. + * \remarks + * - The \a value must point to a sequence of characters that is at least as long as the specified integer type. + * - This function is potentially faster than the width-specific toInt…() because the width-specific functions use + * custom code that may not be optimized by the compiler. (Only GCC optimized it but not Clang and MSVC.) Note + * that the width-specific functions cannot be changed as they are constexpr and thus cannot use memcpy(). */ -CPP_UTILITIES_EXPORT inline void getBytes(std::int16_t value, char *outputbuffer) +template > * = nullptr> CPP_UTILITIES_EXPORT inline T toInt(const char *value) { + auto dst = T(); + std::memcpy(&dst, value, sizeof(T)); #if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0 - outputbuffer[0] = static_cast((value >> 8) & 0xFF); - outputbuffer[1] = static_cast((value)&0xFF); -#else - outputbuffer[1] = static_cast((value >> 8) & 0xFF); - outputbuffer[0] = static_cast((value)&0xFF); -#endif -} - -/*! - * \brief Stores the specified 16-bit unsigned integer value at a specified position in a char array. - */ -CPP_UTILITIES_EXPORT inline void getBytes(std::uint16_t value, char *outputbuffer) -{ -#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0 - outputbuffer[0] = static_cast((value >> 8) & 0xFF); - outputbuffer[1] = static_cast((value)&0xFF); -#else - outputbuffer[1] = static_cast((value >> 8) & 0xFF); - outputbuffer[0] = static_cast((value)&0xFF); + dst = swapOrder(dst); #endif + return dst; } /*! @@ -178,91 +168,18 @@ CPP_UTILITIES_EXPORT inline void getBytes24(std::uint32_t value, char *outputbuf } /*! - * \brief Stores the specified 32-bit signed integer value at a specified position in a char array. + * \brief Stores the specified (unsigned) integer value in a char array. + * \remarks + * - The \a value outputbuffer must point to a sequence of characters that is at least as long as the specified integer type. + * - This function is potentially faster than the width-specific toInt…() because the width-specific functions use + * custom code that may not be optimized by the compiler. (Only GCC optimized it but not Clang and MSVC.) */ -CPP_UTILITIES_EXPORT inline void getBytes(std::int32_t value, char *outputbuffer) +template > * = nullptr> CPP_UTILITIES_EXPORT inline void getBytes(T value, char *outputbuffer) { #if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0 - outputbuffer[0] = static_cast((value >> 24) & 0xFF); - outputbuffer[1] = static_cast((value >> 16) & 0xFF); - outputbuffer[2] = static_cast((value >> 8) & 0xFF); - outputbuffer[3] = static_cast((value)&0xFF); -#else - outputbuffer[3] = static_cast((value >> 24) & 0xFF); - outputbuffer[2] = static_cast((value >> 16) & 0xFF); - outputbuffer[1] = static_cast((value >> 8) & 0xFF); - outputbuffer[0] = static_cast((value)&0xFF); -#endif -} - -/*! - * \brief Stores the specified 32-bit signed integer value at a specified position in a char array. - */ -CPP_UTILITIES_EXPORT inline void getBytes(std::uint32_t value, char *outputbuffer) -{ -#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0 - outputbuffer[0] = static_cast((value >> 24) & 0xFF); - outputbuffer[1] = static_cast((value >> 16) & 0xFF); - outputbuffer[2] = static_cast((value >> 8) & 0xFF); - outputbuffer[3] = static_cast((value)&0xFF); -#else - outputbuffer[3] = static_cast((value >> 24) & 0xFF); - outputbuffer[2] = static_cast((value >> 16) & 0xFF); - outputbuffer[1] = static_cast((value >> 8) & 0xFF); - outputbuffer[0] = static_cast((value)&0xFF); -#endif -} - -/*! - * \brief Stores the specified 64-bit signed integer value at a specified position in a char array. - */ -CPP_UTILITIES_EXPORT inline void getBytes(std::int64_t value, char *outputbuffer) -{ -#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0 - outputbuffer[0] = static_cast((value >> 56) & 0xFF); - outputbuffer[1] = static_cast((value >> 48) & 0xFF); - outputbuffer[2] = static_cast((value >> 40) & 0xFF); - outputbuffer[3] = static_cast((value >> 32) & 0xFF); - outputbuffer[4] = static_cast((value >> 24) & 0xFF); - outputbuffer[5] = static_cast((value >> 16) & 0xFF); - outputbuffer[6] = static_cast((value >> 8) & 0xFF); - outputbuffer[7] = static_cast((value)&0xFF); -#else - outputbuffer[7] = static_cast((value >> 56) & 0xFF); - outputbuffer[6] = static_cast((value >> 48) & 0xFF); - outputbuffer[5] = static_cast((value >> 40) & 0xFF); - outputbuffer[4] = static_cast((value >> 32) & 0xFF); - outputbuffer[3] = static_cast((value >> 24) & 0xFF); - outputbuffer[2] = static_cast((value >> 16) & 0xFF); - outputbuffer[1] = static_cast((value >> 8) & 0xFF); - outputbuffer[0] = static_cast((value)&0xFF); -#endif -} - -/*! - * \brief Stores the specified 64-bit unsigned integer value at a specified position in a char array. - */ -CPP_UTILITIES_EXPORT inline void getBytes(std::uint64_t value, char *outputbuffer) -{ -#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0 - outputbuffer[0] = static_cast((value >> 56) & 0xFF); - outputbuffer[1] = static_cast((value >> 48) & 0xFF); - outputbuffer[2] = static_cast((value >> 40) & 0xFF); - outputbuffer[3] = static_cast((value >> 32) & 0xFF); - outputbuffer[4] = static_cast((value >> 24) & 0xFF); - outputbuffer[5] = static_cast((value >> 16) & 0xFF); - outputbuffer[6] = static_cast((value >> 8) & 0xFF); - outputbuffer[7] = static_cast((value)&0xFF); -#else - outputbuffer[7] = static_cast((value >> 56) & 0xFF); - outputbuffer[6] = static_cast((value >> 48) & 0xFF); - outputbuffer[5] = static_cast((value >> 40) & 0xFF); - outputbuffer[4] = static_cast((value >> 32) & 0xFF); - outputbuffer[3] = static_cast((value >> 24) & 0xFF); - outputbuffer[2] = static_cast((value >> 16) & 0xFF); - outputbuffer[1] = static_cast((value >> 8) & 0xFF); - outputbuffer[0] = static_cast((value)&0xFF); + value = swapOrder(value); #endif + std::memcpy(outputbuffer, &value, sizeof(T)); } /*! diff --git a/io/binaryreader.h b/io/binaryreader.h index 253c8d7..4e52b48 100644 --- a/io/binaryreader.h +++ b/io/binaryreader.h @@ -243,7 +243,7 @@ inline void BinaryReader::read(std::vector &buffer, std::streamsize length inline std::int16_t BinaryReader::readInt16BE() { m_stream->read(m_buffer, sizeof(std::int16_t)); - return BE::toInt16(m_buffer); + return BE::toInt(m_buffer); } /*! @@ -252,7 +252,7 @@ inline std::int16_t BinaryReader::readInt16BE() inline std::uint16_t BinaryReader::readUInt16BE() { m_stream->read(m_buffer, sizeof(std::uint16_t)); - return BE::toUInt16(m_buffer); + return BE::toInt(m_buffer); } /*! @@ -262,7 +262,7 @@ inline std::int32_t BinaryReader::readInt24BE() { *m_buffer = 0; m_stream->read(m_buffer + 1, 3); - auto val = BE::toInt32(m_buffer); + auto val = BE::toInt(m_buffer); if (val >= 0x800000) { val = -(0x1000000 - val); } @@ -276,7 +276,7 @@ inline std::uint32_t BinaryReader::readUInt24BE() { *m_buffer = 0; m_stream->read(m_buffer + 1, 3); - return BE::toUInt32(m_buffer); + return BE::toInt(m_buffer); } /*! @@ -285,7 +285,7 @@ inline std::uint32_t BinaryReader::readUInt24BE() inline std::int32_t BinaryReader::readInt32BE() { m_stream->read(m_buffer, sizeof(std::int32_t)); - return BE::toInt32(m_buffer); + return BE::toInt(m_buffer); } /*! @@ -294,7 +294,7 @@ inline std::int32_t BinaryReader::readInt32BE() inline std::uint32_t BinaryReader::readUInt32BE() { m_stream->read(m_buffer, sizeof(std::uint32_t)); - return BE::toUInt32(m_buffer); + return BE::toInt(m_buffer); } /*! @@ -304,7 +304,7 @@ inline std::int64_t BinaryReader::readInt40BE() { *m_buffer = *(m_buffer + 1) = *(m_buffer + 2) = 0; m_stream->read(m_buffer + 3, 5); - auto val = BE::toInt64(m_buffer); + auto val = BE::toInt(m_buffer); if (val >= 0x8000000000) { val = -(0x10000000000 - val); } @@ -318,7 +318,7 @@ inline std::uint64_t BinaryReader::readUInt40BE() { *m_buffer = *(m_buffer + 1) = *(m_buffer + 2) = 0; m_stream->read(m_buffer + 3, 5); - return BE::toUInt64(m_buffer); + return BE::toInt(m_buffer); } /*! @@ -328,7 +328,7 @@ inline std::int64_t BinaryReader::readInt56BE() { *m_buffer = 0; m_stream->read(m_buffer + 1, 7); - auto val = BE::toInt64(m_buffer); + auto val = BE::toInt(m_buffer); if (val >= 0x80000000000000) { val = -(0x100000000000000 - val); } @@ -342,7 +342,7 @@ inline std::uint64_t BinaryReader::readUInt56BE() { *m_buffer = 0; m_stream->read(m_buffer + 1, 7); - return BE::toUInt64(m_buffer); + return BE::toInt(m_buffer); } /*! @@ -351,7 +351,7 @@ inline std::uint64_t BinaryReader::readUInt56BE() inline std::int64_t BinaryReader::readInt64BE() { m_stream->read(m_buffer, sizeof(std::int64_t)); - return BE::toInt64(m_buffer); + return BE::toInt(m_buffer); } /*! @@ -360,7 +360,7 @@ inline std::int64_t BinaryReader::readInt64BE() inline std::uint64_t BinaryReader::readUInt64BE() { m_stream->read(m_buffer, sizeof(std::uint64_t)); - return BE::toUInt64(m_buffer); + return BE::toInt(m_buffer); } /*! @@ -370,7 +370,7 @@ inline std::uint64_t BinaryReader::readUInt64BE() inline std::uint64_t BinaryReader::readVariableLengthUIntBE() { bufferVariableLengthInteger(); - return BE::toUInt64(m_buffer); + return BE::toInt(m_buffer); } /*! @@ -397,7 +397,7 @@ inline double BinaryReader::readFloat64BE() inline std::int16_t BinaryReader::readInt16LE() { m_stream->read(m_buffer, sizeof(std::int16_t)); - return LE::toInt16(m_buffer); + return LE::toInt(m_buffer); } /*! @@ -406,7 +406,7 @@ inline std::int16_t BinaryReader::readInt16LE() inline std::uint16_t BinaryReader::readUInt16LE() { m_stream->read(m_buffer, sizeof(std::uint16_t)); - return LE::toUInt16(m_buffer); + return LE::toInt(m_buffer); } /*! @@ -416,7 +416,7 @@ inline std::int32_t BinaryReader::readInt24LE() { *(m_buffer + 3) = 0; m_stream->read(m_buffer, 3); - auto val = LE::toInt32(m_buffer); + auto val = LE::toInt(m_buffer); if (val >= 0x800000) { val = -(0x1000000 - val); } @@ -430,7 +430,7 @@ inline std::uint32_t BinaryReader::readUInt24LE() { *(m_buffer + 3) = 0; m_stream->read(m_buffer, 3); - return LE::toUInt32(m_buffer); + return LE::toInt(m_buffer); } /*! @@ -439,7 +439,7 @@ inline std::uint32_t BinaryReader::readUInt24LE() inline std::int32_t BinaryReader::readInt32LE() { m_stream->read(m_buffer, sizeof(std::int32_t)); - return LE::toInt32(m_buffer); + return LE::toInt(m_buffer); } /*! @@ -448,7 +448,7 @@ inline std::int32_t BinaryReader::readInt32LE() inline std::uint32_t BinaryReader::readUInt32LE() { m_stream->read(m_buffer, sizeof(std::uint32_t)); - return LE::toUInt32(m_buffer); + return LE::toInt(m_buffer); } /*! @@ -458,7 +458,7 @@ inline std::int64_t BinaryReader::readInt40LE() { *(m_buffer + 5) = *(m_buffer + 6) = *(m_buffer + 7) = 0; m_stream->read(m_buffer, 5); - auto val = LE::toInt64(m_buffer); + auto val = LE::toInt(m_buffer); if (val >= 0x8000000000) { val = -(0x10000000000 - val); } @@ -472,7 +472,7 @@ inline std::uint64_t BinaryReader::readUInt40LE() { *(m_buffer + 5) = *(m_buffer + 6) = *(m_buffer + 7) = 0; m_stream->read(m_buffer, 5); - return LE::toUInt64(m_buffer); + return LE::toInt(m_buffer); } /*! @@ -482,7 +482,7 @@ inline std::int64_t BinaryReader::readInt56LE() { *(m_buffer + 7) = 0; m_stream->read(m_buffer, 7); - auto val = LE::toInt64(m_buffer); + auto val = LE::toInt(m_buffer); if (val >= 0x80000000000000) { val = -(0x100000000000000 - val); } @@ -496,7 +496,7 @@ inline std::uint64_t BinaryReader::readUInt56LE() { *(m_buffer + 7) = 0; m_stream->read(m_buffer, 7); - return LE::toUInt64(m_buffer); + return LE::toInt(m_buffer); } /*! @@ -505,7 +505,7 @@ inline std::uint64_t BinaryReader::readUInt56LE() inline std::int64_t BinaryReader::readInt64LE() { m_stream->read(m_buffer, sizeof(std::int64_t)); - return LE::toInt64(m_buffer); + return LE::toInt(m_buffer); } /*! @@ -514,7 +514,7 @@ inline std::int64_t BinaryReader::readInt64LE() inline std::uint64_t BinaryReader::readUInt64LE() { m_stream->read(m_buffer, sizeof(std::uint64_t)); - return LE::toUInt64(m_buffer); + return LE::toInt(m_buffer); } /*! @@ -524,7 +524,7 @@ inline std::uint64_t BinaryReader::readUInt64LE() inline std::uint64_t BinaryReader::readVariableLengthUIntLE() { bufferVariableLengthInteger(); - return LE::toUInt64(m_buffer); + return LE::toInt(m_buffer); } /*!