diff --git a/include/etl/bit.h b/include/etl/bit.h index 4947a17b..861c776d 100644 --- a/include/etl/bit.h +++ b/include/etl/bit.h @@ -50,15 +50,22 @@ namespace etl //*************************************************************************** template ETL_NODISCARD - typename etl::enable_if< !(etl::is_integral::value && etl::is_integral::value) && (sizeof(TDestination) == sizeof(TSource)) - && etl::is_trivially_copyable::value && etl::is_trivially_copyable::value, - TDestination>::type bit_cast(const TSource& source) ETL_NOEXCEPT +#if ETL_USING_BUILTIN_BIT_CAST + ETL_CONSTEXPR14 +#endif + typename etl::enable_if< !(etl::is_integral::value && etl::is_integral::value) && (sizeof(TDestination) == sizeof(TSource)) + && etl::is_trivially_copyable::value && etl::is_trivially_copyable::value, + TDestination>::type bit_cast(const TSource& source) ETL_NOEXCEPT { +#if ETL_USING_BUILTIN_BIT_CAST + return __builtin_bit_cast(TDestination, source); +#else TDestination destination; memcpy(&destination, &source, sizeof(TDestination)); return destination; +#endif } //*************************************************************************** diff --git a/include/etl/profiles/determine_builtin_support.h b/include/etl/profiles/determine_builtin_support.h index b73c9ecd..deadec8c 100644 --- a/include/etl/profiles/determine_builtin_support.h +++ b/include/etl/profiles/determine_builtin_support.h @@ -68,6 +68,10 @@ SOFTWARE. #if !defined(ETL_USING_BUILTIN_IS_CONSTANT_EVALUATED) #define ETL_USING_BUILTIN_IS_CONSTANT_EVALUATED 1 #endif + + #if !defined(ETL_USING_BUILTIN_BIT_CAST) + #define ETL_USING_BUILTIN_BIT_CAST 1 + #endif #endif #if defined(ETL_USE_BUILTIN_MEM_FUNCTIONS) // Set all of them to be true if not @@ -132,6 +136,10 @@ SOFTWARE. #define ETL_USING_BUILTIN_IS_CONSTANT_EVALUATED __has_builtin(__builtin_is_constant_evaluated) #endif + #if !defined(ETL_USING_BUILTIN_BIT_CAST) + #define ETL_USING_BUILTIN_BIT_CAST __has_builtin(__builtin_bit_cast) + #endif + #if !defined(ETL_USING_BUILTIN_MEMCPY) #define ETL_USING_BUILTIN_MEMCPY __has_builtin(__builtin_memcpy) #endif @@ -174,6 +182,13 @@ SOFTWARE. #if !defined(ETL_USING_BUILTIN_IS_TRIVIALLY_COPYABLE) #define ETL_USING_BUILTIN_IS_TRIVIALLY_COPYABLE 1 #endif + + // MSVC has supported __builtin_bit_cast since VS2019 16.6. __has_builtin is not + // used for Microsoft (see above) to avoid a VS2022 intellisense issue, so the + // intrinsic is enabled here directly for the no-STL configuration. + #if !defined(ETL_USING_BUILTIN_BIT_CAST) && (_MSC_VER >= 1926) + #define ETL_USING_BUILTIN_BIT_CAST 1 + #endif #endif // The default. Set to 0, if not already set. @@ -245,6 +260,17 @@ SOFTWARE. #define ETL_USING_BUILTIN_BUILTIN_IS_CPP_TRIVIALLY_RELOCATABLE 0 #endif +#if !defined(ETL_USING_BUILTIN_BIT_CAST) + #define ETL_USING_BUILTIN_BIT_CAST 0 +#endif + +// __builtin_bit_cast is only of use to the ETL from C++11 onwards. +// Disable it for older standards, so that the generated code for those users is unchanged. +#if ETL_USING_BUILTIN_BIT_CAST && !ETL_USING_CPP11 + #undef ETL_USING_BUILTIN_BIT_CAST + #define ETL_USING_BUILTIN_BIT_CAST 0 +#endif + namespace etl { namespace traits @@ -268,6 +294,7 @@ namespace etl static ETL_CONSTANT bool using_builtin_is_virtual_base_of = (ETL_USING_BUILTIN_IS_VIRTUAL_BASE_OF == 1); static ETL_CONSTANT bool using_builtin_is_trivially_relocatable = (ETL_USING_BUILTIN_IS_TRIVIALLY_RELOCATABLE == 1); static ETL_CONSTANT bool using_builtin_builtin_is_cpp_trivially_relocatable = (ETL_USING_BUILTIN_BUILTIN_IS_CPP_TRIVIALLY_RELOCATABLE == 1); + static ETL_CONSTANT bool using_builtin_bit_cast = (ETL_USING_BUILTIN_BIT_CAST == 1); } // namespace traits } // namespace etl diff --git a/include/etl/unaligned_type.h b/include/etl/unaligned_type.h index 6f4189dc..cba1874f 100644 --- a/include/etl/unaligned_type.h +++ b/include/etl/unaligned_type.h @@ -46,10 +46,7 @@ SOFTWARE. #include "iterator.h" #include "type_traits.h" -#if ETL_USING_CPP20 && ETL_USING_STL - #include -#endif - +#include #include namespace etl @@ -102,12 +99,14 @@ namespace etl //************************************************************************* /// Default constructor //************************************************************************* - unaligned_type_common() {} + ETL_CONSTEXPR14 unaligned_type_common() + { + } //************************************************************************* /// Size of the storage. //************************************************************************* - size_t size() const + ETL_CONSTEXPR14 size_t size() const { return Size_; } @@ -115,7 +114,7 @@ namespace etl //************************************************************************* /// Pointer to the beginning of the storage. //************************************************************************* - pointer data() + ETL_CONSTEXPR14 pointer data() { return get_storage(); } @@ -123,7 +122,7 @@ namespace etl //************************************************************************* /// Const pointer to the beginning of the storage. //************************************************************************* - const_pointer data() const + ETL_CONSTEXPR14 const_pointer data() const { return get_storage(); } @@ -245,7 +244,7 @@ namespace etl //************************************************************************* /// Get a pointer to the storage. //************************************************************************* - pointer get_storage() + ETL_CONSTEXPR14 pointer get_storage() { return static_cast(this)->storage; } @@ -253,7 +252,7 @@ namespace etl //************************************************************************* /// Get a const pointer to the storage. //************************************************************************* - const_pointer get_storage() const + ETL_CONSTEXPR14 const_pointer get_storage() const { return static_cast(this)->storage; } @@ -277,7 +276,7 @@ namespace etl protected: //******************************* - unaligned_type_storage() + ETL_CONSTEXPR14 unaligned_type_storage() : storage() { } @@ -326,6 +325,52 @@ namespace etl }; ETL_END_PACKED + //************************************************************************* + /// The shift, in bits, that positions byte 'index' of a value of 'Size' + /// bytes, for the target endianness. + //************************************************************************* + template + ETL_CONSTEXPR size_t byte_shift(size_t index) + { + return ((Endian == ETL_ENDIAN_LITTLE) ? index : (Size - 1U - index)) * 8U; + } + +#if ETL_USING_BUILTIN_BIT_CAST + //************************************************************************* + /// Unsigned integer type of a given byte size, used as a constexpr-capable + /// 'proxy' for extracting/inserting the bytes of a floating point value. + //************************************************************************* + template + struct uint_of_size; + + template <> + struct uint_of_size<2U> + { + typedef uint16_t type; + }; + + template <> + struct uint_of_size<4U> + { + typedef uint32_t type; + }; + + template <> + struct uint_of_size<8U> + { + typedef uint64_t type; + }; + + //************************************************************************* + /// Whether the bit_cast fast path may be used for a type of 'Size' bytes. + /// It requires a same-size unsigned integer proxy type to exist. + //************************************************************************* + template + struct use_bit_cast : etl::conditional<(Size == 2U) || (Size == 4U) || (Size == 8U), etl::true_type, etl::false_type> + { + }; +#endif + //************************************************************************* /// Unaligned copy //************************************************************************* @@ -335,6 +380,10 @@ namespace etl //************************************************************************* /// Unaligned copy /// For integrals. + /// Bytes are extracted/inserted directly in the target endianness using + /// shifts, rather than a native-endianness memcpy + conditional reverse. + /// This makes the conversion independent of the host's endianness and + /// usable in a constexpr context (C++14 and later). //************************************************************************* template ETL_PACKED_CLASS(unaligned_copy) @@ -347,44 +396,42 @@ namespace etl //******************************* template - static void copy_value_to_store(const T& value, pointer store) + static ETL_CONSTEXPR14 void copy_value_to_store(const T& value, pointer store) { - memcpy(store, &value, Size_); + // Note: 'etl::unsigned_type' is used rather than 'etl::make_unsigned', as the + // latter is not defined for 'bool'. + typedef typename etl::unsigned_type::type unsigned_t; -#if ETL_HAS_CONSTEXPR_ENDIANNESS - if ETL_IF_CONSTEXPR (Endian_ != etl::endianness::value()) -#else - if (Endian_ != etl::endianness::value()) -#endif + unsigned_t uvalue = static_cast(value); + + for (size_t i = 0UL; i < Size_; ++i) { - etl::reverse(store, store + Size_); + store[i] = static_cast(uvalue >> private_unaligned_type::byte_shift(i)); } } //******************************* template - static void copy_store_to_value(const_pointer store, T & value) + static ETL_CONSTEXPR14 void copy_store_to_value(const_pointer store, T & value) { - memcpy(&value, store, Size_); + typedef typename etl::unsigned_type::type unsigned_t; -#if ETL_HAS_CONSTEXPR_ENDIANNESS - if ETL_IF_CONSTEXPR (Endian_ != etl::endianness::value()) -#else - if (Endian_ != etl::endianness::value()) -#endif + unsigned_t uvalue = unsigned_t(0); + + for (size_t i = 0UL; i < Size_; ++i) { - value = etl::reverse_bytes(value); + uvalue = static_cast(uvalue | (static_cast(store[i]) << private_unaligned_type::byte_shift(i))); } + + value = static_cast(uvalue); } //******************************* - static void copy_store_to_store(const_pointer src, int endian_src, pointer dst) + static ETL_CONSTEXPR14 void copy_store_to_store(const_pointer src, int endian_src, pointer dst) { - memcpy(dst, src, Size_); - - if (Endian_ != endian_src) + for (size_t i = 0UL; i < Size_; ++i) { - etl::reverse(dst, dst + Size_); + dst[i] = (Endian_ == endian_src) ? src[i] : src[Size_ - 1U - i]; } } }; @@ -404,8 +451,28 @@ namespace etl typedef typename private_unaligned_type::unaligned_type_storage< Size_>::const_pointer const_pointer; //******************************* + // The bit_cast fast path is only valid when a same-size unsigned integer + // proxy type exists (currently 2, 4 or 8 bytes, e.g. a 16-bit float, float, + // or double). Other sizes (e.g. 80/96/128-bit 'long double') fall back to + // memcpy. + //******************************* +#if ETL_USING_BUILTIN_BIT_CAST template - static void copy_value_to_store(const T& value, pointer store) + static ETL_CONSTEXPR14 void do_copy_value_to_store(const T& value, pointer store, etl::true_type) + { + typedef typename private_unaligned_type::uint_of_size::type uint_t; + + uint_t uvalue = etl::bit_cast(value); + + for (size_t i = 0UL; i < Size_; ++i) + { + store[i] = static_cast(uvalue >> private_unaligned_type::byte_shift(i)); + } + } +#endif + + template + static void do_copy_value_to_store(const T& value, pointer store, etl::false_type) { memcpy(store, &value, Size_); @@ -421,7 +488,41 @@ namespace etl //******************************* template - static void copy_store_to_value(const_pointer store, T & value) + static +#if ETL_USING_BUILTIN_BIT_CAST + ETL_CONSTEXPR14 +#endif + void + copy_value_to_store(const T& value, pointer store) + { +#if ETL_USING_BUILTIN_BIT_CAST + typedef typename private_unaligned_type::use_bit_cast::type use_bit_cast_t; +#else + typedef etl::false_type use_bit_cast_t; +#endif + do_copy_value_to_store(value, store, use_bit_cast_t()); + } + + //******************************* +#if ETL_USING_BUILTIN_BIT_CAST + template + static ETL_CONSTEXPR14 void do_copy_store_to_value(const_pointer store, T & value, etl::true_type) + { + typedef typename private_unaligned_type::uint_of_size::type uint_t; + + uint_t uvalue = uint_t(0); + + for (size_t i = 0UL; i < Size_; ++i) + { + uvalue = static_cast(uvalue | (static_cast(store[i]) << private_unaligned_type::byte_shift(i))); + } + + value = etl::bit_cast(uvalue); + } +#endif + + template + static void do_copy_store_to_value(const_pointer store, T & value, etl::false_type) { memcpy(&value, store, Size_); @@ -436,13 +537,32 @@ namespace etl } //******************************* - static void copy_store_to_store(const_pointer src, int endian_src, pointer dst) + template + static +#if ETL_USING_BUILTIN_BIT_CAST + ETL_CONSTEXPR14 +#endif + void + copy_store_to_value(const_pointer store, T & value) { - memcpy(dst, src, Size_); +#if ETL_USING_BUILTIN_BIT_CAST + typedef typename private_unaligned_type::use_bit_cast::type use_bit_cast_t; +#else + typedef etl::false_type use_bit_cast_t; +#endif + do_copy_store_to_value(store, value, use_bit_cast_t()); + } - if (Endian_ != endian_src) + //******************************* + // This is pure byte manipulation (copy + optional reversal), with no + // floating point arithmetic involved, so it is always constexpr-capable + // (C++14 and later), regardless of bit_cast/builtin availability. + //******************************* + static ETL_CONSTEXPR14 void copy_store_to_store(const_pointer src, int endian_src, pointer dst) + { + for (size_t i = 0UL; i < Size_; ++i) { - etl::reverse(dst, dst + Size_); + dst[i] = (Endian_ == endian_src) ? src[i] : src[Size_ - 1U - i]; } } }; @@ -486,7 +606,7 @@ namespace etl //************************************************************************* /// Construct from a value. //************************************************************************* - unaligned_type(T value) + ETL_CONSTEXPR14 unaligned_type(T value) { unaligned_copy::copy_value_to_store(value, this->storage); } @@ -501,6 +621,10 @@ namespace etl //************************************************************************* /// Construct from an address and buffer size. + /// \note 'buffer_size' must be greater than or equal to 'sizeof(T)'. + /// This is a precondition, checked by ETL_ASSERT. If the checks are + /// disabled (e.g. ETL_NO_CHECKS) then passing a smaller buffer size + /// results in a read beyond the end of the buffer. //************************************************************************* unaligned_type(const void* address, size_t buffer_size) { @@ -509,10 +633,38 @@ namespace etl etl::copy_n(reinterpret_cast(address), sizeof(T), this->storage); } + //************************************************************************* + /// Construct from a byte buffer. + /// Unlike the 'const void*' overload above, this does not require a + /// reinterpret_cast, so it is usable in a constexpr context (C++14 and + /// later), allowing compile time decoding of a byte buffer with an + /// explicit endianness. + //************************************************************************* + ETL_CONSTEXPR14 unaligned_type(const unsigned char* address) + { + etl::copy_n(address, sizeof(T), this->storage); + } + + //************************************************************************* + /// Construct from a byte buffer and buffer size. + /// See the note on the 'const unsigned char*' overload above regarding + /// constexpr usability. + /// \note 'buffer_size' must be greater than or equal to 'sizeof(T)'. + /// This is a precondition, checked by ETL_ASSERT. If the checks are + /// disabled (e.g. ETL_NO_CHECKS) then passing a smaller buffer size + /// results in a read beyond the end of the buffer. + //************************************************************************* + ETL_CONSTEXPR14 unaligned_type(const unsigned char* address, size_t buffer_size) + { + ETL_ASSERT(sizeof(T) <= buffer_size, ETL_ERROR(etl::unaligned_type_buffer_size)); + + etl::copy_n(address, sizeof(T), this->storage); + } + //************************************************************************* /// Copy constructor //************************************************************************* - unaligned_type(const unaligned_type& other) + ETL_CONSTEXPR14 unaligned_type(const unaligned_type& other) { unaligned_copy::copy_store_to_store(other.data(), Endian, this->storage); } @@ -521,7 +673,7 @@ namespace etl /// Copy constructor //************************************************************************* template - unaligned_type(const unaligned_type& other) + ETL_CONSTEXPR14 unaligned_type(const unaligned_type& other) { unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage); } @@ -529,7 +681,7 @@ namespace etl //************************************************************************* /// Assignment operator //************************************************************************* - unaligned_type& operator=(T value) + ETL_CONSTEXPR14 unaligned_type& operator=(T value) { unaligned_copy::copy_value_to_store(value, this->storage); @@ -539,7 +691,7 @@ namespace etl //************************************************************************* /// Assignment operator. //************************************************************************* - unaligned_type& operator=(const unaligned_type& other) + ETL_CONSTEXPR14 unaligned_type& operator=(const unaligned_type& other) { unaligned_copy::copy_store_to_store(other.data(), Endian_, this->storage); @@ -550,7 +702,7 @@ namespace etl /// Assignment operator from other endianness. //************************************************************************* template - unaligned_type& operator=(const unaligned_type& other) + ETL_CONSTEXPR14 unaligned_type& operator=(const unaligned_type& other) { unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage); @@ -560,7 +712,7 @@ namespace etl //************************************************************************* /// Conversion operator //************************************************************************* - operator T() const + ETL_CONSTEXPR14 operator T() const { T value = T(); @@ -572,7 +724,7 @@ namespace etl //************************************************************************* /// Get the value. //************************************************************************* - T value() const + ETL_CONSTEXPR14 T value() const { T value = T(); diff --git a/test/test_unaligned_type.cpp b/test/test_unaligned_type.cpp index 62cf500e..ad1e50c9 100644 --- a/test/test_unaligned_type.cpp +++ b/test/test_unaligned_type.cpp @@ -34,9 +34,69 @@ SOFTWARE. #include "etl/private/diagnostic_useless_cast_push.h" #include +#include namespace { + //*************************************************************************** + /// Representation level helpers. + /// 'memcpy' is used rather than 'etl::bit_cast' so that these work in every + /// configuration, including those without a bit_cast builtin. + //*************************************************************************** + uint32_t bits_of(float value) + { + uint32_t bits = 0U; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; + } + + //*************************************************************************** + float float_from_bits(uint32_t bits) + { + float value = 0.0f; + std::memcpy(&value, &bits, sizeof(value)); + return value; + } + + //*************************************************************************** + uint64_t bits_of(double value) + { + uint64_t bits = 0U; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; + } + + //*************************************************************************** + double double_from_bits(uint64_t bits) + { + double value = 0.0; + std::memcpy(&value, &bits, sizeof(value)); + return value; + } + +#if ETL_USING_CPP14 && ETL_USING_BUILTIN_BIT_CAST + //*************************************************************************** + /// Encodes 'bits' as a floating point value in the storage of 'TUnaligned', + /// then decodes it back, returning the resulting bit pattern. + /// Used to check that the constexpr bit_cast path is representation + /// preserving. + //*************************************************************************** + template + constexpr TBits round_trip_bits(TBits bits) + { + return etl::bit_cast(TUnaligned(etl::bit_cast(bits)).value()); + } + + //*************************************************************************** + /// The storage byte at 'index' when 'bits' is encoded in 'TUnaligned'. + //*************************************************************************** + template + constexpr unsigned char storage_byte_of(TBits bits, size_t index) + { + return TUnaligned(etl::bit_cast(bits)).data()[index]; + } +#endif + SUITE(test_unaligned_type) { //************************************************************************* @@ -175,6 +235,490 @@ namespace CHECK_THROW(etl::be_uint32_t be_v3(buffer.data(), buffer.size() - 1), etl::unaligned_type_buffer_size); } + //************************************************************************* + /// The 'const unsigned char*' overloads are distinct from the + /// 'const void*' ones above, so check their buffer size contract too. + /// Note that the size check is an ETL_ASSERT, so this only holds when + /// checks are enabled. See the note on the constructor. + //************************************************************************* + TEST(test_construction_from_byte_buffer_size_boundary) + { + const std::array buffer = {0x12, 0x34, 0x56, 0x78, 0x9A}; + + // Exactly the required number of bytes. + etl::le_uint32_t le_exact(buffer.data(), sizeof(uint32_t)); + etl::be_uint32_t be_exact(buffer.data(), sizeof(uint32_t)); + + CHECK_EQUAL(uint32_t(0x78563412), le_exact.value()); + CHECK_EQUAL(uint32_t(0x12345678), be_exact.value()); + + // More bytes than required. The surplus is ignored. + etl::le_uint32_t le_larger(buffer.data(), buffer.size()); + etl::be_uint32_t be_larger(buffer.data(), buffer.size()); + + CHECK_EQUAL(uint32_t(0x78563412), le_larger.value()); + CHECK_EQUAL(uint32_t(0x12345678), be_larger.value()); + + // One byte short. + CHECK_THROW(etl::le_uint32_t le_short(buffer.data(), sizeof(uint32_t) - 1U), etl::unaligned_type_buffer_size); + CHECK_THROW(etl::be_uint32_t be_short(buffer.data(), sizeof(uint32_t) - 1U), etl::unaligned_type_buffer_size); + + // Empty buffer. + CHECK_THROW(etl::le_uint32_t le_empty(buffer.data(), 0U), etl::unaligned_type_buffer_size); + CHECK_THROW(etl::be_uint32_t be_empty(buffer.data(), 0U), etl::unaligned_type_buffer_size); + + // The widest type is checked too, as it has the largest storage. + CHECK_THROW(etl::le_uint64_t le_wide(buffer.data(), buffer.size()), etl::unaligned_type_buffer_size); + CHECK_THROW(etl::be_uint64_t be_wide(buffer.data(), buffer.size()), etl::unaligned_type_buffer_size); + + // Single byte types accept a single byte buffer. + etl::le_uint8_t le_byte(buffer.data(), 1U); + etl::be_uint8_t be_byte(buffer.data(), 1U); + + CHECK_EQUAL(uint8_t(0x12), le_byte.value()); + CHECK_EQUAL(uint8_t(0x12), be_byte.value()); + + CHECK_THROW(etl::le_uint8_t le_byte_empty(buffer.data(), 0U), etl::unaligned_type_buffer_size); + CHECK_THROW(etl::be_uint8_t be_byte_empty(buffer.data(), 0U), etl::unaligned_type_buffer_size); + } + + //************************************************************************* + // The following tests demonstrate the 'decode' direction: given a raw byte + // buffer (e.g. as received from a file, network socket or memory-mapped + // device), interpret it as an explicitly little/big endian unaligned_type + // and read back the correctly decoded native value. + //************************************************************************* + TEST(test_decode_buffer_uint16) + { + // The same two bytes, interpreted with an explicit endianness. + const std::array buffer = {0x12, 0x34}; + + etl::le_uint16_t le_v(buffer.data(), buffer.size()); + etl::be_uint16_t be_v(buffer.data(), buffer.size()); + + CHECK_EQUAL(uint16_t(0x3412), le_v.value()); + CHECK_EQUAL(uint16_t(0x1234), be_v.value()); + } + + //************************************************************************* + // 'bool' has no 'make_unsigned' equivalent, so check that it is supported. + TEST(test_bool) + { + etl::unaligned_type le_v(true); + etl::unaligned_type be_v(false); + + CHECK_EQUAL(true, le_v.value()); + CHECK_EQUAL(false, be_v.value()); + + le_v = false; + be_v = true; + + CHECK_EQUAL(false, le_v.value()); + CHECK_EQUAL(true, be_v.value()); + } + + //************************************************************************* + TEST(test_decode_buffer_int32) + { + const std::array buffer = {0x12, 0x34, 0x56, 0x78}; + + etl::le_int32_t le_v(buffer.data(), buffer.size()); + etl::be_int32_t be_v(buffer.data(), buffer.size()); + + CHECK_EQUAL(int32_t(0x78563412), le_v.value()); + CHECK_EQUAL(int32_t(0x12345678), be_v.value()); + } + + //************************************************************************* + TEST(test_decode_buffer_uint64) + { + const std::array buffer = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; + + etl::le_uint64_t le_v(buffer.data(), buffer.size()); + etl::be_uint64_t be_v(buffer.data(), buffer.size()); + + CHECK_EQUAL(uint64_t(0xEFCDAB8967452301), le_v.value()); + CHECK_EQUAL(uint64_t(0x0123456789ABCDEF), be_v.value()); + } + + //************************************************************************* + /// The decode tests above cover a subset of the widths only, and no + /// negative or extreme values. These cover every signed and unsigned + /// width at its boundary values, in both directions. + //************************************************************************* + template + void check_boundary_value(TValue value) + { + ETL_STATIC_ASSERT(sizeof(TValue) == TLittle::Size, "Mismatched size"); + ETL_STATIC_ASSERT(sizeof(TValue) == TBig::Size, "Mismatched size"); + + const TLittle le_v(value); + const TBig be_v(value); + + // The two encodings are byte reversals of each other. + for (size_t i = 0U; i < sizeof(TValue); ++i) + { + CHECK_EQUAL(int(le_v.data()[i]), int(be_v.data()[sizeof(TValue) - 1U - i])); + } + + // Decoding the encoded bytes returns the original value. + CHECK_EQUAL(value, TLittle(le_v.data(), sizeof(TValue)).value()); + CHECK_EQUAL(value, TBig(be_v.data(), sizeof(TValue)).value()); + } + + //************************************************************************* + TEST(test_integral_boundary_values) + { + check_boundary_value(0); + check_boundary_value(-1); + check_boundary_value(etl::integral_limits::min); + check_boundary_value(etl::integral_limits::max); + + check_boundary_value(0U); + check_boundary_value(etl::integral_limits::max); + + check_boundary_value(0); + check_boundary_value(-1); + check_boundary_value(etl::integral_limits::min); + check_boundary_value(etl::integral_limits::max); + + check_boundary_value(0U); + check_boundary_value(etl::integral_limits::max); + + check_boundary_value(0); + check_boundary_value(-1); + check_boundary_value(etl::integral_limits::min); + check_boundary_value(etl::integral_limits::max); + + check_boundary_value(0U); + check_boundary_value(etl::integral_limits::max); + + check_boundary_value(0); + check_boundary_value(-1); + check_boundary_value(etl::integral_limits::min); + check_boundary_value(etl::integral_limits::max); + + check_boundary_value(0U); + check_boundary_value(etl::integral_limits::max); + } + + //************************************************************************* + /// Explicit byte level checks for the widths that the decode tests above + /// do not cover, using negative values so that the sign bit is exercised. + //************************************************************************* + TEST(test_decode_buffer_negative_values) + { + // -2 == 0xFE, 0xFFFE, 0xFFFFFFFE, 0xFFFFFFFFFFFFFFFE. + const std::array buffer = {0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + CHECK_EQUAL(int8_t(-2), etl::le_int8_t(buffer.data(), buffer.size()).value()); + CHECK_EQUAL(int8_t(-2), etl::be_int8_t(buffer.data(), buffer.size()).value()); + + CHECK_EQUAL(int16_t(-2), etl::le_int16_t(buffer.data(), buffer.size()).value()); + CHECK_EQUAL(int32_t(-2), etl::le_int32_t(buffer.data(), buffer.size()).value()); + CHECK_EQUAL(int64_t(-2), etl::le_int64_t(buffer.data(), buffer.size()).value()); + + // Big endian reads the same bytes in the opposite order. + CHECK_EQUAL(int16_t(-257), etl::be_int16_t(buffer.data(), buffer.size()).value()); + CHECK_EQUAL(int32_t(-16777217), etl::be_int32_t(buffer.data(), buffer.size()).value()); + CHECK_EQUAL(int64_t(-72057594037927937LL), etl::be_int64_t(buffer.data(), buffer.size()).value()); + } + + //************************************************************************* + TEST(test_decode_buffer_float) + { + // 1.5f in IEEE-754 single precision is 0x3FC00000. + const std::array le_buffer = {0x00, 0x00, 0xC0, 0x3F}; + const std::array be_buffer = {0x3F, 0xC0, 0x00, 0x00}; + + etl::le_float_t le_v(le_buffer.data(), le_buffer.size()); + etl::be_float_t be_v(be_buffer.data(), be_buffer.size()); + + CHECK_EQUAL(1.5f, le_v.value()); + CHECK_EQUAL(1.5f, be_v.value()); + } + + //************************************************************************* + TEST(test_decode_buffer_double) + { + // 1.5 in IEEE-754 double precision is 0x3FF8000000000000. + const std::array le_buffer = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x3F}; + const std::array be_buffer = {0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + etl::le_double_t le_v(le_buffer.data(), le_buffer.size()); + etl::be_double_t be_v(be_buffer.data(), be_buffer.size()); + + CHECK_EQUAL(1.5, le_v.value()); + CHECK_EQUAL(1.5, be_v.value()); + } + + //************************************************************************* + /// Representation level checks for 'float'. + /// Ordinary values such as 1.5f have a zero mantissa tail, a clear sign + /// bit and a mid range exponent, so they would still pass under several + /// plausible masking or byte ordering faults. These patterns exercise the + /// sign of zero, the infinities, a NaN payload, the subnormal range and + /// the normal range limits. + //************************************************************************* + TEST(test_float_representations) + { + static const uint32_t patterns[] = { + 0x00000000U, // +0.0 + 0x80000000U, // -0.0 + 0x7F800000U, // +infinity + 0xFF800000U, // -infinity + 0x7FC0DEADU, // Quiet NaN with a payload. + 0xFFC0DEADU, // Negative quiet NaN with a payload. + 0x00000001U, // Smallest positive subnormal. + 0x007FFFFFU, // Largest subnormal. + 0x00800000U, // Smallest positive normal. + 0x7F7FFFFFU, // Largest normal. + 0xBFC00000U // -1.5 + }; + + for (size_t i = 0U; i < (sizeof(patterns) / sizeof(patterns[0])); ++i) + { + const uint32_t bits = patterns[i]; + + etl::le_float_t le_v(float_from_bits(bits)); + etl::be_float_t be_v(float_from_bits(bits)); + + // The storage must hold the exact IEEE-754 bytes, in the requested order. + for (size_t b = 0U; b < sizeof(float); ++b) + { + CHECK_EQUAL(int((bits >> (8U * b)) & 0xFFU), int(le_v.data()[b])); + CHECK_EQUAL(int((bits >> (8U * (sizeof(float) - 1U - b))) & 0xFFU), int(be_v.data()[b])); + } + + // The value must survive the round trip bit for bit, including the + // sign of zero and the NaN payload. + CHECK_EQUAL(bits, bits_of(le_v.value())); + CHECK_EQUAL(bits, bits_of(be_v.value())); + + // Decoding the raw bytes must give the same result as encoding them. + etl::le_float_t le_decoded(le_v.data(), le_v.size()); + etl::be_float_t be_decoded(be_v.data(), be_v.size()); + + CHECK_EQUAL(bits, bits_of(le_decoded.value())); + CHECK_EQUAL(bits, bits_of(be_decoded.value())); + } + } + + //************************************************************************* + /// Representation level checks for 'double'. See the note on the 'float' + /// test above. + //************************************************************************* + TEST(test_double_representations) + { + static const uint64_t patterns[] = { + 0x0000000000000000ULL, // +0.0 + 0x8000000000000000ULL, // -0.0 + 0x7FF0000000000000ULL, // +infinity + 0xFFF0000000000000ULL, // -infinity + 0x7FF80000DEADBEEFULL, // Quiet NaN with a payload. + 0xFFF80000DEADBEEFULL, // Negative quiet NaN with a payload. + 0x0000000000000001ULL, // Smallest positive subnormal. + 0x000FFFFFFFFFFFFFULL, // Largest subnormal. + 0x0010000000000000ULL, // Smallest positive normal. + 0x7FEFFFFFFFFFFFFFULL, // Largest normal. + 0xBFF8000000000000ULL // -1.5 + }; + + for (size_t i = 0U; i < (sizeof(patterns) / sizeof(patterns[0])); ++i) + { + const uint64_t bits = patterns[i]; + + etl::le_double_t le_v(double_from_bits(bits)); + etl::be_double_t be_v(double_from_bits(bits)); + + for (size_t b = 0U; b < sizeof(double); ++b) + { + CHECK_EQUAL(int((bits >> (8U * b)) & 0xFFU), int(le_v.data()[b])); + CHECK_EQUAL(int((bits >> (8U * (sizeof(double) - 1U - b))) & 0xFFU), int(be_v.data()[b])); + } + + CHECK_EQUAL(bits, bits_of(le_v.value())); + CHECK_EQUAL(bits, bits_of(be_v.value())); + + etl::le_double_t le_decoded(le_v.data(), le_v.size()); + etl::be_double_t be_decoded(be_v.data(), be_v.size()); + + CHECK_EQUAL(bits, bits_of(le_decoded.value())); + CHECK_EQUAL(bits, bits_of(be_decoded.value())); + } + } + + //************************************************************************* + /// 'long double' is typically 80, 96 or 128 bits wide, so it has no same + /// sized unsigned integer proxy and falls back to the non bit_cast, + /// non constexpr path. That fallback still has to work. + //************************************************************************* + TEST(test_long_double_round_trip) + { + const long double value = 1.5L; + + etl::le_long_double_t le_v(value); + etl::be_long_double_t be_v(value); + + CHECK_EQUAL(sizeof(long double), le_v.size()); + CHECK_EQUAL(sizeof(long double), be_v.size()); + + // The little and big endian storage must be byte reversals of each other. + for (size_t i = 0U; i < sizeof(long double); ++i) + { + CHECK_EQUAL(int(le_v.data()[i]), int(be_v.data()[sizeof(long double) - 1U - i])); + } + + CHECK_EQUAL(value, le_v.value()); + CHECK_EQUAL(value, be_v.value()); + + // Decoding the raw bytes must give the same result as encoding them. + etl::le_long_double_t le_decoded(le_v.data(), le_v.size()); + etl::be_long_double_t be_decoded(be_v.data(), be_v.size()); + + CHECK_EQUAL(value, le_decoded.value()); + CHECK_EQUAL(value, be_decoded.value()); + } + +#if ETL_HAS_CONSTEXPR_ENDIANNESS + //************************************************************************* + TEST(test_decode_buffer_host_order) + { + // Decoding a buffer known to be in host order must yield the same + // value regardless of what that host order actually is. + const uint32_t value = 0x12345678U; + + const etl::host_uint32_t encoded(value); + etl::host_uint32_t decoded(encoded.data(), encoded.size()); + + CHECK_EQUAL(value, decoded.value()); + } +#endif + +#if ETL_USING_CPP14 + //************************************************************************* + // Demonstrates that decoding a raw byte buffer with an explicit endianness + // is now usable at compile time (constexpr), via the 'const unsigned char*' + // constructor overloads - not just runtime CHECK_EQUAL. + //************************************************************************* + TEST(test_constexpr_decode_buffer_integral) + { + static ETL_CONSTANT unsigned char le_buffer[4] = {0x78, 0x56, 0x34, 0x12}; + static ETL_CONSTANT unsigned char be_buffer[4] = {0x12, 0x34, 0x56, 0x78}; + + constexpr etl::le_uint32_t le_v(le_buffer, sizeof(le_buffer)); + constexpr etl::be_uint32_t be_v(be_buffer, sizeof(be_buffer)); + + static_assert(le_v.value() == 0x12345678U, "le_uint32_t constexpr decode from buffer + size"); + static_assert(be_v.value() == 0x12345678U, "be_uint32_t constexpr decode from buffer + size"); + + // Buffer-only overload (no explicit size). + constexpr etl::le_uint32_t le_v2(le_buffer); + constexpr etl::be_uint32_t be_v2(be_buffer); + + static_assert(le_v2.value() == 0x12345678U, "le_uint32_t constexpr decode from buffer"); + static_assert(be_v2.value() == 0x12345678U, "be_uint32_t constexpr decode from buffer"); + + // Prove the checks above are genuine compile-time facts, not just + // syntactically valid constexpr, by also exercising them at runtime. + CHECK_EQUAL(0x12345678U, le_v.value()); + CHECK_EQUAL(0x12345678U, be_v.value()); + } + + #if ETL_USING_BUILTIN_BIT_CAST + //************************************************************************* + TEST(test_constexpr_decode_buffer_float) + { + // 1.5f in IEEE-754 single precision is 0x3FC00000. + static ETL_CONSTANT unsigned char le_buffer[4] = {0x00, 0x00, 0xC0, 0x3F}; + static ETL_CONSTANT unsigned char be_buffer[4] = {0x3F, 0xC0, 0x00, 0x00}; + + constexpr etl::le_float_t le_v(le_buffer, sizeof(le_buffer)); + constexpr etl::be_float_t be_v(be_buffer, sizeof(be_buffer)); + + // Bit patterns are compared, rather than the float values, to avoid + // '-Wfloat-equal'. + static_assert(etl::bit_cast(le_v.value()) == 0x3FC00000U, "le_float_t constexpr decode from buffer"); + static_assert(etl::bit_cast(be_v.value()) == 0x3FC00000U, "be_float_t constexpr decode from buffer"); + + CHECK_EQUAL(1.5f, le_v.value()); + CHECK_EQUAL(1.5f, be_v.value()); + } + + //************************************************************************* + /// The constexpr path bit_casts through a same sized unsigned integer. + /// Check, at compile time, that this is exact for the awkward + /// representations too, not just for ordinary values such as 1.5f. + //************************************************************************* + TEST(test_constexpr_float_representations) + { + // Encode, then decode, and require the bit pattern to be unchanged. + static_assert(round_trip_bits(0x00000000U) == 0x00000000U, "le +0.0"); + static_assert(round_trip_bits(0x80000000U) == 0x80000000U, "le -0.0"); + static_assert(round_trip_bits(0x7F800000U) == 0x7F800000U, "le +infinity"); + static_assert(round_trip_bits(0xFF800000U) == 0xFF800000U, "le -infinity"); + static_assert(round_trip_bits(0x7FC0DEADU) == 0x7FC0DEADU, "le NaN payload"); + static_assert(round_trip_bits(0x00000001U) == 0x00000001U, "le smallest subnormal"); + static_assert(round_trip_bits(0x007FFFFFU) == 0x007FFFFFU, "le largest subnormal"); + static_assert(round_trip_bits(0x00800000U) == 0x00800000U, "le smallest normal"); + static_assert(round_trip_bits(0x7F7FFFFFU) == 0x7F7FFFFFU, "le largest normal"); + + static_assert(round_trip_bits(0x80000000U) == 0x80000000U, "be -0.0"); + static_assert(round_trip_bits(0xFF800000U) == 0xFF800000U, "be -infinity"); + static_assert(round_trip_bits(0x7FC0DEADU) == 0x7FC0DEADU, "be NaN payload"); + static_assert(round_trip_bits(0x00000001U) == 0x00000001U, "be smallest subnormal"); + static_assert(round_trip_bits(0x7F7FFFFFU) == 0x7F7FFFFFU, "be largest normal"); + + // The stored bytes must be the IEEE-754 bytes, in the requested order. + static_assert(storage_byte_of(0x7FC0DEADU, 0U) == 0xAD, "le NaN byte 0"); + static_assert(storage_byte_of(0x7FC0DEADU, 1U) == 0xDE, "le NaN byte 1"); + static_assert(storage_byte_of(0x7FC0DEADU, 2U) == 0xC0, "le NaN byte 2"); + static_assert(storage_byte_of(0x7FC0DEADU, 3U) == 0x7F, "le NaN byte 3"); + + static_assert(storage_byte_of(0x7FC0DEADU, 0U) == 0x7F, "be NaN byte 0"); + static_assert(storage_byte_of(0x7FC0DEADU, 1U) == 0xC0, "be NaN byte 1"); + static_assert(storage_byte_of(0x7FC0DEADU, 2U) == 0xDE, "be NaN byte 2"); + static_assert(storage_byte_of(0x7FC0DEADU, 3U) == 0xAD, "be NaN byte 3"); + + // Prove the checks above are genuine compile time facts, not just + // syntactically valid constexpr, by also exercising them at runtime. + CHECK_EQUAL(0x80000000U, (round_trip_bits(0x80000000U))); + CHECK_EQUAL(0x7FC0DEADU, (round_trip_bits(0x7FC0DEADU))); + } + + //************************************************************************* + /// As 'test_constexpr_float_representations', for the 8 byte proxy. + //************************************************************************* + TEST(test_constexpr_double_representations) + { + static_assert(round_trip_bits(0x0000000000000000ULL) == 0x0000000000000000ULL, "le +0.0"); + static_assert(round_trip_bits(0x8000000000000000ULL) == 0x8000000000000000ULL, "le -0.0"); + static_assert(round_trip_bits(0x7FF0000000000000ULL) == 0x7FF0000000000000ULL, "le +infinity"); + static_assert(round_trip_bits(0xFFF0000000000000ULL) == 0xFFF0000000000000ULL, "le -infinity"); + static_assert(round_trip_bits(0x7FF80000DEADBEEFULL) == 0x7FF80000DEADBEEFULL, "le NaN payload"); + static_assert(round_trip_bits(0x0000000000000001ULL) == 0x0000000000000001ULL, "le smallest subnormal"); + static_assert(round_trip_bits(0x000FFFFFFFFFFFFFULL) == 0x000FFFFFFFFFFFFFULL, "le largest subnormal"); + static_assert(round_trip_bits(0x0010000000000000ULL) == 0x0010000000000000ULL, "le smallest normal"); + static_assert(round_trip_bits(0x7FEFFFFFFFFFFFFFULL) == 0x7FEFFFFFFFFFFFFFULL, "le largest normal"); + + static_assert(round_trip_bits(0x8000000000000000ULL) == 0x8000000000000000ULL, "be -0.0"); + static_assert(round_trip_bits(0xFFF0000000000000ULL) == 0xFFF0000000000000ULL, "be -infinity"); + static_assert(round_trip_bits(0x7FF80000DEADBEEFULL) == 0x7FF80000DEADBEEFULL, "be NaN payload"); + static_assert(round_trip_bits(0x0000000000000001ULL) == 0x0000000000000001ULL, "be smallest subnormal"); + static_assert(round_trip_bits(0x7FEFFFFFFFFFFFFFULL) == 0x7FEFFFFFFFFFFFFFULL, "be largest normal"); + + static_assert(storage_byte_of(0x7FF80000DEADBEEFULL, 0U) == 0xEF, "le NaN byte 0"); + static_assert(storage_byte_of(0x7FF80000DEADBEEFULL, 7U) == 0x7F, "le NaN byte 7"); + + static_assert(storage_byte_of(0x7FF80000DEADBEEFULL, 0U) == 0x7F, "be NaN byte 0"); + static_assert(storage_byte_of(0x7FF80000DEADBEEFULL, 7U) == 0xEF, "be NaN byte 7"); + + CHECK_EQUAL(0x8000000000000000ULL, (round_trip_bits(0x8000000000000000ULL))); + CHECK_EQUAL(0x7FF80000DEADBEEFULL, (round_trip_bits(0x7FF80000DEADBEEFULL))); + } + #endif +#endif + //************************************************************************* TEST(test_endianness) { @@ -1122,6 +1666,74 @@ namespace CHECK_EQUAL(value, uint16_t(be_from_host)); } #endif + +#if ETL_USING_CPP14 + //************************************************************************* + // Demonstrates that integral endian conversion is now usable at compile + // time (constexpr), via genuine static_assert checks - not just runtime + // CHECK_EQUAL. The conversion is evaluated entirely by the compiler. + //************************************************************************* + TEST(test_constexpr_integral_endian_conversion) + { + // Constructing from a value and reading it back via value()/operator T() + // is constexpr for integral types, regardless of endianness. + constexpr etl::le_uint32_t le_v(0x12345678U); + constexpr etl::be_uint32_t be_v(0x12345678U); + + static_assert(le_v.value() == 0x12345678U, "le_uint32_t constexpr value() round trip"); + static_assert(be_v.value() == 0x12345678U, "be_uint32_t constexpr value() round trip"); + + static_assert(uint32_t(le_v) == 0x12345678U, "le_uint32_t constexpr operator T() round trip"); + static_assert(uint32_t(be_v) == 0x12345678U, "be_uint32_t constexpr operator T() round trip"); + + // Verify the underlying byte order is also usable at compile time. + static_assert(le_v.data()[0] == 0x78, "le_uint32_t constexpr byte 0"); + static_assert(le_v.data()[1] == 0x56, "le_uint32_t constexpr byte 1"); + static_assert(le_v.data()[2] == 0x34, "le_uint32_t constexpr byte 2"); + static_assert(le_v.data()[3] == 0x12, "le_uint32_t constexpr byte 3"); + + static_assert(be_v.data()[0] == 0x12, "be_uint32_t constexpr byte 0"); + static_assert(be_v.data()[1] == 0x34, "be_uint32_t constexpr byte 1"); + static_assert(be_v.data()[2] == 0x56, "be_uint32_t constexpr byte 2"); + static_assert(be_v.data()[3] == 0x78, "be_uint32_t constexpr byte 3"); + + // Prove the checks above are genuine compile-time facts, not just + // syntactically valid constexpr, by also exercising them at runtime. + CHECK_EQUAL(0x12345678U, uint32_t(le_v)); + CHECK_EQUAL(0x12345678U, uint32_t(be_v)); + } +#endif + +#if ETL_USING_CPP14 && ETL_USING_BUILTIN_BIT_CAST + //************************************************************************* + // Demonstrates that floating point endian conversion is now usable at + // compile time (constexpr) too, via etl::bit_cast (no std::bit_cast or + // STL dependency required). + //************************************************************************* + TEST(test_constexpr_float_endian_conversion) + { + constexpr etl::le_float_t le_v(1.5f); + constexpr etl::be_float_t be_v(1.5f); + + // Bit patterns are compared, rather than the float values, to avoid + // '-Wfloat-equal'. + static_assert(etl::bit_cast(le_v.value()) == 0x3FC00000U, "le_float_t constexpr value() round trip"); + static_assert(etl::bit_cast(be_v.value()) == 0x3FC00000U, "be_float_t constexpr value() round trip"); + + static_assert(etl::bit_cast(float(le_v)) == 0x3FC00000U, "le_float_t constexpr operator T() round trip"); + static_assert(etl::bit_cast(float(be_v)) == 0x3FC00000U, "be_float_t constexpr operator T() round trip"); + + // LE and BE storage bytes must be the reverse of each other, checked at + // compile time. + static_assert(le_v.data()[0] == be_v.data()[3], "byte 0/3 mismatch"); + static_assert(le_v.data()[1] == be_v.data()[2], "byte 1/2 mismatch"); + static_assert(le_v.data()[2] == be_v.data()[1], "byte 2/1 mismatch"); + static_assert(le_v.data()[3] == be_v.data()[0], "byte 3/0 mismatch"); + + CHECK_EQUAL(1.5f, float(le_v)); + CHECK_EQUAL(1.5f, float(be_v)); + } +#endif } } // namespace