diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 3adf13a3f4..1c92ae8a80 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -90,6 +90,7 @@ add_library(common STATIC microprofileui.h multi_level_page_table.cpp multi_level_page_table.h + new_bit_field.h nvidia_flags.cpp nvidia_flags.h overflow.h diff --git a/src/common/new_bit_field.h b/src/common/new_bit_field.h new file mode 100644 index 0000000000..30acaeb288 --- /dev/null +++ b/src/common/new_bit_field.h @@ -0,0 +1,398 @@ +// 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}; \ + } diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 1e158f3759..8485d96417 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -7,6 +7,7 @@ add_executable(tests common/container_hash.cpp common/fibers.cpp common/host_memory.cpp + common/new_bit_field.cpp common/param_package.cpp common/range_map.cpp common/ring_buffer.cpp diff --git a/src/tests/common/new_bit_field.cpp b/src/tests/common/new_bit_field.cpp new file mode 100644 index 0000000000..7f5e49d30f --- /dev/null +++ b/src/tests/common/new_bit_field.cpp @@ -0,0 +1,216 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/new_bit_field.h" + +namespace Common::BitField { + +enum class U8Enum : std::uint8_t {}; +enum class U16Enum : std::uint16_t {}; +enum class U32Enum : std::uint32_t {}; +enum class U64Enum : std::uint64_t {}; + +enum class S8Enum : std::int8_t {}; +enum class S16Enum : std::int16_t {}; +enum class S32Enum : std::int32_t {}; +enum class S64Enum : std::int64_t {}; + +template +struct NByteStruct { + std::array raw; +}; + +struct NonTrivialStruct { + NonTrivialStruct() {} +}; + +struct NonTriviallyCopyableStruct { + NonTriviallyCopyableStruct(const NonTriviallyCopyableStruct&) {} +}; + +struct NonStandardLayoutStruct { + std::uint8_t a; + +private: + std::uint8_t b; +}; + +struct NonPODStruct { + virtual void Foo() const = 0; +}; + +// clang-format off + +// Tests that must pass. + +static_assert(std::is_same_v>); + +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); + +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v, 1>>); +static_assert(std::is_same_v, 8>>); +static_assert(std::is_same_v, 1>>); +static_assert(std::is_same_v, 8>>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v, 9>>); +static_assert(std::is_same_v, 16>>); +static_assert(std::is_same_v, 9>>); +static_assert(std::is_same_v, 16>>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v, 17>>); +static_assert(std::is_same_v, 32>>); +static_assert(std::is_same_v, 17>>); +static_assert(std::is_same_v, 32>>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v, 33>>); +static_assert(std::is_same_v, 64>>); +static_assert(std::is_same_v, 33>>); +static_assert(std::is_same_v, 64>>); + +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); + +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); + +static_assert(std::is_same_v, 1>>); +static_assert(std::is_same_v, 8>>); +static_assert(std::is_same_v, 1>>); +static_assert(std::is_same_v, 8>>); +static_assert(std::is_same_v, 1>>); +static_assert(std::is_same_v, 16>>); +static_assert(std::is_same_v, 1>>); +static_assert(std::is_same_v, 16>>); +static_assert(std::is_same_v, 1>>); +static_assert(std::is_same_v, 32>>); +static_assert(std::is_same_v, 1>>); +static_assert(std::is_same_v, 32>>); +static_assert(std::is_same_v, 1>>); +static_assert(std::is_same_v, 64>>); +static_assert(std::is_same_v, 1>>); +static_assert(std::is_same_v, 64>>); + +static_assert(std::is_same_v, BitFieldOutputType, 8>>); +static_assert(std::is_same_v, BitFieldOutputType, 16>>); +static_assert(std::is_same_v, BitFieldOutputType, 24>>); +static_assert(std::is_same_v, BitFieldOutputType, 32>>); +static_assert(std::is_same_v, BitFieldOutputType, 40>>); +static_assert(std::is_same_v, BitFieldOutputType, 48>>); +static_assert(std::is_same_v, BitFieldOutputType, 56>>); +static_assert(std::is_same_v, BitFieldOutputType, 64>>); + +static_assert(TypeTraits::SignExtend == true); +static_assert(TypeTraits::SignExtend == false); +static_assert(TypeTraits, 8>::SignExtend == true); +static_assert(TypeTraits, 8>::SignExtend == true); +static_assert(TypeTraits, 8>::SignExtend == false); +static_assert(TypeTraits, 8>::SignExtend == false); + +// Tests that will fail if uncommented. + +// NumBits must not be 0. + +// static_assert(std::is_same_v>); + +// For bool, NumBits must be exactly 1. + +// static_assert(std::is_same_v>); + +// For signed integers, use SignExtSInt or ZeroExtSInt instead. + +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); + +// SignExtSInt: Type must be a signed integral. + +// static_assert(std::is_same_v, 8>>); +// static_assert(std::is_same_v, 32>>); +// static_assert(std::is_same_v, 8>>); +// static_assert(std::is_same_v, BitFieldOutputType>, 8>>); + +// ZeroExtSInt: Type must be a signed integral. + +// static_assert(std::is_same_v, 8>>); +// static_assert(std::is_same_v, 32>>); +// static_assert(std::is_same_v, 8>>); +// static_assert(std::is_same_v, BitFieldOutputType>, 8>>); + +// Type must be a POD type. + +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); + +// Type must contain at least NumBits bits. + +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); + +// Type must contain exactly NumBits bits. + +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v>); +// static_assert(std::is_same_v, BitFieldOutputType, 7>>); +// static_assert(std::is_same_v, BitFieldOutputType, 15>>); +// static_assert(std::is_same_v, BitFieldOutputType, 23>>); +// static_assert(std::is_same_v, BitFieldOutputType, 31>>); +// static_assert(std::is_same_v, BitFieldOutputType, 39>>); +// static_assert(std::is_same_v, BitFieldOutputType, 47>>); +// static_assert(std::is_same_v, BitFieldOutputType, 55>>); +// static_assert(std::is_same_v, BitFieldOutputType, 63>>); + +// clang-format on + +} // namespace Common::BitField