mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 12:44:46 +00:00
1796 lines
48 KiB
C++
1796 lines
48 KiB
C++
|
|
//-----------------------------------------------------------------------------
|
||
|
|
// Copyright (c) 2012 GarageGames, LLC
|
||
|
|
//
|
||
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
|
// of this software and associated documentation files (the "Software"), to
|
||
|
|
// deal in the Software without restriction, including without limitation the
|
||
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
||
|
|
// furnished to do so, subject to the following conditions:
|
||
|
|
//
|
||
|
|
// The above copyright notice and this permission notice shall be included in
|
||
|
|
// all copies or substantial portions of the Software.
|
||
|
|
//
|
||
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||
|
|
// IN THE SOFTWARE.
|
||
|
|
//-----------------------------------------------------------------------------
|
||
|
|
|
||
|
|
#include "platform/platformMemory.h"
|
||
|
|
#include "console/dynamicTypes.h"
|
||
|
|
#include "console/engineAPI.h"
|
||
|
|
#include "core/stream/fileStream.h"
|
||
|
|
#include "core/strings/stringFunctions.h"
|
||
|
|
#include "console/console.h"
|
||
|
|
#include "platform/profiler.h"
|
||
|
|
#include "platform/threads/mutex.h"
|
||
|
|
#include "core/module.h"
|
||
|
|
|
||
|
|
// 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
|
||
|
|
// profiler (talk about a long sentence...)
|
||
|
|
|
||
|
|
#ifdef TORQUE_ENABLE_PROFILE_PATH
|
||
|
|
# undef PROFILE_START
|
||
|
|
# undef PROFILE_END
|
||
|
|
# undef PROFILE_SCOPE
|
||
|
|
# define PROFILE_START( x )
|
||
|
|
# define PROFILE_END()
|
||
|
|
# define PROFILE_SCOPE( x )
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#ifdef TORQUE_MULTITHREAD
|
||
|
|
void * gMemMutex = NULL;
|
||
|
|
#endif
|
||
|
|
|
||
|
|
//-------------------------------------- Make sure we don't have the define set
|
||
|
|
#ifdef new
|
||
|
|
#undef new
|
||
|
|
#endif
|
||
|
|
|
||
|
|
enum MemConstants {
|
||
|
|
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 ),
|
||
|
|
mAllocatedOnly( allocatedOnly ),
|
||
|
|
mCurrentHeader( NULL )
|
||
|
|
{
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
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)
|
||
|
|
{
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
void freeTreeNode(TreeNode *tn)
|
||
|
|
{
|
||
|
|
tn->parent = gTreeFreeList;
|
||
|
|
gTreeFreeList = tn;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static U32 validateTreeRecurse(TreeNode *tree)
|
||
|
|
{
|
||
|
|
if(tree == NIL)
|
||
|
|
return 1;
|
||
|
|
// check my left tree
|
||
|
|
int 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)
|
||
|
|
{
|
||
|
|
if(tree->left->parent != tree)
|
||
|
|
Platform::debugBreak();
|
||
|
|
|
||
|
|
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;
|
||
|
|
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)
|
||
|
|
{
|
||
|
|
TreeNode *temp = pparent->right;
|
||
|
|
if(temp->color == Red)
|
||
|
|
{
|
||
|
|
parent->color = Black;
|
||
|
|
temp->color = Black;
|
||
|
|
pparent->color = Red;
|
||
|
|
hdr = pparent;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if(hdr == parent->right)
|
||
|
|
{
|
||
|
|
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
|
||
|
|
|
||
|
|
#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++;
|
||
|
|
|
||
|
|
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)
|
||
|
|
{
|
||
|
|
w->left->color = Black;
|
||
|
|
rotateRight(w);
|
||
|
|
w = x->parent->right;
|
||
|
|
}
|
||
|
|
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)
|
||
|
|
{
|
||
|
|
w->right->color = Black;
|
||
|
|
rotateLeft(w);
|
||
|
|
w = x->parent->left;
|
||
|
|
}
|
||
|
|
w->color = x->parent->color;
|
||
|
|
x->parent->color = Black;
|
||
|
|
w->left->color = Black;
|
||
|
|
rotateRight(x->parent);
|
||
|
|
x = gFreeTreeRoot;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
x->color = Black;
|
||
|
|
}
|
||
|
|
//validateTree();
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#if !defined(TORQUE_DISABLE_MEMORY_MANAGER)
|
||
|
|
static FreeHeader *treeFindSmallestGreaterThan(dsize_t size)
|
||
|
|
{
|
||
|
|
TreeNode *bestMatch = NIL;
|
||
|
|
TreeNode *walk = gFreeTreeRoot;
|
||
|
|
while(walk != NIL)
|
||
|
|
{
|
||
|
|
if(size == walk->size)
|
||
|
|
return walk->queueHead;
|
||
|
|
else if(size > walk->size)
|
||
|
|
walk = walk->right;
|
||
|
|
else // size < walk->size
|
||
|
|
{
|
||
|
|
bestMatch = walk;
|
||
|
|
walk = walk->left;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//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 )
|
||
|
|
{
|
||
|
|
AllocatedHeader* header = ( AllocatedHeader* ) *iter;
|
||
|
|
if( header->getUserPtr() == ptr )
|
||
|
|
{
|
||
|
|
char buffer[ 1024 ];
|
||
|
|
|
||
|
|
#ifdef TORQUE_DEBUG_GUARD
|
||
|
|
if( !checkGuard( *iter, true ) )
|
||
|
|
{
|
||
|
|
dSprintf( buffer, sizeof( buffer ), "0x%x is a valid heap pointer but has its guards corrupted", ptr );
|
||
|
|
Platform::outputDebugString( buffer );
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
//dSprintf( buffer, sizeof( buffer ), "0x%x is a valid heap pointer", ptr );
|
||
|
|
//Platform::outputDebugString( buffer );
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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 )
|
||
|
|
{
|
||
|
|
AllocatedHeader* header = ( AllocatedHeader* ) *iter;
|
||
|
|
if( !( header->flags & GlobalFlag ) )
|
||
|
|
{
|
||
|
|
// 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
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if( numLeaks )
|
||
|
|
Platform::outputDebugString( "NUM LEAKS: %i (%i bytes)", numLeaks, bytesLeaked );
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
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++;
|
||
|
|
|
||
|
|
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
|
||
|
|
DefineConsoleFunction( validateMemory, void, ( ),,
|
||
|
|
"@brief Used to validate memory space for the game.\n\n"
|
||
|
|
"@ingroup Debugging" )
|
||
|
|
{
|
||
|
|
validate();
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
DefineConsoleFunction( 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)
|
||
|
|
{
|
||
|
|
if (probe->flags & Allocated)
|
||
|
|
{
|
||
|
|
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
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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);
|
||
|
|
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, ( int 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 )
|
||
|
|
{
|
||
|
|
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
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
//---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
//---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
#if !defined(TORQUE_DISABLE_MEMORY_MANAGER)
|
||
|
|
|
||
|
|
// Manage our own memory, add overloaded memory operators and functions
|
||
|
|
|
||
|
|
void* FN_CDECL operator new(dsize_t size, const char* fileName, const U32 line)
|
||
|
|
{
|
||
|
|
return Memory::alloc(size, false, fileName, line);
|
||
|
|
}
|
||
|
|
|
||
|
|
void* FN_CDECL operator new[](dsize_t size, const char* fileName, const U32 line)
|
||
|
|
{
|
||
|
|
return Memory::alloc(size, true, fileName, line);
|
||
|
|
}
|
||
|
|
|
||
|
|
void* FN_CDECL operator new(dsize_t size)
|
||
|
|
{
|
||
|
|
return Memory::alloc(size, false, NULL, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void* FN_CDECL operator new[](dsize_t size)
|
||
|
|
{
|
||
|
|
return Memory::alloc(size, true, NULL, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void FN_CDECL operator delete(void* mem)
|
||
|
|
{
|
||
|
|
Memory::free(mem, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
void FN_CDECL operator delete[](void* mem)
|
||
|
|
{
|
||
|
|
Memory::free(mem, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line)
|
||
|
|
{
|
||
|
|
return Memory::alloc(in_size, false, fileName, line);
|
||
|
|
}
|
||
|
|
|
||
|
|
void dFree(void* in_pFree)
|
||
|
|
{
|
||
|
|
Memory::free(in_pFree, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line)
|
||
|
|
{
|
||
|
|
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
|
||
|
|
void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line)
|
||
|
|
{
|
||
|
|
return malloc(in_size);
|
||
|
|
}
|
||
|
|
|
||
|
|
void dFree(void* in_pFree)
|
||
|
|
{
|
||
|
|
free(in_pFree);
|
||
|
|
}
|
||
|
|
|
||
|
|
void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line)
|
||
|
|
{
|
||
|
|
return realloc(in_pResize,in_size);
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif
|