/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Contains an allocator base class. * \file IceAllocator.cpp * \author Pierre Terdiman * \date December, 19, 2003 */ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Precompiled Header #include "StdAfx.h" #include using namespace Opcode; //#define ZERO_OVERHEAD_RELEASE #define NEW_CODE // For some reason dmalloc seems a lot slower than the system malloc? //#define USE_DMALLOC #ifdef USE_DMALLOC #include "dmalloc.h" #define LOCAL_MALLOC dlmalloc #define LOCAL_FREE dlfree #else #define LOCAL_MALLOC ::malloc #define LOCAL_FREE ::free #endif // WARNING: this makes allocations a lot slower. Only use when tracking memory leaks. //#define ALLOC_STRINGS // ### doesn't seem that useful //#define FAST_BUFFER_SIZE 256*1024 #define DEBUG_IDENTIFIER 0xBeefBabe #define DEBUG_DEALLOCATED 0xDeadDead #ifdef ALLOC_STRINGS static const char* AllocString(const char* str) { if(!str) return null; char* mem = (char*)LOCAL_MALLOC(strlen(str)+1); strcpy(mem, str); return mem; } static void FreeString(const char* str) { if(str) LOCAL_FREE((void*)str); } #endif class DefaultAllocator : public Allocator { public: DefaultAllocator(); virtual ~DefaultAllocator(); void reset(); override(Allocator) void* malloc(size_t size, MemoryType type); override(Allocator) void* mallocDebug(size_t size, const char* filename, udword line, const char* class_name, MemoryType type); override(Allocator) void* realloc(void* memory, size_t size); override(Allocator) void* shrink(void* memory, size_t size); override(Allocator) void free(void* memory); void DumpCurrentMemoryState() const; void** mMemBlockList; udword mMemBlockListSize; #ifdef NEW_CODE udword mFirstFree; #else udword mMemBlockFirstFree; #endif udword mMemBlockUsed; sdword mNbAllocatedBytes; sdword mHighWaterMark; sdword mTotalNbAllocs; sdword mNbAllocs; sdword mNbReallocs; #ifdef FAST_BUFFER_SIZE udword mNbFastBytes; udword mFastBufferOffset; ubyte* mFastBuffer; #endif }; #define MEMBLOCKSTART 64 struct DebugBlock { udword mCheckValue; #ifdef FAST_BUFFER_SIZE MemoryType mType; #endif udword mSize; const char* mFilename; udword mLine; udword mSlotIndex; const char* mClassName; }; #ifndef FAST_BUFFER_SIZE ICE_COMPILE_TIME_ASSERT(sizeof(DebugBlock)==24); // Prevents surprises..... #endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// DefaultAllocator::DefaultAllocator() : mNbAllocatedBytes(0), mHighWaterMark(0), mTotalNbAllocs(0), mNbAllocs(0), mNbReallocs(0) { mMemBlockList = null; #ifdef _DEBUG // Initialize the Memory blocks list (DEBUG mode only) mMemBlockList = (void**)LOCAL_MALLOC(MEMBLOCKSTART*sizeof(void*)); ZeroMemory(mMemBlockList, MEMBLOCKSTART*sizeof(void*)); mMemBlockListSize = MEMBLOCKSTART; #ifdef NEW_CODE mFirstFree = INVALID_ID; #else mMemBlockFirstFree = 0; #endif mMemBlockUsed = 0; #endif #ifdef FAST_BUFFER_SIZE mNbFastBytes = 0; mFastBufferOffset = 0; mFastBuffer = (ubyte*)LOCAL_MALLOC(FAST_BUFFER_SIZE); #endif } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// DefaultAllocator::~DefaultAllocator() { #ifdef FAST_BUFFER_SIZE mNbFastBytes = 0; mFastBufferOffset = 0; if(mFastBuffer) LOCAL_FREE(mFastBuffer); mFastBuffer = null; #endif #ifdef _DEBUG // Ok, it is a bad idea to use _F() here, because it internally uses the allocator (for the log string). So let's use good old C style here. char Buffer[4096]; if(mNbAllocatedBytes) { sprintf(Buffer, "Memory leak detected: %d bytes non released\n", mNbAllocatedBytes); // IceTrace(Buffer); // IceTrace(_F("Memory leak detected: %d bytes non released\n", mNbAllocatedBytes)); } if(mNbAllocs) { sprintf(Buffer, "Remaining allocs: %d\n", mNbAllocs); // IceTrace(Buffer); // IceTrace(_F("Remaining allocs: %d\n", mNbAllocs)); } // IceTrace(_F("Nb alloc: %d\n", mTotalNbAllocs)); sprintf(Buffer, "Total nb alloc: %d\n", mTotalNbAllocs); // IceTrace(Buffer); // IceTrace(_F("Nb realloc: %d\n", mNbReallocs)); sprintf(Buffer, "Nb realloc: %d\n", mNbReallocs); // IceTrace(Buffer); // IceTrace(_F("High water mark: %d Kb\n", mHighWaterMark/1024)); sprintf(Buffer, "High water mark: %d Kb\n", mHighWaterMark/1024); // IceTrace(Buffer); // Scanning for memory leaks if(mMemBlockList && mNbAllocs) { udword NbLeaks = 0; // IceTrace("\n\n ICE Message Memory leaks detected :\n\n"); #ifdef NEW_CODE for(udword i=0; imSize, DB->mClassName, DB->mFilename, DB->mLine)); sprintf(Buffer, " Address 0x%.8X, %d bytes (%s), allocated in: %s(%d):\n\n", DB+1, DB->mSize, DB->mClassName, DB->mFilename, DB->mLine); // IceTrace(Buffer); NbLeaks++; // Free(cur+4); } #else for(udword i=0, j=0; imSize, DB->mClassName, DB->mFilename, DB->mLine)); sprintf(Buffer, " Address 0x%.8X, %d bytes (%s), allocated in: %s(%d):\n\n", DB+1, DB->mSize, DB->mClassName, DB->mFilename, DB->mLine); IceTrace(Buffer); NbLeaks++; // Free(cur+4); } #endif // IceTrace(_F("\n Dump complete (%d leaks)\n\n", NbLeaks)); sprintf(Buffer, "\n Dump complete (%d leaks)\n\n", NbLeaks); // IceTrace(Buffer); } // Free the Memory Block list if(mMemBlockList) LOCAL_FREE(mMemBlockList); mMemBlockList = null; #endif } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void DefaultAllocator::reset() { mNbAllocatedBytes = 0; mHighWaterMark = 0; mNbAllocs = 0; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void* DefaultAllocator::malloc(udword size, MemoryType type) { // return ::malloc(size); #ifdef _DEBUG return mallocDebug(size, null, 0, "Undefined", type); #endif if(!size) { #ifdef _DEBUG // IceTrace("Warning: trying to allocate 0 bytes\n"); #endif return null; } mTotalNbAllocs++; mNbAllocs++; mNbAllocatedBytes+=size; if(mNbAllocatedBytes>mHighWaterMark) mHighWaterMark = mNbAllocatedBytes; #ifdef ZERO_OVERHEAD_RELEASE return LOCAL_MALLOC(size); #else void* ptr = (void*)LOCAL_MALLOC(size+8); udword* blockStart = (udword*)ptr; blockStart[0] = DEBUG_IDENTIFIER; blockStart[1] = size; return ((udword*)ptr)+2; #endif } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void* DefaultAllocator::mallocDebug(size_t size, const char* filename, udword line, const char* class_name, MemoryType type) { #ifdef _DEBUG if(!size) { // IceTrace("Warning: trying to allocate 0 bytes\n"); return null; } // Catch improper use of alloc macro... if(0 && class_name) { const char* c = class_name; while(*c) { if(*c==']' || *c=='[') { int stop=0; } c++; } } // Make sure size is even if(size&1) size++; #ifdef FAST_BUFFER_SIZE // Allocate one debug block in front of each real allocation void* ptr = null; if(type==MEMORY_TEMP) { udword NeededSize = size + sizeof(DebugBlock); if(mFastBufferOffset + NeededSize <= FAST_BUFFER_SIZE) { ptr = mFastBuffer + mFastBufferOffset; mFastBufferOffset += NeededSize; mNbFastBytes += NeededSize; } } if(!ptr) { ptr = (void*)LOCAL_MALLOC(size + sizeof(DebugBlock)); type = MEMORY_PERSISTENT; } #else // Allocate one debug block in front of each real allocation void* ptr = (void*)LOCAL_MALLOC(size + sizeof(DebugBlock)); #endif ASSERT(IS_ALIGNED_2(udword(ptr))); // Fill debug block DebugBlock* DB = (DebugBlock*)ptr; DB->mCheckValue = DEBUG_IDENTIFIER; #ifdef FAST_BUFFER_SIZE DB->mType = type; #endif DB->mSize = size; DB->mLine = line; DB->mSlotIndex = INVALID_ID; #ifdef ALLOC_STRINGS DB->mFilename = AllocString(filename); DB->mClassName = AllocString(class_name); #else DB->mFilename = filename; DB->mClassName = class_name; #endif // Update global stats mTotalNbAllocs++; mNbAllocs++; mNbAllocatedBytes += size; if(mNbAllocatedBytes>mHighWaterMark) mHighWaterMark = mNbAllocatedBytes; // Insert the allocated block in the debug memory block list if(mMemBlockList) { #ifdef NEW_CODE if(mFirstFree!=INVALID_ID) { // Recycle old location udword NextFree = udword(mMemBlockList[mFirstFree]); if(NextFree!=INVALID_ID) NextFree>>=1; mMemBlockList[mFirstFree] = ptr; DB->mSlotIndex = mFirstFree; mFirstFree = NextFree; } else { if(mMemBlockUsed==mMemBlockListSize) { // Allocate a bigger block void** tps = (void**)LOCAL_MALLOC((mMemBlockListSize+MEMBLOCKSTART)*sizeof(void*)); // Copy already used part CopyMemory(tps, mMemBlockList, mMemBlockListSize*sizeof(void*)); // Initialize remaining part void* Next = tps + mMemBlockListSize; ZeroMemory(Next, MEMBLOCKSTART*sizeof(void*)); // Free previous memory, setup new pointer LOCAL_FREE(mMemBlockList); mMemBlockList = tps; // Setup new size mMemBlockListSize += MEMBLOCKSTART; } mMemBlockList[mMemBlockUsed] = ptr; DB->mSlotIndex = mMemBlockUsed++; } #else // Store allocated pointer in first free slot mMemBlockList[mMemBlockFirstFree] = ptr; DB->mSlotIndex = mMemBlockFirstFree; // Count number of used slots mMemBlockUsed++; // Resize if needed if(mMemBlockUsed==mMemBlockListSize) { // Allocate a bigger block void** tps = (void**)LOCAL_MALLOC((mMemBlockListSize+MEMBLOCKSTART)*sizeof(void*)); // Copy already used part CopyMemory(tps, mMemBlockList, mMemBlockListSize*sizeof(void*)); // Initialize remaining part void* Next = tps + mMemBlockListSize; ZeroMemory(Next, MEMBLOCKSTART*sizeof(void*)); // Free previous memory, setup new pointer LOCAL_FREE(mMemBlockList); mMemBlockList = tps; // -1 because we'll do a ++ just afterwards mMemBlockFirstFree = mMemBlockListSize-1; // Setup new size mMemBlockListSize += MEMBLOCKSTART; } // Look for first free ### recode this ugly thing while(mMemBlockList[++mMemBlockFirstFree] && (mMemBlockFirstFreemCheckValue!=DEBUG_IDENTIFIER) { // Not a valid memory block return null; } if(size>DB->mSize) { // New size should be smaller! return null; } // Try to shrink the block void* Reduced = _expand(SystemPointer, size + sizeof(DebugBlock)); if(!Reduced) return null; if(Reduced!=SystemPointer) { // Should not be possible?! } // Update stats mNbAllocatedBytes -= DB->mSize; mNbAllocatedBytes += size; // Setup new size DB->mSize = size; return memory; // The pointer should not have changed! #else // Release codepath udword* SystemPointer = ((udword*)memory)-2; if(SystemPointer[0]!=DEBUG_IDENTIFIER) { // Not a valid memory block return null; } if(size>SystemPointer[1]) { // New size should be smaller! return null; } // Try to shrink the block void* Reduced = _expand(SystemPointer, size+8); if(!Reduced) return null; if(Reduced!=SystemPointer) { // Should not be possible?! } // Update stats mNbAllocatedBytes -= SystemPointer[1]; mNbAllocatedBytes += size; // Setup new size SystemPointer[1] = size; return memory; // The pointer should not have changed! #endif } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void* DefaultAllocator::realloc(void* memory, udword size) { // return ::realloc(memory, size); ASSERT(0); return null; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void DefaultAllocator::free(void* memory) { if(!memory) { #ifdef _DEBUG // IceTrace("Warning: trying to free null pointer\n"); #endif return; } #ifdef _DEBUG DebugBlock* DB = ((DebugBlock*)memory)-1; // DebugBlock TmpDB = *DB; // Keep a local copy to have readable data when ::free() fails! // Check we allocated it if(DB->mCheckValue!=DEBUG_IDENTIFIER) { // IceTrace("Error: free unknown memory!!\n"); // ### should we really continue?? return; } // Update global stats mNbAllocatedBytes -= DB->mSize; mNbAllocs--; #ifdef NEW_CODE // Remove the block from the Memory block list if(mMemBlockList) { udword FreeSlot = DB->mSlotIndex; ASSERT(mMemBlockList[FreeSlot]==DB); udword NextFree = mFirstFree; if(NextFree!=INVALID_ID) { NextFree<<=1; NextFree|=1; } mMemBlockList[FreeSlot] = (void*)NextFree; mFirstFree = FreeSlot; } #else udword MemBlockFirstFree = DB->mSlotIndex; // The slot we are in udword Line = DB->mLine; const char* File = DB->mFilename; // Remove the block from the Memory block list if(mMemBlockList) { ASSERT(mMemBlockList[MemBlockFirstFree]==DB); mMemBlockList[MemBlockFirstFree] = null; mMemBlockUsed--; } #endif #ifdef ALLOC_STRINGS FreeString(DB->mClassName); FreeString(DB->mFilename); #endif #ifdef FAST_BUFFER_SIZE if(DB->mType==MEMORY_TEMP) { mNbFastBytes -= DB->mSize + sizeof(DebugBlock); if(mNbFastBytes==0) { mFastBufferOffset = 0; } return; } #endif // ### should be useless since we'll release the memory just afterwards DB->mCheckValue = DEBUG_DEALLOCATED; DB->mSize = 0; DB->mClassName = null; DB->mFilename = null; DB->mSlotIndex = INVALID_ID; DB->mLine = INVALID_ID; LOCAL_FREE(DB); #else // Release codepath #ifdef ZERO_OVERHEAD_RELEASE // mNbAllocatedBytes -= ptr[1]; // ### use _msize() ? mNbAllocs--; LOCAL_FREE(memory); #else udword* ptr = ((udword*)memory)-2; if(ptr[0]!=DEBUG_IDENTIFIER) { #ifdef _DEBUG IceTrace("Error: free unknown memory!!\n"); #endif } mNbAllocatedBytes -= ptr[1]; if(mNbAllocatedBytes<0) { #ifdef _DEBUG IceTrace(_F("Oops (%d)\n", ptr[1])); #endif } mNbAllocs--; ptr[0]=DEBUG_DEALLOCATED; ptr[1]=0; LOCAL_FREE(ptr); #endif #endif } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// inline_ bool ValidAddress(const void* addy) { #ifdef NEW_CODE return (addy && !(udword(addy)&1)); #else return addy!=null; #endif } void DefaultAllocator::DumpCurrentMemoryState() const { #ifdef _DEBUG // Scanning for memory leaks if(mMemBlockList && mMemBlockUsed) { // IceTrace("\n\n----ALLOCATOR MEMORY DUMP:\n\n"); // We can't just use the "const char*" stored in the debug blocks because they're not guaranteed to // be unique for similar strings. For example if a Container is allocated from two different DLLs, // the "Container" character string will be duplicated (one per DLL). So we need to group similar // strings together using the actual characters, not just the string address. We also have to do this // without allocating any new memory, since it would add new entries to the memory debug structure. // // The good news is that we don't care about speed too much in this function, since it's not supposed // to be called all the time. struct Local { struct TmpStruct { const char* mName; udword mSize; }; static int SortCB(const void* elem1, const void* elem2) { const TmpStruct* s1 = (const TmpStruct*)elem1; const TmpStruct* s2 = (const TmpStruct*)elem2; return strcmp(s1->mName, s2->mName); } }; Local::TmpStruct* SortedStrings = (Local::TmpStruct*)LOCAL_MALLOC(sizeof(Local::TmpStruct)*mMemBlockListSize); udword NbStrings = 0; udword TotalSize = 0; for(udword i=0;imClassName) { SortedStrings[NbStrings].mName = DB->mClassName; // Memory by class // SortedStrings[NbStrings].mName = DB->mFilename; // Memory by file SortedStrings[NbStrings].mSize = DB->mSize; TotalSize += DB->mSize; NbStrings++; } } } qsort(SortedStrings, NbStrings, sizeof(Local::TmpStruct), Local::SortCB); // Strings are now sorted. They might still be duplicated, i.e. we may have two strings for the same // class. So now we parse the list and collect used memory for all classes. Then we sort this again, // to report results in order of increasing memory. udword NbClasses=0; udword* Classes = (udword*)LOCAL_MALLOC(sizeof(udword)*NbStrings); udword* Sizes = (udword*)LOCAL_MALLOC(sizeof(udword)*NbStrings); udword CurrentSize = SortedStrings[0].mSize; const char* CurrentClass = SortedStrings[0].mName; for(udword i=1;i<=NbStrings;i++) // One more time on purpose { const char* Current = null; if(i!=NbStrings) { Current = SortedStrings[i].mName; } if(Current && strcmp(Current, CurrentClass)==0) { // Same class CurrentSize += SortedStrings[i].mSize; } else { // New class // Store previous class if(CurrentClass) { Classes[NbClasses] = (udword)CurrentClass; // We can store this pointer now because it's unique in our new array Sizes[NbClasses++] = CurrentSize; } // Next one if(Current) { CurrentClass = Current; CurrentSize = SortedStrings[i].mSize; } } } udword* Ranks0 = (udword*)LOCAL_MALLOC(sizeof(udword)*NbClasses); udword* Ranks1 = (udword*)LOCAL_MALLOC(sizeof(udword)*NbClasses); StackRadixSort(RS, Ranks0, Ranks1); const udword* Sorted = RS.Sort(Sizes, NbClasses).GetRanks(); for(udword i=0;iDumpCurrentMemoryState(); } void InitDefaultAllocator() { // gDefault = ::new DefaultAllocator; } void ReleaseDefaultAllocator() { // if(gDefault) ::delete gDefault; // gDefault = null; }