From 8c812cb44896b85e4dac41d6bb6a2684c6dedd34 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 4 May 2025 10:16:49 +0100 Subject: [PATCH] initial commit This change makes the memory manager work again for detecting leaks, the built in one kept coming into de-ref and other bugs so this is the start of a refactor to get it working. --- Engine/source/app/mainLoop.cpp | 34 - Engine/source/main/main.cpp | 11 +- Engine/source/platform/platformMemory.cpp | 1758 ++--------------- Engine/source/platform/platformMemory.h | 37 +- Engine/source/ts/assimp/assimpAppMaterial.cpp | 11 + Engine/source/ts/assimp/assimpAppMaterial.h | 12 + Engine/source/ts/assimp/assimpAppMesh.cpp | 12 + Engine/source/ts/assimp/assimpAppNode.cpp | 13 + Engine/source/ts/assimp/assimpAppNode.h | 12 + Engine/source/ts/assimp/assimpAppSequence.h | 12 + Engine/source/ts/assimp/assimpShapeLoader.cpp | 11 + 11 files changed, 258 insertions(+), 1665 deletions(-) diff --git a/Engine/source/app/mainLoop.cpp b/Engine/source/app/mainLoop.cpp index 2acb64845..0d3c77c41 100644 --- a/Engine/source/app/mainLoop.cpp +++ b/Engine/source/app/mainLoop.cpp @@ -114,29 +114,6 @@ namespace engineAPI } - -// The following are some tricks to make the memory leak checker run after global -// dtors have executed by placing some code in the termination segments. - -#if defined( TORQUE_DEBUG ) && !defined( TORQUE_DISABLE_MEMORY_MANAGER ) - - #ifdef TORQUE_COMPILER_VISUALC - # pragma data_seg( ".CRT$XTU" ) - - static void* sCheckMemBeforeTermination = &Memory::ensureAllFreed; - - # pragma data_seg() - #elif defined( TORQUE_COMPILER_GCC ) - - __attribute__ ( ( destructor ) ) static void _ensureAllFreed() - { - Memory::ensureAllFreed(); - } - - #endif - -#endif - // Process a time event and update all sub-processes void processTimeEvent(S32 elapsedTime) { @@ -216,10 +193,6 @@ void StandardMainLoop::init() gStartupTimer = PlatformTimer::create(); #endif - #ifdef TORQUE_DEBUG_GUARD - Memory::flagCurrentAllocs( Memory::FLAG_Global ); - #endif - Platform::setMathControlStateKnown(); // Asserts should be created FIRST @@ -327,10 +300,6 @@ void StandardMainLoop::init() // Hook in for UDP notification Net::getPacketReceiveEvent().notify(GNet, &NetInterface::processPacketReceiveEvent); - - #ifdef TORQUE_DEBUG_GUARD - Memory::flagCurrentAllocs( Memory::FLAG_Static ); - #endif } void StandardMainLoop::shutdown() @@ -378,9 +347,6 @@ void StandardMainLoop::shutdown() // asserts should be destroyed LAST PlatformAssert::destroy(); -#if defined( TORQUE_DEBUG ) && !defined( TORQUE_DISABLE_MEMORY_MANAGER ) - Memory::validate(); -#endif } void StandardMainLoop::preShutdown() diff --git a/Engine/source/main/main.cpp b/Engine/source/main/main.cpp index dc2e19c19..826f14d16 100644 --- a/Engine/source/main/main.cpp +++ b/Engine/source/main/main.cpp @@ -205,6 +205,7 @@ int main(int argc, const char **argv) #include "platform/platform.h" #include "app/mainLoop.h" #include "T3D/gameFunctions.h" +#include "platform/platformMemory.h" #if defined(WIN32) || defined(_WIN32) //tell switchable graphics supported systems that they need to use the beefier GPU @@ -230,8 +231,9 @@ S32 TorqueMain(S32 argc, const char **argv) // argv = argvFake; // } - // Memory::enableLogging("testMem.log"); - // Memory::setBreakAlloc(104717); +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) + Memory::init(); +#endif // Initialize the subsystems. StandardMainLoop::init(); @@ -254,6 +256,11 @@ S32 TorqueMain(S32 argc, const char **argv) if( StandardMainLoop::requiresRestart() ) Platform::restartInstance(); + +#if defined( TORQUE_DEBUG ) && !defined( TORQUE_DISABLE_MEMORY_MANAGER ) + Memory::shutdown(); +#endif + // Return. return StandardMainLoop::getReturnStatus(); } diff --git a/Engine/source/platform/platformMemory.cpp b/Engine/source/platform/platformMemory.cpp index f10113308..33d0a1336 100644 --- a/Engine/source/platform/platformMemory.cpp +++ b/Engine/source/platform/platformMemory.cpp @@ -30,6 +30,14 @@ #include "platform/threads/mutex.h" #include "core/module.h" +#ifdef _WIN32 +#include +#include +#pragma comment(lib, "Dbghelp.lib") +#else +#include +#endif + // If profile paths are enabled, disable profiling of the // memory manager as that would cause a cyclic dependency // through the string table's allocation stuff used by the @@ -45,7 +53,7 @@ #endif #ifdef TORQUE_MULTITHREAD -void * gMemMutex = NULL; +void* gMemMutex = NULL; #endif //-------------------------------------- Make sure we don't have the define set @@ -53,1663 +61,213 @@ void * gMemMutex = NULL; #undef new #endif -enum MemConstants : U32 -{ - Allocated = BIT(0), - Array = BIT(1), - DebugFlag = BIT(2), - Reallocated = BIT(3), /// This flag is set if the memory has been allocated, then 'realloc' is called - GlobalFlag = BIT(4), - StaticFlag = BIT(5), - AllocatedGuard = 0xCEDEFEDE, - FreeGuard = 0x5555FFFF, - MaxAllocationAmount = 0xFFFFFFFF, - TreeNodeAllocCount = 2048, -}; - -inline U32 flagToBit( Memory::EFlag flag ) -{ - using namespace Memory; - - U32 bit = 0; - switch( flag ) - { - case FLAG_Debug: bit = DebugFlag; break; - case FLAG_Global: bit = GlobalFlag; break; - case FLAG_Static: bit = StaticFlag; break; - } - return bit; -} - -enum RedBlackTokens { - Red = 0, - Black = 1 -}; - -static U32 MinPageSize = 8 * 1024 * 1024; - -#if !defined(TORQUE_SHIPPING) && defined(TORQUE_DEBUG_GUARD) -#define LOG_PAGE_ALLOCS -#endif - -U32 gNewNewTotal = 0; -U32 gImageAlloc = 0; - //--------------------------------------------------------------------------- namespace Memory { - -ConsoleFunctionGroupBegin( Memory, "Memory manager utility functions."); - -struct FreeHeader; - -/// Red/Black Tree Node - used to store queues of free blocks -struct TreeNode -{ - U32 size; - TreeNode *parent; - TreeNode *left; - TreeNode *right; - U32 color; - FreeHeader *queueHead; - FreeHeader *queueTail; - U32 unused; -}; - -struct Header -{ - // doubly linked list of allocated and free blocks - - // contiguous in memory. -#ifdef TORQUE_DEBUG_GUARD - U32 preguard[4]; -#endif - Header *next; - Header *prev; - dsize_t size; - U32 flags; -#ifdef TORQUE_DEBUG_GUARD - #ifdef TORQUE_ENABLE_PROFILE_PATH - U32 unused[5]; - #else - U32 unused[4]; - #endif - U32 postguard[4]; -#endif -}; - -struct AllocatedHeader -{ -#ifdef TORQUE_DEBUG_GUARD - U32 preguard[4]; -#endif - Header *next; - Header *prev; - dsize_t size; - U32 flags; - -#ifdef TORQUE_DEBUG_GUARD - // an allocated header will only have this stuff if TORQUE_DEBUG_GUARD - U32 line; - U32 allocNum; - const char *fileName; - #ifdef TORQUE_ENABLE_PROFILE_PATH - const char * profilePath; - #endif - U32 realSize; - U32 postguard[4]; -#endif - - void* getUserPtr() - { - return ( this + 1 ); - } -}; - -struct FreeHeader -{ -#ifdef TORQUE_DEBUG_GUARD - U32 preguard[4]; -#endif - Header *next; - Header *prev; - dsize_t size; - U32 flags; - -// since a free header has at least one cache line (16 bytes) -// we can tag some more stuff on: - - FreeHeader *nextQueue; // of the same size - FreeHeader *prevQueue; // doubly linked - TreeNode *treeNode; // which tree node we're coming off of. - U32 guard; -#ifdef TORQUE_DEBUG_GUARD - #ifdef TORQUE_ENABLE_PROFILE_PATH - U32 unused; - #endif - U32 postguard[4]; -#endif -}; - -struct PageRecord -{ - dsize_t allocSize; - PageRecord *prevPage; - Header *headerList; // if headerList is NULL, this is a treeNode page - void *basePtr; - U32 unused[4]; // even out the record to 32 bytes... - // so if we're on a 32-byte cache-line comp, the tree nodes - // will cache better -}; - -PageRecord *gPageList = NULL; -TreeNode nil; -TreeNode *NIL = &nil; -TreeNode *gFreeTreeRoot = &nil; -TreeNode *gTreeFreeList = NULL; - -U32 gInsertCount = 0; -U32 gRemoveCount = 0; -U32 gBreakAlloc = 0xFFFFFFFF; -U32 gCurrAlloc = 0; -char gLogFilename[256] = "memlog.txt"; -bool gEnableLogging = false; -bool gNeverLogLeaks = 0; -bool gAlwaysLogLeaks = 0; -U32 gBytesAllocated = 0; -U32 gBlocksAllocated = 0; -U32 gPageBytesAllocated = 0; - -struct HeapIterator -{ - PageRecord* mCurrentPage; - Header* mCurrentHeader; - bool mAllocatedOnly; - - HeapIterator( bool allocatedOnly = true ) - : mCurrentPage( gPageList ), - mCurrentHeader( NULL ), - mAllocatedOnly( allocatedOnly ) - { - if( mCurrentPage ) - { - mCurrentHeader = mCurrentPage->headerList; - while( !mCurrentHeader && mCurrentPage ) - { - mCurrentPage = mCurrentPage->prevPage; - mCurrentHeader = mCurrentPage->headerList; - } - - if( mCurrentHeader && mAllocatedOnly && !( mCurrentHeader->flags & Allocated ) ) - ++ ( *this ); // Advance to first allocated record. - } - } - - bool isValid() const - { - return ( mCurrentHeader != NULL ); - } - HeapIterator& operator ++() - { - do - { - if( !mCurrentHeader ) - { - if( mCurrentPage ) - mCurrentPage = mCurrentPage->prevPage; - - if( !mCurrentPage ) - break; - mCurrentHeader = mCurrentPage->headerList; - } - else - mCurrentHeader = mCurrentHeader->next; - } - while( !mCurrentHeader || ( mAllocatedOnly && !( mCurrentHeader->flags & Allocated ) ) ); - - return *this; - } - operator Header*() const - { - return mCurrentHeader; - } - Header* operator *() const - { - return mCurrentHeader; - } - Header* operator ->() const - { - return mCurrentHeader; - } -}; - -#ifdef TORQUE_DEBUG_GUARD - -static bool checkGuard(Header *header, bool alloc) -{ - U32 guardVal = alloc ? AllocatedGuard : FreeGuard; - for(U32 i = 0; i < 4; i++) - if(header->preguard[i] != guardVal || header->postguard[i] != guardVal) - { - Platform::debugBreak(); - return false; - } - - return true; -} - #if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void setGuard(Header *header, bool alloc) -{ - U32 guardVal = alloc ? AllocatedGuard : FreeGuard; - for(U32 i = 0; i < 4; i++) - header->preguard[i] = header->postguard[i] = guardVal; -} -#endif // !defined(TORQUE_DISABLE_MEMORY_MANAGER) -#endif // TORQUE_DEBUG_GUARD -static void memoryError() -{ - // free all the pages - PageRecord *walk = gPageList; - while(walk) { - PageRecord *prev = walk->prevPage; - dRealFree(walk); - walk = prev; - } - AssertFatal(false, "Error allocating memory! Shutting down."); - Platform::AlertOK("Torque Memory Error", "Error allocating memory. Shutting down.\n"); - Platform::forceShutdown(-1); -} + static const U32 MaxAllocs = 10240; + static MemInfo allocList[MaxAllocs]; + static U32 allocCount = 0; + static U32 currentAllocId = 0; + static bool initialized = false; + char gLogFilename[256] = "memlog.txt"; + bool gStackTrace = false; -PageRecord *allocPage(dsize_t pageSize) -{ - pageSize += sizeof(PageRecord); - void* base = dRealMalloc(pageSize); - if (base == NULL) - memoryError(); - - PageRecord *rec = (PageRecord *) base; - rec->basePtr = (void *) (rec + 1); - rec->allocSize = pageSize; - rec->prevPage = gPageList; - gPageList = rec; - rec->headerList = NULL; - return rec; -} - -TreeNode *allocTreeNode() -{ - if(!gTreeFreeList) + void init() { - PageRecord *newPage = allocPage(TreeNodeAllocCount * sizeof(TreeNode)); - TreeNode *walk = (TreeNode *) newPage->basePtr; - U32 i; - gTreeFreeList = walk; - for(i = 0; i < TreeNodeAllocCount - 1; i++, walk++) - walk->parent = walk + 1; - walk->parent = NULL; - } - TreeNode *ret = gTreeFreeList; - gTreeFreeList = ret->parent; - return ret; -} + if (initialized) return; + std::memset(allocList, 0, sizeof(allocList)); + allocCount = 0; + currentAllocId = 0; + initialized = true; -void freeTreeNode(TreeNode *tn) -{ - tn->parent = gTreeFreeList; - gTreeFreeList = tn; -} - - -static U32 validateTreeRecurse(TreeNode *tree) -{ - if(tree == NIL) - return 1; - // check my left tree - S32 lcount, rcount, nc = 0; - - if(tree->color == Red) - { - if(tree->left->color == Red || tree->right->color == Red) - Platform::debugBreak(); - } - else - nc = 1; - - FreeHeader *walk = tree->queueHead; - if(!walk) - Platform::debugBreak(); - - FreeHeader *prev = NULL; - while(walk) - { - if(walk->prevQueue != prev) - Platform::debugBreak(); - if(walk->treeNode != tree) - Platform::debugBreak(); - if(walk->size != tree->size) - Platform::debugBreak(); - if(!walk->nextQueue && walk != tree->queueTail) - Platform::debugBreak(); - prev = walk; - walk = walk->nextQueue; } - lcount = validateTreeRecurse(tree->left); - rcount = validateTreeRecurse(tree->right); - if(lcount != rcount) - Platform::debugBreak(); - return lcount + nc; -} - -static void validateParentageRecurse(TreeNode *tree) -{ - if(tree->left != NIL) + void shutdown() { - if(tree->left->parent != tree) - Platform::debugBreak(); + if (!initialized) return; - if(tree->left->size > tree->size) - Platform::debugBreak(); - validateParentageRecurse(tree->left); - } - if(tree->right != NIL) - { - if(tree->right->parent != tree) - Platform::debugBreak(); - - if(tree->right->size < tree->size) - Platform::debugBreak(); - validateParentageRecurse(tree->right); - } -} - -static void validateTree() -{ - if(gFreeTreeRoot == NIL) - return; - validateParentageRecurse(gFreeTreeRoot); - validateTreeRecurse(gFreeTreeRoot); -} - -void validate() -{ -#ifdef TORQUE_MULTITHREAD - if(!gMemMutex) - gMemMutex = Mutex::createMutex(); - - Mutex::lockMutex(gMemMutex); -#endif - - - // first validate the free tree: - validateTree(); - // now validate all blocks: - for(PageRecord *list = gPageList; list; list = list->prevPage) - { - Header *prev = NULL; - for(Header *walk = list->headerList; walk; walk = walk->next) - { -#ifdef TORQUE_DEBUG_GUARD - checkGuard(walk, walk->flags & Allocated); -#endif - if(walk->prev != prev) - Platform::debugBreak(); - prev = walk; - if(walk->next && ((const char *)(walk->next) != (const char *)(walk) + sizeof(Header) + walk->size)) - Platform::debugBreak(); - } - } - -#ifdef TORQUE_MULTITHREAD - Mutex::unlockMutex(gMemMutex); -#endif -} - -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void rotateLeft(TreeNode *hdr) -{ - TreeNode *temp = hdr->right; - hdr->right = temp->left; - if(temp->left != NIL) - temp->left->parent = hdr; - temp->parent = hdr->parent; - if(temp->parent == NIL) - gFreeTreeRoot = temp; - else if(hdr == hdr->parent->left) - hdr->parent->left = temp; - else - hdr->parent->right = temp; - temp->left = hdr; - hdr->parent = temp; -} -#endif - -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void rotateRight(TreeNode *hdr) -{ - TreeNode *temp = hdr->left; - hdr->left = temp->right; - if(temp->right != NIL) - temp->right->parent = hdr; - temp->parent = hdr->parent; - if(temp->parent == NIL) - gFreeTreeRoot = temp; - else if(hdr == hdr->parent->left) - hdr->parent->left = temp; - else - hdr->parent->right = temp; - temp->right = hdr; - hdr->parent = temp; -} -#endif - -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void treeInsert(FreeHeader *fhdr) -{ -#ifdef TORQUE_DEBUG_GUARD - checkGuard((Header *) fhdr, true); // check to see that it's got allocated guards - setGuard((Header *) fhdr, false); -#endif - //gInsertCount++; - - TreeNode *newParent = NIL; - TreeNode *walk = gFreeTreeRoot; - while(walk != NIL) - { - newParent = walk; - if(fhdr->size < walk->size) - walk = walk->left; - else if(fhdr->size > walk->size) - walk = walk->right; - else // tag it on the end of the queue... - { - // insert it on this header... - walk->queueTail->nextQueue = fhdr; - fhdr->prevQueue = walk->queueTail; - walk->queueTail = fhdr; - fhdr->nextQueue = NULL; - fhdr->treeNode = walk; + FILE* log = std::fopen(gLogFilename, "w"); + if (!log) return; - } - } - TreeNode *hdr = allocTreeNode(); - hdr->size = fhdr->size; - hdr->queueHead = hdr->queueTail = fhdr; - fhdr->nextQueue = fhdr->prevQueue = NULL; - fhdr->treeNode = hdr; - hdr->left = NIL; - hdr->right = NIL; - - hdr->parent = newParent; - - if(newParent == NIL) - gFreeTreeRoot = hdr; - else if(hdr->size < newParent->size) - newParent->left = hdr; - else - newParent->right = hdr; - - // do red/black rotations - hdr->color = Red; - while(hdr != gFreeTreeRoot && (hdr->parent->color == Red)) - { - TreeNode *parent = hdr->parent; - TreeNode *pparent = hdr->parent->parent; - - if(parent == pparent->left) + std::fprintf(log, "\n--- Memory Leak Report ---\n"); + for (U32 i = 0; i < allocCount; ++i) { - TreeNode *temp = pparent->right; - if(temp->color == Red) + if (allocList[i].ptr != nullptr) { - parent->color = Black; - temp->color = Black; - pparent->color = Red; - hdr = pparent; - } - else - { - if(hdr == parent->right) + std::fprintf(log, "Leak: %p (%zu bytes) from %s:%u [id=%u]\n", + allocList[i].ptr, allocList[i].size, + allocList[i].file ? allocList[i].file : "(null)", allocList[i].line, + allocList[i].allocId); + if (gStackTrace) { - hdr = parent; - rotateLeft(hdr); - parent = hdr->parent; - pparent = hdr->parent->parent; - } - parent->color = Black; - pparent->color = Red; - rotateRight(pparent); - } - } - else - { - TreeNode *temp = pparent->left; - if(temp->color == Red) - { - parent->color = Black; - temp->color = Black; - pparent->color = Red; - hdr = pparent; - } - else - { - if(hdr == parent->left) - { - hdr = parent; - rotateRight(hdr); - parent = hdr->parent; - pparent = hdr->parent->parent; - } - parent->color = Black; - pparent->color = Red; - rotateLeft(pparent); - } - } - } - gFreeTreeRoot->color = Black; - //validateTree(); -} -#endif +#ifdef _WIN32 + SYMBOL_INFO* symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO) + 256); + symbol->MaxNameLen = 255; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void treeRemove(FreeHeader *hdr) -{ -#ifdef TORQUE_DEBUG_GUARD - checkGuard((Header *) hdr, false); - setGuard((Header *) hdr, true); -#endif - //validateTree(); - //gRemoveCount++; + HANDLE process = GetCurrentProcess(); + SymInitialize(process, NULL, TRUE); - FreeHeader *prev = hdr->prevQueue; - FreeHeader *next = hdr->nextQueue; - - if(prev) - prev->nextQueue = next; - else - hdr->treeNode->queueHead = next; - - if(next) - next->prevQueue = prev; - else - hdr->treeNode->queueTail = prev; - - if(prev || next) - return; - - TreeNode *z = hdr->treeNode; - - nil.color = Black; - - TreeNode *y, *x; - if(z->left == NIL || z->right == NIL) - y = z; - else - { - y = z->right; - while(y->left != NIL) - y = y->left; - } - if(y->left != NIL) - x = y->left; - else - x = y->right; - - x->parent = y->parent; - if(y->parent == NIL) - gFreeTreeRoot = x; - else if(y == y->parent->left) - y->parent->left = x; - else - y->parent->right = x; - - U32 yColor = y->color; - if(y != z) - { - // copy y's important fields into z (since we're going to free y) - if(z->parent->left == z) - z->parent->left = y; - else if(z->parent->right == z) - z->parent->right = y; - y->left = z->left; - y->right = z->right; - if(y->left != NIL) - y->left->parent = y; - if(y->right != NIL) - y->right->parent = y; - y->parent = z->parent; - if(z->parent == NIL) - gFreeTreeRoot = y; - y->color = z->color; - if(x->parent == z) - x->parent = y; - //validateTree(); - } - freeTreeNode(z); - - if(yColor == Black) - { - while(x != gFreeTreeRoot && x->color == Black) - { - TreeNode *w; - if(x == x->parent->left) - { - w = x->parent->right; - if(w->color == Red) - { - w->color = Black; - x->parent->color = Red; - rotateLeft(x->parent); - w = x->parent->right; - } - if(w->left->color == Black && w->right->color == Black) - { - w->color = Red; - x = x->parent; - } - else - { - if(w->right->color == Black) + for (int j = 0; j < allocList[i].backtraceSize; ++j) { - w->left->color = Black; - rotateRight(w); - w = x->parent->right; + DWORD64 addr = (DWORD64)(allocList[i].backtracePtrs[j]); + if (SymFromAddr(process, addr, 0, symbol)) + { + std::fprintf(log, " [%d] %s - 0x%0llX\n", j, symbol->Name, symbol->Address); + } + else + { + std::fprintf(log, " [%d] ??? - 0x%0llX\n", j, addr); + } } - w->color = x->parent->color; - x->parent->color = Black; - w->right->color = Black; - rotateLeft(x->parent); - x = gFreeTreeRoot; - } - } - else - { - w = x->parent->left; - if(w->color == Red) - { - w->color = Black; - x->parent->color = Red; - rotateRight(x->parent); - w = x->parent->left; - } - if(w->left->color == Black && w->right->color == Black) - { - w->color = Red; - x = x->parent; - } - else - { - if(w->left->color == Black) + + std::free(symbol); +#else + char** symbols = backtrace_symbols(allocList[i].backtracePtrs, allocList[i].backtraceSize); + for (int j = 0; j < allocList[i].backtraceSize; ++j) { - w->right->color = Black; - rotateLeft(w); - w = x->parent->left; + std::fprintf(log, " [%d] %s\n", j, symbols[j]); } - w->color = x->parent->color; - x->parent->color = Black; - w->left->color = Black; - rotateRight(x->parent); - x = gFreeTreeRoot; + std::free(symbols); +#endif } } } - x->color = Black; + std::fclose(log); + initialized = false; } - //validateTree(); -} -#endif -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static FreeHeader *treeFindSmallestGreaterThan(dsize_t size) -{ - TreeNode *bestMatch = NIL; - TreeNode *walk = gFreeTreeRoot; - while(walk != NIL) + void checkPtr(void* ptr) { - if(size == walk->size) - return walk->queueHead; - else if(size > walk->size) - walk = walk->right; - else // size < walk->size + for (U32 i = 0; i < allocCount; ++i) + if (allocList[i].ptr == ptr) + return; + + Platform::debugBreak(); + } + + static void* alloc(dsize_t size, bool array, const char* fileName, U32 line) + { + if (size == 0) + return nullptr; + + void* ptr = std::malloc(size); + if (!ptr) + return nullptr; + + if (!initialized || allocCount >= MaxAllocs) + return ptr; + + MemInfo& info = allocList[allocCount++]; + info.ptr = ptr; + info.size = size; + info.file = fileName ? fileName : "unknown"; + info.line = line; + info.allocId = currentAllocId++; + info.flagged = false; + + if (gStackTrace) { - bestMatch = walk; - walk = walk->left; +#ifdef _WIN32 + info.backtraceSize = CaptureStackBackTrace(0, 16, info.backtracePtrs, nullptr); +#else + info.backtraceSize = backtrace(info.backtracePtrs, MaxBacktraceDepth); +#endif } + + return ptr; } - //validateTree(); - if(bestMatch != NIL) - return bestMatch->queueHead; - return NULL; -} -#endif - -/// Trigger a breakpoint if ptr is not a valid heap pointer or if its memory guards -/// have been destroyed (only if TORQUE_DEBUG_GUARD is enabled). -/// -/// @note This function does not allow interior pointers! - -void checkPtr( void* ptr ) -{ - for( HeapIterator iter; iter.isValid(); ++ iter ) + static void free(void* ptr, bool array) { - AllocatedHeader* header = ( AllocatedHeader* ) *iter; - if( header->getUserPtr() == ptr ) + if (!ptr || !initialized) + return; + + + for (U32 i = 0; i < allocCount; ++i) { -#ifdef TORQUE_DEBUG_GUARD - char buffer[ 1024 ]; - if( !checkGuard( *iter, true ) ) + if (allocList[i].ptr == ptr) { - dSprintf( buffer, sizeof( buffer ), "0x%x is a valid heap pointer but has its guards corrupted", ptr ); - Platform::outputDebugString( buffer ); + std::free(ptr); + allocList[i] = allocList[allocCount - 1]; + allocList[--allocCount] = {}; return; } - //dSprintf( buffer, sizeof( buffer ), "0x%x is a valid heap pointer", ptr ); - //Platform::outputDebugString( buffer ); -#endif - return; } + + // Unknown pointer, still free it. + std::free(ptr); } - char buffer[ 1024 ]; - dSprintf( buffer, sizeof( buffer ), "0x%x is not a valid heap pointer", ptr ); - Platform::outputDebugString( buffer ); - - Platform::debugBreak(); -} - -/// Dump info on all memory blocks that are still allocated. -/// @note Only works if TORQUE_DISABLE_MEMORY_MANAGER is not defined; otherwise this is a NOP. - -void ensureAllFreed() -{ -#ifndef TORQUE_DISABLE_MEMORY_MANAGER - - U32 numLeaks = 0; - U32 bytesLeaked = 0; - - for( HeapIterator iter; iter.isValid(); ++ iter ) + static void* realloc(void* oldPtr, dsize_t newSize, const char* fileName, U32 line) { - AllocatedHeader* header = ( AllocatedHeader* ) *iter; - if( !( header->flags & GlobalFlag ) ) + if (!initialized) + return std::realloc(oldPtr, newSize); // fallback if not tracking + + if (newSize == 0) { - // Note: can't spill profile paths here since they by - // now are all invalid (they're on the now freed string table) - -#ifdef TORQUE_DEBUG_GUARD - Platform::outputDebugString( "MEMORY LEAKED: 0x%x %i %s %s:%i = %i (%i)", - header->getUserPtr(), - header->allocNum, - ( header->flags & StaticFlag ? "(static)" : "" ), - header->fileName, header->line, header->realSize, header->size ); - numLeaks ++; - bytesLeaked += header->size; -#endif + free(oldPtr, false); + return nullptr; } - } - if( numLeaks ) - Platform::outputDebugString( "NUM LEAKS: %i (%i bytes)", numLeaks, bytesLeaked ); -#endif -} + if (oldPtr == nullptr) + return alloc(newSize, false, fileName, line); -struct MemDumpLog -{ - U32 size; - U32 count; - U32 depthTotal; - U32 maxDepth; - U32 minDepth; -}; -void logDumpTraverse(MemDumpLog *sizes, TreeNode *header, U32 depth) -{ - if(header == NIL) - return; - MemDumpLog *mySize = sizes; - while(mySize->size < header->size) - mySize++; + void* newPtr = std::realloc(oldPtr, newSize); + if (!newPtr) + return nullptr; - U32 cnt = 0; - for(FreeHeader *walk = header->queueHead; walk; walk = walk->nextQueue) - cnt++; - mySize->count += cnt; - mySize->depthTotal += depth * cnt; - mySize->maxDepth = depth > mySize->maxDepth ? depth : mySize->maxDepth; - mySize->minDepth = depth < mySize->minDepth ? depth : mySize->minDepth; - logDumpTraverse(sizes, header->left, depth + 1); - logDumpTraverse(sizes, header->right, depth + 1); -} -#ifdef TORQUE_DEBUG -DefineEngineFunction( validateMemory, void, ( ),, - "@brief Used to validate memory space for the game.\n\n" - "@ingroup Debugging" ) -{ - validate(); -} -#endif - -DefineEngineFunction( freeMemoryDump, void, ( ),, - "@brief Dumps some useful statistics regarding free memory.\n\n" - "Dumps an analysis of \'free chunks\' of memory. " - "Does not print how much memory is free.\n\n" - "@ingroup Debugging" ) -{ - U32 startSize = 16; - MemDumpLog memSizes[20]; - U32 i; - for(i = 0; i < 20; i++) - { - memSizes[i].size = startSize << i; - memSizes[i].count = 0; - memSizes[i].depthTotal = 0; - memSizes[i].maxDepth = 0; - memSizes[i].minDepth = 1000; - } - memSizes[19].size = MaxAllocationAmount; - logDumpTraverse(memSizes, gFreeTreeRoot, 1); - MemDumpLog fullMem; - fullMem.count = 0; - fullMem.depthTotal = 0; - fullMem.maxDepth = 0; - fullMem.minDepth = 1000; - - for(i = 0; i < 20; i++) - { - if(memSizes[i].count) - Con::printf("Size: %d - Free blocks: %d Max Depth: %d Min Depth: %d Average Depth: %g", - memSizes[i].size, memSizes[i].count, memSizes[i].maxDepth, memSizes[i].minDepth, - F32(memSizes[i].depthTotal) / F32(memSizes[i].count)); - - fullMem.count += memSizes[i].count; - fullMem.depthTotal += memSizes[i].depthTotal; - fullMem.maxDepth = memSizes[i].maxDepth > fullMem.maxDepth ? memSizes[i].maxDepth : fullMem.maxDepth; - fullMem.minDepth = memSizes[i].minDepth < fullMem.minDepth ? memSizes[i].minDepth : fullMem.minDepth; - } - Con::printf("Total free blocks: %d Max Depth: %d Min Depth: %d Average Depth: %g", - fullMem.count, fullMem.maxDepth, fullMem.minDepth, F32(fullMem.depthTotal) / F32(fullMem.count)); -} - -#ifdef TORQUE_DEBUG_GUARD - -void flagCurrentAllocs( EFlag flag ) -{ -#ifdef TORQUE_ENABLE_PROFILE_PATH - if (gProfiler && !gProfiler->isEnabled()) - { - gProfiler->enable(true); - // warm it up - //gProfiler->dumpToConsole(); - } -#endif - - U32 bit = flagToBit( flag ); - for( HeapIterator iter; iter.isValid(); ++ iter ) - iter->flags |= bit; -} - -DefineEngineFunction(flagCurrentAllocs, void, (),, - "@brief Flags all current memory allocations.\n\n" - "Flags all current memory allocations for exclusion in subsequent calls to dumpUnflaggedAllocs(). " - "Helpful in detecting memory leaks and analyzing memory usage.\n\n" - "@ingroup Debugging" ) -{ - flagCurrentAllocs(); -} - -void dumpUnflaggedAllocs(const char *file, EFlag flag) -{ - countUnflaggedAllocs(file, NULL, flag); -} - -S32 countUnflaggedAllocs(const char * filename, S32 *outUnflaggedRealloc, EFlag flag) -{ - S32 unflaggedAllocCount = 0; - S32 unflaggedReAllocCount = 0; - - FileStream fws; - bool useFile = filename && *filename; - if (useFile) - useFile = fws.open(filename, Torque::FS::File::Write); - char buffer[1024]; - - U32 bit = flagToBit( flag ); - - PageRecord* walk; - for (walk = gPageList; walk; walk = walk->prevPage) - { - for(Header *probe = walk->headerList; probe; probe = probe->next) + // Update existing record + for (U32 i = 0; i < allocCount; ++i) { - if (probe->flags & Allocated) + if (allocList[i].ptr == oldPtr) { - AllocatedHeader* pah = (AllocatedHeader*)probe; - if (!(pah->flags & bit)) - { - // If you want to extract further information from an unflagged - // memory allocation, do the following: - // U8 *foo = (U8 *)pah; - // foo += sizeof(Header); - // FooObject *obj = (FooObject *)foo; - dSprintf(buffer, 1023, "%s%s\t%d\t%d\t%d\r\n", - pah->flags & Reallocated ? "[R] " : "", - pah->fileName != NULL ? pah->fileName : "Undetermined", - pah->line, pah->realSize, pah->allocNum); - - if( pah->flags & Reallocated ) - unflaggedReAllocCount++; - else - unflaggedAllocCount++; - - if (useFile) - { - fws.write(dStrlen(buffer), buffer); - fws.write(2, "\r\n"); - } - else - { - if( pah->flags & Reallocated ) - Con::warnf(buffer); - else - Con::errorf(buffer); - } - -#ifdef TORQUE_ENABLE_PROFILE_PATH - static char line[4096]; - dSprintf(line, sizeof(line), " %s\r\nreal size=%d", - pah->profilePath ? pah->profilePath : "unknown", - pah->realSize); - - if (useFile) - { - fws.write(dStrlen(line), line); - fws.write(2, "\r\n"); - } - else - { - if( pah->flags & Reallocated ) - Con::warnf(line); - else - Con::errorf(line); - } -#endif - - } + allocList[i].ptr = newPtr; + allocList[i].size = newSize; + allocList[i].file = fileName; + allocList[i].line = line; + allocList[i].allocId = currentAllocId++; + return newPtr; } } - } - if (useFile) - fws.close(); - - if( outUnflaggedRealloc != NULL ) - *outUnflaggedRealloc = unflaggedReAllocCount; - - return unflaggedAllocCount; -} - -DefineEngineFunction(dumpUnflaggedAllocs, void, ( const char* fileName ), ( "" ), - "@brief Dumps all unflagged memory allocations.\n\n" - "Dumps all memory allocations that were made after a call to flagCurrentAllocs(). " - "Helpful when used with flagCurrentAllocs() for detecting memory leaks and analyzing general memory usage.\n\n" - "@param fileName Optional file path and location to dump all memory allocations not flagged by flagCurrentAllocs(). " - "If left blank, data will be dumped to the console.\n\n" - "@tsexample\n" - "dumpMemSnapshot(); // dumps info to console\n" - "dumpMemSnapshot( \"C:/Torque/profilerlog1.txt\" ); // dumps info to file\n" - "@endtsexample\n\n" - "@note Available in debug builds only. " - "In torqueConfig.h, TORQUE_DISABLE_MEMORY_MANAGER must be undefined to use this function.\n\n" - "@ingroup Debugging" ) -{ - dumpUnflaggedAllocs(fileName); -} - -static void initLog() -{ - static const char* sInitString = " --- INIT MEMORY LOG (ACTION): (FILE) (LINE) (SIZE) (ALLOCNUMBER) ---\r\n"; - - FileStream fws; - fws.open(gLogFilename, Torque::FS::File::Write); - fws.write(dStrlen(sInitString), sInitString); - fws.close(); -} - -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void logAlloc(const AllocatedHeader* hdr, S32 memSize) -{ - FileStream fws; - fws.open(gLogFilename, Torque::FS::File::ReadWrite); - fws.setPosition(fws.getStreamSize()); - - char buffer[1024]; - dSprintf(buffer, 1023, "alloc: %s %d %d %d\r\n", - hdr->fileName != NULL ? hdr->fileName : "Undetermined", - hdr->line, memSize, hdr->allocNum); - fws.write(dStrlen(buffer), buffer); - fws.close(); -} -#endif - -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void logRealloc(const AllocatedHeader* hdr, S32 memSize) -{ - FileStream fws; - fws.open(gLogFilename, Torque::FS::File::ReadWrite); - fws.setPosition(fws.getStreamSize()); - - char buffer[1024]; - dSprintf(buffer, 1023, "realloc: %s %d %d %d\r\n", - hdr->fileName != NULL ? hdr->fileName : "Undetermined", - hdr->line, memSize, hdr->allocNum); - fws.write(dStrlen(buffer), buffer); - fws.close(); -} -#endif - -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void logFree(const AllocatedHeader* hdr) -{ - FileStream fws; - fws.open(gLogFilename, Torque::FS::File::ReadWrite); - fws.setPosition(fws.getStreamSize()); - - char buffer[1024]; - dSprintf(buffer, 1023, "free: %s %d %d\r\n", - hdr->fileName != NULL ? hdr->fileName : "Undetermined", - hdr->line, hdr->allocNum); - fws.write(dStrlen(buffer), buffer); - fws.close(); -} -#endif // !defined(TORQUE_DISABLE_MEMORY_MANAGER) - -#endif - -void enableLogging(const char* fileName) -{ - dStrcpy(gLogFilename, fileName, 256); - if (!gEnableLogging) - { - gEnableLogging = true; -#ifdef TORQUE_DEBUG_GUARD - initLog(); -#endif - } -} - -void disableLogging() -{ - gLogFilename[0] = '\0'; - gEnableLogging = false; -} - -// CodeReview - this is never called so commented out to save warning. -// Do we want to re-enable it? Might be nice to get leak tracking on -// exit...or maybe that is just a problematical feature we shouldn't -// worry about. -//static void shutdown() -//{ -//#ifdef TORQUE_MULTITHREAD -// Mutex::destroyMutex(gMemMutex); -// gMemMutex = NULL; -//#endif -// -//#ifdef TORQUE_DEBUG_GUARD -// -// // write out leaks and such -// const U32 maxNumLeaks = 1024; -// U32 numLeaks = 0; -// -// AllocatedHeader* pLeaks[maxNumLeaks]; -// for (PageRecord * walk = gPageList; walk; walk = walk->prevPage) -// for(Header *probe = walk->headerList; probe; probe = probe->next) -// if ((probe->flags & Allocated) && ((AllocatedHeader *)probe)->fileName != NULL) -// pLeaks[numLeaks++] = (AllocatedHeader *) probe; -// -// if (numLeaks && !gNeverLogLeaks) -// { -// if (gAlwaysLogLeaks || Platform::AlertOKCancel("Memory Status", "Memory leaks detected. Write to memoryLeaks.log?") == true) -// { -// char buffer[1024]; -// FileStream logFile; -// logFile.open("memoryLeaks.log", Torque::FS::File::Write); -// -// for (U32 i = 0; i < numLeaks; i++) -// { -// dSprintf(buffer, 1023, "Leak in %s: %d (%d)\r\n", pLeaks[i]->fileName, pLeaks[i]->line, pLeaks[i]->allocNum); -// logFile.write(dStrlen(buffer), buffer); -// } -// logFile.close(); -// } -// } -//#endif -// -// // then free all the memory pages -// for (PageRecord * walk = gPageList; walk; ) -// { -// PageRecord *prev = walk->prevPage; -// dRealFree(walk); -// walk = prev; -// } -//} - -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static Header *allocMemPage(dsize_t pageSize) -{ - pageSize += sizeof(Header); - if(pageSize < MinPageSize) - pageSize = MinPageSize; - PageRecord *base = allocPage(pageSize); - - Header* rec = (Header*)base->basePtr; - base->headerList = rec; - - rec->size = pageSize - sizeof(Header); - rec->next = NULL; - rec->prev = NULL; - rec->flags = 0; -#ifdef TORQUE_DEBUG_GUARD - setGuard(rec, true); -#endif - -#ifdef LOG_PAGE_ALLOCS - gPageBytesAllocated += pageSize; - // total bytes allocated so far will be 0 when TORQUE_DEBUG_GUARD is disabled, so convert that into more meaningful string - const U32 StrSize = 256; - char strBytesAllocated[StrSize]; - if (gBytesAllocated > 0) - dSprintf(strBytesAllocated, sizeof(strBytesAllocated), "%i", gBytesAllocated); - else - dStrncpy(strBytesAllocated,"unknown - enable TORQUE_DEBUG_GUARD", StrSize); - -#ifndef TORQUE_MULTITHREAD // May deadlock. - // NOTE: This code may be called within Con::_printf, and if that is the case - // this will infinitly recurse. This is the reason for the code in Con::_printf - // that sets Con::active to false. -patw - if (Con::isActive()) - Con::errorf("PlatformMemory: allocating new page, total bytes allocated so far: %s (total bytes in all pages=%i)",strBytesAllocated,gPageBytesAllocated); -#endif -#endif - return rec; -} -#endif - -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void checkUnusedAlloc(FreeHeader *header, U32 size) -{ - //validate(); - if(header->size >= size + sizeof(Header) + 16) - { - U8 *basePtr = (U8 *) header; - basePtr += sizeof(Header); - FreeHeader *newHeader = (FreeHeader *) (basePtr + size); - newHeader->next = header->next; - newHeader->prev = (Header *) header; - header->next = (Header *) newHeader; - if(newHeader->next) - newHeader->next->prev = (Header *) newHeader; - newHeader->flags = 0; - newHeader->size = header->size - size - sizeof(Header); - header->size = size; -#ifdef TORQUE_DEBUG_GUARD - setGuard((Header *) newHeader, true); -#endif - treeInsert(newHeader); - } -} -#endif - -#if defined(TORQUE_MULTITHREAD) && !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static bool gReentrantGuard = false; -#endif - -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void* alloc(dsize_t size, bool array, const char* fileName, const U32 line) -{ - AssertFatal(size < MaxAllocationAmount, "Memory::alloc - tried to allocate > MaxAllocationAmount!"); - -#ifdef TORQUE_MULTITHREAD - if(!gMemMutex && !gReentrantGuard) - { - gReentrantGuard = true; - gMemMutex = Mutex::createMutex(); - gReentrantGuard = false; - } - - if(!gReentrantGuard) - Mutex::lockMutex(gMemMutex); - -#endif - - AssertFatal(size < MaxAllocationAmount, "Size error."); - //validate(); - if (size == 0) - { -#ifdef TORQUE_MULTITHREAD - if(!gReentrantGuard) - Mutex::unlockMutex(gMemMutex); -#endif - return NULL; - } - -#ifndef TORQUE_ENABLE_PROFILE_PATH - // Note: will cause crash if profile path is on - PROFILE_START(MemoryAlloc); -#endif - -#ifdef TORQUE_DEBUG_GUARD - // if we're guarding, round up to the nearest DWORD - size = ((size + 3) & ~0x3); -#else - // round up size to nearest 16 byte boundary (cache lines and all...) - size = ((size + 15) & ~0xF); -#endif - - FreeHeader *header = treeFindSmallestGreaterThan(size); - if(header) - treeRemove(header); - else - header = (FreeHeader *) allocMemPage(size); - - // ok, see if there's enough room in the block to make another block - // for this to happen it has to have enough room for a header - // and 16 more bytes. - - U8 *basePtr = (U8 *) header; - basePtr += sizeof(Header); - - checkUnusedAlloc(header, size); - - AllocatedHeader *retHeader = (AllocatedHeader *) header; - retHeader->flags = array ? (Allocated | Array) : Allocated; - -#ifdef TORQUE_DEBUG_GUARD - retHeader->line = line; - retHeader->fileName = fileName; - retHeader->allocNum = gCurrAlloc; - retHeader->realSize = size; -#ifdef TORQUE_ENABLE_PROFILE_PATH - retHeader->profilePath = gProfiler ? gProfiler->getProfilePath() : "pre"; -#endif - gBytesAllocated += size; - gBlocksAllocated ++; - //static U32 skip = 0; - //if ((++skip % 1000) == 0) - // Con::printf("new=%i, newnew=%i, imagenew=%i",gBytesAllocated,gNewNewTotal,gImageAlloc); - if (gEnableLogging) - logAlloc(retHeader, size); -#endif - if(gCurrAlloc == gBreakAlloc && gBreakAlloc != 0xFFFFFFFF) - Platform::debugBreak(); - gCurrAlloc++; -#ifndef TORQUE_ENABLE_PROFILE_PATH - PROFILE_END(); -#endif - //validate(); - -#ifdef TORQUE_DEBUG - // fill the block with the fill value. although this is done in free(), that won't fill - // newly allocated MM memory (which hasn't been freed yet). We use a different fill value - // to diffentiate filled freed memory from filled new memory; this may aid debugging. - #ifndef TORQUE_ENABLE_PROFILE_PATH - PROFILE_START(stompMem1); - #endif - dMemset(basePtr, 0xCF, size); - #ifndef TORQUE_ENABLE_PROFILE_PATH - PROFILE_END(); - #endif -#endif - - if(gCurrAlloc == gBreakAlloc && gBreakAlloc != 0xFFFFFFFF) - Platform::debugBreak(); - - gCurrAlloc++; - -#ifdef TORQUE_MULTITHREAD - if(!gReentrantGuard) - Mutex::unlockMutex(gMemMutex); -#endif - - return basePtr; -} -#endif - -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void free(void* mem, bool array) -{ - // validate(); - - if (!mem) - return; - -#ifdef TORQUE_MULTITHREAD - if(!gMemMutex) - gMemMutex = Mutex::createMutex(); - - if( mem != gMemMutex ) - Mutex::lockMutex(gMemMutex); - else - gMemMutex = NULL; -#endif - - PROFILE_START(MemoryFree); - AllocatedHeader *hdr = ((AllocatedHeader *)mem) - 1; - - AssertFatal(hdr->flags & Allocated, avar("Not an allocated block!")); - AssertFatal(((bool)((hdr->flags & Array)==Array))==array, avar("Array alloc mismatch. ")); - - gBlocksAllocated --; -#ifdef TORQUE_DEBUG_GUARD - gBytesAllocated -= hdr->realSize; - if (gEnableLogging) - logFree(hdr); -#endif - - hdr->flags = 0; - - // fill the block with the fill value - -#ifdef TORQUE_DEBUG - #ifndef TORQUE_ENABLE_PROFILE_PATH - PROFILE_START(stompMem2); - #endif - dMemset(mem, 0xCE, hdr->size); - #ifndef TORQUE_ENABLE_PROFILE_PATH - PROFILE_END(); - #endif -#endif - - // see if we can merge hdr with the block after it. - - Header* next = hdr->next; - if (next && next->flags == 0) - { - treeRemove((FreeHeader *) next); - hdr->size += next->size + sizeof(Header); - hdr->next = next->next; - if(next->next) - next->next->prev = (Header *) hdr; - } - - // see if we can merge hdr with the block before it. - Header* prev = hdr->prev; - - if (prev && prev->flags == 0) - { - treeRemove((FreeHeader *) prev); - prev->size += hdr->size + sizeof(Header); - prev->next = hdr->next; - if (hdr->next) - hdr->next->prev = prev; - - hdr = (AllocatedHeader *) prev; - } - - // throw this puppy into the tree! - treeInsert((FreeHeader *) hdr); - PROFILE_END(); - -// validate(); - -#ifdef TORQUE_MULTITHREAD - Mutex::unlockMutex(gMemMutex); -#endif -} -#endif - -#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) -static void* realloc(void* mem, dsize_t size, const char* fileName, const U32 line) -{ - //validate(); - if (!size) { - free(mem, false); - return NULL; - } - if(!mem) - return alloc(size, false, fileName, line); - -#ifdef TORQUE_MULTITHREAD - if(!gMemMutex) - gMemMutex = Mutex::createMutex(); - - Mutex::lockMutex(gMemMutex); -#endif - - AllocatedHeader* hdr = ((AllocatedHeader *)mem) - 1; -#ifdef TORQUE_DEBUG_GUARD - checkGuard( ( Header* ) hdr, true ); -#endif - - AssertFatal((hdr->flags & Allocated) == Allocated, "Bad block flags."); - - size = (size + 0xF) & ~0xF; - - U32 oldSize = hdr->size; - if(oldSize == size) - { -#ifdef TORQUE_MULTITHREAD - Mutex::unlockMutex(gMemMutex); -#endif - return mem; - } - PROFILE_START(MemoryRealloc); - - FreeHeader *next = (FreeHeader *) hdr->next; - -#ifdef TORQUE_DEBUG_GUARD - // adjust header size and allocated bytes size - hdr->realSize += size - oldSize; - gBytesAllocated += size - oldSize; - if (gEnableLogging) - logRealloc(hdr, size); - - // Add reallocated flag, note header changes will not persist if the realloc - // decides tofree, and then perform a fresh allocation for the memory. The flag will - // be manually set again after this takes place, down at the bottom of this fxn. - hdr->flags |= Reallocated; - - // Note on Above ^ - // A more useful/robust implementation can be accomplished by storing an additional - // AllocatedHeader* in DEBUG_GUARD builds inside the AllocatedHeader structure - // itself to create a sort of reallocation history. This will be, essentially, - // a allocation header stack for each allocation. Each time the memory is reallocated - // it should use dRealMalloc (IMPORTANT!!) to allocate a AllocatedHeader* and chain - // it to the reallocation history chain, and the dump output changed to display - // reallocation history. It is also important to clean up this chain during 'free' - // using dRealFree (Since memory for the chain was allocated via dRealMalloc). - // - // See patw for details. -#endif - if (next && !(next->flags & Allocated) && next->size + hdr->size + sizeof(Header) >= size) - { - // we can merge with the next dude. - treeRemove(next); - hdr->size += sizeof(Header) + next->size; - hdr->next = next->next; - if(next->next) - next->next->prev = (Header *) hdr; - - checkUnusedAlloc((FreeHeader *) hdr, size); - //validate(); - PROFILE_END(); -#ifdef TORQUE_MULTITHREAD - Mutex::unlockMutex(gMemMutex); -#endif - return mem; - } - else if(size < oldSize) - { - checkUnusedAlloc((FreeHeader *) hdr, size); - PROFILE_END(); -#ifdef TORQUE_MULTITHREAD - Mutex::unlockMutex(gMemMutex); -#endif - return mem; - } -#ifdef TORQUE_DEBUG_GUARD - // undo above adjustment because we're going though alloc instead - hdr->realSize -= size - oldSize; - gBytesAllocated -= size - oldSize; -#endif - void* ret = alloc(size, false, fileName, line); - dMemcpy(ret, mem, oldSize); - free(mem, false); - PROFILE_END(); - - // Re-enable the 'Reallocated' flag so that this allocation can be ignored by - // a non-strict run of the flag/dumpunflagged. - hdr = ((AllocatedHeader *)ret) - 1; - hdr->flags |= Reallocated; - -#ifdef TORQUE_MULTITHREAD - Mutex::unlockMutex(gMemMutex); -#endif - return ret; -} -#endif - -dsize_t getMemoryUsed() -{ - U32 size = 0; - - PageRecord* walk; - for (walk = gPageList; walk; walk = walk->prevPage) { - for(Header *probe = walk->headerList; probe; probe = probe->next) - if (probe->flags & Allocated) { - size += probe->size; - } - } - - return size; -} - -#ifdef TORQUE_DEBUG_GUARD -DefineEngineFunction( dumpAlloc, void, ( S32 allocNum ),, - "@brief Dumps information about the given allocated memory block.\n\n" - "@param allocNum Memory block to dump information about." - "@note Available in debug builds only. " - "In torqueConfig.h, TORQUE_DISABLE_MEMORY_MANAGER must be undefined to use this function.\n\n" - "@ingroup Debugging") -{ - PageRecord* walk; - for( walk = gPageList; walk; walk = walk->prevPage ) - for( Header* probe = walk->headerList; probe; probe = probe->next ) - if( probe->flags & Allocated ) + // Not found — see if newPtr is already being tracked + for (U32 i = 0; i < allocCount; ++i) + { + if (allocList[i].ptr == newPtr) { - AllocatedHeader* pah = ( AllocatedHeader* ) probe; - if( pah->allocNum == allocNum ) - { - Con::printf( "file: %s\n" - "line: %i\n" - "size: %i\n" - "allocNum: %i\n" - "reallocated: %s", - pah->fileName != NULL ? pah->fileName : "Undetermined", - pah->line, - pah->realSize, - pah->allocNum, - pah->flags & Reallocated ? "yes" : "no" - ); - - // Dump the profile path, if we have one. - - #ifdef TORQUE_ENABLE_PROFILE_PATH - if( pah->profilePath && pah->profilePath[ 0 ] ) - Con::printf( "profilepath: %s", pah->profilePath ); - #endif - } - } -} + allocList[i].size = newSize; + allocList[i].file = fileName; + allocList[i].line = line; + allocList[i].allocId = currentAllocId++; -DefineEngineFunction( dumpMemSnapshot, void, ( const char* fileName ),, - "@brief Dumps a snapshot of current memory to a file.\n\n" - "The total memory used will also be output to the console.\n" - "This function will attempt to create the file if it does not already exist.\n" - "@param fileName Name and path of file to save profiling stats to. Must use forward slashes (/)\n" - "@tsexample\n" - "dumpMemSnapshot( \"C:/Torque/ProfilerLogs/profilerlog1.txt\" );\n" - "@endtsexample\n\n" - "@note Available in debug builds only. " - "In torqueConfig.h, TORQUE_DISABLE_MEMORY_MANAGER must be undefined to use this function.\n\n" - "@ingroup Debugging") -{ - FileStream fws; - fws.open(fileName, Torque::FS::File::Write); - char buffer[ 2048 ]; - - PageRecord* walk; - for (walk = gPageList; walk; walk = walk->prevPage) { - for(Header *probe = walk->headerList; probe; probe = probe->next) - if (probe->flags & Allocated) { - AllocatedHeader* pah = (AllocatedHeader*)probe; - - dSprintf( buffer, sizeof( buffer ), "%s%s\t%d\t%d\t%d\r\n", - pah->flags & Reallocated ? "[R] " : "", - pah->fileName != NULL ? pah->fileName : "Undetermined", - pah->line, pah->realSize, pah->allocNum); - fws.write(dStrlen(buffer), buffer); - - // Dump the profile path, if we have one. - - #ifdef TORQUE_ENABLE_PROFILE_PATH - if( pah->profilePath ) - { - dSprintf( buffer, sizeof( buffer ), "%s\r\n\r\n", pah->profilePath ); - fws.write( dStrlen( buffer ), buffer ); - } - #endif + return newPtr; } + } + + // Still not found — treat as a new allocation + if (allocCount < MaxAllocs) + { + MemInfo& info = allocList[allocCount++]; + info = {}; + info.ptr = newPtr; + info.size = newSize; + info.file = fileName; + info.line = line; + info.allocId = currentAllocId++; + } + return newPtr; } - Con::errorf("total memory used: %d",getMemoryUsed()); - fws.close(); -} #endif - -dsize_t getMemoryAllocated() -{ - return 0; } - -void getMemoryInfo( void* ptr, Info& info ) -{ - #ifndef TORQUE_DISABLE_MEMORY_MANAGER - - AllocatedHeader* header = ( ( AllocatedHeader* ) ptr ) - 1; - - info.mAllocSize = header->size; - #ifdef TORQUE_DEBUG_GUARD - info.mAllocNumber = header->allocNum; - info.mLineNumber = header->line; - info.mFileName = header->fileName; - #endif - info.mIsArray = header->flags & Array; - info.mIsGlobal = header->flags & GlobalFlag; - info.mIsStatic = header->flags & StaticFlag; - - #endif -} - -void setBreakAlloc(U32 breakAlloc) -{ - gBreakAlloc = breakAlloc; -} - -ConsoleFunctionGroupEnd( Memory ); - -} // namespace Memory - -void setMinimumAllocUnit(U32 allocUnit) -{ - AssertFatal(isPow2(allocUnit) && allocUnit > (2 << 20), - "Error, allocunit must be a power of two, and greater than 2 megs"); - - MinPageSize = allocUnit; -} - //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- @@ -1763,16 +321,6 @@ void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const return Memory::realloc(in_pResize, in_size, fileName, line); } -AFTER_MODULE_INIT( Sim ) -{ - Con::addVariable( "$Memory::numBlocksAllocated", TypeS32, &Memory::gBlocksAllocated, - "Total number of memory blocks currently allocated.\n\n" - "@ingroup Debugging" ); - Con::addVariable( "$Memory::numBytesAllocated", TypeS32, &Memory::gBytesAllocated, - "Total number of bytes currently allocated.\n\n" - "@ingroup Debugging" ); -} - #else // Don't manage our own memory diff --git a/Engine/source/platform/platformMemory.h b/Engine/source/platform/platformMemory.h index 96db06584..06b5e2fd2 100644 --- a/Engine/source/platform/platformMemory.h +++ b/Engine/source/platform/platformMemory.h @@ -27,33 +27,22 @@ namespace Memory { - enum EFlag + struct MemInfo { - FLAG_Debug, - FLAG_Global, - FLAG_Static + void* ptr; + dsize_t size; + const char* file; + U32 line; + U32 allocId; + bool flagged; + + void* backtracePtrs[16]; + int backtraceSize; }; - struct Info - { - U32 mAllocNumber; - U32 mAllocSize; - const char* mFileName; - U32 mLineNumber; - bool mIsArray; - bool mIsGlobal; - bool mIsStatic; - }; - - void checkPtr( void* ptr ); - void flagCurrentAllocs( EFlag flag = FLAG_Debug ); - void ensureAllFreed(); - void dumpUnflaggedAllocs(const char *file, EFlag flag = FLAG_Debug ); - S32 countUnflaggedAllocs(const char *file, S32 *outUnflaggedRealloc = NULL, EFlag flag = FLAG_Debug ); - dsize_t getMemoryUsed(); - dsize_t getMemoryAllocated(); - void getMemoryInfo( void* ptr, Info& info ); - void validate(); + void init(); + void shutdown(); + void checkPtr(void* ptr); } #endif // _TORQUE_PLATFORM_PLATFORMMEMORY_H_ diff --git a/Engine/source/ts/assimp/assimpAppMaterial.cpp b/Engine/source/ts/assimp/assimpAppMaterial.cpp index cf62d6cbb..5a45dc741 100644 --- a/Engine/source/ts/assimp/assimpAppMaterial.cpp +++ b/Engine/source/ts/assimp/assimpAppMaterial.cpp @@ -29,12 +29,23 @@ #include "ts/tsMaterialList.h" #include "core/stream/fileStream.h" +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +#ifdef new +#undef new +#endif +#endif + // assimp include files. #include #include #include #include +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +# define _new new(__FILE__, __LINE__) +# define new _new +#endif + U32 AssimpAppMaterial::sDefaultMatNumber = 0; String AppMaterial::cleanString(const String& str) diff --git a/Engine/source/ts/assimp/assimpAppMaterial.h b/Engine/source/ts/assimp/assimpAppMaterial.h index 0e973aaeb..4b80b5430 100644 --- a/Engine/source/ts/assimp/assimpAppMaterial.h +++ b/Engine/source/ts/assimp/assimpAppMaterial.h @@ -26,8 +26,20 @@ #ifndef _APPMATERIAL_H_ #include "ts/loader/appMaterial.h" #endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +#ifdef new +#undef new +#endif +#endif + #include +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +# define _new new(__FILE__, __LINE__) +# define new _new +#endif + class Material; class AssimpAppMaterial : public AppMaterial diff --git a/Engine/source/ts/assimp/assimpAppMesh.cpp b/Engine/source/ts/assimp/assimpAppMesh.cpp index bd116869a..0eb015473 100644 --- a/Engine/source/ts/assimp/assimpAppMesh.cpp +++ b/Engine/source/ts/assimp/assimpAppMesh.cpp @@ -24,12 +24,24 @@ #include "ts/collada/colladaExtensions.h" #include "ts/assimp/assimpAppMesh.h" + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +#ifdef new +#undef new +#endif +#endif + // assimp include files. #include #include #include #include +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +# define _new new(__FILE__, __LINE__) +# define new _new +#endif + bool AssimpAppMesh::fixedSizeEnabled = false; S32 AssimpAppMesh::fixedSize = 2; diff --git a/Engine/source/ts/assimp/assimpAppNode.cpp b/Engine/source/ts/assimp/assimpAppNode.cpp index ac1802468..62d1ad5a3 100644 --- a/Engine/source/ts/assimp/assimpAppNode.cpp +++ b/Engine/source/ts/assimp/assimpAppNode.cpp @@ -25,12 +25,25 @@ #include "ts/assimp/assimpAppNode.h" #include "ts/assimp/assimpAppMesh.h" + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +#ifdef new +#undef new +#endif +#endif + // assimp include files. #include #include #include #include +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +# define _new new(__FILE__, __LINE__) +# define new _new +#endif + + aiAnimation* AssimpAppNode::sActiveSequence = NULL; F32 AssimpAppNode::sTimeMultiplier = 1.0f; diff --git a/Engine/source/ts/assimp/assimpAppNode.h b/Engine/source/ts/assimp/assimpAppNode.h index db5e18a81..311b42d93 100644 --- a/Engine/source/ts/assimp/assimpAppNode.h +++ b/Engine/source/ts/assimp/assimpAppNode.h @@ -33,11 +33,23 @@ #include "ts/collada/colladaExtensions.h" #endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +#ifdef new +#undef new +#endif +#endif + #ifndef AI_TYPES_H_INC #include #endif #include +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +# define _new new(__FILE__, __LINE__) +# define new _new +#endif + class AssimpAppMesh; class AssimpAppNode : public AppNode diff --git a/Engine/source/ts/assimp/assimpAppSequence.h b/Engine/source/ts/assimp/assimpAppSequence.h index 1c4ac16dd..5468e1ce0 100644 --- a/Engine/source/ts/assimp/assimpAppSequence.h +++ b/Engine/source/ts/assimp/assimpAppSequence.h @@ -18,8 +18,20 @@ #include "ts/loader/appSequence.h" #endif +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +#ifdef new +#undef new +#endif +#endif + #include +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +# define _new new(__FILE__, __LINE__) +# define new _new +#endif + + class AssimpAppSequence : public AppSequence { String mSequenceName; diff --git a/Engine/source/ts/assimp/assimpShapeLoader.cpp b/Engine/source/ts/assimp/assimpShapeLoader.cpp index 19162ac82..c261f11cd 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.cpp +++ b/Engine/source/ts/assimp/assimpShapeLoader.cpp @@ -51,6 +51,11 @@ #include "gfx/bitmap/gBitmap.h" #include "gui/controls/guiTreeViewCtrl.h" +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +#ifdef new +#undef new +#endif +#endif // assimp include files. #include #include @@ -59,6 +64,12 @@ #include #include +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +# define _new new(__FILE__, __LINE__) +# define new _new +#endif + + MODULE_BEGIN( AssimpShapeLoader ) MODULE_INIT_AFTER( ShapeLoader ) MODULE_INIT