| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #pragma once |
| |
| #include "config.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstring> |
| #include <type_traits> |
| |
| #if defined(__cpp_lib_endian) && __cpp_lib_endian >= 201907L |
| # define IGNITE_STD_ENDIAN 1 |
| #endif |
| #if defined(__cpp_lib_byteswap) && __cpp_lib_byteswap >= 202110L |
| # define IGNITE_STD_BYTESWAP 1 |
| #endif |
| |
| #if IGNITE_STD_ENDIAN || IGNITE_STD_BYTESWAP |
| # include <bit> |
| #endif |
| |
| #if !IGNITE_STD_ENDIAN |
| # if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ |
| # define IGNITE_NATIVE_BYTE_ORDER BIG |
| # elif defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ |
| # define IGNITE_NATIVE_BYTE_ORDER LITTLE |
| # elif defined(_WIN32) |
| # define IGNITE_NATIVE_BYTE_ORDER LITTLE |
| # else |
| # error "Unknown endianess" |
| # endif |
| #endif |
| |
| #if !IGNITE_STD_BYTESWAP |
| # if defined(_MSC_VER) |
| # include <cstdlib> |
| # define IGNITE_BYTESWAP_16(x) _byteswap_ushort(x) |
| # define IGNITE_BYTESWAP_32(x) _byteswap_ulong(x) |
| # define IGNITE_BYTESWAP_64(x) _byteswap_uint64(x) |
| # define IGNITE_BYTESWAP_SPECIFIER |
| # elif defined(__GNUC__) || defined(__clang__) |
| // Some rather old gcc and clang versions do not provide the required builtin functions. But we already |
| // require C++17 support and we can safely assume that if a compiler is modern enough for this standard |
| // it is also modern enough for these builtins. |
| # define IGNITE_BYTESWAP_16(x) __builtin_bswap16(x) |
| # define IGNITE_BYTESWAP_32(x) __builtin_bswap32(x) |
| # define IGNITE_BYTESWAP_64(x) __builtin_bswap64(x) |
| # else |
| # define IGNITE_BYTESWAP_16(x) ignite::bytes::swap16(x) |
| # define IGNITE_BYTESWAP_32(x) ignite::bytes::swap32(x) |
| # define IGNITE_BYTESWAP_64(x) ignite::bytes::swap64(x) |
| # endif |
| #endif |
| |
| #ifndef IGNITE_BYTESWAP_SPECIFIER |
| # define IGNITE_BYTESWAP_SPECIFIER constexpr |
| #endif |
| |
| namespace ignite { |
| |
| /** |
| * Byte order enum. |
| */ |
| enum class endian { |
| #if IGNITE_STD_ENDIAN |
| LITTLE = std::endian::little, |
| BIG = std::endian::big, |
| NATIVE = std::endian::native, |
| #else |
| LITTLE, |
| BIG, |
| NATIVE = IGNITE_NATIVE_BYTE_ORDER, |
| #endif |
| }; |
| |
| /** |
| * @brief Returns true if the host platform has big-endian byte order. |
| * |
| * @return true If the host platform has big-endian byte order. |
| * @return false If the host platform has other byte order. |
| */ |
| constexpr bool is_big_endian_platform() noexcept { |
| return endian::NATIVE == endian::BIG; |
| } |
| |
| /** |
| * @brief Returns true if the host platform has little-endian byte order. |
| * |
| * @return true If the host platform has little-endian byte order. |
| * @return false If the host platform has other byte order. |
| */ |
| constexpr bool is_little_endian_platform() noexcept { |
| return endian::NATIVE == endian::LITTLE; |
| } |
| |
| // Namespace for byte utilities |
| namespace bytes { |
| |
| /** |
| * @brief Reverses bytes in a 2-byte unsigned integer. |
| * |
| * This is a generic function that does not use compiler intrinsics. |
| * |
| * @param value Original integer value. |
| * @return Value with reversed bytes. |
| */ |
| constexpr std::uint16_t swap16(std::uint16_t value) noexcept { |
| return (value << 8) | (value >> 8); |
| } |
| |
| /** |
| * @brief Reverses bytes in a 4-byte unsigned integer. |
| * |
| * This is a generic function that does not use compiler intrinsics. |
| * |
| * @param value Original integer value. |
| * @return Value with reversed bytes. |
| */ |
| constexpr std::uint32_t swap32(std::uint32_t value) noexcept { |
| return (value << 24) | (value >> 24) | ((value << 8) & 0x00ff0000) | ((value >> 8) & 0x0000ff00); |
| } |
| |
| /** |
| * @brief Reverses bytes in a 8-byte unsigned integer. |
| * |
| * This is a generic function that does not use compiler intrinsics. |
| * |
| * @param value Original integer value. |
| * @return Value with reversed bytes. |
| */ |
| constexpr std::uint64_t swap64(std::uint64_t value) noexcept { |
| return (value << 56) | (value >> 56) | ((value << 40) & 0x00ff000000000000) | ((value >> 40) & 0x000000000000ff00) |
| | ((value << 24) & 0x0000ff0000000000) | ((value >> 24) & 0x0000000000ff0000) |
| | ((value << 8) & 0x000000ff00000000) | ((value >> 8) & 0x00000000ff000000); |
| } |
| |
| /** |
| * @brief Reinterpret a value of one type as a value of another type avoiding aliasing problems of reinterpret_cast. |
| * |
| * This is basically a C++17-compatible version of C++20 std::bit_cast but w/o constexpr that is not that much needed |
| * here. Typically the whole operation is optimized out by the compiler and becomes a no-op. |
| * |
| * @tparam T Required type. |
| * @tparam S Original type. |
| * @param src Original value. |
| * @return Value reinterpreted as having the required type. |
| */ |
| template<typename T, typename S> |
| T cast(const S &src) noexcept { |
| static_assert(sizeof(T) == sizeof(S) && std::is_trivially_copyable_v<T> && std::is_trivially_copyable_v<S>, |
| "Unsuitable types for casting"); |
| |
| T dst; |
| std::memcpy(&dst, &src, sizeof(T)); |
| return dst; |
| } |
| |
| namespace detail { |
| |
| // A helper to implement byte swapping. |
| template<std::size_t Size> |
| struct swapper; |
| |
| // Specialization for 1-byte types. |
| template<> |
| struct swapper<1> { |
| using type = std::uint8_t; |
| |
| static IGNITE_BYTESWAP_SPECIFIER type swap(type value) noexcept { return value; } |
| }; |
| |
| // Specialization for 2-byte types. |
| template<> |
| struct swapper<2> { |
| using type = std::uint16_t; |
| |
| static IGNITE_BYTESWAP_SPECIFIER type swap(type value) noexcept { return IGNITE_BYTESWAP_16(value); } |
| }; |
| |
| // Specialization for 4-byte types. |
| template<> |
| struct swapper<4> { |
| using type = std::uint32_t; |
| |
| static IGNITE_BYTESWAP_SPECIFIER type swap(type value) noexcept { return IGNITE_BYTESWAP_32(value); } |
| }; |
| |
| // Specialization for 8-byte types. |
| template<> |
| struct swapper<8> { |
| using type = std::uint64_t; |
| |
| static IGNITE_BYTESWAP_SPECIFIER type swap(type value) noexcept { return IGNITE_BYTESWAP_64(value); } |
| }; |
| |
| // A type suitable for swapping bytes. |
| template<typename T> |
| using swap_type = typename swapper<sizeof(T)>::type; |
| |
| } // namespace detail |
| |
| /** |
| * @brief Reverses bytes in an integer value. |
| * |
| * This function uses compiler intrinsics if possible. |
| * |
| * @tparam T Integer type. |
| * @param value Original integer value. |
| * @return Value with reversed bytes. |
| */ |
| template<typename T> |
| IGNITE_BYTESWAP_SPECIFIER T reverse(T value) noexcept { |
| static_assert(std::is_integral_v<T>, "reverse() is unimplemented for this type"); |
| |
| // Although std::byteswap() can potentially support other sizes we would like to keep |
| // different implementations in sync to have identical diagnostics on all compilers. |
| static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, |
| "reverse() is unimplemented for types of this size"); |
| |
| #if IGNITE_STD_BYTESWAP |
| return std::byteswap(value); |
| #else |
| return detail::swapper<sizeof(T)>::swap(value); |
| #endif |
| } |
| |
| /** |
| * @brief Convert a value from one byte order to another. |
| * |
| * @tparam T Integer type. |
| * @tparam X Original byte-order. |
| * @tparam Y Required byte-order. |
| * @param value Original value. |
| * @return Converted value. |
| */ |
| template<typename T, endian X, endian Y> |
| static T adjust_order(T value) noexcept { |
| if constexpr (X == Y) { |
| return value; |
| } else { |
| static_assert((X == endian::LITTLE && Y == endian::BIG) || (X == endian::BIG && Y == endian::LITTLE), |
| "unimplemented byte order conversion"); |
| return reverse(value); |
| } |
| } |
| |
| /** |
| * @brief Convert a value from little-endian to big-endian byte order. |
| * |
| * @tparam T Integer type. |
| * @param value Original value. |
| * @return Converted value. |
| */ |
| template<typename T> |
| static T ltob(T value) noexcept { |
| return adjust_order<T, endian::LITTLE, endian::BIG>(value); |
| } |
| |
| /** |
| * @brief Convert a value from little-endian to native (host) byte order. |
| * |
| * @tparam T Integer type. |
| * @param value Original value. |
| * @return Converted value. |
| */ |
| template<typename T> |
| static T ltoh(T value) noexcept { |
| return adjust_order<T, endian::LITTLE, endian::NATIVE>(value); |
| } |
| |
| /** |
| * @brief Convert a value from native (host) to little-endian byte order. |
| * |
| * @tparam T Integer type. |
| * @param value Original value. |
| * @return Converted value. |
| */ |
| template<typename T> |
| static T htol(T value) noexcept { |
| return adjust_order<T, endian::NATIVE, endian::LITTLE>(value); |
| } |
| |
| /** |
| * @brief Convert a value from big-endian to little-endian byte order. |
| * |
| * @tparam T Integer type. |
| * @param value Original value. |
| * @return Converted value. |
| */ |
| template<typename T> |
| static T btol(T value) noexcept { |
| return adjust_order<T, endian::BIG, endian::LITTLE>(value); |
| } |
| |
| /** |
| * @brief Convert a value from big-endian to native (host) byte order. |
| * |
| * @tparam T Integer type. |
| * @param value Original value. |
| * @return Converted value. |
| */ |
| template<typename T> |
| static T btoh(T value) noexcept { |
| return adjust_order<T, endian::BIG, endian::NATIVE>(value); |
| } |
| |
| /** |
| * @brief Convert a value from native (host) to little-endian byte order. |
| * |
| * @tparam T Integer type. |
| * @param value Original value. |
| * @return Converted value. |
| */ |
| template<typename T> |
| static T htob(T value) noexcept { |
| return adjust_order<T, endian::NATIVE, endian::BIG>(value); |
| } |
| |
| /** |
| * @brief Load a value from a byte sequence. |
| * |
| * @tparam T Value type. |
| * @param bytes Pointer to byte storage. |
| * @return Loaded value. |
| */ |
| template<typename T> |
| T load_raw(const std::byte *bytes) noexcept { |
| static_assert(std::is_trivially_copyable_v<T>, "Unsuitable type for byte copying"); |
| |
| T value; |
| std::memcpy(&value, bytes, sizeof(T)); |
| return value; |
| } |
| |
| /** |
| * @brief Store a value as a sequence of bytes. |
| * |
| * @tparam T Value type. |
| * @param bytes Pointer to byte storage. |
| * @param value Value to store. |
| */ |
| template<typename T> |
| void store_raw(std::byte *bytes, T value) noexcept { |
| static_assert(std::is_trivially_copyable_v<T>, "Unsuitable type for byte copying"); |
| |
| std::memcpy(bytes, &value, sizeof(T)); |
| } |
| |
| /** |
| * @brief Load a value from a byte sequence with byte-order adjustment. |
| * |
| * @tparam E Byte order. |
| * @tparam T Value type. |
| * @param bytes Pointer to byte storage. |
| * @return Loaded value. |
| */ |
| template<endian E, typename T> |
| T load(const std::byte *bytes) noexcept { |
| if constexpr (E == endian::NATIVE) { |
| return load_raw<T>(bytes); |
| } else if constexpr (std::is_integral_v<T>) { |
| return adjust_order<T, E, endian::NATIVE>(load_raw<T>(bytes)); |
| } else { |
| using S = detail::swap_type<T>; |
| return cast<T>(adjust_order<S, E, endian::NATIVE>(load_raw<S>(bytes))); |
| } |
| } |
| |
| /** |
| * @brief Store a value as a sequence of bytes with byte-order adjustment. |
| * |
| * @tparam E Byte order. |
| * @tparam T Value type. |
| * @param bytes Pointer to byte storage. |
| * @param value Value to store. |
| */ |
| template<endian E, typename T> |
| void store(std::byte *bytes, T value) noexcept { |
| if constexpr (E == endian::NATIVE) { |
| store_raw(bytes, value); |
| } else if constexpr (std::is_integral_v<T>) { |
| store_raw(bytes, adjust_order<T, endian::NATIVE, E>(value)); |
| } else { |
| using U = detail::swap_type<T>; |
| store_raw(bytes, adjust_order<U, endian::NATIVE, E>(cast<U>(value))); |
| } |
| } |
| |
| } // namespace bytes |
| } // namespace ignite |