diff --git a/Source/Core/Common/BitUtils.h b/Source/Core/Common/BitUtils.h index eacf6bf099..8d5398fb39 100644 --- a/Source/Core/Common/BitUtils.h +++ b/Source/Core/Common/BitUtils.h @@ -99,4 +99,26 @@ constexpr Result ExtractBits(const T src) noexcept return ExtractBits(src, begin, end); } + +/// +/// Verifies whether the supplied value is a valid bit mask of the form 0b00...0011...11. +/// Both edge cases of all zeros and all ones are considered valid masks, too. +/// +/// @param mask The mask value to test for validity. +/// +/// @tparam T The type of the value. +/// +/// @return A bool indicating whether the mask is valid. +/// +template +constexpr bool IsValidLowMask(const T mask) noexcept +{ + static_assert(std::is_integral::value, "Mask must be an integral type."); + static_assert(std::is_unsigned::value, "Signed masks can introduce hard to find bugs."); + + // Can be efficiently determined without looping or bit counting. It's the counterpart + // to https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 + // and doesn't require special casing either edge case. + return (mask & (mask + 1)) == 0; +} } // namespace Common diff --git a/Source/UnitTests/Common/BitUtilsTest.cpp b/Source/UnitTests/Common/BitUtilsTest.cpp index 987868e6e4..75f7adf50e 100644 --- a/Source/UnitTests/Common/BitUtilsTest.cpp +++ b/Source/UnitTests/Common/BitUtilsTest.cpp @@ -57,3 +57,35 @@ TEST(BitUtils, ExtractBits) // Ensure bit extraction with type overriding works as expected EXPECT_EQ((Common::ExtractBits<0, 31, s32, s32>(negative_one)), -1); } + +TEST(BitUtils, IsValidLowMask) +{ + EXPECT_TRUE(Common::IsValidLowMask(0b0u)); + EXPECT_TRUE(Common::IsValidLowMask(0b1u)); + EXPECT_FALSE(Common::IsValidLowMask(0b10u)); + EXPECT_TRUE(Common::IsValidLowMask(0b11u)); + EXPECT_FALSE(Common::IsValidLowMask(0b1110u)); + EXPECT_TRUE(Common::IsValidLowMask(0b1111u)); + EXPECT_FALSE(Common::IsValidLowMask(0b10000u)); + EXPECT_FALSE(Common::IsValidLowMask(0b101111u)); + + EXPECT_TRUE(Common::IsValidLowMask((u8)~0b0)); + EXPECT_FALSE(Common::IsValidLowMask((u8)(~0b0 - 1))); + EXPECT_FALSE(Common::IsValidLowMask((u8)~(0b10000))); + EXPECT_FALSE(Common::IsValidLowMask((u8)(~((u8)(~0b0) >> 1) | 0b1111))); + + EXPECT_TRUE(Common::IsValidLowMask((u16)~0b0)); + EXPECT_FALSE(Common::IsValidLowMask((u16)(~0b0 - 1))); + EXPECT_FALSE(Common::IsValidLowMask((u16)~(0b10000))); + EXPECT_FALSE(Common::IsValidLowMask((u16)(~((u16)(~0b0) >> 1) | 0b1111))); + + EXPECT_TRUE(Common::IsValidLowMask((u32)~0b0)); + EXPECT_FALSE(Common::IsValidLowMask((u32)(~0b0 - 1))); + EXPECT_FALSE(Common::IsValidLowMask((u32)~(0b10000))); + EXPECT_FALSE(Common::IsValidLowMask((u32)(~((u32)(~0b0) >> 1) | 0b1111))); + + EXPECT_TRUE(Common::IsValidLowMask((u64)~0b0)); + EXPECT_FALSE(Common::IsValidLowMask((u64)(~0b0 - 1))); + EXPECT_FALSE(Common::IsValidLowMask((u64)~(0b10000))); + EXPECT_FALSE(Common::IsValidLowMask((u64)(~((u64)(~0b0) >> 1) | 0b1111))); +}