diff --git a/Source/Core/Common/BitUtils.h b/Source/Core/Common/BitUtils.h index 2f7f0c5f9a..64c55a76db 100644 --- a/Source/Core/Common/BitUtils.h +++ b/Source/Core/Common/BitUtils.h @@ -154,14 +154,27 @@ public: return result; } + inline auto operator[](std::size_t index) const + { + using S = std::conditional_t::value, const std::byte, std::byte>; + S* const target = reinterpret_cast(m_ptr) + index * sizeof(T); + return BitCastPtrType{target}; + } + private: PtrType* m_ptr; }; // Provides an aliasing-safe alternative to reinterpret_cast'ing pointers to structs // Conversion constructor and operator= provided for a convenient syntax. -// Usage: MyStruct s = BitCastPtr(some_ptr); +// Usage: +// MyStruct s = BitCastPtr(some_ptr); // BitCastPtr(some_ptr) = s; +// +// Array example: +// BitCastPtr unaligned_array(unaligned_ptr); +// MyStruct s = unaligned_array[2]; +// unaligned_array[2] = s; template inline auto BitCastPtr(PtrType* ptr) noexcept -> BitCastPtrType { diff --git a/Source/UnitTests/Common/BitUtilsTest.cpp b/Source/UnitTests/Common/BitUtilsTest.cpp index 8244941cf8..a140d07f54 100644 --- a/Source/UnitTests/Common/BitUtilsTest.cpp +++ b/Source/UnitTests/Common/BitUtilsTest.cpp @@ -3,6 +3,9 @@ #include +#include +#include + #include "Common/BitUtils.h" #include "Common/CommonTypes.h" @@ -88,3 +91,52 @@ TEST(BitUtils, IsValidLowMask) EXPECT_FALSE(Common::IsValidLowMask((u64) ~(0b10000))); EXPECT_FALSE(Common::IsValidLowMask((u64)(~((u64)(~0b0) >> 1) | 0b1111))); } + +TEST(BitUtils, BitCastPtr) +{ + EXPECT_EQ(std::endian::native, std::endian::little); + + std::vector data{0, 1, 0, 2, 0, 3, 0, 0xFF, 0xFF}; + u8* buffer = data.data(); + + EXPECT_EQ(s16(1), Common::BitCastPtr(buffer + 1)); + Common::BitCastPtr(buffer + 1) = s16(-1); + EXPECT_EQ(u16(0xFFFF), Common::BitCastPtr(buffer + 1)); + EXPECT_EQ(data, std::vector({0, 0xFF, 0xFF, 2, 0, 3, 0, 0xFF, 0xFF})); + + EXPECT_EQ(s32(0xFFFF0003), Common::BitCastPtr(buffer + 1)[1]); + Common::BitCastPtr(buffer + 1)[1] = u32(0xFFFFFFFF); + EXPECT_EQ(s32(-1), Common::BitCastPtr(buffer + 1)[1]); + EXPECT_EQ(data, std::vector({0, 0xFF, 0xFF, 2, 0, 0xFF, 0xFF, 0xFF, 0xFF})); + +#pragma pack(push, 1) + struct MyStruct + { + u16 v16; + u8 v8; + }; +#pragma pack(pop) + + MyStruct s1 = Common::BitCastPtr(buffer + 1); + EXPECT_EQ(u16(0xFFFF), s1.v16); + EXPECT_EQ(u8(2), s1.v8); + s1.v16 = 4; + s1.v8 = 5; + Common::BitCastPtr(buffer + 1) = s1; + EXPECT_EQ(s16(4), Common::BitCastPtr(buffer + 1)); + EXPECT_EQ(s8(5), Common::BitCastPtr(buffer + 3)); + EXPECT_EQ(data, std::vector({0, 4, 0, 5, 0, 0xFF, 0xFF, 0xFF, 0xFF})); + + auto struct_array = Common::BitCastPtr(buffer + 1); + const MyStruct s1_again = struct_array[0]; + EXPECT_EQ(u16(4), s1_again.v16); + EXPECT_EQ(u8(5), s1_again.v8); + MyStruct s2 = struct_array[1]; + EXPECT_EQ(u16(0xFF00), s2.v16); + EXPECT_EQ(u8(0xFF), s2.v8); + struct_array[1] = s1_again; + s2 = struct_array[1]; + EXPECT_EQ(u16(4), s2.v16); + EXPECT_EQ(u8(5), s2.v8); + EXPECT_EQ(data, std::vector({0, 4, 0, 5, 4, 0, 5, 0xFF, 0xFF})); +}