// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once #include #include #include #include #include #include "common/bit_cast.h" // Forward declarations struct AutoUInt; struct AutoSignExtSInt; struct AutoZeroExtSInt; struct AutoFloat; template struct SignExtSInt; template struct ZeroExtSInt; namespace Common::BitField { template constexpr size_t BitSize() { return sizeof(T) * 8; } template concept IsPOD = std::is_trivial_v && std::is_standard_layout_v; template ::type> struct ToUnderlyingType { using type = T; }; template struct ToUnderlyingType { using type = std::underlying_type_t; }; // clang-format off // Automatically deduces the smallest unsigned integer type that contains at least NumBits bits. template using SmallestUIntType = std::conditional_t(), std::uint8_t, std::conditional_t(), std::uint16_t, std::conditional_t(), std::uint32_t, std::conditional_t(), std::uint64_t, std::uint64_t>>>>; // Automatically deduces the smallest signed integer type that contains at least NumBits bits. template using SmallestSIntType = std::conditional_t(), std::int8_t, std::conditional_t(), std::int16_t, std::conditional_t(), std::int32_t, std::conditional_t(), std::int64_t, std::int64_t>>>>; // Automatically deduces the smallest floating point type that contains at exactly NumBits bits. template using SmallestFloatType = std::conditional_t(), float, std::conditional_t(), double, double>>; // clang-format on struct TagType {}; struct AutoType : public TagType {}; struct SIntType : public TagType {}; template ::type> struct DeduceAutoType { using type = void; }; template struct DeduceAutoType { // clang-format off using type = std::conditional_t, Common::BitField::SmallestUIntType, std::conditional_t, Common::BitField::SmallestSIntType, std::conditional_t, Common::BitField::SmallestSIntType, std::conditional_t, Common::BitField::SmallestFloatType, void>>>>; // clang-format on }; template ::type> struct DeduceSIntType { using type = void; }; template struct DeduceSIntType { using type = typename T::OutputType; }; template ::type> struct DeduceSignExtendValue { static constexpr bool value = false; }; template struct DeduceSignExtendValue { static constexpr bool value = T::SignExtend; }; template struct TypeTraits { static_assert(NumBits != 0, "NumBits must not be 0."); static_assert(!std::is_same_v || NumBits == 1, "For bool, NumBits must be exactly 1."); static_assert(!std::signed_integral::type>, "For signed integers, use SignExtSInt or ZeroExtSInt instead."); using AutoType = typename DeduceAutoType::type; using SIntType = typename DeduceSIntType::type; // clang-format off using OutputType = std::conditional_t, AutoType, std::conditional_t, SIntType, T>>; // clang-format on static_assert(IsPOD, "Type must be a POD type."); using UnderlyingType = typename ToUnderlyingType::type; // For integral types, assert that OutputType contains at least NumBits bits. static_assert(!std::is_integral_v || BitSize() >= NumBits, "Type must contain at least NumBits bits."); // For all other types, assert that OutputType contains exactly NumBits bits. static_assert(std::is_integral_v || BitSize() == NumBits, "Type must contain exactly NumBits bits."); static constexpr bool SignExtend = DeduceSignExtendValue::value; }; template constexpr To AutoBitCast(const From& from) { if constexpr (sizeof(To) != sizeof(From)) { using FromUnsignedType = SmallestUIntType()>; using ToUnsignedType = SmallestUIntType()>; return BitCast(static_cast(BitCast(from))); } else { return BitCast(from); } } template constexpr UnsignedRawType GenerateBitMask() { static_assert(std::unsigned_integral); if constexpr (BitSize() == NumBits) { return ~UnsignedRawType{0}; } else { return ((UnsignedRawType{1} << NumBits) - 1) << Position; } } template constexpr void InsertBits(RawType& raw, OutputType value) { using UnsignedType = SmallestUIntType()>; static_assert(sizeof(RawType) == sizeof(UnsignedType)); constexpr auto Mask = GenerateBitMask(); raw = AutoBitCast((AutoBitCast(raw) & ~Mask) | ((AutoBitCast(value) << Position) & Mask)); } template constexpr OutputType ExtractBits(const RawType& raw) { if constexpr (SignExtend) { using SignedType = SmallestSIntType()>; static_assert(sizeof(RawType) == sizeof(SignedType)); constexpr auto RightShift = BitSize() - NumBits; constexpr auto LeftShift = RightShift - Position; return AutoBitCast((AutoBitCast(raw) << LeftShift) >> RightShift); } else { using UnsignedType = SmallestUIntType()>; static_assert(sizeof(RawType) == sizeof(UnsignedType)); constexpr auto Mask = GenerateBitMask(); return AutoBitCast((AutoBitCast(raw) & Mask) >> Position); } } template class BitFieldHelper final { public: constexpr BitFieldHelper(RawType& raw_) : raw{raw_} {} BitFieldHelper(const BitFieldHelper&) = delete; BitFieldHelper& operator=(const BitFieldHelper&) = delete; BitFieldHelper(BitFieldHelper&&) = delete; BitFieldHelper& operator=(BitFieldHelper&&) = delete; constexpr void Set(OutputType value) noexcept { return InsertBits(raw, value); } constexpr BitFieldHelper& operator=(OutputType value) noexcept { Set(value); return *this; } [[nodiscard]] constexpr OutputType Get() const noexcept { return ExtractBits(raw); } [[nodiscard]] constexpr operator OutputType() const noexcept { return Get(); } template [[nodiscard]] friend constexpr bool operator==(const BitFieldHelper& lhs, const Other& rhs) noexcept { return lhs.Get() == rhs; } template [[nodiscard]] friend constexpr bool operator==(const Other& lhs, const BitFieldHelper& rhs) noexcept { return lhs == rhs.Get(); } [[nodiscard]] constexpr bool operator==(const BitFieldHelper& other) const noexcept { return Get() == other.Get(); } private: RawType& raw; }; template inline auto format_as(BitFieldHelper bitfield) { return bitfield.Get(); } } // namespace Common::BitField /** * A type tag that automatically deduces the smallest unsigned integer type that * contains at least NumBits bits in the bitfield. * Currently supported unsigned integer types: * - u8 (8 bits) * - u16 (16 bits) * - u32 (32 bits) * - u64 (64 bits) */ struct AutoUInt : public Common::BitField::AutoType { static constexpr bool SignExtend = false; }; /** * A type tag that automatically deduces the smallest signed integer type that * contains at least NumBits bits in the bitfield. * Additionally, the value is treated as a NumBits-bit 2's complement integer * which is sign-extended to fit in the output type, preserving its sign and value. * Currently supported signed integer types: * - s8 (8 bits) * - s16 (16 bits) * - s32 (32 bits) * - s64 (64 bits) */ struct AutoSignExtSInt : public Common::BitField::AutoType { static constexpr bool SignExtend = true; }; /** * A type tag that automatically deduces the smallest signed integer type that * contains at least NumBits bits in the bitfield. * Unlike AutoSignExtInt, the value is zero-extended to fit in the output type, * effectively treating it as an unsigned integer that is bitcast to a signed integer. * Its sign and value are not preserved, unless the output type contains exactly NumBits bits. * Currently supported signed integer types: * - s8 (8 bits) * - s16 (16 bits) * - s32 (32 bits) * - s64 (64 bits) */ struct AutoZeroExtSInt : public Common::BitField::AutoType { static constexpr bool SignExtend = false; }; /** * A type tag that automatically deduces the smallest floating point type that * contains exactly NumBits bits in the bitfield. * Currently supported floating point types: * - float (32 bits) * - double (64 bits) */ struct AutoFloat : public Common::BitField::AutoType { static constexpr bool SignExtend = false; }; /** * A type tag that treats the value as a NumBits-bit 2's Complement Integer * which is sign-extended to fit in the output type, preserving its sign and value. * Currently supported signed integer types: * - s8 (8 bits) * - s16 (16 bits) * - s32 (32 bits) * - s64 (64 bits) */ template struct SignExtSInt : public Common::BitField::SIntType { static_assert(std::signed_integral::type>, "SignExtSInt: Type must be a signed integral."); using OutputType = T; static constexpr bool SignExtend = true; }; /** * A type tag that, unlike SignExtInt, zero-extends the value to fit in the output type, * effectively treating it as an unsigned integer that is bitcast to a signed integer. * Its sign and value are not preserved, unless the output type contains exactly NumBits bits. * Currently supported signed integer types: * - s8 (8 bits) * - s16 (16 bits) * - s32 (32 bits) * - s64 (64 bits) */ template struct ZeroExtSInt : public Common::BitField::SIntType { static_assert(std::signed_integral::type>, "ZeroExtSInt: Type must be a signed integral."); using OutputType = T; static constexpr bool SignExtend = false; }; template using BitFieldOutputType = typename Common::BitField::TypeTraits::OutputType; #define YUZU_RO_BITFIELD(Position, NumBits, Type, Name) \ constexpr BitFieldOutputType Name() const { \ using BitFieldTypeTraits = Common::BitField::TypeTraits; \ using OutputType = BitFieldTypeTraits::OutputType; \ constexpr bool SignExtend = BitFieldTypeTraits::SignExtend; \ using ThisType = std::remove_cvref_t; \ static_assert(!std::is_union_v, \ "An object containing BitFields cannot be a union type."); \ static_assert(Common::BitField::IsPOD, \ "An object containing BitFields must be a POD type."); \ static_assert(Common::BitField::BitSize() <= 64, \ "An object containing BitFields must be at most 64 bits in size."); \ /* A structured binding is used to decompose *this into its constituent members. */ \ /* It also allows us to guarantee that we only have one member in *this object. */ \ /* Bit manipulation is performed on this member, so it must support bit operators. */ \ const auto& [yuzu_raw_value] = *this; \ using RawType = std::remove_cvref_t; \ static_assert(Common::BitField::IsPOD, \ "An object containing BitFields must be a POD type."); \ static_assert(Common::BitField::BitSize() <= 64, \ "An object containing BitFields must be at most 64 bits in size."); \ static_assert(Position < Common::BitField::BitSize(), \ "BitField is out of range."); \ static_assert(NumBits <= Common::BitField::BitSize(), \ "BitField is out of range."); \ static_assert(Position + NumBits <= Common::BitField::BitSize(), \ "BitField is out of range."); \ return Common::BitField::ExtractBits( \ yuzu_raw_value); \ } #define YUZU_BITFIELD(Position, NumBits, Type, Name) \ YUZU_RO_BITFIELD(Position, NumBits, Type, Name); \ constexpr auto Name() { \ using BitFieldTypeTraits = Common::BitField::TypeTraits; \ using OutputType = BitFieldTypeTraits::OutputType; \ constexpr bool SignExtend = BitFieldTypeTraits::SignExtend; \ using ThisType = std::remove_cvref_t; \ static_assert(!std::is_union_v, \ "An object containing BitFields cannot be a union type."); \ static_assert(Common::BitField::IsPOD, \ "An object containing BitFields must be a POD type."); \ static_assert(Common::BitField::BitSize() <= 64, \ "An object containing BitFields must be at most 64 bits in size."); \ /* A structured binding is used to decompose *this into its constituent members. */ \ /* It also allows us to guarantee that we only have one member in *this object. */ \ /* Bit manipulation is performed on this member, so it must support bit operators. */ \ auto& [yuzu_raw_value] = *this; \ using RawType = std::remove_cvref_t; \ static_assert(Common::BitField::IsPOD, \ "An object containing BitFields must be a POD type."); \ static_assert(Common::BitField::BitSize() <= 64, \ "An object containing BitFields must be at most 64 bits in size."); \ static_assert(Position < Common::BitField::BitSize(), \ "BitField is out of range."); \ static_assert(NumBits <= Common::BitField::BitSize(), \ "BitField is out of range."); \ static_assert(Position + NumBits <= Common::BitField::BitSize(), \ "BitField is out of range."); \ return Common::BitField::BitFieldHelper{yuzu_raw_value}; \ }