Avoid relying on compiler optimizations for binary conversions

* 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
This commit is contained in:
Martchus 2023-02-05 21:29:22 +01:00
parent 147d36a578
commit 07e9546855
3 changed files with 46 additions and 128 deletions

View File

@ -5,6 +5,7 @@
#include "../misc/traits.h" #include "../misc/traits.h"
#include <cstdint> #include <cstdint>
#include <cstring>
// use helpers from bits header if available instead of custom code using bit operations // use helpers from bits header if available instead of custom code using bit operations
#if __cplusplus >= 202002L #if __cplusplus >= 202002L

View File

@ -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 <class T, Traits::EnableIf<std::is_integral<T>> * = 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 #if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
outputbuffer[0] = static_cast<char>((value >> 8) & 0xFF); dst = swapOrder(dst);
outputbuffer[1] = static_cast<char>((value)&0xFF);
#else
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((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<char>((value >> 8) & 0xFF);
outputbuffer[1] = static_cast<char>((value)&0xFF);
#else
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((value)&0xFF);
#endif #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 <class T, Traits::EnableIf<std::is_integral<T>> * = nullptr> CPP_UTILITIES_EXPORT inline void getBytes(T value, char *outputbuffer)
{ {
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0 #if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
outputbuffer[0] = static_cast<char>((value >> 24) & 0xFF); value = swapOrder(value);
outputbuffer[1] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[3] = static_cast<char>((value)&0xFF);
#else
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((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<char>((value >> 24) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[3] = static_cast<char>((value)&0xFF);
#else
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((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<char>((value >> 56) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 48) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 40) & 0xFF);
outputbuffer[3] = static_cast<char>((value >> 32) & 0xFF);
outputbuffer[4] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[5] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[6] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[7] = static_cast<char>((value)&0xFF);
#else
outputbuffer[7] = static_cast<char>((value >> 56) & 0xFF);
outputbuffer[6] = static_cast<char>((value >> 48) & 0xFF);
outputbuffer[5] = static_cast<char>((value >> 40) & 0xFF);
outputbuffer[4] = static_cast<char>((value >> 32) & 0xFF);
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((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<char>((value >> 56) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 48) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 40) & 0xFF);
outputbuffer[3] = static_cast<char>((value >> 32) & 0xFF);
outputbuffer[4] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[5] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[6] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[7] = static_cast<char>((value)&0xFF);
#else
outputbuffer[7] = static_cast<char>((value >> 56) & 0xFF);
outputbuffer[6] = static_cast<char>((value >> 48) & 0xFF);
outputbuffer[5] = static_cast<char>((value >> 40) & 0xFF);
outputbuffer[4] = static_cast<char>((value >> 32) & 0xFF);
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((value)&0xFF);
#endif #endif
std::memcpy(outputbuffer, &value, sizeof(T));
} }
/*! /*!

View File

@ -243,7 +243,7 @@ inline void BinaryReader::read(std::vector<char> &buffer, std::streamsize length
inline std::int16_t BinaryReader::readInt16BE() inline std::int16_t BinaryReader::readInt16BE()
{ {
m_stream->read(m_buffer, sizeof(std::int16_t)); m_stream->read(m_buffer, sizeof(std::int16_t));
return BE::toInt16(m_buffer); return BE::toInt<std::int16_t>(m_buffer);
} }
/*! /*!
@ -252,7 +252,7 @@ inline std::int16_t BinaryReader::readInt16BE()
inline std::uint16_t BinaryReader::readUInt16BE() inline std::uint16_t BinaryReader::readUInt16BE()
{ {
m_stream->read(m_buffer, sizeof(std::uint16_t)); m_stream->read(m_buffer, sizeof(std::uint16_t));
return BE::toUInt16(m_buffer); return BE::toInt<std::uint16_t>(m_buffer);
} }
/*! /*!
@ -262,7 +262,7 @@ inline std::int32_t BinaryReader::readInt24BE()
{ {
*m_buffer = 0; *m_buffer = 0;
m_stream->read(m_buffer + 1, 3); m_stream->read(m_buffer + 1, 3);
auto val = BE::toInt32(m_buffer); auto val = BE::toInt<std::int32_t>(m_buffer);
if (val >= 0x800000) { if (val >= 0x800000) {
val = -(0x1000000 - val); val = -(0x1000000 - val);
} }
@ -276,7 +276,7 @@ inline std::uint32_t BinaryReader::readUInt24BE()
{ {
*m_buffer = 0; *m_buffer = 0;
m_stream->read(m_buffer + 1, 3); m_stream->read(m_buffer + 1, 3);
return BE::toUInt32(m_buffer); return BE::toInt<std::uint32_t>(m_buffer);
} }
/*! /*!
@ -285,7 +285,7 @@ inline std::uint32_t BinaryReader::readUInt24BE()
inline std::int32_t BinaryReader::readInt32BE() inline std::int32_t BinaryReader::readInt32BE()
{ {
m_stream->read(m_buffer, sizeof(std::int32_t)); m_stream->read(m_buffer, sizeof(std::int32_t));
return BE::toInt32(m_buffer); return BE::toInt<std::int32_t>(m_buffer);
} }
/*! /*!
@ -294,7 +294,7 @@ inline std::int32_t BinaryReader::readInt32BE()
inline std::uint32_t BinaryReader::readUInt32BE() inline std::uint32_t BinaryReader::readUInt32BE()
{ {
m_stream->read(m_buffer, sizeof(std::uint32_t)); m_stream->read(m_buffer, sizeof(std::uint32_t));
return BE::toUInt32(m_buffer); return BE::toInt<std::uint32_t>(m_buffer);
} }
/*! /*!
@ -304,7 +304,7 @@ inline std::int64_t BinaryReader::readInt40BE()
{ {
*m_buffer = *(m_buffer + 1) = *(m_buffer + 2) = 0; *m_buffer = *(m_buffer + 1) = *(m_buffer + 2) = 0;
m_stream->read(m_buffer + 3, 5); m_stream->read(m_buffer + 3, 5);
auto val = BE::toInt64(m_buffer); auto val = BE::toInt<std::int64_t>(m_buffer);
if (val >= 0x8000000000) { if (val >= 0x8000000000) {
val = -(0x10000000000 - val); val = -(0x10000000000 - val);
} }
@ -318,7 +318,7 @@ inline std::uint64_t BinaryReader::readUInt40BE()
{ {
*m_buffer = *(m_buffer + 1) = *(m_buffer + 2) = 0; *m_buffer = *(m_buffer + 1) = *(m_buffer + 2) = 0;
m_stream->read(m_buffer + 3, 5); m_stream->read(m_buffer + 3, 5);
return BE::toUInt64(m_buffer); return BE::toInt<std::uint64_t>(m_buffer);
} }
/*! /*!
@ -328,7 +328,7 @@ inline std::int64_t BinaryReader::readInt56BE()
{ {
*m_buffer = 0; *m_buffer = 0;
m_stream->read(m_buffer + 1, 7); m_stream->read(m_buffer + 1, 7);
auto val = BE::toInt64(m_buffer); auto val = BE::toInt<std::int64_t>(m_buffer);
if (val >= 0x80000000000000) { if (val >= 0x80000000000000) {
val = -(0x100000000000000 - val); val = -(0x100000000000000 - val);
} }
@ -342,7 +342,7 @@ inline std::uint64_t BinaryReader::readUInt56BE()
{ {
*m_buffer = 0; *m_buffer = 0;
m_stream->read(m_buffer + 1, 7); m_stream->read(m_buffer + 1, 7);
return BE::toUInt64(m_buffer); return BE::toInt<std::uint64_t>(m_buffer);
} }
/*! /*!
@ -351,7 +351,7 @@ inline std::uint64_t BinaryReader::readUInt56BE()
inline std::int64_t BinaryReader::readInt64BE() inline std::int64_t BinaryReader::readInt64BE()
{ {
m_stream->read(m_buffer, sizeof(std::int64_t)); m_stream->read(m_buffer, sizeof(std::int64_t));
return BE::toInt64(m_buffer); return BE::toInt<std::int64_t>(m_buffer);
} }
/*! /*!
@ -360,7 +360,7 @@ inline std::int64_t BinaryReader::readInt64BE()
inline std::uint64_t BinaryReader::readUInt64BE() inline std::uint64_t BinaryReader::readUInt64BE()
{ {
m_stream->read(m_buffer, sizeof(std::uint64_t)); m_stream->read(m_buffer, sizeof(std::uint64_t));
return BE::toUInt64(m_buffer); return BE::toInt<std::uint64_t>(m_buffer);
} }
/*! /*!
@ -370,7 +370,7 @@ inline std::uint64_t BinaryReader::readUInt64BE()
inline std::uint64_t BinaryReader::readVariableLengthUIntBE() inline std::uint64_t BinaryReader::readVariableLengthUIntBE()
{ {
bufferVariableLengthInteger(); bufferVariableLengthInteger();
return BE::toUInt64(m_buffer); return BE::toInt<std::uint64_t>(m_buffer);
} }
/*! /*!
@ -397,7 +397,7 @@ inline double BinaryReader::readFloat64BE()
inline std::int16_t BinaryReader::readInt16LE() inline std::int16_t BinaryReader::readInt16LE()
{ {
m_stream->read(m_buffer, sizeof(std::int16_t)); m_stream->read(m_buffer, sizeof(std::int16_t));
return LE::toInt16(m_buffer); return LE::toInt<std::int16_t>(m_buffer);
} }
/*! /*!
@ -406,7 +406,7 @@ inline std::int16_t BinaryReader::readInt16LE()
inline std::uint16_t BinaryReader::readUInt16LE() inline std::uint16_t BinaryReader::readUInt16LE()
{ {
m_stream->read(m_buffer, sizeof(std::uint16_t)); m_stream->read(m_buffer, sizeof(std::uint16_t));
return LE::toUInt16(m_buffer); return LE::toInt<std::uint16_t>(m_buffer);
} }
/*! /*!
@ -416,7 +416,7 @@ inline std::int32_t BinaryReader::readInt24LE()
{ {
*(m_buffer + 3) = 0; *(m_buffer + 3) = 0;
m_stream->read(m_buffer, 3); m_stream->read(m_buffer, 3);
auto val = LE::toInt32(m_buffer); auto val = LE::toInt<std::int32_t>(m_buffer);
if (val >= 0x800000) { if (val >= 0x800000) {
val = -(0x1000000 - val); val = -(0x1000000 - val);
} }
@ -430,7 +430,7 @@ inline std::uint32_t BinaryReader::readUInt24LE()
{ {
*(m_buffer + 3) = 0; *(m_buffer + 3) = 0;
m_stream->read(m_buffer, 3); m_stream->read(m_buffer, 3);
return LE::toUInt32(m_buffer); return LE::toInt<std::uint32_t>(m_buffer);
} }
/*! /*!
@ -439,7 +439,7 @@ inline std::uint32_t BinaryReader::readUInt24LE()
inline std::int32_t BinaryReader::readInt32LE() inline std::int32_t BinaryReader::readInt32LE()
{ {
m_stream->read(m_buffer, sizeof(std::int32_t)); m_stream->read(m_buffer, sizeof(std::int32_t));
return LE::toInt32(m_buffer); return LE::toInt<std::int32_t>(m_buffer);
} }
/*! /*!
@ -448,7 +448,7 @@ inline std::int32_t BinaryReader::readInt32LE()
inline std::uint32_t BinaryReader::readUInt32LE() inline std::uint32_t BinaryReader::readUInt32LE()
{ {
m_stream->read(m_buffer, sizeof(std::uint32_t)); m_stream->read(m_buffer, sizeof(std::uint32_t));
return LE::toUInt32(m_buffer); return LE::toInt<std::uint32_t>(m_buffer);
} }
/*! /*!
@ -458,7 +458,7 @@ inline std::int64_t BinaryReader::readInt40LE()
{ {
*(m_buffer + 5) = *(m_buffer + 6) = *(m_buffer + 7) = 0; *(m_buffer + 5) = *(m_buffer + 6) = *(m_buffer + 7) = 0;
m_stream->read(m_buffer, 5); m_stream->read(m_buffer, 5);
auto val = LE::toInt64(m_buffer); auto val = LE::toInt<std::int64_t>(m_buffer);
if (val >= 0x8000000000) { if (val >= 0x8000000000) {
val = -(0x10000000000 - val); val = -(0x10000000000 - val);
} }
@ -472,7 +472,7 @@ inline std::uint64_t BinaryReader::readUInt40LE()
{ {
*(m_buffer + 5) = *(m_buffer + 6) = *(m_buffer + 7) = 0; *(m_buffer + 5) = *(m_buffer + 6) = *(m_buffer + 7) = 0;
m_stream->read(m_buffer, 5); m_stream->read(m_buffer, 5);
return LE::toUInt64(m_buffer); return LE::toInt<std::uint64_t>(m_buffer);
} }
/*! /*!
@ -482,7 +482,7 @@ inline std::int64_t BinaryReader::readInt56LE()
{ {
*(m_buffer + 7) = 0; *(m_buffer + 7) = 0;
m_stream->read(m_buffer, 7); m_stream->read(m_buffer, 7);
auto val = LE::toInt64(m_buffer); auto val = LE::toInt<std::int64_t>(m_buffer);
if (val >= 0x80000000000000) { if (val >= 0x80000000000000) {
val = -(0x100000000000000 - val); val = -(0x100000000000000 - val);
} }
@ -496,7 +496,7 @@ inline std::uint64_t BinaryReader::readUInt56LE()
{ {
*(m_buffer + 7) = 0; *(m_buffer + 7) = 0;
m_stream->read(m_buffer, 7); m_stream->read(m_buffer, 7);
return LE::toUInt64(m_buffer); return LE::toInt<std::uint64_t>(m_buffer);
} }
/*! /*!
@ -505,7 +505,7 @@ inline std::uint64_t BinaryReader::readUInt56LE()
inline std::int64_t BinaryReader::readInt64LE() inline std::int64_t BinaryReader::readInt64LE()
{ {
m_stream->read(m_buffer, sizeof(std::int64_t)); m_stream->read(m_buffer, sizeof(std::int64_t));
return LE::toInt64(m_buffer); return LE::toInt<std::int64_t>(m_buffer);
} }
/*! /*!
@ -514,7 +514,7 @@ inline std::int64_t BinaryReader::readInt64LE()
inline std::uint64_t BinaryReader::readUInt64LE() inline std::uint64_t BinaryReader::readUInt64LE()
{ {
m_stream->read(m_buffer, sizeof(std::uint64_t)); m_stream->read(m_buffer, sizeof(std::uint64_t));
return LE::toUInt64(m_buffer); return LE::toInt<std::uint64_t>(m_buffer);
} }
/*! /*!
@ -524,7 +524,7 @@ inline std::uint64_t BinaryReader::readUInt64LE()
inline std::uint64_t BinaryReader::readVariableLengthUIntLE() inline std::uint64_t BinaryReader::readVariableLengthUIntLE()
{ {
bufferVariableLengthInteger(); bufferVariableLengthInteger();
return LE::toUInt64(m_buffer); return LE::toInt<std::uint64_t>(m_buffer);
} }
/*! /*!