From cd1a8da1a85b75def55e847f76c4bbe6168376b6 Mon Sep 17 00:00:00 2001 From: _Redstone_c_ Date: Fri, 18 Mar 2022 20:17:28 +0800 Subject: [PATCH] feat(memory): complete low-level memory management utilities --- .../Source/Private/Memory/Memory.cpp | 85 +++++++++++ .../Source/Private/Testing/MemoryTesting.cpp | 142 ++++++++++++++++++ .../Source/Public/Memory/Alignment.h | 48 ++++++ .../Source/Public/Memory/Memory.h | 110 ++++++++++++++ .../Source/Public/Miscellaneous/Platform.h | 2 + .../Source/Public/Testing/MemoryTesting.h | 16 ++ 6 files changed, 403 insertions(+) create mode 100644 Redcraft.Utility/Source/Private/Memory/Memory.cpp create mode 100644 Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp create mode 100644 Redcraft.Utility/Source/Public/Memory/Alignment.h create mode 100644 Redcraft.Utility/Source/Public/Memory/Memory.h create mode 100644 Redcraft.Utility/Source/Public/Testing/MemoryTesting.h diff --git a/Redcraft.Utility/Source/Private/Memory/Memory.cpp b/Redcraft.Utility/Source/Private/Memory/Memory.cpp new file mode 100644 index 0000000..81e4931 --- /dev/null +++ b/Redcraft.Utility/Source/Private/Memory/Memory.cpp @@ -0,0 +1,85 @@ +#include "Memory/Memory.h" + +#include "Memory/Alignment.h" + +#if PLATFORM_WINDOWS +#include +#endif + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +NAMESPACE_BEGIN(Memory) + +void* Malloc(size_t Count, size_t Alignment) +{ + const size_t MinimumAlignment = Count >= 16 ? 16 : 8; + Alignment = MinimumAlignment > Alignment ? MinimumAlignment : Alignment; + + void* Result = nullptr; + +#if PLATFORM_WINDOWS + if (Count != 0) Result = _aligned_malloc(Count, Alignment); +#else + void* Ptr = SystemMalloc(Count + Alignment + sizeof(void*) + sizeof(size_t)); + if (Ptr) + { + Result = Align(reinterpret_cast(Ptr) + sizeof(void*) + sizeof(size_t), Alignment); + *reinterpret_cast(reinterpret_cast(Result) - sizeof(void*)) = Ptr; + *reinterpret_cast(reinterpret_cast(Result) - sizeof(void*) - sizeof(size_t)) = Count; + } +#endif + + return Result; +} + +void* Realloc(void* Ptr, size_t Count, size_t Alignment) +{ + const size_t MinimumAlignment = Count >= 16 ? 16 : 8; + Alignment = MinimumAlignment > Alignment ? MinimumAlignment : Alignment; + + if (Ptr && Count) + { +#if PLATFORM_WINDOWS + return _aligned_realloc(Ptr, Count, Alignment); +#else + void* Result = Malloc(Count, Alignment); + size_t PtrSize = *reinterpret_cast(reinterpret_cast(Ptr) - sizeof(void*) - sizeof(size_t)); + Memcpy(Result, Ptr, Count < PtrSize ? Count : PtrSize); + Free(Ptr); + return Result; +#endif + } + else if (Ptr == nullptr) + { + return Malloc(Count, Alignment); + } + else + { + Free(Ptr); + return nullptr; + } +} + +void Free(void* Ptr) +{ +#if PLATFORM_WINDOWS + _aligned_free(Ptr); +#else + SystemFree(*reinterpret_cast(reinterpret_cast(Ptr) - sizeof(void*))); +#endif +} + +size_t QuantizeSize(size_t Count, size_t Alignment) +{ + return Count; +} + +NAMESPACE_END(Memory) + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END + +REPLACEMENT_OPERATOR_NEW_AND_DELETE diff --git a/Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp b/Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp new file mode 100644 index 0000000..410f0bc --- /dev/null +++ b/Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp @@ -0,0 +1,142 @@ +#include "Testing/MemoryTesting.h" + +#include "Memory/Memory.h" +#include "Memory/Alignment.h" +#include "Miscellaneous/AssertionMacros.h" + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +void TestMemory() +{ + TestAlignment(); + TestMemoryBuffer(); + TestMemoryMalloc(); +} + +void TestAlignment() +{ + int32 Unaligned = 0xAAAA; + + int32 Aligned8 = Memory::Align(Unaligned, 8); + int32 Aligned16 = Memory::Align(Unaligned, 16); + int32 Aligned32 = Memory::Align(Unaligned, 32); + int32 Aligned64 = Memory::Align(Unaligned, 64); + + int32 AlignedDown8 = Memory::AlignDown(Unaligned, 8); + int32 AlignedDown16 = Memory::AlignDown(Unaligned, 16); + int32 AlignedDown32 = Memory::AlignDown(Unaligned, 32); + int32 AlignedDown64 = Memory::AlignDown(Unaligned, 64); + + int32 AlignedArbitrary8 = Memory::AlignArbitrary(Unaligned, 8); + int32 AlignedArbitrary16 = Memory::AlignArbitrary(Unaligned, 16); + int32 AlignedArbitrary32 = Memory::AlignArbitrary(Unaligned, 32); + int32 AlignedArbitrary64 = Memory::AlignArbitrary(Unaligned, 64); + + always_check((Memory::IsAligned(Aligned8, 8) && Aligned8 > Unaligned)); + always_check((Memory::IsAligned(Aligned16, 16) && Aligned16 > Unaligned)); + always_check((Memory::IsAligned(Aligned32, 32) && Aligned32 > Unaligned)); + always_check((Memory::IsAligned(Aligned64, 64) && Aligned64 > Unaligned)); + + always_check((Memory::IsAligned(Aligned8, 8) && AlignedDown8 < Unaligned)); + always_check((Memory::IsAligned(Aligned16, 16) && AlignedDown16 < Unaligned)); + always_check((Memory::IsAligned(Aligned32, 32) && AlignedDown32 < Unaligned)); + always_check((Memory::IsAligned(Aligned64, 64) && AlignedDown64 < Unaligned)); + + always_check((Memory::IsAligned(AlignedArbitrary8, 8))); + always_check((Memory::IsAligned(AlignedArbitrary16, 16))); + always_check((Memory::IsAligned(AlignedArbitrary32, 32))); + always_check((Memory::IsAligned(AlignedArbitrary64, 64))); + +} + +void TestMemoryBuffer() +{ + int64 TempA; + int64 TempB; + int64 TempC; + int64 TempD; + uint8* PtrA = reinterpret_cast(&TempA); + uint8* PtrB = reinterpret_cast(&TempB); + uint8* PtrC = reinterpret_cast(&TempC); + uint8* PtrD = reinterpret_cast(&TempD); + + TempA = 0x0123456789ABCDEF; + TempB = 0x0123456789AB0000; + Memory::Memmove(PtrA, PtrA + 2, 6); + always_check((TempA << 16) == TempB); + + TempA = 1004; + TempB = 1005; + TempC = 1005; + TempD = 1006; + int32 ResultA = Memory::Memcmp(PtrA, PtrB, sizeof(int64)); + int32 ResultB = Memory::Memcmp(PtrB, PtrC, sizeof(int64)); + int32 ResultC = Memory::Memcmp(PtrC, PtrD, sizeof(int64)); + always_check((ResultA < 0) != (ResultB < 0)); + always_check(ResultB == 0); + + Memory::Memset(PtrA, 0x3F, sizeof(int64)); + always_check(TempA == 0x3F3F3F3F3F3F3F3F); + Memory::Memset(TempB, 0x3F); + always_check(TempB == 0x3F3F3F3F3F3F3F3F); + + Memory::Memzero(PtrA, sizeof(int64)); + always_check(TempA == 0); + Memory::Memzero(TempB); + always_check(TempB == 0); + + TempA = 0x0123456789ABCDEF; + Memory::Memcpy(PtrC, PtrA, sizeof(int64)); + always_check(TempA == TempC); + TempB = 0xDEDCBA9876543210; + Memory::Memcpy(TempD, TempB); + always_check(TempB == TempD); + +} + +void TestMemoryMalloc() +{ + int32* PtrA; + int64* PtrB; + + PtrA = reinterpret_cast(Memory::SystemMalloc(sizeof(int32))); + *PtrA = 0x01234567; + always_check(*PtrA == 0x01234567); + PtrB = reinterpret_cast(Memory::SystemRealloc(PtrA, sizeof(int64))); + *PtrB = 0x0123456789ABCDEF; + always_check(*PtrB == 0x0123456789ABCDEF); + Memory::SystemFree(PtrB); + + PtrA = reinterpret_cast(Memory::Malloc(sizeof(int32), 1024)); + always_check(Memory::IsAligned(PtrA, 1024)); + *PtrA = 0x01234567; + always_check(*PtrA == 0x01234567); + PtrB = reinterpret_cast(Memory::Realloc(PtrA, sizeof(int64), 1024)); + always_check(Memory::IsAligned(PtrB, 1024)); + *PtrB = 0x0123456789ABCDEF; + always_check(*PtrB == 0x0123456789ABCDEF); + Memory::Free(PtrB); + + PtrA = new int32; + PtrB = new int64; + *PtrA = 0x01234567; + always_check(*PtrA == 0x01234567); + *PtrB = 0x0123456789ABCDEF; + always_check(*PtrB == 0x0123456789ABCDEF); + delete PtrA; + delete PtrB; + + struct alignas(1024) FTest { int32 A; }; + FTest* PtrC = new FTest[4]; + always_check(Memory::IsAligned(PtrC, 1024)); + PtrC->A = 0x01234567; + always_check(PtrC->A == 0x01234567); + delete[] PtrC; + +} + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/Memory/Alignment.h b/Redcraft.Utility/Source/Public/Memory/Alignment.h new file mode 100644 index 0000000..58ae027 --- /dev/null +++ b/Redcraft.Utility/Source/Public/Memory/Alignment.h @@ -0,0 +1,48 @@ +#pragma once + +#include "CoreTypes.h" +#include "TypeTraits/TypeTraits.h" + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +NAMESPACE_BEGIN(Memory) + +template +FORCEINLINE constexpr T Align(T InValue, size_t Alignment) +{ + static_assert(TIsIntegral::Value || TIsPointer::Value, "Align expects an integer or pointer type"); + + return (T)(((uintptr_t)(InValue) + static_cast(Alignment) - 1) & ~(static_cast(Alignment) - 1)); +} + +template +FORCEINLINE constexpr T AlignDown(T InValue, size_t Alignment) +{ + static_assert(TIsIntegral::Value || TIsPointer::Value, "AlignDown expects an integer or pointer type"); + + return (T)((uintptr_t)(InValue) & ~(static_cast(Alignment) - 1)); +} + +template +FORCEINLINE constexpr T AlignArbitrary(T InValue, size_t Alignment) +{ + static_assert(TIsIntegral::Value || TIsPointer::Value, "AlignArbitrary expects an integer or pointer type"); + + return (T)((((uintptr_t)(InValue) + static_cast(Alignment) - 1) / static_cast(Alignment)) * static_cast(Alignment)); +} + +template +FORCEINLINE constexpr bool IsAligned(T InValue, size_t Alignment) +{ + static_assert(TIsIntegral::Value || TIsPointer::Value, "IsAligned expects an integer or pointer type"); + + return !((uintptr_t)(InValue) & (static_cast(Alignment) - 1)); +} + +NAMESPACE_END(Memory) + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/Memory/Memory.h b/Redcraft.Utility/Source/Public/Memory/Memory.h new file mode 100644 index 0000000..b3bda74 --- /dev/null +++ b/Redcraft.Utility/Source/Public/Memory/Memory.h @@ -0,0 +1,110 @@ +#pragma once + +#include "CoreTypes.h" +#include "TypeTraits/TypeTraits.h" + +#include +#include +#include + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +NAMESPACE_BEGIN(Memory) + +inline constexpr size_t DEFAULT_ALIGNMENT = 0; +inline constexpr size_t MINIMUM_ALIGNMENT = 8; + +#ifdef __cpp_lib_hardware_interference_size + +inline constexpr size_t DESTRUCTIVE_INTERFERENCE = std::hardware_destructive_interference_size; +inline constexpr size_t CONSTRUCTIVE_INTERFERENCE = std::hardware_constructive_interference_size; + +#else + +inline constexpr size_t DESTRUCTIVE_INTERFERENCE = 64; +inline constexpr size_t CONSTRUCTIVE_INTERFERENCE = 64; + +#endif + +FORCEINLINE void* Memmove(void* Destination, const void* Source, size_t Count) +{ + return std::memmove(Destination, Source, Count); +} + +FORCEINLINE int32 Memcmp(const void* BufferLHS, const void* BufferRHS, size_t Count) +{ + return std::memcmp(BufferLHS, BufferRHS, Count); +} + +FORCEINLINE void Memset(void* Destination, uint8 ValueToSet, size_t Count) +{ + std::memset(Destination, ValueToSet, Count); +} + +FORCEINLINE void* Memzero(void* Destination, size_t Count) +{ + return std::memset(Destination, 0, Count); +} + +FORCEINLINE void* Memcpy(void* Destination, const void* Source, size_t Count) +{ + return std::memcpy(Destination, Source, Count); +} + +template +FORCEINLINE void Memset(T& Source, uint8 ValueToSet) +{ + static_assert(!TIsPointer::Value, "For pointers use the three parameters function"); + Memset(&Source, ValueToSet, sizeof(T)); +} + +template +FORCEINLINE void Memzero(T& Source) +{ + static_assert(!TIsPointer::Value, "For pointers use the two parameters function"); + Memzero(&Source, sizeof(T)); +} + +template +FORCEINLINE void Memcpy(T& Destination, const T& Source) +{ + static_assert(!TIsPointer::Value, "For pointers use the three parameters function"); + Memcpy(&Destination, &Source, sizeof(T)); +} + +FORCEINLINE void* SystemMalloc(size_t Count) +{ + return std::malloc(Count); +} + +FORCEINLINE void* SystemRealloc(void* Ptr, size_t Count) +{ + return std::realloc(Ptr, Count); +} + +FORCEINLINE void SystemFree(void* Ptr) +{ + std::free(Ptr); +} + +REDCRAFTUTILITY_API void* Malloc(size_t Count, size_t Alignment = DEFAULT_ALIGNMENT); +REDCRAFTUTILITY_API void* Realloc(void* Ptr, size_t Count, size_t Alignment = DEFAULT_ALIGNMENT); +REDCRAFTUTILITY_API void Free(void* Ptr); +REDCRAFTUTILITY_API size_t QuantizeSize(size_t Count, size_t Alignment = DEFAULT_ALIGNMENT); + +NAMESPACE_END(Memory) + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END + +#pragma warning(disable : 28251) + +// The global overload operators new/delete do not cross .dll boundaries, and the macros should be placed in the .cpp of each module. +#define REPLACEMENT_OPERATOR_NEW_AND_DELETE \ + void* operator new(std::size_t Count) { return NAMESPACE_REDCRAFT::Memory::Malloc(Count); } \ + void* operator new(std::size_t Count, std::align_val_t Alignment) { return NAMESPACE_REDCRAFT::Memory::Malloc(Count, static_cast(Alignment)); } \ + void operator delete(void* Ptr) noexcept { NAMESPACE_REDCRAFT::Memory::Free(Ptr); } \ + void operator delete(void* Ptr, std::align_val_t Alignment) noexcept { NAMESPACE_REDCRAFT::Memory::Free(Ptr); } diff --git a/Redcraft.Utility/Source/Public/Miscellaneous/Platform.h b/Redcraft.Utility/Source/Public/Miscellaneous/Platform.h index 4eb974d..0d9dc3b 100644 --- a/Redcraft.Utility/Source/Public/Miscellaneous/Platform.h +++ b/Redcraft.Utility/Source/Public/Miscellaneous/Platform.h @@ -4,6 +4,7 @@ #include #include +#include NAMESPACE_REDCRAFT_BEGIN NAMESPACE_MODULE_BEGIN(Redcraft) @@ -142,6 +143,7 @@ typedef WIDECHAR TCHAR; typedef NAMESPACE_STD::uintptr_t uintptr_t; typedef NAMESPACE_STD::intptr_t intptr_t; +typedef NAMESPACE_STD::ptrdiff_t ptrdiff_t; typedef NAMESPACE_STD::size_t size_t; typedef intptr_t ssize_t; diff --git a/Redcraft.Utility/Source/Public/Testing/MemoryTesting.h b/Redcraft.Utility/Source/Public/Testing/MemoryTesting.h new file mode 100644 index 0000000..655d004 --- /dev/null +++ b/Redcraft.Utility/Source/Public/Testing/MemoryTesting.h @@ -0,0 +1,16 @@ +#pragma once + +#include "CoreTypes.h" + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +REDCRAFTUTILITY_API void TestMemory(); +REDCRAFTUTILITY_API void TestAlignment(); +REDCRAFTUTILITY_API void TestMemoryBuffer(); +REDCRAFTUTILITY_API void TestMemoryMalloc(); + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END