tge/engine/platform/platformMemory.cc
2025-02-17 23:17:30 -06:00

1304 lines
32 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/fileStream.h"
#include "console/console.h"
#include "platform/profiler.h"
#include "platform/platformMutex.h"
#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),
FlaggityFlag = BIT(2),
AllocatedGuard = 0xCEDEFEDE,
FreeGuard = 0x5555FFFF,
MaxAllocationAmount = 0xFFFFFFFF,
TreeNodeAllocCount = 2048,
};
enum RedBlackTokens {
Red = 0,
Black = 1
};
static U32 MinPageSize = 8 * 1024 * 1024;
//---------------------------------------------------------------------------
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
U32 unused[4];
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 debug_guard
U32 line;
U32 allocNum;
const char *fileName;
U32 realSize;
U32 postguard[4];
#endif
};
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
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;
#ifdef TORQUE_DEBUG_GUARD
static void 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();
}
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
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 validateParentage()
{
if(gFreeTreeRoot == NIL)
return;
validateParentageRecurse(gFreeTreeRoot);
}
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
}
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;
}
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;
}
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();
}
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();
}
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;
}
static void check()
{
//for (S32 i = 0; i < mNumUsedHeaders; i++) {
// MemoryHeader* probe = &mHeaderBase[i];
// if (probe->flags & Allocated && probe->ptr->header != probe)
// Platform::debugBreak();
//}
}
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
ConsoleFunction(ValidateMemory, void, 1, 1, "ValidateMemory();")
{
argc; argv;
validate();
}
#endif
ConsoleFunction(FreeMemoryDump, void, 1, 1, "FreeMemoryDump();")
{
argc; argv;
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()
{
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;
pah->flags |= FlaggityFlag;
}
}
}
ConsoleFunction(FlagCurrentAllocs, void, 1, 1, "FlagCurrentAllocs();")
{
argc; argv;
flagCurrentAllocs();
}
ConsoleFunction(DumpUnflaggedAllocs, void, 2, 2, "DumpUnflaggedAllocs(filename);")
{
argc;
FileStream fws;
fws.open(argv[1], FileStream::Write);
char buffer[1024];
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 & FlaggityFlag))
{
// 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\t%d\t%d\t%d\r\n",
pah->fileName != NULL ? pah->fileName : "Undetermined",
pah->line, pah->realSize, pah->allocNum);
fws.write(dStrlen(buffer), buffer);
}
}
}
}
fws.close();
}
static void initLog()
{
static const char* sInitString = " --- INIT MEMORY LOG (ACTION): (FILE) (LINE) (SIZE) (ALLOCNUMBER) ---\r\n";
FileStream fws;
fws.open(gLogFilename, FileStream::Write);
fws.write(dStrlen(sInitString), sInitString);
fws.close();
}
static void logAlloc(const AllocatedHeader* hdr, S32 memSize)
{
FileStream fws;
fws.open(gLogFilename, FileStream::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();
}
static void logRealloc(const AllocatedHeader* hdr, S32 memSize)
{
FileStream fws;
fws.open(gLogFilename, FileStream::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();
}
static void logFree(const AllocatedHeader* hdr)
{
FileStream fws;
fws.open(gLogFilename, FileStream::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
void enableLogging(const char* fileName)
{
dStrcpy(gLogFilename, fileName);
gEnableLogging = true;
}
void disableLogging()
{
gLogFilename[0] = '\0';
gEnableLogging = false;
}
static void shutdown()
{
#ifdef TORQUE_MULTITHREAD
Mutex::destroyMutex(gMemMutex);
gMemMutex = NULL;
#endif
// write out leaks and such
const U32 maxNumLeaks = 1024;
U32 numLeaks = 0;
PageRecord * walk;
#ifdef TORQUE_DEBUG_GUARD
AllocatedHeader* pLeaks[maxNumLeaks];
for (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", FileStream::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
walk = gPageList;
while(walk) {
PageRecord *prev = walk->prevPage;
dRealFree(walk);
walk = prev;
}
}
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
return rec;
}
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);
}
}
#ifdef TORQUE_MULTITHREAD
static bool gReentrantGuard = false;
#endif
static void* alloc(dsize_t size, bool array, const char* fileName, const U32 line)
{
fileName, 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
if (size == 0)
{
#ifdef TORQUE_MULTITHREAD
if(!gReentrantGuard)
Mutex::unlockMutex(gMemMutex);
#endif
return NULL;
}
PROFILE_START(MemoryAlloc);
#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;
if (gEnableLogging)
logAlloc(retHeader, size);
#endif
#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.
// TODO: control with a new define.
dMemset(basePtr, 0xCF, size);
#endif
if(gCurrAlloc == gBreakAlloc && gBreakAlloc != 0xFFFFFFFF)
Platform::debugBreak();
gCurrAlloc++;
PROFILE_END();
#ifdef TORQUE_MULTITHREAD
if(!gReentrantGuard)
Mutex::unlockMutex(gMemMutex);
#endif
return basePtr;
}
static void free(void* mem, bool array)
{
// validate();
if (!mem)
return;
#ifdef TORQUE_MULTITHREAD
if(gMemMutex && gMemMutex != mem)
Mutex::lockMutex(gMemMutex);
#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. "));
#ifdef TORQUE_DEBUG_GUARD
if (gEnableLogging)
logFree(hdr);
#endif
hdr->flags = 0;
// fill the block with the fill value
#ifdef TORQUE_DEBUG
dMemset(mem, 0xCE, hdr->size);
#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
}
static void* realloc(void* mem, dsize_t size)
{
//validate();
if (!size) {
free(mem, false);
return NULL;
}
if(!mem)
return alloc(size, false, NULL, 0);
#ifdef TORQUE_MULTITHREAD
if(!gMemMutex)
gMemMutex = Mutex::createMutex();
Mutex::lockMutex(gMemMutex);
#endif
AllocatedHeader* hdr = ((AllocatedHeader *)mem) - 1;
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
hdr->realSize = size;
if (gEnableLogging)
logRealloc(hdr, size);
#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;
}
void* ret = alloc(size, false, NULL, 0);
dMemcpy(ret, mem, oldSize);
free(mem, false);
PROFILE_END();
#ifdef TORQUE_MULTITHREAD
Mutex::unlockMutex(gMemMutex);
#endif
return ret;
}
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
ConsoleFunction(dumpMemSnapshot,void,2,2,"(string fileName) Dump a snapshot of current memory to a file.")
{
argc;
FileStream fws;
fws.open(argv[1], FileStream::Write);
char buffer[1024];
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, 1023, "%s\t%d\t%d\t%d\r\n",
pah->fileName != NULL ? pah->fileName : "Undetermined",
pah->line, pah->realSize, pah->allocNum);
fws.write(dStrlen(buffer), buffer);
}
}
fws.close();
}
#endif
dsize_t getMemoryAllocated()
{
return 0;
}
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;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#include <stdlib.h>
#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(void* in_pResize, dsize_t in_size)
{
return Memory::realloc(in_pResize, in_size);
}
#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(void* in_pResize, dsize_t in_size)
{
return realloc(in_pResize,in_size);
}
#endif // #else