From a72a3646cd65283d3b367f47ce40354ecd116512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A8=D1=83=D1=80=D1=83=D0=BF=D0=BE=D0=B2=20=D0=98=D0=BB?= =?UTF-8?q?=D1=8C=D1=8F=20=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D1=87?= Date: Thu, 14 May 2026 21:01:12 +0300 Subject: [PATCH] initial not working --- Allocators/CMakeLists.txt | 15 ++ Allocators/README.md | 36 +++ Allocators/private/Allocators.cpp | 21 ++ Allocators/private/HeapAllocator.cpp | 76 +++++++ Allocators/private/HeapAllocatorGlobal.cpp | 232 +++++++++++++++++++ Allocators/public/Allocators.hpp | 23 ++ Allocators/public/AllocatorsTypes.hpp | 100 +++++++++ Allocators/public/ChunkAllocator.hpp | 136 +++++++++++ Allocators/public/HeapAllocator.hpp | 26 +++ Allocators/public/HeapAllocatorGlobal.hpp | 44 ++++ Allocators/public/PoolAllocator.hpp | 161 +++++++++++++ Allocators/public/PrivateConfig.hpp | 9 + Allocators/tests/Test.cpp | 249 +++++++++++++++++++++ CMakeLists.txt | 38 ++-- 14 files changed, 1148 insertions(+), 18 deletions(-) create mode 100644 Allocators/CMakeLists.txt create mode 100644 Allocators/README.md create mode 100644 Allocators/private/Allocators.cpp create mode 100644 Allocators/private/HeapAllocator.cpp create mode 100644 Allocators/private/HeapAllocatorGlobal.cpp create mode 100644 Allocators/public/Allocators.hpp create mode 100644 Allocators/public/AllocatorsTypes.hpp create mode 100644 Allocators/public/ChunkAllocator.hpp create mode 100644 Allocators/public/HeapAllocator.hpp create mode 100644 Allocators/public/HeapAllocatorGlobal.hpp create mode 100644 Allocators/public/PoolAllocator.hpp create mode 100644 Allocators/public/PrivateConfig.hpp create mode 100644 Allocators/tests/Test.cpp diff --git a/Allocators/CMakeLists.txt b/Allocators/CMakeLists.txt new file mode 100644 index 0000000..8b59432 --- /dev/null +++ b/Allocators/CMakeLists.txt @@ -0,0 +1,15 @@ +project(Allocators) + +### ---------------------- Static Library --------------------- ### +file(GLOB SOURCES "./private/*.cpp") +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 Callstack) + +### -------------------------- Tests -------------------------- ### +enable_testing() +file(GLOB TEST_SOURCES "./tests/*.cpp") +add_executable(Tests${PROJECT_NAME} ${TEST_SOURCES}) +target_link_libraries(Tests${PROJECT_NAME} ${PROJECT_NAME} UnitTest++) +add_test(NAME Tests${PROJECT_NAME} COMMAND Tests${PROJECT_NAME}) \ No newline at end of file diff --git a/Allocators/README.md b/Allocators/README.md new file mode 100644 index 0000000..d2beeba --- /dev/null +++ b/Allocators/README.md @@ -0,0 +1,36 @@ +# Profiling + +## Memory Leaks + +Example program with memory leaks: + +```c++ +#include "allocators.h" + +void test_call22() { new int; } +void test_call21() { new float; } + +void test_call11() { + test_call21(); + test_call22(); +} + +int main(char argc, char* argv[]) { + tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleAllocators, NULL }; + tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies); + TestModule.initialize(); + + test_call11(); + + TestModule.deinitialize(); +} + +``` +If memory leaks were detected it will be logged in the output console. + +![image](https://user-images.githubusercontent.com/63184036/222794298-3f238de4-c0b8-41fa-b7ec-c0c675da8f05.png) + +Also debug.memleaks binary will be generated in the working directory that can be viewed with MemLeaks Viewer. + +![image](https://user-images.githubusercontent.com/63184036/222793169-a405effe-72be-42fc-b375-bb06dce0a735.png) + diff --git a/Allocators/private/Allocators.cpp b/Allocators/private/Allocators.cpp new file mode 100644 index 0000000..ab98472 --- /dev/null +++ b/Allocators/private/Allocators.cpp @@ -0,0 +1,21 @@ + +#include "Allocators.hpp" +#include "HeapAllocatorGlobal.hpp" + +#include +#include + +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) 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 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/HeapAllocator.cpp b/Allocators/private/HeapAllocator.cpp new file mode 100644 index 0000000..f4e1cdf --- /dev/null +++ b/Allocators/private/HeapAllocator.cpp @@ -0,0 +1,76 @@ + + +#include "HeapAllocator.hpp" +#include "HeapAllocatorGlobal.hpp" + +#include "PrivateConfig.hpp" + +#include + +using namespace tp; + +#if not defined(MEM_DEBUG) + +// ----------------------- Release Implementation ---------------------------- // +void* HeapAlloc::allocate(ualni aBlockSize) { return malloc(aBlockSize); } +void HeapAlloc::deallocate(void* aPtr) { + if (!aPtr) return; + free(aPtr); +} +HeapAlloc::~HeapAlloc() {} + +#else + +namespace tp { + struct MemHeadLocal { + MemHeadLocal* mPrev; + MemHeadLocal* mNext; + }; +} + +void* HeapAlloc::allocate(ualni aBlockSize) { + auto head = (MemHeadLocal*) HeapAllocGlobal::allocate(aBlockSize + sizeof(MemHeadLocal)); + auto out = head + 1; + + mNumAllocations++; + if (mEntry) { + DEBUG_ASSERT(!mEntry->mNext); + head->mNext = nullptr; + head->mPrev = mEntry; + mEntry->mNext = head; + } else { + head->mNext = nullptr; + head->mPrev = nullptr; + } + mEntry = head; + + return out; +} + +void HeapAlloc::deallocate(void* aPtr) { + if (!aPtr) return; + + auto head = ((MemHeadLocal*) (aPtr)) - 1; + + mNumAllocations--; + DEBUG_ASSERT(!mEntry->mNext); + if (head->mNext) head->mNext->mPrev = head->mPrev; + if (head->mPrev) head->mPrev->mNext = head->mNext; + if (head == mEntry) { + mEntry = head->mPrev; + } + + HeapAllocGlobal::deallocate(head); +} + +HeapAlloc::~HeapAlloc() { + if (mNumAllocations) { + DEBUG_ASSERT(0 && "Destruction of not freed Allocator"); + +#ifdef MEM_STACK_TRACE + // TODO : log leaks and free them up +#endif + } +} + +#endif diff --git a/Allocators/private/HeapAllocatorGlobal.cpp b/Allocators/private/HeapAllocatorGlobal.cpp new file mode 100644 index 0000000..9d2a76e --- /dev/null +++ b/Allocators/private/HeapAllocatorGlobal.cpp @@ -0,0 +1,232 @@ + + +#include "HeapAllocatorGlobal.hpp" +#include "PrivateConfig.hpp" + +#include "AllocatorsTypes.hpp" +// #include "Callstack.hpp" + +#include +#include + +using namespace tp; + +#if not defined(MEM_DEBUG) + +// ----------------------- Release Implementation ---------------------------- // + +void* HeapAllocGlobal::allocate(ualni aBlockSize) { return malloc(aBlockSize); } +void HeapAllocGlobal::deallocate(void* aPtr) { + if (!aPtr) return; + free(aPtr); +} +HeapAllocGlobal::~HeapAllocGlobal() = default; +bool HeapAllocGlobal::checkLeaks() { return false; } +void HeapAllocGlobal::startIgnore() {} +void HeapAllocGlobal::stopIgnore() {} +ualni HeapAllocGlobal::getNAllocations() { return 0; } + +#else + +tp::MemHead* tp::HeapAllocGlobal::mEntry = nullptr; +tp::ualni tp::HeapAllocGlobal::mNumAllocations = 0; +std::mutex tp::HeapAllocGlobal::mMutex; +bool tp::HeapAllocGlobal::mIgnore = true; +bool tp::HeapAllocGlobal::mEnableCallstack = true; + +#ifdef MEM_STACK_TRACE +tp::CallStackCapture tp::HeapAllocGlobal::mCallstack; +#endif + + // ----------------------- Debug Implementation ---------------------------- // +// |----------------| +// | MemHead | +// |----------------| +// | wrap top | +// |----------------| - Allocated Block Layout +// | data | +// |----------------| +// | wrap bottom | +// |----------------| + +namespace tp { + struct MemHead { + MemHead* mPrev; + MemHead* mNext; + uhalni mBlockSize; + uhalni mIgnored; +#ifdef MEM_STACK_TRACE + const CallStackCapture::CallStack* mCallStack; +#else + void* p; +#endif + }; +} + +enum : ualni { + ALIGNED_SIZE = ENV_ALNI_SIZE_B, + WRAP_SIZE = MEM_WRAP_SIZE * ALIGNED_SIZE, + WRAP_VAL = MEM_WRAP_FILL_VAL, + HEAD_SIZE = sizeof(MemHead), + CLEAR_ALLOC_VAL = MEM_CLEAR_ON_ALLOC_VAL, + CLEAR_DEALLOC_VAL = MEM_CLEAR_ON_DEALLOC_VAL, +}; + +void* HeapAllocGlobal::allocate(ualni aBlockSize) { + + static_assert(HEAD_SIZE % ALIGNED_SIZE == 0, "Heap Allocator Configuration Error"); + + if (aBlockSize % ALIGNED_SIZE) { + aBlockSize = (aBlockSize / ALIGNED_SIZE + 1) * ALIGNED_SIZE; + } + +// 1) Allocate the block +ALLOCATE: + auto head = (MemHead*) malloc(aBlockSize + WRAP_SIZE * 2 + HEAD_SIZE); + if (!head) { + printf("WARNING : Cant allocate memory. Trying again\n"); + goto ALLOCATE; // Just freeze if no memory is available + } + + auto wrap_top = (int1*) (head + 1); + auto data = wrap_top + WRAP_SIZE; + auto wrap_bottom = data + aBlockSize; + + head->mBlockSize = aBlockSize; + head->mIgnored = mIgnore; + + // 2) Link with existing blocks + mMutex.lock(); + mNumAllocations++; + if (mEntry) { + DEBUG_ASSERT(mEntry->mNext == nullptr); + head->mNext = nullptr; + head->mPrev = mEntry; + mEntry->mNext = head; + } else { + head->mNext = nullptr; + head->mPrev = nullptr; + } + mEntry = head; + + // 3) Trace the stack +#ifdef MEM_STACK_TRACE + // check if somewhat decides to call new within static variable initialization + head->mCallStack = (mEnableCallstack && mCallstack.initialized) ? mCallstack.getSnapshot() : nullptr; +#endif + + mMutex.unlock(); + + // 4) Wrap fill + memSetVal(wrap_top, WRAP_SIZE, WRAP_VAL); + memSetVal(wrap_bottom, WRAP_SIZE, WRAP_VAL); + + // 5) clear data +#ifdef MEM_CLEAR_ON_ALLOC + memSetVal(data, aBlockSize, CLEAR_ALLOC_VAL); +#endif + + return data; +} + +void HeapAllocGlobal::deallocate(void* aPtr) { + if (!aPtr) return; + + // 1) Restore the pointers + 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 + mMutex.lock(); + mNumAllocations--; + DEBUG_ASSERT(!mEntry->mNext); + if (head->mNext) head->mNext->mPrev = head->mPrev; + if (head->mPrev) head->mPrev->mNext = head->mNext; + if (head == mEntry) { + mEntry = head->mPrev; + } + + if (!head->mIgnored) { + // 3) Check the wrap + if (memCompareVal(wrap_top, WRAP_SIZE, WRAP_VAL)) { +#ifdef MEM_STACK_TRACE + if (head->mCallStack) mCallstack.printSnapshot(head->mCallStack); +#endif + ASSERT(!"Allocated Block Wrap Corrupted!"); + } + + if (memCompareVal(wrap_bottom, WRAP_SIZE, WRAP_VAL)) { +#ifdef MEM_STACK_TRACE + if (head->mCallStack) mCallstack.printSnapshot(head->mCallStack); +#endif + ASSERT(!"Allocated Block Wrap Corrupted!"); + } + + // 4) clear data +#ifdef MEM_CLEAR_ON_ALLOC + memSetVal(data, head->mBlockSize, CLEAR_DEALLOC_VAL); +#endif + } + + mMutex.unlock(); + + // 5) free the block + free(head); +} + +bool HeapAllocGlobal::checkLeaks() { + ualni ignoredCount = 0; + for (auto iter = mEntry; iter; iter = iter->mPrev) { + ignoredCount += iter->mIgnored; + } + + // 1) Check for not deallocated memory + if (mNumAllocations && ignoredCount < mNumAllocations) { + +#ifdef MEM_STACK_TRACE + for (auto iter = mEntry; iter; iter = iter->mPrev) { + if (!iter->mIgnored && iter->mCallStack) mCallstack.printSnapshot(iter->mCallStack); + } +#endif + + printf(" Count : %llu", mNumAllocations - ignoredCount); + + ASSERT(!"Destruction of not freed Allocator"); + return true; + } + return false; +} + +void HeapAllocGlobal::startIgnore() { + mMutex.lock(); + mIgnore = true; + mMutex.unlock(); +} + +void HeapAllocGlobal::stopIgnore() { + mMutex.lock(); + mIgnore = false; + mMutex.unlock(); +} + +ualni HeapAllocGlobal::getNAllocations() { + return mNumAllocations; +} + +void HeapAllocGlobal::enableCallstack() { + mMutex.lock(); + mEnableCallstack = true; + mMutex.unlock(); +} + +void HeapAllocGlobal::disableCallstack() { + mMutex.lock(); + mEnableCallstack = false; + mMutex.unlock(); +} + +HeapAllocGlobal::~HeapAllocGlobal() = default; + +#endif diff --git a/Allocators/public/Allocators.hpp b/Allocators/public/Allocators.hpp new file mode 100644 index 0000000..b03dd39 --- /dev/null +++ b/Allocators/public/Allocators.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "AllocatorsTypes.hpp" + +#include "ChunkAllocator.hpp" +#include "HeapAllocator.hpp" +#include "HeapAllocatorGlobal.hpp" +#include "PoolAllocator.hpp" + +void* operator new(std::size_t aSize); +void* operator new[](std::size_t aSize); +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); +void operator delete(void* aPtr, tp::HeapAlloc& aAlloc); +void operator delete[](void* aPtr, tp::HeapAlloc& aAlloc); + +void* operator new(std::size_t aSize, tp::HeapAllocGlobal& aAlloc); +void* operator new[](std::size_t aSize, tp::HeapAllocGlobal& aAlloc); +void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc); +void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc); \ No newline at end of file diff --git a/Allocators/public/AllocatorsTypes.hpp b/Allocators/public/AllocatorsTypes.hpp new file mode 100644 index 0000000..99b94bd --- /dev/null +++ b/Allocators/public/AllocatorsTypes.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include + +#define DEBUG_ASSERT(x) assert(x) +#define ASSERT(x) assert(x) + +// #define MEM_STACK_TRACE +#define MEM_DEBUG + +namespace tp { + using ualni = unsigned long long int; + using alni = unsigned long long int; + using halnf = float; + using uhalni = unsigned long int; + using int1 = char; + using uint1 = unsigned char; + + enum { + ENV_ALNI_SIZE_B = sizeof(ualni) + }; + + inline int1 memCompare(const void* left, const void* right, uhalni len) { + if (!len) return 0; + + ualni alignedLength = len / sizeof(alni); + for (ualni idx = 0; idx < alignedLength; idx++) { + if (((alni*) left)[idx] == ((alni*) right)[idx]) { + continue; + } + if (((alni*) left)[idx] > ((alni*) right)[idx]) { + return 1; + } + return -1; + } + + ualni unalignedLength = len - (alignedLength * sizeof(alni)); + for (ualni idx = 0; idx < unalignedLength; idx++) { + if (((uint1*) left)[len - idx - 1] == ((uint1*) right)[len - idx - 1]) { + continue; + } + if (((uint1*) left)[len - idx - 1] > ((uint1*) right)[len - idx - 1]) { + return 1; + } + return -1; + } + return 0; + } + + inline bool memEqual(const void* left, const void* right, uhalni len) { return memCompare(left, right, len) == 0; } + + inline int1 memCompareVal(const void* left, uhalni len, uint1 val) { + 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; + } + + inline void memSetVal(void* p, uhalni byteSize, uint1 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 unalignedLen = byteSize - (alignedLen * sizeof(alni)); + for (ualni idx = 0; idx < unalignedLen; idx++) { + ((uint1*) p)[byteSize - idx - 1] = val; + } + } +} diff --git a/Allocators/public/ChunkAllocator.hpp b/Allocators/public/ChunkAllocator.hpp new file mode 100644 index 0000000..6f20eb8 --- /dev/null +++ b/Allocators/public/ChunkAllocator.hpp @@ -0,0 +1,136 @@ +#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 "AllocatorsTypes.hpp" +#include "HeapAllocatorGlobal.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. + template + class ChunkAlloc { + + enum : ualni { + ALIGNED_SIZE = ENV_ALNI_SIZE_B, + + WRAP_SIZE_ALN = MEM_WRAP_SIZE / 2, + 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, + }; + + 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* mNextBlock; + ualni mNumFreeBlocks; + ualni mNumInitBlocks; + ualni mBuff[tNumBlocks * blockSize() * ALIGNED_SIZE]; + + 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 diff --git a/Allocators/public/HeapAllocator.hpp b/Allocators/public/HeapAllocator.hpp new file mode 100644 index 0000000..d51035f --- /dev/null +++ b/Allocators/public/HeapAllocator.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "AllocatorsTypes.hpp" + +namespace tp { + + 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); + + public: + [[nodiscard]] bool checkWrap() const { return false; } + void checkValid() {} + }; +} diff --git a/Allocators/public/HeapAllocatorGlobal.hpp b/Allocators/public/HeapAllocatorGlobal.hpp new file mode 100644 index 0000000..d32a377 --- /dev/null +++ b/Allocators/public/HeapAllocatorGlobal.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "AllocatorsTypes.hpp" + +// #include "Callstack.hpp" + +#include + +namespace tp { + + class HeapAllocGlobal { + +#ifdef MEM_DEBUG + static ualni mNumAllocations; + static struct MemHead* mEntry; + static std::mutex mMutex; + static bool mIgnore; + static bool mEnableCallstack; +#ifdef MEM_STACK_TRACE // Save stack on allocation call + static CallStackCapture mCallstack; +#endif +#endif + + public: + HeapAllocGlobal() = default; + ~HeapAllocGlobal(); + + public: + static void* allocate(ualni aBlockSize); + static void deallocate(void* aPtr); + + static bool checkLeaks(); + static void startIgnore(); + static void stopIgnore(); + static ualni getNAllocations(); + + static void enableCallstack(); + static void disableCallstack(); + + public: + [[nodiscard]] bool checkWrap() const { return false; } + void checkValid() {} + }; +} diff --git a/Allocators/public/PoolAllocator.hpp b/Allocators/public/PoolAllocator.hpp new file mode 100644 index 0000000..1b53267 --- /dev/null +++ b/Allocators/public/PoolAllocator.hpp @@ -0,0 +1,161 @@ +#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 + template + class PoolAlloc { + + typedef ChunkAlloc Chunk; + + struct Chunks { + + 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) { + 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 = 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) { + if (!aPtr) return; + 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/public/PrivateConfig.hpp b/Allocators/public/PrivateConfig.hpp new file mode 100644 index 0000000..e8d735e --- /dev/null +++ b/Allocators/public/PrivateConfig.hpp @@ -0,0 +1,9 @@ +#pragma once + +#define MEM_WRAP_SIZE 8 // Wrapping Size in aligned units +#define MEM_WRAP_FILL_VAL 0xBB // Wrapping Fill Value +#define MEM_CLEAR_ON_ALLOC // Clear data on allocation +#define MEM_CLEAR_ON_DEALLOC // Clear data on free +#define MEM_CLEAR_ON_DEALLOC_VAL 0xAA // Clear data on free +#define MEM_CLEAR_ON_ALLOC_VAL 0xCC // Clear data on free +#define MEM_STACK_TRACE_MAX_DEPTH 32 // Call stack max depth \ No newline at end of file diff --git a/Allocators/tests/Test.cpp b/Allocators/tests/Test.cpp new file mode 100644 index 0000000..4b0563d --- /dev/null +++ b/Allocators/tests/Test.cpp @@ -0,0 +1,249 @@ + +#include "UnitTest++/UnitTest++.h" + +#include "Allocators.hpp" +#include "Utils.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]); + ASSERT(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; + + ASSERT(!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 = 500; + +template +void testAlloc() { + try { + TestBenches heapTests{}; + heapTests.runTests(); + } catch (...) { + ASSERT(false); + } +} + +SUITE(Allocators) { + TEST(GlobalHeap) { testAlloc(); } + + TEST(Heap) { testAlloc(); } + + TEST(Chunk) { + testAlloc>(); + testAlloc>(); + } + + TEST(Pool) { + testAlloc>(); + testAlloc>(); + testAlloc>(); + } + + TEST(Simple) { + auto a = new TestStruct(-1); + delete a; + } +} + + +int main() { + + tp::ModuleManifest* deps[] = { &tp::gModuleAllocators, nullptr }; + tp::ModuleManifest testModule("AllocatorsTest", nullptr, nullptr, deps); + + if (!testModule.initialize()) { + return 1; + } + + bool res = UnitTest::RunAllTests(); + + testModule.deinitialize(); + + return res; +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index bf93755..85deb03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,24 +12,26 @@ include(cmake/ModulesOptions.txt) set(WINDOWS_LIBRARIES "../moduleswindowsl" CACHE STRING "Svn repository with windows libraries https://svn.riouxsvn.com/moduleswindowsl") -include(cmake/FindGLEW.cmake) -include(cmake/FindOIDN.cmake) -include(cmake/FindPortAudio.cmake) +#include(cmake/FindGLEW.cmake) +#include(cmake/FindOIDN.cmake) +#include(cmake/FindPortAudio.cmake) -add_subdirectory(Externals) +#add_subdirectory(Externals) -add_subdirectory(Modules) -add_subdirectory(Containers) -add_subdirectory(Math) +add_subdirectory(Allocators) + +#add_subdirectory(Modules) +#add_subdirectory(Containers) +#add_subdirectory(Math) # add_subdirectory(Language) -add_subdirectory(Connection) -add_subdirectory(Graphics) -add_subdirectory(DataAnalysis) -add_subdirectory(Objects) -add_subdirectory(Widgets) -add_subdirectory(LibraryViewer) -add_subdirectory(RasterRender) -add_subdirectory(3DScene) -add_subdirectory(RayTracer) -add_subdirectory(Sketch3D) -add_subdirectory(3DEditor) +#add_subdirectory(Connection) +#add_subdirectory(Graphics) +#add_subdirectory(DataAnalysis) +#add_subdirectory(Objects) +#add_subdirectory(Widgets) +#add_subdirectory(LibraryViewer) +#add_subdirectory(RasterRender) +#add_subdirectory(3DScene) +#add_subdirectory(RayTracer) +#add_subdirectory(Sketch3D) +#add_subdirectory(3DEditor)