//----------------------------------------------------------------------------- // 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); } } Con::errorf("total memory used: %d",getMemoryUsed()); 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 #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