Torque3D/Engine/source/platform/platformMemory.cpp

1795 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 : 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);
}
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
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)
{
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 )
{
#ifdef TORQUE_DEBUG_GUARD
char buffer[ 1024 ];
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;
}
//dSprintf( buffer, sizeof( buffer ), "0x%x is a valid heap pointer", ptr );
//Platform::outputDebugString( buffer );
#endif
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, 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 )
{
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