1 // Copyright 2024 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef BASE_NUMERICS_BASIC_OPS_IMPL_H_ 6 #define BASE_NUMERICS_BASIC_OPS_IMPL_H_ 7 8 #include <bit> 9 #include <cstdint> 10 #include <cstdlib> 11 #include <cstring> 12 #include <span> 13 #include <type_traits> 14 15 namespace base::internal { 16 17 // The correct type to perform math operations on given values of type `T`. This 18 // may be a larger type than `T` to avoid promotion to `int` which involves sign 19 // conversion! 20 template <class T> 21 requires(std::is_integral_v<T>) 22 using MathType = std::conditional_t< 23 sizeof(T) >= sizeof(int), 24 T, 25 std::conditional_t<std::is_signed_v<T>, int, unsigned int>>; 26 27 // Reverses the byte order of the integer. 28 template <class T> requires(std::is_unsigned_v<T> && std::is_integral_v<T>)29 requires(std::is_unsigned_v<T> && std::is_integral_v<T>) 30 inline constexpr T SwapBytes(T value) { 31 // MSVC intrinsics are not constexpr, so we provide our own constexpr 32 // implementation. We provide it unconditionally so we can test it on all 33 // platforms for correctness. 34 if (std::is_constant_evaluated()) { 35 if constexpr (sizeof(T) == 1u) { 36 return value; 37 } else if constexpr (sizeof(T) == 2u) { 38 MathType<T> a = (MathType<T>(value) >> 0) & MathType<T>{0xff}; 39 MathType<T> b = (MathType<T>(value) >> 8) & MathType<T>{0xff}; 40 return static_cast<T>((a << 8) | (b << 0)); 41 } else if constexpr (sizeof(T) == 4u) { 42 T a = (value >> 0) & T{0xff}; 43 T b = (value >> 8) & T{0xff}; 44 T c = (value >> 16) & T{0xff}; 45 T d = (value >> 24) & T{0xff}; 46 return (a << 24) | (b << 16) | (c << 8) | (d << 0); 47 } else { 48 static_assert(sizeof(T) == 8u); 49 T a = (value >> 0) & T{0xff}; 50 T b = (value >> 8) & T{0xff}; 51 T c = (value >> 16) & T{0xff}; 52 T d = (value >> 24) & T{0xff}; 53 T e = (value >> 32) & T{0xff}; 54 T f = (value >> 40) & T{0xff}; 55 T g = (value >> 48) & T{0xff}; 56 T h = (value >> 56) & T{0xff}; 57 return (a << 56) | (b << 48) | (c << 40) | (d << 32) | // 58 (e << 24) | (f << 16) | (g << 8) | (h << 0); 59 } 60 } 61 62 #if _MSC_VER 63 if constexpr (sizeof(T) == 1u) { 64 return value; 65 } else if constexpr (sizeof(T) == sizeof(unsigned short)) { 66 using U = unsigned short; 67 return _byteswap_ushort(U{value}); 68 } else if constexpr (sizeof(T) == sizeof(unsigned long)) { 69 using U = unsigned long; 70 return _byteswap_ulong(U{value}); 71 } else { 72 static_assert(sizeof(T) == 8u); 73 return _byteswap_uint64(value); 74 } 75 #else 76 if constexpr (sizeof(T) == 1u) { 77 return value; 78 } else if constexpr (sizeof(T) == 2u) { 79 return __builtin_bswap16(uint16_t{value}); 80 } else if constexpr (sizeof(T) == 4u) { 81 return __builtin_bswap32(value); 82 } else { 83 static_assert(sizeof(T) == 8u); 84 return __builtin_bswap64(value); 85 } 86 #endif 87 } 88 89 // Signed values are byte-swapped as unsigned values. 90 template <class T> requires(std::is_signed_v<T> && std::is_integral_v<T>)91 requires(std::is_signed_v<T> && std::is_integral_v<T>) 92 inline constexpr T SwapBytes(T value) { 93 return static_cast<T>(SwapBytes(static_cast<std::make_unsigned_t<T>>(value))); 94 } 95 96 // Converts from a byte array to an integer. 97 template <class T> requires(std::is_unsigned_v<T> && std::is_integral_v<T>)98 requires(std::is_unsigned_v<T> && std::is_integral_v<T>) 99 inline constexpr T FromLittleEndian(std::span<const uint8_t, sizeof(T)> bytes) { 100 T val; 101 if (std::is_constant_evaluated()) { 102 val = T{0}; 103 for (size_t i = 0u; i < sizeof(T); i += 1u) { 104 // SAFETY: `i < sizeof(T)` (the number of bytes in T), so `(8 * i)` is 105 // less than the number of bits in T. 106 val |= MathType<T>(bytes[i]) << (8u * i); 107 } 108 } else { 109 // SAFETY: `bytes` has sizeof(T) bytes, and `val` is of type `T` so has 110 // sizeof(T) bytes, and the two can not alias as `val` is a stack variable. 111 memcpy(&val, bytes.data(), sizeof(T)); 112 } 113 return val; 114 } 115 116 template <class T> requires(std::is_signed_v<T> && std::is_integral_v<T>)117 requires(std::is_signed_v<T> && std::is_integral_v<T>) 118 inline constexpr T FromLittleEndian(std::span<const uint8_t, sizeof(T)> bytes) { 119 return static_cast<T>(FromLittleEndian<std::make_unsigned_t<T>>(bytes)); 120 } 121 122 // Converts to a byte array from an integer. 123 template <class T> requires(std::is_unsigned_v<T> && std::is_integral_v<T>)124 requires(std::is_unsigned_v<T> && std::is_integral_v<T>) 125 inline constexpr std::array<uint8_t, sizeof(T)> ToLittleEndian(T val) { 126 auto bytes = std::array<uint8_t, sizeof(T)>(); 127 if (std::is_constant_evaluated()) { 128 for (size_t i = 0u; i < sizeof(T); i += 1u) { 129 const auto last_byte = static_cast<uint8_t>(val & 0xff); 130 // The low bytes go to the front of the array in little endian. 131 bytes[i] = last_byte; 132 // If `val` is one byte, this shift would be UB. But it's also not needed 133 // since the loop will not run again. 134 if constexpr (sizeof(T) > 1u) { 135 val >>= 8u; 136 } 137 } 138 } else { 139 // SAFETY: `bytes` has sizeof(T) bytes, and `val` is of type `T` so has 140 // sizeof(T) bytes, and the two can not alias as `val` is a stack variable. 141 memcpy(bytes.data(), &val, sizeof(T)); 142 } 143 return bytes; 144 } 145 146 template <class T> requires(std::is_signed_v<T> && std::is_integral_v<T>)147 requires(std::is_signed_v<T> && std::is_integral_v<T>) 148 inline constexpr std::array<uint8_t, sizeof(T)> ToLittleEndian(T val) { 149 return ToLittleEndian(static_cast<std::make_unsigned_t<T>>(val)); 150 } 151 } // namespace base::internal 152 153 #endif // BASE_NUMERICS_BASIC_OPS_IMPL_H_ 154