initial not working

This commit is contained in:
Шурупов Илья Викторович 2026-05-14 21:01:12 +03:00
parent 10c1566c6c
commit a72a3646cd
14 changed files with 1148 additions and 18 deletions

15
Allocators/CMakeLists.txt Normal file
View file

@ -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})

36
Allocators/README.md Normal file
View file

@ -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)

View file

@ -0,0 +1,21 @@
#include "Allocators.hpp"
#include "HeapAllocatorGlobal.hpp"
#include <cstdlib>
#include <stdio.h>
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); }

View file

@ -0,0 +1,76 @@
#include "HeapAllocator.hpp"
#include "HeapAllocatorGlobal.hpp"
#include "PrivateConfig.hpp"
#include <malloc.h>
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

View file

@ -0,0 +1,232 @@
#include "HeapAllocatorGlobal.hpp"
#include "PrivateConfig.hpp"
#include "AllocatorsTypes.hpp"
// #include "Callstack.hpp"
#include <cstdio>
#include <cstdlib>
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

View file

@ -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);

View file

@ -0,0 +1,100 @@
#pragma once
#include <cassert>
#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;
}
}
}

View file

@ -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 <typename tType, ualni tNumBlocks>
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; }
};
}

View file

@ -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() {}
};
}

View file

@ -0,0 +1,44 @@
#pragma once
#include "AllocatorsTypes.hpp"
// #include "Callstack.hpp"
#include <mutex>
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() {}
};
}

View file

@ -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 <typename tType, ualni tNumBlocks>
class PoolAlloc {
typedef ChunkAlloc<tType, tNumBlocks> 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]);
}
}
}
}
};
}

View file

@ -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

249
Allocators/tests/Test.cpp Normal file
View file

@ -0,0 +1,249 @@
#include "UnitTest++/UnitTest++.h"
#include "Allocators.hpp"
#include "Utils.hpp"
#include <cmath>
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 <alni tSize, class tAllocator>
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<alni> 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<alni> 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 <typename Alloc>
void testAlloc() {
try {
TestBenches<size, Alloc> heapTests{};
heapTests.runTests();
} catch (...) {
ASSERT(false);
}
}
SUITE(Allocators) {
TEST(GlobalHeap) { testAlloc<HeapAllocGlobal>(); }
TEST(Heap) { testAlloc<tp::HeapAlloc>(); }
TEST(Chunk) {
testAlloc<ChunkAlloc<TestStruct, size>>();
testAlloc<ChunkAlloc<TestStruct, size * 2>>();
}
TEST(Pool) {
testAlloc<PoolAlloc<TestStruct, 1>>();
testAlloc<PoolAlloc<TestStruct, size / 100>>();
testAlloc<PoolAlloc<TestStruct, size>>();
}
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;
}

View file

@ -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)