Modules/Allocators/private/HeapAllocatorGlobal.cpp
2024-03-16 12:51:09 +03:00

230 lines
5.3 KiB
C++

#include "HeapAllocatorGlobal.hpp"
#include "PrivateConfig.hpp"
#include "Callstack.hpp"
#include "Utils.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 = false;
#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;
#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