From 9e5ac1e975922d012ce4460fb8c8861813abe48c Mon Sep 17 00:00:00 2001 From: IlushaShurupov Date: Fri, 7 Jul 2023 22:11:22 +0300 Subject: [PATCH] Allocators Testing Added. Pull and Chunk allocators updates. --- Allocators/CMakeLists.txt | 10 +- Allocators/README.md | 2 +- Allocators/private/Allocators.cpp | 18 +- Allocators/private/ChunkAllocator.cpp | 114 ------- Allocators/private/HeapAllocator.cpp | 30 +- Allocators/private/HeapAllocatorGlobal.cpp | 57 ++-- Allocators/private/PickAllocator.cpp | 1 - Allocators/private/PoolAllocator.cpp | 131 -------- Allocators/public/Allocators.hpp | 8 +- Allocators/public/ChunkAllocator.hpp | 135 +++++++- Allocators/public/HeapAllocator.hpp | 14 +- Allocators/public/HeapAllocatorGlobal.hpp | 17 +- Allocators/public/PickAllocator.hpp | 13 - Allocators/public/PoolAllocator.hpp | 166 ++++++++-- .../{private => public}/PrivateConfig.hpp | 0 Allocators/tests/Tests.cpp | 300 +----------------- Allocators/tests/Tests.hpp | 6 + Allocators/tests/TestsAll.cpp | 248 +++++++++++++++ CMakeLists.txt | 2 +- Containers/tests/Tests.cpp | 2 +- Math/public/Mat.hpp | 4 +- Math/public/Vec.hpp | 2 +- Modules/public/Module.hpp | 2 +- TODO | 4 +- Utils/private/Debugging.cpp | 20 +- Utils/private/Testing.cpp | 6 +- Utils/private/Utils.cpp | 76 +++-- Utils/public/Debugging.hpp | 3 + Utils/public/Utils.hpp | 10 +- Utils/tests/Tests.cpp | 4 +- 30 files changed, 704 insertions(+), 701 deletions(-) delete mode 100644 Allocators/private/ChunkAllocator.cpp delete mode 100644 Allocators/private/PickAllocator.cpp delete mode 100644 Allocators/private/PoolAllocator.cpp delete mode 100644 Allocators/public/PickAllocator.hpp rename Allocators/{private => public}/PrivateConfig.hpp (100%) create mode 100644 Allocators/tests/TestsAll.cpp diff --git a/Allocators/CMakeLists.txt b/Allocators/CMakeLists.txt index 70ad8d9..fae102e 100644 --- a/Allocators/CMakeLists.txt +++ b/Allocators/CMakeLists.txt @@ -7,14 +7,16 @@ project(Allocator) ### ---------------------- Static Library --------------------- ### file(GLOB SOURCES "./private/*.cpp") -add_library(${PROJECT_NAME} STATIC ${SOURCES}) +file(GLOB HEADERS "./public/*.hpp") +add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) target_include_directories(${PROJECT_NAME} PUBLIC ./public/) target_link_libraries(${PROJECT_NAME} PUBLIC Utils) ### -------------------------- Tests -------------------------- ### enable_testing() -add_executable(${PROJECT_NAME}Tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Tests.cpp) -target_link_libraries(${PROJECT_NAME}Tests ${PROJECT_NAME}) -add_test(${PROJECT_NAME} ${PROJECT_NAME}Tests) +file(GLOB TEST_SOURCES "./tests/*.cpp") +add_executable(${PROJECT_NAME}Tests ${TEST_SOURCES}) +target_link_libraries(${PROJECT_NAME}Tests ${PROJECT_NAME} Utils) +add_test(NAME ${PROJECT_NAME}Tests COMMAND ${PROJECT_NAME}Tests) install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib) \ No newline at end of file diff --git a/Allocators/README.md b/Allocators/README.md index a2b4861..6062b25 100644 --- a/Allocators/README.md +++ b/Allocators/README.md @@ -16,7 +16,7 @@ void test_call11() { } int main(char argc, char* argv[]) { - tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleAllocator, NULL }; + tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleAllocators, NULL }; tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies); TestModule.initialize(); diff --git a/Allocators/private/Allocators.cpp b/Allocators/private/Allocators.cpp index e4e38f4..18bc13c 100644 --- a/Allocators/private/Allocators.cpp +++ b/Allocators/private/Allocators.cpp @@ -1,21 +1,23 @@ #include "Allocators.hpp" -static tp::ModuleManifest* sModuleDependencies[] = { &tp::gModuleBase, NULL }; -tp::ModuleManifest tp::gModuleAllocator = ModuleManifest("Allocators", NULL, NULL, sModuleDependencies); +#include + +static tp::ModuleManifest* sModuleDependencies[] = { &tp::gModuleBase, nullptr }; +tp::ModuleManifest tp::gModuleAllocators = ModuleManifest("Allocators", nullptr, nullptr, sModuleDependencies); void* operator new(size_t aSize) { return tp::HeapAllocGlobal::allocate(aSize); } void* operator new[](size_t aSize) { return tp::HeapAllocGlobal::allocate(aSize); } -void operator delete(void* aPtr) { tp::HeapAllocGlobal::deallocate(aPtr); } -void operator delete[](void* aPtr) { tp::HeapAllocGlobal::deallocate(aPtr); } +void operator delete(void* aPtr) noexcept { tp::HeapAllocGlobal::deallocate(aPtr); } +void operator delete[](void* aPtr) noexcept { tp::HeapAllocGlobal::deallocate(aPtr); } void* operator new(size_t aSize, tp::HeapAlloc& aAlloc) { return aAlloc.allocate(aSize); } void* operator new[](size_t aSize, tp::HeapAlloc& aAlloc) { return aAlloc.allocate(aSize); } void operator delete(void* aPtr, tp::HeapAlloc& aAlloc) { aAlloc.deallocate(aPtr); } void operator delete[](void* aPtr, tp::HeapAlloc& aAlloc) { aAlloc.deallocate(aPtr); } -void* operator new(size_t aSize, tp::HeapAllocGlobal& aAlloc) { return aAlloc.allocate(aSize); } -void* operator new[](size_t aSize, tp::HeapAllocGlobal& aAlloc) { return aAlloc.allocate(aSize); } -void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc) { aAlloc.deallocate(aPtr); } -void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc) { aAlloc.deallocate(aPtr); } \ No newline at end of file +void* operator new(size_t aSize, tp::HeapAllocGlobal& aAlloc) { return tp::HeapAllocGlobal::allocate(aSize); } +void* operator new[](size_t aSize, tp::HeapAllocGlobal& aAlloc) { return tp::HeapAllocGlobal::allocate(aSize); } +void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc) { tp::HeapAllocGlobal::deallocate(aPtr); } +void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc) { tp::HeapAllocGlobal::deallocate(aPtr); } \ No newline at end of file diff --git a/Allocators/private/ChunkAllocator.cpp b/Allocators/private/ChunkAllocator.cpp deleted file mode 100644 index 7040a71..0000000 --- a/Allocators/private/ChunkAllocator.cpp +++ /dev/null @@ -1,114 +0,0 @@ - -/* -* -* Implementation uses embedded one-directional linked list to track free blocks. -* The embedded part ensures that there is no memory overhead on block specifically. -* Linked list is initialized iteratively on each allocation if it has not been already. - -* Allocating: -* 1) updating list entry to stored in the entry itself next free pointer -* 2) returning entry before (1). -* -* Deallocating: -* 1) assigning list entry value to the deleted block -* 2) updating list entry to that block. -* -*/ - -#include "ChunkAllocator.hpp" -#include "PrivateConfig.hpp" - -#include "HeapAllocatorGlobal.hpp" - -using namespace tp; - -enum : ualni { - ALIGNED_SIZE = ENV_ALNI_SIZE_B, - WRAP_SIZE_ALN = MEM_WRAP_SIZE, - WRAP_SIZE = WRAP_SIZE_ALN * ALIGNED_SIZE, - WRAP_VAL = MEM_WRAP_FILL_VAL, - CLEAR_ALLOC_VAL = MEM_CLEAR_ON_ALLOC_VAL, - CLEAR_DEALLOC_VAL = MEM_CLEAR_ON_DEALLOC_VAL, -}; - -ChunkAlloc::ChunkAlloc(ualni aBlockSize, void* aMemory, ualni aMemSize) { - auto const temp = aBlockSize / ALIGNED_SIZE; - mBSize = ((aBlockSize % ALIGNED_SIZE) ? temp + 1 : temp) + WRAP_SIZE_ALN * 2; - mBuff = (ualni*) aMemory; - mNBlocks = (aMemSize / ALIGNED_SIZE) / mBSize; - mNFreeBlocks = mNBlocks; -} - -ChunkAlloc::ChunkAlloc(ualni aBlockSize, ualni aNBlocks) { - auto const temp = aBlockSize / ALIGNED_SIZE; - mBSize = ((aBlockSize % ALIGNED_SIZE) ? temp + 1 : temp) + WRAP_SIZE_ALN * 2; - mNBlocks = aNBlocks; - mNFreeBlocks = mNBlocks; - mBuff = (ualni*)HeapAllocGlobal::allocate(mNBlocks * mBSize * ALIGNED_SIZE); - mOwnBuff = true; -} - -void* ChunkAlloc::allocate() { - ASSERT(mNFreeBlocks && "Out Of Memory"); - - // 1) PreInitialize blocks - if (mNInitBlocks < mNBlocks) { - *(mBuff + mNInitBlocks * mBSize) = (ualni)(mBuff + (mNInitBlocks++) * mBSize); - } - - // 2) Find free block and update next free block - auto data = mNextBlock; - mNextBlock = (ualni*)(*data); - mNFreeBlocks--; - - #ifdef MEM_DEBUG - // 3) Fill Wrap and offset data - auto wrap_top = data; - data += WRAP_SIZE_ALN; - auto wrap_bottom = data + mBSize; - - memsetv(wrap_top, WRAP_SIZE, WRAP_VAL); - memsetv(wrap_bottom, WRAP_SIZE, WRAP_VAL); - - // 4) Clear data - #ifdef MEM_CLEAR_ON_ALLOC - memsetv(data, mBSize * ALIGNED_SIZE, CLEAR_ALLOC_VAL); - #endif - #endif - - return data; -} - -void ChunkAlloc::deallocate(void* aPtr) { - auto block = (ualni*)aPtr; - - #ifdef MEM_DEBUG - // 3) Check Wrap and offset data - auto wrap_bottom = block + mBSize; - auto wrap_top = block - WRAP_SIZE_ALN; - block = wrap_top; - - // 3) Check the wrap - RelAssert(memequalv(wrap_top, WRAP_SIZE, WRAP_VAL) && memequalv(wrap_bottom, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!"); - - // 4) Clear data - #ifdef MEM_CLEAR_ON_ALLOC - memsetv(aPtr, mBSize * ALIGNED_SIZE, CLEAR_DEALLOC_VAL); - #endif - - #endif - - (*block) = (ualni)mNextBlock; - mNextBlock = block; - mNFreeBlocks++; -} - -bool ChunkAlloc::isFull() const { return !mNFreeBlocks; } -bool ChunkAlloc::isEmpty() const { return mNFreeBlocks == mNBlocks; } - -ChunkAlloc::~ChunkAlloc() { - // TODO : check for leaks - if (mOwnBuff) { - HeapAllocGlobal::deallocate(mBuff); - } -} \ No newline at end of file diff --git a/Allocators/private/HeapAllocator.cpp b/Allocators/private/HeapAllocator.cpp index 916b4c5..a1994da 100644 --- a/Allocators/private/HeapAllocator.cpp +++ b/Allocators/private/HeapAllocator.cpp @@ -5,9 +5,6 @@ #include "PrivateConfig.hpp" -#include -#include - using namespace tp; #if not defined(MEM_DEBUG) @@ -24,7 +21,7 @@ namespace tp { MemHeadLocal* mPrev; MemHeadLocal* mNext; }; -}; +} void* HeapAlloc::allocate(ualni aBlockSize) { auto head = (MemHeadLocal*) HeapAllocGlobal::allocate(aBlockSize + sizeof(MemHeadLocal)); @@ -32,12 +29,11 @@ void* HeapAlloc::allocate(ualni aBlockSize) { mNumAllocations++; if (mEntry) { - head->mNext = mEntry->mNext; - head->mPrev = mEntry->mPrev; - if (mEntry->mNext) mEntry->mNext->mPrev = head; - if (mEntry->mPrev) mEntry->mPrev->mNext = head; - } - else { + DEBUG_ASSERT(!mEntry->mNext) + head->mNext = nullptr; + head->mPrev = mEntry; + mEntry->mNext = head; + } else { head->mNext = nullptr; head->mPrev = nullptr; } @@ -50,15 +46,11 @@ void HeapAlloc::deallocate(void* aPtr) { auto head = ((MemHeadLocal*)(aPtr)) - 1; mNumAllocations--; - if (mEntry->mNext) mEntry->mNext->mPrev = mEntry->mPrev; - if (mEntry->mPrev) mEntry->mPrev->mNext = mEntry->mNext; + DEBUG_ASSERT(!mEntry->mNext) + if (head->mNext) head->mNext->mPrev = head->mPrev; + if (head->mPrev) head->mPrev->mNext = head->mNext; if (head == mEntry) { - if (mEntry->mNext) { - mEntry = mEntry->mNext; - } - else { - mEntry = mEntry->mPrev; - } + mEntry = head->mPrev; } HeapAllocGlobal::deallocate(head); @@ -66,7 +58,7 @@ void HeapAlloc::deallocate(void* aPtr) { HeapAlloc::~HeapAlloc() { if (mNumAllocations) { - DEBUG_BREAK("Destruction of not freed Allocator"); + DEBUG_BREAK("Destruction of not freed Allocator") #ifdef MEM_STACK_TRACE // TODO : log leaks and free them up diff --git a/Allocators/private/HeapAllocatorGlobal.cpp b/Allocators/private/HeapAllocatorGlobal.cpp index 303190a..da31e46 100644 --- a/Allocators/private/HeapAllocatorGlobal.cpp +++ b/Allocators/private/HeapAllocatorGlobal.cpp @@ -3,9 +3,9 @@ #include "HeapAllocatorGlobal.hpp" #include "PrivateConfig.hpp" +#include "Utils.hpp" #include "Debugging.hpp" -#include #include using namespace tp; @@ -40,10 +40,10 @@ namespace tp { MemHead* mNext; ualni mBlockSize; #ifdef MEM_STACK_TRACE - CallStackSnapshots::StackShapshot mCallStack; + const CallStackCapture::CallStack* mCallStack; #endif }; -}; +} enum : ualni { ALIGNED_SIZE = ENV_ALNI_SIZE_B, @@ -74,29 +74,28 @@ void* HeapAllocGlobal::allocate(ualni aBlockSize) { // 2) Link with existing blocks mNumAllocations++; if (mEntry) { - head->mNext = mEntry->mNext; - head->mPrev = mEntry->mPrev; - if (mEntry->mNext) mEntry->mNext->mPrev = head; - if (mEntry->mPrev) mEntry->mPrev->mNext = head; - } - else { + DEBUG_ASSERT(!mEntry->mNext) + head->mNext = nullptr; + head->mPrev = mEntry; + mEntry->mNext = head; + } else { head->mNext = nullptr; head->mPrev = nullptr; } mEntry = head; // 3) Wrap fill - memsetv(wrap_top, WRAP_SIZE, WRAP_VAL); - memsetv(wrap_bottom, WRAP_SIZE, WRAP_VAL); + memSetVal(wrap_top, WRAP_SIZE, WRAP_VAL); + memSetVal(wrap_bottom, WRAP_SIZE, WRAP_VAL); // 4) Trace the stack #ifdef MEM_STACK_TRACE - head->mCallStack = gCallStackSnapshots.capture(); + head->mCallStack = gCSCapture->getSnapshot(); #endif // 5) clear data #ifdef MEM_CLEAR_ON_ALLOC - memsetv(data, aBlockSize, CLEAR_ALLOC_VAL); + memSetVal(data, aBlockSize, CLEAR_ALLOC_VAL); #endif return data; @@ -104,46 +103,48 @@ void* HeapAllocGlobal::allocate(ualni aBlockSize) { void HeapAllocGlobal::deallocate(void* aPtr) { // 1) Restore the pointers - auto head = ((MemHead*)(aPtr)) - 1; + auto head = ((MemHead*)((int1*)aPtr - WRAP_SIZE)) - 1; auto wrap_top = (int1*)(head + 1); auto data = wrap_top + WRAP_SIZE; auto wrap_bottom = data + head->mBlockSize; // 2) Unlink with blocks mNumAllocations--; - if (mEntry->mNext) mEntry->mNext->mPrev = mEntry->mPrev; - if (mEntry->mPrev) mEntry->mPrev->mNext = mEntry->mNext; + DEBUG_ASSERT(!mEntry->mNext) + if (head->mNext) head->mNext->mPrev = head->mPrev; + if (head->mPrev) head->mPrev->mNext = head->mNext; if (head == mEntry) { - if (mEntry->mNext) { - mEntry = mEntry->mNext; - } - else { - mEntry = mEntry->mPrev; - } + mEntry = head->mPrev; } // 3) Check the wrap - RelAssert(memequalv(wrap_top, WRAP_SIZE, WRAP_VAL) && memequalv(wrap_bottom, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!"); + ASSERT(!memCompareVal(wrap_top, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!") + ASSERT(!memCompareVal(wrap_bottom, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!") // 4) clear data #ifdef MEM_CLEAR_ON_ALLOC - memsetv(data, head->mBlockSize, CLEAR_DEALLOC_VAL); + memSetVal(data, head->mBlockSize, CLEAR_DEALLOC_VAL); #endif // 5) free the block - free(aPtr); + free(head); } -HeapAllocGlobal::~HeapAllocGlobal() { +bool HeapAllocGlobal::checkLeaks() { // 1) Check for not deallocated memory if (mNumAllocations) { - DEBUG_BREAK("Destruction of not freed Allocator"); #ifdef MEM_STACK_TRACE - // TODO: log leaks + gCSCapture->logLeaks(); #endif + + DEBUG_BREAK("Destruction of not freed Allocator") + return true; } + return false; } +HeapAllocGlobal::~HeapAllocGlobal() = default; + #endif diff --git a/Allocators/private/PickAllocator.cpp b/Allocators/private/PickAllocator.cpp deleted file mode 100644 index d3f5a12..0000000 --- a/Allocators/private/PickAllocator.cpp +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Allocators/private/PoolAllocator.cpp b/Allocators/private/PoolAllocator.cpp deleted file mode 100644 index ea4d52b..0000000 --- a/Allocators/private/PoolAllocator.cpp +++ /dev/null @@ -1,131 +0,0 @@ - -/* -* -Implementation: -* Adding chunk pointer to each chunk to form one-directional list that keeps track of free chunk -* Storing ordered pointers to chunks in order to find desired chunk from delete pointer on de-allocation in log time -* -* Allocations: -* 1) allocate with chunk stored in list entry -* 2) ... -* -* De-allocations: -* 1) binary-search with delete pointer to find desired chunk -* 2) ... -* -*/ - -#include "Allocators.hpp" -#include "PrivateConfig.hpp" - -void tp::PoolAlloc::Chunks::add(Chunk* aChunk) { - // ensure order - auto smaller_address = findUtil(mBuff, mBuff + mUsedLen, aChunk); - for (auto iter = mBuff + mUsedLen; iter != smaller_address; iter--) { *(iter + 1) = *iter; } - - *(smaller_address + 1) = aChunk; - mUsedLen++; - - // check for buff overflow - if (mUsedLen == mLen) { - auto prevBuff = mBuff; - mBuff = (Chunk**) HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen * 2); - memcp(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen); - mLen *= 2; - HeapAllocGlobal::deallocate(prevBuff); - } -} - -void tp::PoolAlloc::Chunks::remove(Chunk* aChunk) { - // ensure order - auto del_address = findUtil(mBuff, mBuff + mUsedLen, aChunk); - for (auto iter = del_address; iter != mBuff + mUsedLen; iter++) { *iter = *(iter + 1); } - - mUsedLen--; - - // check for buff low usage - if ((halnf)mUsedLen / (halnf)mLen < 0.25f) { - auto prevBuff = mBuff; - mBuff = (Chunk**)HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen / 2); - memcp(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen); - mLen /= 2; - HeapAllocGlobal::deallocate(prevBuff); - } -} - -tp::PoolAlloc::Chunk* tp::PoolAlloc::Chunks::find(void* aPtr) { - return *findUtil(mBuff, mBuff + mUsedLen, aPtr); -} - -tp::PoolAlloc::Chunk* tp::PoolAlloc::Chunks::findNotFull() { - for (ualni idx = 0; idx < mUsedLen; idx++) { - if (!mBuff[idx]->isFull()) { - return mBuff[idx]; - } - } - return nullptr; -} - -tp::PoolAlloc::Chunk** tp::PoolAlloc::Chunks::findUtil(Chunk** aLeft, Chunk** aRight, void* aPtr) { - auto range = ualni(aRight - aLeft); - if (range == 1) { return aLeft; } - auto middle = aLeft + range / 2; - return (aPtr > *middle) ? findUtil(middle, aRight, aPtr) : findUtil(aLeft, middle, aPtr); -} - - -tp::PoolAlloc::PoolAlloc(ualni aBlockSize, ualni aChunkSize) : mBlockSize(aBlockSize), mChunkSize(aChunkSize) {} - -void* tp::PoolAlloc::allocate() { - - if (!mFreeChunk || mFreeChunk->isFull()) { - auto new_free_chunk = mChunks.findNotFull(); - - if (!new_free_chunk) { - - new_free_chunk = new ((Chunk*)HeapAllocGlobal::allocate(sizeof(Chunk))) Chunk(mBlockSize, mChunkSize); - ASSERT(new_free_chunk); - mChunks.add(new_free_chunk); - - if (mFreeChunk) { - new_free_chunk->mNext = mFreeChunk->mNext; - new_free_chunk->mPrev = mFreeChunk->mPrev; - if (mFreeChunk->mNext) mFreeChunk->mNext->mPrev = new_free_chunk; - if (mFreeChunk->mPrev) mFreeChunk->mPrev->mNext = new_free_chunk; - } - else { - new_free_chunk->mNext = nullptr; - new_free_chunk->mPrev = nullptr; - } - - mFreeChunk = new_free_chunk; - } - } - - return mFreeChunk->allocate(); -} - -void tp::PoolAlloc::deallocate(void* aPtr) { - auto chunk = mChunks.find(aPtr); - chunk->deallocate(aPtr); - - if (mFreeChunk->isEmpty()) { - - Chunk* new_chunk = nullptr; - for (ualni idx = 0; idx < mChunks.mUsedLen; idx++) { - if (!mChunks.mBuff[idx]->isFull() && new_chunk != mFreeChunk) { - new_chunk = mChunks.mBuff[idx]; - } - } - - if (new_chunk) { - if (mFreeChunk->mNext) mFreeChunk->mNext->mPrev = mFreeChunk->mPrev; - if (mFreeChunk->mPrev) mFreeChunk->mPrev->mNext = mFreeChunk->mNext; - mChunks.remove(mFreeChunk); - HeapAllocGlobal::deallocate(mFreeChunk); - mFreeChunk = new_chunk; - } - } -} - -tp::PoolAlloc::~PoolAlloc() = default; diff --git a/Allocators/public/Allocators.hpp b/Allocators/public/Allocators.hpp index b13c4ab..da01ede 100644 --- a/Allocators/public/Allocators.hpp +++ b/Allocators/public/Allocators.hpp @@ -8,15 +8,15 @@ #include "PoolAllocator.hpp" namespace tp { - extern ModuleManifest gModuleAllocator; -}; + extern ModuleManifest gModuleAllocators; +} inline void* operator new(std::size_t aSize, void* aWhere) noexcept { return aWhere; } void* operator new(std::size_t aSize); void* operator new[](std::size_t aSize); -void operator delete(void* aPtr); -void operator delete[](void* aPtr); +void operator delete(void* aPtr) noexcept; +void operator delete[](void* aPtr) noexcept; void* operator new(std::size_t aSize, tp::HeapAlloc& aAlloc); void* operator new[](std::size_t aSize, tp::HeapAlloc& aAlloc); diff --git a/Allocators/public/ChunkAllocator.hpp b/Allocators/public/ChunkAllocator.hpp index 21391e6..99247fa 100644 --- a/Allocators/public/ChunkAllocator.hpp +++ b/Allocators/public/ChunkAllocator.hpp @@ -1,32 +1,135 @@ #pragma once +/* +* Implementation uses embedded one-directional linked list to track free blocks. +* The embedded part ensures that there is no memory overhead on block specifically. +* Linked list is initialized iteratively on each allocation if it has not been already. + +* Allocating: +* 1) updating list entry to stored in the entry itself next free pointer +* 2) returning entry before (1). +* +* Deallocating: +* 1) assigning list entry value to the deleted block +* 2) updating list entry to that block. +*/ + #include "Environment.hpp" +#include "PrivateConfig.hpp" namespace tp { // Chunk Allocator // Constant time allocations and de-allocations in any order. // Memory blocks are fixed in size and number of blocks can not exceed given parameter. - struct ChunkAlloc { + template + class ChunkAlloc { - ChunkAlloc(ualni aBlockSize, void* aMemory, ualni aMemSize); - ChunkAlloc(ualni aBlockSize, ualni aNBlocks); + enum : ualni { + ALIGNED_SIZE = ENV_ALNI_SIZE_B, - void* allocate(); - void deallocate(void* aPtr); - [[nodiscard]] bool isFull() const; - [[nodiscard]] bool isEmpty() const; + WRAP_SIZE_ALN = MEM_WRAP_SIZE / 2, + WRAP_SIZE = WRAP_SIZE_ALN * ALIGNED_SIZE, - ~ChunkAlloc(); + WRAP_VAL = MEM_WRAP_FILL_VAL, + CLEAR_ALLOC_VAL = MEM_CLEAR_ON_ALLOC_VAL, + CLEAR_DEALLOC_VAL = MEM_CLEAR_ON_DEALLOC_VAL, + }; + + static constexpr ualni dataSize() { + auto BLOCK_SIZE_BYTES = sizeof(tType); + auto BLOCK_SIZE_ALIGNED = BLOCK_SIZE_BYTES / ALIGNED_SIZE; + return BLOCK_SIZE_ALIGNED; + } + + static constexpr ualni blockSize() { + auto BLOCK_SIZE_BYTES = sizeof(tType); + auto BLOCK_SIZE = dataSize() + bool(BLOCK_SIZE_BYTES % ALIGNED_SIZE) + WRAP_SIZE_ALN * 2; + return BLOCK_SIZE; + } private: - ualni mBSize; // Size of data in aligned units - ualni mNBlocks; + ualni* mNextBlock; + ualni mNumFreeBlocks; + ualni mNumInitBlocks; + ualni mBuff[tNumBlocks * blockSize() * ALIGNED_SIZE]; - ualni* mBuff = nullptr; - ualni* mNextBlock = nullptr; - ualni mNFreeBlocks; - ualni mNInitBlocks = 0; - bool mOwnBuff = false; + public: + ChunkAlloc() { + mNumFreeBlocks = tNumBlocks; + mNumInitBlocks = 0; + mNextBlock = mBuff; + } + + ~ChunkAlloc() = default; // TODO : check for leaks + + public: + void* allocate(ualni) { + DEBUG_ASSERT(mNumFreeBlocks && "Out Of Memory") + + // 1) PreInitialize blocks + if (mNumInitBlocks < tNumBlocks) { + mBuff[mNumInitBlocks * blockSize()] = (ualni) (mBuff + (mNumInitBlocks + 1) * blockSize()); + mNumInitBlocks++; + } + + // 2) Find free block and update next free block + auto data = mNextBlock; + mNextBlock = (ualni*)(*data); + mNumFreeBlocks--; + + #ifdef MEM_DEBUG + // 3) Fill Wrap and offset data + auto wrap_top = data; + auto wrap_bottom = data + WRAP_SIZE_ALN + dataSize(); + + memSetVal(wrap_top, WRAP_SIZE, WRAP_VAL); + memSetVal(wrap_bottom, WRAP_SIZE, WRAP_VAL); + + // 4) Clear data + #ifdef MEM_CLEAR_ON_ALLOC + memSetVal(data + WRAP_SIZE_ALN, dataSize() * ALIGNED_SIZE, CLEAR_ALLOC_VAL); + #endif + + data += WRAP_SIZE_ALN; + #endif + + return data; + } + + void deallocate(void* aPtr) { + DEBUG_ASSERT(aPtr >= mBuff && aPtr < mBuff + tNumBlocks * blockSize()) + + auto block = (ualni*)aPtr; + + #ifdef MEM_DEBUG + // 3) Check Wrap and offset data + auto wrap_bottom = block + dataSize(); + auto wrap_top = block - WRAP_SIZE_ALN; + block = wrap_top; + + // 3) Check the wrap + ASSERT(!memCompareVal(wrap_top, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!") + ASSERT(!memCompareVal(wrap_bottom, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!") + + // 4) Clear data + #ifdef MEM_CLEAR_ON_ALLOC + memSetVal(block, blockSize() * ALIGNED_SIZE, CLEAR_DEALLOC_VAL); + #endif + + #endif + + (*block) = (ualni)mNextBlock; + mNextBlock = block; + mNumFreeBlocks++; + } + + [[nodiscard]] bool checkWrap() const { return false; } + void checkValid() {} + + public: + [[nodiscard]] bool isFull() const { return !mNumFreeBlocks; } + [[nodiscard]] bool isEmpty() const { return mNumFreeBlocks == tNumBlocks; } + [[nodiscard]] const ualni* getBuff() const { return mBuff; } }; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/Allocators/public/HeapAllocator.hpp b/Allocators/public/HeapAllocator.hpp index 80b2996..59aedba 100644 --- a/Allocators/public/HeapAllocator.hpp +++ b/Allocators/public/HeapAllocator.hpp @@ -4,15 +4,23 @@ namespace tp { - struct HeapAlloc { + class HeapAlloc { #ifdef MEM_DEBUG ualni mNumAllocations = 0; struct MemHeadLocal* mEntry = nullptr; #endif + public: + HeapAlloc() = default; + ~HeapAlloc(); + + public: void* allocate(ualni aBlockSize); void deallocate(void* aPtr); - ~HeapAlloc(); + + public: + [[nodiscard]] bool checkWrap() const { return false; } + void checkValid() {} }; -}; +} diff --git a/Allocators/public/HeapAllocatorGlobal.hpp b/Allocators/public/HeapAllocatorGlobal.hpp index fd071d7..18cae0e 100644 --- a/Allocators/public/HeapAllocatorGlobal.hpp +++ b/Allocators/public/HeapAllocatorGlobal.hpp @@ -4,15 +4,24 @@ namespace tp { - struct HeapAllocGlobal { + class HeapAllocGlobal { #ifdef MEM_DEBUG static ualni mNumAllocations; static struct MemHead* mEntry; #endif - + + public: + HeapAllocGlobal() = default; + ~HeapAllocGlobal(); + + public: static void* allocate(ualni aBlockSize); static void deallocate(void* aPtr); - ~HeapAllocGlobal(); + + static bool checkLeaks(); + public: + [[nodiscard]] bool checkWrap() const { return false; } + void checkValid() {} }; -}; +} diff --git a/Allocators/public/PickAllocator.hpp b/Allocators/public/PickAllocator.hpp deleted file mode 100644 index 52b23bc..0000000 --- a/Allocators/public/PickAllocator.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "HeapAllocatorGlobal.hpp" - -namespace tp { - - struct PickAlloc { - PickAlloc(); - void* allocate(ualni aBlockSize); - void deallocate(void* aPtr); - ~PickAlloc(); - }; -}; diff --git a/Allocators/public/PoolAllocator.hpp b/Allocators/public/PoolAllocator.hpp index fa68650..98f1596 100644 --- a/Allocators/public/PoolAllocator.hpp +++ b/Allocators/public/PoolAllocator.hpp @@ -1,42 +1,162 @@ #pragma once +/* +Implementation: +* Adding chunk pointer to each chunk to form one-directional list that keeps track of free chunk +* Storing ordered pointers to chunks in order to find desired chunk from delete pointer on de-allocation in log time +* +* Allocations: +* 1) allocate with chunk stored in list entry +* 2) ... +* +* De-allocations: +* 1) binary-search with delete pointer to find desired chunk +* 2) ... +* +*/ + #include "ChunkAllocator.hpp" namespace tp { // Pool Allocator // Overcomes chunk allocator fixed number of max allocations - struct PoolAlloc { + template + class PoolAlloc { - PoolAlloc(ualni aBlockSize, ualni aChunkSize); - void* allocate(); - void deallocate(void* aPtr); - ~PoolAlloc(); - - private: - - struct Chunk : public ChunkAlloc { - Chunk(ualni aBlockSize, ualni aChunlSize) : ChunkAlloc(aBlockSize, aChunlSize) {} - Chunk* mNext = NULL; - Chunk* mPrev = NULL; - }; + typedef ChunkAlloc Chunk; struct Chunks { - void add(Chunk*); - void remove(Chunk*); - Chunk* find(void* aPtr); - Chunk* findNotFull(); - Chunk** mBuff = NULL; + + void add(Chunk* aChunk){ + + if (!mBuff) { + mLen = 16; + mBuff = (Chunk**) HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen); + mUsedLen = 1; + mBuff[0] = aChunk; + return; + } + + // ensure order + auto smaller_address = findUtil(mBuff, mBuff + mUsedLen, aChunk); + for (auto iter = mBuff + mUsedLen; iter != smaller_address; iter--) { + *iter = *(iter - 1); + } + + *(smaller_address) = aChunk; + mUsedLen++; + + // check for buff overflow + if (mUsedLen == mLen) { + auto prevBuff = mBuff; + mBuff = (Chunk**) HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen * 2); + memCopy(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen); + mLen *= 2; + HeapAllocGlobal::deallocate(prevBuff); + } + } + + void remove(Chunk** del_address){ + if (mUsedLen == 1) { + mLen = 0; + mUsedLen = 0; + HeapAllocGlobal::deallocate(mBuff); + mBuff = nullptr; + return; + } + + // ensure order + for (auto iter = del_address; iter != mBuff + mUsedLen - 1; iter++) { + *iter = *(iter + 1); + } + + mUsedLen--; + + // check for buff low usage + if ((halnf)mUsedLen / (halnf)mLen < 0.25f) { + auto prevBuff = mBuff; + mBuff = (Chunk**)HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen / 2); + memCopy(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen); + mLen /= 2; + HeapAllocGlobal::deallocate(prevBuff); + } + } + + [[nodiscard]] Chunk** find(void* aPtr) { + return findUtil(mBuff, mBuff + mUsedLen, aPtr) - 1; + } + + [[nodiscard]] Chunk* findNotFull() const { + for (ualni idx = 0; idx < mUsedLen; idx++) { + if (!mBuff[idx]->isFull()) { + return mBuff[idx]; + } + } + return nullptr; + } + + Chunk** mBuff = nullptr; ualni mUsedLen = 0; ualni mLen = 0; private: - Chunk** findUtil(Chunk** aLeft, Chunk** aRight, void* aPtr); + Chunk** findUtil(Chunk** aLeft, Chunk** aRight, void* aPtr) { + auto range = ualni(aRight - aLeft); + if (range == 1) { + return (aPtr < *aLeft) ? aLeft : aRight; + } + auto middle = aLeft + range / 2; + return (aPtr >= (*middle)) ? findUtil(middle, aRight, aPtr) : findUtil(aLeft, middle, aPtr); + } }; + private: Chunks mChunks; - Chunk* mFreeChunk = NULL; - ualni mBlockSize; - ualni mChunkSize; + Chunk* mFreeChunk = nullptr; + + public: + PoolAlloc() = default; + ~PoolAlloc() = default; + + public: + void* allocate(ualni) { + if (!mFreeChunk || mFreeChunk->isFull()) { + auto new_free_chunk = mChunks.findNotFull(); + if (!new_free_chunk) { + new_free_chunk = new (HeapAllocGlobal::allocate(sizeof(Chunk))) Chunk(); + DEBUG_ASSERT(new_free_chunk) + mChunks.add(new_free_chunk); + } + mFreeChunk = new_free_chunk; + } + return mFreeChunk->allocate(0); + } + + void deallocate(void* aPtr) { + auto chunk = mChunks.find(aPtr); + (*chunk)->deallocate(aPtr); + if ((*chunk)->isEmpty()) { + if (mFreeChunk == *chunk) mFreeChunk = nullptr; + HeapAllocGlobal::deallocate(*chunk); + mChunks.remove(chunk); + } + } + + public: + [[nodiscard]] bool checkWrap() const { return false; } + + void checkValid() { + return; + for (auto i = 0; i < mChunks.mUsedLen; i++) { + for (auto j = 0; j < mChunks.mUsedLen; j++) { + if (i > j) { + ASSERT(mChunks.mBuff[i] > mChunks.mBuff[j]) + } else if (i < j) { + ASSERT(mChunks.mBuff[i] < mChunks.mBuff[j]) + } + } + } + } }; -}; +} diff --git a/Allocators/private/PrivateConfig.hpp b/Allocators/public/PrivateConfig.hpp similarity index 100% rename from Allocators/private/PrivateConfig.hpp rename to Allocators/public/PrivateConfig.hpp diff --git a/Allocators/tests/Tests.cpp b/Allocators/tests/Tests.cpp index dfd8834..760d6c5 100644 --- a/Allocators/tests/Tests.cpp +++ b/Allocators/tests/Tests.cpp @@ -1,302 +1,20 @@ #include "Allocators.hpp" -#include -#include +#include "Tests.hpp" + +using namespace tp; int main() { - tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleAllocators, NULL }; - tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies); - if (!TestModule.initialize()) { + tp::ModuleManifest* deps[] = { &tp::gModuleAllocators, &tp::gModuleUtils, nullptr }; + tp::ModuleManifest testModule("AllocatorsTest", nullptr, nullptr, deps); + + if (!testModule.initialize()) { return 1; } - tp::HeapAllocGlobal alloc; + testAll(); - int* val = new(alloc) int(); - delete(alloc, val); + testModule.deinitialize(); } - -struct test_struct { - tp::alni val = 0; - - test_struct() { val = 1; } - ~test_struct() { val = -1; } - - bool operator==(const test_struct& in) { return in.val == val; } -}; - -template -struct allocator_test { - test_struct data[size]; - bool is_allocated[size]; - tp::alni n_loaded = 0; - - test_struct* allocations[size]; - - tp::AbstractAllocator* alloc; - tp::AbstractAllocator* parent_alloc; - const char* allocator_name = NULL; - - tp::alni rand_idx(bool state) { - RAND: - - tp::alni idx = (tp::alni)(tp::randf() * (size + 1)); - CLAMP(idx, 0, size - 1); - - if (state == is_allocated[idx]) { - goto RAND; - } - return idx; - } - - allocator_test(tp::AbstractAllocator* palloc, const char* pallocator_name, tp::AbstractAllocator* p_parent_alloc) { - allocator_name = pallocator_name; - parent_alloc = p_parent_alloc; - alloc = palloc; - for (tp::alni i = 0; i < size; i++) { - RAND: - tp::alni val = tp::alni(tp::randf() * (size + 100.f)); - for (tp::alni check_idx = 0; check_idx < size; check_idx++) { - if (data[check_idx].val == val) { - goto RAND; - } - } - - data[i].val = val; - is_allocated[i] = false; - allocations[i] = 0; - } - } - - void verify_integrity() { - // verify data integrity - for (tp::alni i = 0; i < size; i++) { - if (is_allocated[i]) { - assert(*allocations[i] == data[i] && "data is currupted\n"); - } - } - - if (alloc->isWrapSupport()) assert(!alloc->isWrapCorrupted()); - if (parent_alloc && parent_alloc->isWrapSupport()) - assert(!parent_alloc->isWrapCorrupted()); - - verify_sizes(); - } - - void verify_sizes() { - return; -#ifdef MEM_TRACE - assert(alloc->sizeInuse() == n_loaded * sizeof(test_struct) && - "invalid inuse size\n"); - assert(alloc->sizeReserved() >= n_loaded * (tp::alni)sizeof(test_struct) && - "invalid reserved size\n"); -#endif - } - - void load_item(tp::alni idx) { - if (!is_allocated[idx]) { - allocations[idx] = new (alloc) test_struct(); - - assert(allocations[idx] && "allocator returned NULL"); - - allocations[idx]->val = data[idx].val; - is_allocated[idx] = true; - n_loaded++; - verify_integrity(); - } - } - - void unload_item(tp::alni idx) { - if (is_allocated[idx]) { - verify_integrity(); - delete allocations[idx]; - is_allocated[idx] = false; - n_loaded--; - verify_integrity(); - } - } - - void change_states(tp::Range rg, bool state, bool reversed = false, bool random = false) { - for (auto i : rg) { - tp::alni idx = i; - - if (random) { - idx = rand_idx(state); - } - else if (reversed) { - idx = size - i - 1; - } - - (state) ? load_item(idx) : unload_item(idx); - } - } - - // full down-up load then up-down unload - void test1() { - change_states({ 0, size }, 1); - change_states({ 0, size }, 0, true); - } - - // full down-up load then down-up unload - void test2() { - change_states({ 0, size }, 1); - change_states({ 0, size }, 0); - } - - // full random load then random unload - void test3() { - change_states({ 0, size }, 1, 0, 1); - change_states({ 0, size }, 0, 0, 1); - } - - // multipul tests 1-3 - void test4() { - test1(); - test1(); - - test2(); - test2(); - - test3(); - test3(); - } - - static tp::alnf sineupf(tp::alnf asize, tp::alnf x, bool reverse) { - tp::alnf end = 4 * PI; - tp::alnf a = (2 / 7.f) * asize; - tp::alnf b = end / asize; - - tp::alni c = ((-1 * reverse) + (1 * !reverse)); - tp::alnf c1 = (x - (end * reverse)) / b; - tp::alnf c2 = (a * sin(x - (end * reverse))); - tp::alnf out = c1 + c2; - return c * out; - } - - // sin load & sin unload with ~1/2 drop factor - void test5() { - tp::alnf end = 4 * PI; - tp::alnf step = end / 4.f; - - for (char i = 0; i < 2; i++) { - for (tp::alnf x = 0; x <= end; x += step) { - tp::alni target_alloc_count = (tp::alni)ceil(sineupf(size, x, i)); - CLAMP(target_alloc_count, 0, size); - - while (n_loaded > target_alloc_count) { - unload_item(rand_idx(0)); - } - while (n_loaded < target_alloc_count) { - load_item(rand_idx(1)); - } - } - } - } - -#ifdef MEM_WRAP - void check_wrap(tp::alni offset, bool after) { - CLAMP(offset, 1, WRAP_LEN); - - test_struct* ts = allocations[rand_idx(0)]; - tp::alni shift = (sizeof(test_struct) * after) + (offset - 1) * after - - offset * (!after); - tp::uint1* address = (((tp::uint1*)ts) + shift); - - tp::uint1 val = *address; - *address = 5; - assert(alloc->isWrapCorrupted()); - *address = val; -} -#endif - - // mem guards test - void test6() { - change_states({ 0, size }, 1); - -#ifdef MEM_WRAP - for (tp::alni after = 0; after < 2; after++) { - for (tp::alni offset = 1; offset <= WRAP_LEN; offset++) { - check_wrap(offset, after); - } - } -#endif - - change_states({ 0, size }, 0); - } - - void run_tests() { - try { - test1(); - test2(); - test3(); - test4(); - test5(); - if (alloc->isWrapSupport()) { - test6(); - } - - printf("%s - passed\n", allocator_name); - if (!alloc->isWrapSupport()) { - printf(" WARNING: %s has no wrap support!! \n", allocator_name); - } - } - catch (...) { - printf("%s - failed\n", allocator_name); - } - } -}; - -void heap_alloc_test() { - tp::alloc_init(); { - try { - allocator_test<150> hatest(tp::heapalloc, "heap allocator", NULL); - hatest.run_tests(); - } - catch (...) { - printf("heap alloc failed\n"); - } - } tp::alloc_uninit(); -} - -void chunk_alloc_test() { - tp::alloc_init(); { - try { - tp::ChunkAlloc calloc(sizeof(test_struct), 50); - allocator_test<50> ca_test(&calloc, "chunk allocator", tp::heapalloc); - ca_test.run_tests(); - } - catch (...) { - printf("chunk alloc failed\n"); - } - } tp::alloc_uninit(); -} - -void pool_alloc_test() { - tp::alloc_init(); { - try { - tp::PoolAlloc palloc(sizeof(test_struct), 50); - allocator_test<150> pa_test(&palloc, "pool allocator", tp::heapalloc); - pa_test.run_tests(); - } - catch (...) { - printf("chunk alloc failed\n"); - } - } tp::alloc_uninit(); -} - -void allocators_test() { - printf("running tests on alocators:\n"); - - heap_alloc_test(); - chunk_alloc_test(); - pool_alloc_test(); -} - - -CROSSPLATFORM_MAIN; -int main(int argc, char* argv[]) { - tp::print_env_info(); - allocators_test(); -} \ No newline at end of file diff --git a/Allocators/tests/Tests.hpp b/Allocators/tests/Tests.hpp index e69de29..8345a9d 100644 --- a/Allocators/tests/Tests.hpp +++ b/Allocators/tests/Tests.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "Utils.hpp" +#include "Testing.hpp" + +void testAll(); \ No newline at end of file diff --git a/Allocators/tests/TestsAll.cpp b/Allocators/tests/TestsAll.cpp new file mode 100644 index 0000000..cb90f78 --- /dev/null +++ b/Allocators/tests/TestsAll.cpp @@ -0,0 +1,248 @@ + +#include "Utils.hpp" +#include "Testing.hpp" + +#include "HeapAllocatorGlobal.hpp" +#include "HeapAllocator.hpp" +#include "ChunkAllocator.hpp" +#include "PoolAllocator.hpp" + +#include + +using namespace tp; + +struct TestStruct { + alni val = 0; + + TestStruct() : val(0) {} + explicit TestStruct(alni val) : val(val) {} + TestStruct(const TestStruct& in) : val(in.val) {} + + ~TestStruct() { val = -1; } + + bool operator==(const TestStruct& in) const { + return in.val == val; + } +}; + +template +class TestBenches { + + tAllocator mAlloc{}; + TestStruct mData[tSize]{}; + TestStruct* mLoaded[tSize]{}; + bool mIsLoaded[tSize]{}; + alni mLoadedNum = 0; + +public: + TestBenches() { + for (alni i = 0; i < tSize; i++) { + mData[i].val = i; + mIsLoaded[i] = false; + mLoaded[i] = nullptr; + } + } + + void runTests() { + try { + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + } catch (...) { + ASSERT(false) + } + } + +private: + alni randomIdx(bool state, Range range = { 0, tSize }) { + RAND: + auto idx = alni(alnf(range.idxBegin()) + randomFloat() * alnf(range.idxDiff() + 1)); + idx = clamp(idx, alni(0), tSize - 1); + if (state == mIsLoaded[idx]) goto RAND; + return idx; + } + + void verifyIntegrity() { + mAlloc.checkValid(); + ASSERT(!mAlloc.checkWrap()) + for (alni i = 0; i < tSize; i++) { + if (mIsLoaded[i]) { + ASSERT(*mLoaded[i] == mData[i]) + } + } + } + + void loadItem(alni idx) { + if (mIsLoaded[idx]) return; + verifyIntegrity(); + mLoaded[idx] = new (mAlloc.allocate(sizeof(TestStruct))) TestStruct(mData[idx]); + TEST(mLoaded[idx]); + mIsLoaded[idx] = true; + mLoadedNum++; + verifyIntegrity(); + } + + void unloadItem(alni idx) { + if (!mIsLoaded[idx]) return; + verifyIntegrity(); + mLoaded[idx]->~TestStruct(); + mAlloc.deallocate(mLoaded[idx]); + mIsLoaded[idx] = false; + mLoadedNum--; + verifyIntegrity(); + } + + void changeStates(Range rg, bool load, bool reversed = false, bool random = false) { + for (auto i : rg) { + alni idx = i; + if (random) { + idx = randomIdx(load, rg); + } else if (reversed) { + idx = rg.idxEnd() - i - 1; + } + (load) ? loadItem(idx) : unloadItem(idx); + } + } + + // full down-up load then up-down unload + void test1() { + changeStates({ 0, tSize }, true); + changeStates({ 0, tSize }, false, true); + } + + // full down-up load then down-up unload + void test2() { + changeStates({0, tSize}, true); + changeStates({0, tSize}, false); + } + + // full random load then random unload + void test3() { + changeStates({0, tSize}, true, false, true); + changeStates({0, tSize}, false, false, true); + } + + // combo tests 1-3 + void test4() { + test1(); + test1(); + + test2(); + test2(); + + test3(); + test3(); + } + + static alnf sineUpFunction(alnf aSize, alnf aX, bool aReverse) { + alnf end = 4 * 3.14159; + alnf a = (2 / 7.f) * aSize; + alnf b = end / aSize; + + alni c = ((-1 * aReverse) + (1 * !aReverse)); + alnf c1 = (aX - (end * aReverse)) / b; + alnf c2 = (a * sin(aX - (end * aReverse))); + alnf out = c1 + c2; + return (alnf) c * out; + } + + // sin load & sin unload with ~1/2 drop factor + void test5() { + alnf end = 4 * 3.14159; + alnf step = end / 4.f; + + for (char i = 0; i < 2; i++) { + for (alnf x = 0; x <= end; x += step) { + + alni target_alloc_count = (alni) ceil(sineUpFunction(tSize, x, i)); + target_alloc_count = clamp(target_alloc_count, alni(0), tSize); + + while (mLoadedNum > target_alloc_count) { + unloadItem(randomIdx(0)); + } + while (mLoadedNum < target_alloc_count) { + loadItem(randomIdx(1)); + } + } + } + } + + void checkWrap(ualni offset, bool after) { + offset = clamp(offset, (ualni) 1, (ualni) MEM_WRAP_SIZE); + + TestStruct* ts = mLoaded[randomIdx(0)]; + ualni shift = (sizeof(TestStruct) * after) + (offset - 1) * after - offset * (!after); + uint1* address = (((uint1*)ts) + shift); + + uint1 val = *address; + *address = 5; + + TEST(!mAlloc.checkWrap()); + + *address = val; + } + + // mem guards test + void test6() { + changeStates({0, tSize}, 1); + #ifdef MEM_DEBUG + for (alni after = 0; after < 2; after++) { + for (alni offset = 1; offset <= MEM_WRAP_SIZE; offset++) { + checkWrap(offset, after); + } + } + #endif + changeStates({0, tSize}, 0); + } +}; + +const ualni size = 1000; + +template +void testAlloc() { + try { + TestBenches heapTests{}; + heapTests.runTests(); + } catch (...) { + TEST(false); + } +} + +TEST_DEF_STATIC(GlobalHeap) { + testAlloc(); +} + +TEST_DEF_STATIC(Heap) { + testAlloc(); +} + +TEST_DEF_STATIC(Chunk) { + testAlloc>(); + testAlloc>(); +} + +TEST_DEF_STATIC(Pool) { + testAlloc>(); + testAlloc>(); + testAlloc>(); +} + +TEST_DEF_STATIC(Simple) { + auto a = new TestStruct(-1); + delete a; +} + +TEST_DEF(All) { + testSimple(); + + testGlobalHeap(); + testHeap(); + testChunk(); + testPool(); + + // TEST(HeapAllocGlobal::checkLeaks()); + // TEST(false); +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index e1931f6..d23e6cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,4 +14,4 @@ add_subdirectory(Modules) add_subdirectory(Utils) add_subdirectory(Containers) add_subdirectory(Math) -#add_subdirectory(Allocators) \ No newline at end of file +add_subdirectory(Allocators) \ No newline at end of file diff --git a/Containers/tests/Tests.cpp b/Containers/tests/Tests.cpp index 0cd960f..25a659d 100644 --- a/Containers/tests/Tests.cpp +++ b/Containers/tests/Tests.cpp @@ -26,7 +26,7 @@ tp::ualni TestAllocator::getAllocationsCount() const { int main() { - tp::ModuleManifest* deps[] = { &tp::gModuleUtils, nullptr }; + tp::ModuleManifest* deps[] = { &tp::gModuleContainers, &tp::gModuleUtils, nullptr }; tp::ModuleManifest testModule("ContainersTest", init, nullptr, deps); if (!testModule.initialize()) { diff --git a/Math/public/Mat.hpp b/Math/public/Mat.hpp index d4c8fbb..e5ce928 100644 --- a/Math/public/Mat.hpp +++ b/Math/public/Mat.hpp @@ -34,7 +34,7 @@ namespace tp { Mat& operator=(const Mat& in) { if (&in == this) return *this; - memcp(this, &in, sizeof(Mat)); + memCopy(this, &in, sizeof(Mat)); return *this; } @@ -341,7 +341,7 @@ namespace tp { } Mat& operator=(const Mat& in) { - memcp(this, &in, sizeof(Mat2)); + memCopy(this, &in, sizeof(Mat2)); return *this; } diff --git a/Math/public/Vec.hpp b/Math/public/Vec.hpp index a2301f1..5c165e1 100644 --- a/Math/public/Vec.hpp +++ b/Math/public/Vec.hpp @@ -53,7 +53,7 @@ namespace tp { } Vec(const Vec& in) { - memcp(mBuff, in.mBuff, sizeof(Type) * tSize); + memCopy(mBuff, in.mBuff, sizeof(Type) * tSize); } Type& operator[](ualni i) { diff --git a/Modules/public/Module.hpp b/Modules/public/Module.hpp index da2a79c..d198af2 100644 --- a/Modules/public/Module.hpp +++ b/Modules/public/Module.hpp @@ -4,7 +4,7 @@ #include "Common.hpp" #include "Assert.hpp" -#define MODULE_SANITY_CHECK(name) ASSERT(name.isInitialized() && "Modules Is Not Initialized" && #name) +#define MODULE_SANITY_CHECK(name) DEBUG_ASSERT(name.isInitialized() && "Modules Is Not Initialized" && #name) namespace tp { diff --git a/TODO b/TODO index 7405988..7811a43 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ All: + Testing Serialization Containers: @@ -11,9 +12,6 @@ Containers: Strings: Implement -Math: - Testing - Utils: Stack trace fixes diff --git a/Utils/private/Debugging.cpp b/Utils/private/Debugging.cpp index d0006c2..38aade8 100644 --- a/Utils/private/Debugging.cpp +++ b/Utils/private/Debugging.cpp @@ -9,11 +9,12 @@ using namespace tp; CallStackCapture* tp::gCSCapture = nullptr; void initializeCallStackCapture() { - gCSCapture = new CallStackCapture(); + gCSCapture = new (malloc(sizeof(CallStackCapture))) CallStackCapture(); } void deinitializeCallStackCapture() { - delete gCSCapture; + gCSCapture->~CallStackCapture(); + free(gCSCapture); } ualni CallStackCapture::CallStack::getDepth() const { @@ -198,6 +199,21 @@ void CallStackCapture::platformWriteDebugSymbols(FramePointer frame, DebugSymbol free(symbolsArray); } +void CallStackCapture::printSnapshot(const CallStack* snapshot) { + printf("CallStack: \n"); + for (auto frame : *snapshot) { + auto symbols = gCSCapture->getSymbols(frame.getFrame()); + printf(" %s ----- %s:%llu\n", symbols->getFunc(), symbols->getFile(), symbols->getLine()); + } + printf("\n"); +} + +void CallStackCapture::logLeaks() { + for (auto cs : *this) { + printSnapshot(cs.getCallStack()); + } +} + #else void CallStackCapture::platformWriteStackTrace(CallStack* stack) { stack->frames[0] = 0; } void CallStackCapture::platformWriteDebugSymbols(FramePointer frame, DebugSymbols* out) { diff --git a/Utils/private/Testing.cpp b/Utils/private/Testing.cpp index 323ac07..d2aaa56 100644 --- a/Utils/private/Testing.cpp +++ b/Utils/private/Testing.cpp @@ -11,7 +11,8 @@ Testing tp::gTesting; void Testing::startTest(const char* name) { MODULE_SANITY_CHECK(gModuleUtils) - mCurrent->mSubTests.pushBack(new TestingNode{ {}, {}, name, mCurrent }); + auto newNode = new (malloc(sizeof(TestingNode))) TestingNode{ {}, {}, name, mCurrent }; + mCurrent->mSubTests.pushBack(newNode); mCurrent = mCurrent->mSubTests.last()->data; } @@ -76,6 +77,7 @@ void Testing::TestingNode::report(const char* path) const { Testing::TestingNode::~TestingNode() { for (const auto& child : mSubTests) { - delete child.data(); + child.data()->~TestingNode(); + free(child.data()); } } diff --git a/Utils/private/Utils.cpp b/Utils/private/Utils.cpp index 6dbef24..48e351b 100644 --- a/Utils/private/Utils.cpp +++ b/Utils/private/Utils.cpp @@ -10,12 +10,12 @@ void initializeCallStackCapture(); void deinitializeCallStackCapture(); -static bool initialize(const tp::ModuleManifest* self) { +static bool initialize(const tp::ModuleManifest*) { initializeCallStackCapture(); return true; } -static void deinitialize(const tp::ModuleManifest* self) { +static void deinitialize(const tp::ModuleManifest*) { deinitializeCallStackCapture(); tp::gTesting.reportState(); if (tp::gTesting.hasFailed()) { exit(1); } @@ -26,40 +26,40 @@ namespace tp { static ModuleManifest* sModuleUtilsDeps[] = { &gModuleContainers, nullptr }; ModuleManifest gModuleUtils = ModuleManifest("Utils", initialize, deinitialize, sModuleUtilsDeps); - void memsetv(void* p, uhalni bytesize, uint1 val) { + void memSetVal(void* p, uhalni byteSize, uint1 val) { MODULE_SANITY_CHECK(gModuleBase) - alni alignedval = 0; - for (ualni idx = 0; idx < sizeof(alni); idx++) { - ((uint1*) &alignedval)[idx] = val; + alni alignedVal = val; + alignedVal = (alignedVal << 8) | alignedVal; + alignedVal = (alignedVal << 16) | alignedVal; + alignedVal = (alignedVal << 32) | alignedVal; + + ualni alignedLen = byteSize / sizeof(alni); + for (ualni idx = 0; idx < alignedLen; idx++) { + ((alni*) p)[idx] = alignedVal; } - ualni alignedlen = bytesize / sizeof(alni); - for (ualni idx = 0; idx < alignedlen; idx++) { - ((alni*) p)[idx] = alignedval; - } - - ualni unalignedlen = bytesize - (alignedlen * sizeof(alni)); - for (ualni idx = 0; idx < unalignedlen; idx++) { - ((uint1*) p)[bytesize - idx - 1] = val; + ualni unalignedLen = byteSize - (alignedLen * sizeof(alni)); + for (ualni idx = 0; idx < unalignedLen; idx++) { + ((uint1*) p)[byteSize - idx - 1] = val; } } - void memcp(void* left, const void* right, uhalni len) { + void memCopy(void* left, const void* right, uhalni len) { MODULE_SANITY_CHECK(gModuleBase) - ualni alignedlen = len / sizeof(alni); - for (ualni idx = 0; idx < alignedlen; idx++) { + ualni alignedLen = len / sizeof(alni); + for (ualni idx = 0; idx < alignedLen; idx++) { ((alni*) left)[idx] = ((alni*) right)[idx]; } - ualni unalignedlen = len - (alignedlen * sizeof(alni)); - for (ualni idx = 0; idx < unalignedlen; idx++) { + ualni unalignedLen = len - (alignedLen * sizeof(alni)); + for (ualni idx = 0; idx < unalignedLen; idx++) { ((uint1*) left)[len - idx - 1] = ((uint1*) right)[len - idx - 1]; } } - int1 memecomp(const void* left, const void* right, uhalni len) { + int1 memCompare(const void* left, const void* right, uhalni len) { MODULE_SANITY_CHECK(gModuleBase) if (!len) return 0; @@ -87,7 +87,41 @@ namespace tp { return 0; } - alnf randf() { + + int1 memCompareVal(const void* left, uhalni len, uint1 val) { + MODULE_SANITY_CHECK(gModuleBase) + if (!len) return 0; + + alni valAligned = val; + valAligned = (valAligned << 8) | valAligned; + valAligned = (valAligned << 16) | valAligned; + valAligned = (valAligned << 32) | valAligned; + + ualni alignedLength = len / sizeof(alni); + for (ualni idx = 0; idx < alignedLength; idx++) { + if (((alni*) left)[idx] == valAligned) { + continue; + } + if (((alni*) left)[idx] > valAligned) { + return 1; + } + return -1; + } + + ualni unalignedLength = len - (alignedLength * sizeof(alni)); + for (ualni idx = 0; idx < unalignedLength; idx++) { + if (((uint1*) left)[len - idx - 1] == val) { + continue; + } + if (((uint1*) left)[len - idx - 1] > val) { + return 1; + } + return -1; + } + return 0; + } + + alnf randomFloat() { alnf r = static_cast(std::rand()) / static_cast(RAND_MAX); return r; } diff --git a/Utils/public/Debugging.hpp b/Utils/public/Debugging.hpp index b669fa3..26f0cf8 100644 --- a/Utils/public/Debugging.hpp +++ b/Utils/public/Debugging.hpp @@ -54,6 +54,9 @@ namespace tp { [[nodiscard]] const CallStack* getSnapshot(); const DebugSymbols* getSymbols(FramePointer fp); + static void printSnapshot(const CallStack* snapshot); + void logLeaks(); + public: template diff --git a/Utils/public/Utils.hpp b/Utils/public/Utils.hpp index 79f9935..9dd317a 100644 --- a/Utils/public/Utils.hpp +++ b/Utils/public/Utils.hpp @@ -9,13 +9,14 @@ namespace tp { extern ModuleManifest gModuleUtils; - void memsetv(void* p, uhalni bytesize, uint1 val); - void memcp(void* left, const void* right, uhalni len); - int1 memequal(const void* left, const void* right, uhalni len); + void memSetVal(void* p, uhalni byteSize, uint1 val); + void memCopy(void* left, const void* right, uhalni len); + int1 memCompare(const void* left, const void* right, uhalni len); + int1 memCompareVal(const void* left, uhalni len, uint1 val); } namespace tp { - [[nodiscard]] alnf randf(); + [[nodiscard]] alnf randomFloat(); } namespace tp { @@ -73,6 +74,7 @@ namespace tp { tType idxBegin() const { return mBegin; } tType idxEnd() const { return mEnd; } + tType idxDiff() const { return mEnd - mBegin; } Iterator begin() { return Iterator(mBegin); } Iterator end() { return Iterator(mEnd); } }; diff --git a/Utils/tests/Tests.cpp b/Utils/tests/Tests.cpp index 9073e53..3ac860a 100644 --- a/Utils/tests/Tests.cpp +++ b/Utils/tests/Tests.cpp @@ -47,9 +47,7 @@ void root() { TEST_DEF(Debugging) { root(); - for (auto cs : *gCSCapture) { - printSnapshot(cs.getCallStack()); - } + gCSCapture->logLeaks(); } int main() {