blob: 5db9b2d2d1d4bfbd8f0448ea29835657d7a086c6 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MINI_CHROMIUM_BASE_NUMERICS_BASIC_OPS_IMPL_H_
#define MINI_CHROMIUM_BASE_NUMERICS_BASIC_OPS_IMPL_H_
#include <bit>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <span>
#include <type_traits>
namespace base::numerics::internal {
// The correct type to perform math operations on given values of type `T`. This
// may be a larger type than `T` to avoid promotion to `int` which involves sign
// conversion!
template <class T>
requires(std::is_integral_v<T>)
using MathType = std::conditional_t<
sizeof(T) >= sizeof(int),
T,
std::conditional_t<std::is_signed_v<T>, int, unsigned int>>;
// Reverses the byte order of the integer.
template <class T>
requires(std::is_unsigned_v<T> && std::is_integral_v<T>)
inline constexpr T SwapBytes(T value) {
// MSVC intrinsics are not constexpr, so we provide our own constexpr
// implementation. We provide it unconditionally so we can test it on all
// platforms for correctness.
if (std::is_constant_evaluated()) {
if constexpr (sizeof(T) == 1u) {
return value;
} else if constexpr (sizeof(T) == 2u) {
MathType<T> a = (MathType<T>(value) >> 0) & MathType<T>{0xff};
MathType<T> b = (MathType<T>(value) >> 8) & MathType<T>{0xff};
return static_cast<T>((a << 8) | (b << 0));
} else if constexpr (sizeof(T) == 4u) {
T a = (value >> 0) & T{0xff};
T b = (value >> 8) & T{0xff};
T c = (value >> 16) & T{0xff};
T d = (value >> 24) & T{0xff};
return (a << 24) | (b << 16) | (c << 8) | (d << 0);
} else {
static_assert(sizeof(T) == 8u);
T a = (value >> 0) & T{0xff};
T b = (value >> 8) & T{0xff};
T c = (value >> 16) & T{0xff};
T d = (value >> 24) & T{0xff};
T e = (value >> 32) & T{0xff};
T f = (value >> 40) & T{0xff};
T g = (value >> 48) & T{0xff};
T h = (value >> 56) & T{0xff};
return (a << 56) | (b << 48) | (c << 40) | (d << 32) | //
(e << 24) | (f << 16) | (g << 8) | (h << 0);
}
}
#if _MSC_VER
if constexpr (sizeof(T) == 1u) {
return value;
} else if constexpr (sizeof(T) == sizeof(unsigned short)) {
using U = unsigned short;
return _byteswap_ushort(U{value});
} else if constexpr (sizeof(T) == sizeof(unsigned long)) {
using U = unsigned long;
return _byteswap_ulong(U{value});
} else {
static_assert(sizeof(T) == 8u);
return _byteswap_uint64(value);
}
#else
if constexpr (sizeof(T) == 1u) {
return value;
} else if constexpr (sizeof(T) == 2u) {
return __builtin_bswap16(uint16_t{value});
} else if constexpr (sizeof(T) == 4u) {
return __builtin_bswap32(value);
} else {
static_assert(sizeof(T) == 8u);
return __builtin_bswap64(value);
}
#endif
}
// Signed values are byte-swapped as unsigned values.
template <class T>
requires(std::is_signed_v<T> && std::is_integral_v<T>)
inline constexpr T SwapBytes(T value) {
return static_cast<T>(SwapBytes(static_cast<std::make_unsigned_t<T>>(value)));
}
// Converts from a byte array to an integer.
template <class T>
requires(std::is_unsigned_v<T> && std::is_integral_v<T>)
inline constexpr T FromLittleEndian(std::span<const uint8_t, sizeof(T)> bytes) {
T val;
if (std::is_constant_evaluated()) {
val = T{0};
for (size_t i = 0u; i < sizeof(T); i += 1u) {
// SAFETY: `i < sizeof(T)` (the number of bytes in T), so `(8 * i)` is
// less than the number of bits in T.
val |= MathType<T>(bytes[i]) << (8u * i);
}
} else {
// SAFETY: `bytes` has sizeof(T) bytes, and `val` is of type `T` so has
// sizeof(T) bytes, and the two can not alias as `val` is a stack variable.
memcpy(&val, bytes.data(), sizeof(T));
}
return val;
}
// Converts to a byte array from an integer.
template <class T>
requires(std::is_unsigned_v<T> && std::is_integral_v<T>)
inline constexpr std::array<uint8_t, sizeof(T)> ToLittleEndian(T val) {
auto bytes = std::array<uint8_t, sizeof(T)>();
if (std::is_constant_evaluated()) {
for (size_t i = 0u; i < sizeof(T); i += 1u) {
const auto last_byte = static_cast<uint8_t>(val & 0xff);
// The low bytes go to the front of the array in little endian.
bytes[i] = last_byte;
// If `val` is one byte, this shift would be UB. But it's also not needed
// since the loop will not run again.
if constexpr (sizeof(T) > 1u) {
val >>= 8u;
}
}
} else {
// SAFETY: `bytes` has sizeof(T) bytes, and `val` is of type `T` so has
// sizeof(T) bytes, and the two can not alias as `val` is a stack variable.
memcpy(bytes.data(), &val, sizeof(T));
}
return bytes;
}
} // namespace base::numerics::internal
#endif // MINI_CHROMIUM_BASE_NUMERICS_BASIC_OPS_IMPL_H_