1 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3 * Contains an allocator base class.
4 * \file IceAllocator.cpp
5 * \author Pierre Terdiman
6 * \date December, 19, 2003
8 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
15 using namespace Opcode;
17 //#define ZERO_OVERHEAD_RELEASE
19 // For some reason dmalloc seems a lot slower than the system malloc?
24 #define LOCAL_MALLOC dlmalloc
25 #define LOCAL_FREE dlfree
27 #define LOCAL_MALLOC ::malloc
28 #define LOCAL_FREE ::free
32 // WARNING: this makes allocations a lot slower. Only use when tracking memory leaks.
33 //#define ALLOC_STRINGS
35 // ### doesn't seem that useful
36 //#define FAST_BUFFER_SIZE 256*1024
38 #define DEBUG_IDENTIFIER 0xBeefBabe
39 #define DEBUG_DEALLOCATED 0xDeadDead
42 static const char* AllocString(const char* str)
45 char* mem = (char*)LOCAL_MALLOC(strlen(str)+1);
50 static void FreeString(const char* str)
52 if(str) LOCAL_FREE((void*)str);
57 class DefaultAllocator : public Allocator
61 virtual ~DefaultAllocator();
65 override(Allocator) void* malloc(size_t size, MemoryType type);
66 override(Allocator) void* mallocDebug(size_t size, const char* filename, udword line, const char* class_name, MemoryType type);
67 override(Allocator) void* realloc(void* memory, size_t size);
68 override(Allocator) void* shrink(void* memory, size_t size);
69 override(Allocator) void free(void* memory);
71 void DumpCurrentMemoryState() const;
74 udword mMemBlockListSize;
78 udword mMemBlockFirstFree;
82 sdword mNbAllocatedBytes;
83 sdword mHighWaterMark;
84 sdword mTotalNbAllocs;
87 #ifdef FAST_BUFFER_SIZE
89 udword mFastBufferOffset;
94 #define MEMBLOCKSTART 64
99 #ifdef FAST_BUFFER_SIZE
103 const char* mFilename;
106 const char* mClassName;
109 #ifndef FAST_BUFFER_SIZE
110 ICE_COMPILE_TIME_ASSERT(sizeof(DebugBlock)==24); // Prevents surprises.....
113 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
115 DefaultAllocator::DefaultAllocator() : mNbAllocatedBytes(0), mHighWaterMark(0), mTotalNbAllocs(0), mNbAllocs(0), mNbReallocs(0)
117 mMemBlockList = null;
120 // Initialize the Memory blocks list (DEBUG mode only)
121 mMemBlockList = (void**)LOCAL_MALLOC(MEMBLOCKSTART*sizeof(void*));
122 ZeroMemory(mMemBlockList, MEMBLOCKSTART*sizeof(void*));
123 mMemBlockListSize = MEMBLOCKSTART;
125 mFirstFree = INVALID_ID;
127 mMemBlockFirstFree = 0;
133 #ifdef FAST_BUFFER_SIZE
135 mFastBufferOffset = 0;
136 mFastBuffer = (ubyte*)LOCAL_MALLOC(FAST_BUFFER_SIZE);
140 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
142 DefaultAllocator::~DefaultAllocator()
144 #ifdef FAST_BUFFER_SIZE
146 mFastBufferOffset = 0;
147 if(mFastBuffer) LOCAL_FREE(mFastBuffer);
153 // 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.
156 if(mNbAllocatedBytes)
158 sprintf(Buffer, "Memory leak detected: %d bytes non released\n", mNbAllocatedBytes);
160 // IceTrace(_F("Memory leak detected: %d bytes non released\n", mNbAllocatedBytes));
164 sprintf(Buffer, "Remaining allocs: %d\n", mNbAllocs);
166 // IceTrace(_F("Remaining allocs: %d\n", mNbAllocs));
168 // IceTrace(_F("Nb alloc: %d\n", mTotalNbAllocs));
169 sprintf(Buffer, "Total nb alloc: %d\n", mTotalNbAllocs);
172 // IceTrace(_F("Nb realloc: %d\n", mNbReallocs));
173 sprintf(Buffer, "Nb realloc: %d\n", mNbReallocs);
176 // IceTrace(_F("High water mark: %d Kb\n", mHighWaterMark/1024));
177 sprintf(Buffer, "High water mark: %d Kb\n", mHighWaterMark/1024);
180 // Scanning for memory leaks
181 if(mMemBlockList && mNbAllocs)
184 // IceTrace("\n\n ICE Message Memory leaks detected :\n\n");
187 for(udword i=0; i<mMemBlockUsed; i++)
189 if(udword(mMemBlockList[i])&1)
192 const DebugBlock* DB = (const DebugBlock*)mMemBlockList[i];
193 // IceTrace(_F(" Address 0x%.8X, %d bytes (%s), allocated in: %s(%d):\n\n", cur+6, cur[1], (const char*)cur[5], (const char*)cur[2], cur[3]));
194 // IceTrace(_F(" Address 0x%.8X, %d bytes (%s), allocated in: %s(%d):\n\n", DB+1, DB->mSize, DB->mClassName, DB->mFilename, DB->mLine));
195 sprintf(Buffer, " Address 0x%.8X, %d bytes (%s), allocated in: %s(%d):\n\n", DB+1, DB->mSize, DB->mClassName, DB->mFilename, DB->mLine);
202 for(udword i=0, j=0; i<mMemBlockUsed; i++, j++)
205 while(!mMemBlockList[j]) j++;
207 const DebugBlock* DB = (const DebugBlock*)mMemBlockList[j];
208 // IceTrace(_F(" Address 0x%.8X, %d bytes (%s), allocated in: %s(%d):\n\n", cur+6, cur[1], (const char*)cur[5], (const char*)cur[2], cur[3]));
209 // IceTrace(_F(" Address 0x%.8X, %d bytes (%s), allocated in: %s(%d):\n\n", DB+1, DB->mSize, DB->mClassName, DB->mFilename, DB->mLine));
210 sprintf(Buffer, " Address 0x%.8X, %d bytes (%s), allocated in: %s(%d):\n\n", DB+1, DB->mSize, DB->mClassName, DB->mFilename, DB->mLine);
217 // IceTrace(_F("\n Dump complete (%d leaks)\n\n", NbLeaks));
218 sprintf(Buffer, "\n Dump complete (%d leaks)\n\n", NbLeaks);
221 // Free the Memory Block list
222 if(mMemBlockList) LOCAL_FREE(mMemBlockList);
223 mMemBlockList = null;
227 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
229 void DefaultAllocator::reset()
231 mNbAllocatedBytes = 0;
236 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
238 void* DefaultAllocator::malloc(udword size, MemoryType type)
240 // return ::malloc(size);
243 return mallocDebug(size, null, 0, "Undefined", type);
249 // IceTrace("Warning: trying to allocate 0 bytes\n");
257 mNbAllocatedBytes+=size;
258 if(mNbAllocatedBytes>mHighWaterMark) mHighWaterMark = mNbAllocatedBytes;
260 #ifdef ZERO_OVERHEAD_RELEASE
261 return LOCAL_MALLOC(size);
263 void* ptr = (void*)LOCAL_MALLOC(size+8);
264 udword* blockStart = (udword*)ptr;
265 blockStart[0] = DEBUG_IDENTIFIER;
266 blockStart[1] = size;
267 return ((udword*)ptr)+2;
271 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
273 void* DefaultAllocator::mallocDebug(size_t size, const char* filename, udword line, const char* class_name, MemoryType type)
278 // IceTrace("Warning: trying to allocate 0 bytes\n");
282 // Catch improper use of alloc macro...
285 const char* c = class_name;
288 if(*c==']' || *c=='[')
296 // Make sure size is even
299 #ifdef FAST_BUFFER_SIZE
300 // Allocate one debug block in front of each real allocation
302 if(type==MEMORY_TEMP)
304 udword NeededSize = size + sizeof(DebugBlock);
305 if(mFastBufferOffset + NeededSize <= FAST_BUFFER_SIZE)
307 ptr = mFastBuffer + mFastBufferOffset;
308 mFastBufferOffset += NeededSize;
309 mNbFastBytes += NeededSize;
315 ptr = (void*)LOCAL_MALLOC(size + sizeof(DebugBlock));
316 type = MEMORY_PERSISTENT;
319 // Allocate one debug block in front of each real allocation
320 void* ptr = (void*)LOCAL_MALLOC(size + sizeof(DebugBlock));
322 ASSERT(IS_ALIGNED_2(udword(ptr)));
325 DebugBlock* DB = (DebugBlock*)ptr;
326 DB->mCheckValue = DEBUG_IDENTIFIER;
327 #ifdef FAST_BUFFER_SIZE
332 DB->mSlotIndex = INVALID_ID;
334 DB->mFilename = AllocString(filename);
335 DB->mClassName = AllocString(class_name);
337 DB->mFilename = filename;
338 DB->mClassName = class_name;
341 // Update global stats
344 mNbAllocatedBytes += size;
345 if(mNbAllocatedBytes>mHighWaterMark)
346 mHighWaterMark = mNbAllocatedBytes;
348 // Insert the allocated block in the debug memory block list
352 if(mFirstFree!=INVALID_ID)
354 // Recycle old location
356 udword NextFree = udword(mMemBlockList[mFirstFree]);
357 if(NextFree!=INVALID_ID) NextFree>>=1;
359 mMemBlockList[mFirstFree] = ptr;
360 DB->mSlotIndex = mFirstFree;
362 mFirstFree = NextFree;
366 if(mMemBlockUsed==mMemBlockListSize)
368 // Allocate a bigger block
369 void** tps = (void**)LOCAL_MALLOC((mMemBlockListSize+MEMBLOCKSTART)*sizeof(void*));
370 // Copy already used part
371 CopyMemory(tps, mMemBlockList, mMemBlockListSize*sizeof(void*));
372 // Initialize remaining part
373 void* Next = tps + mMemBlockListSize;
374 ZeroMemory(Next, MEMBLOCKSTART*sizeof(void*));
376 // Free previous memory, setup new pointer
377 LOCAL_FREE(mMemBlockList);
380 mMemBlockListSize += MEMBLOCKSTART;
383 mMemBlockList[mMemBlockUsed] = ptr;
384 DB->mSlotIndex = mMemBlockUsed++;
387 // Store allocated pointer in first free slot
388 mMemBlockList[mMemBlockFirstFree] = ptr;
389 DB->mSlotIndex = mMemBlockFirstFree;
391 // Count number of used slots
395 if(mMemBlockUsed==mMemBlockListSize)
397 // Allocate a bigger block
398 void** tps = (void**)LOCAL_MALLOC((mMemBlockListSize+MEMBLOCKSTART)*sizeof(void*));
399 // Copy already used part
400 CopyMemory(tps, mMemBlockList, mMemBlockListSize*sizeof(void*));
401 // Initialize remaining part
402 void* Next = tps + mMemBlockListSize;
403 ZeroMemory(Next, MEMBLOCKSTART*sizeof(void*));
405 // Free previous memory, setup new pointer
406 LOCAL_FREE(mMemBlockList);
408 // -1 because we'll do a ++ just afterwards
409 mMemBlockFirstFree = mMemBlockListSize-1;
411 mMemBlockListSize += MEMBLOCKSTART;
414 // Look for first free ### recode this ugly thing
415 while(mMemBlockList[++mMemBlockFirstFree] && (mMemBlockFirstFree<mMemBlockListSize));
416 if(mMemBlockFirstFree==mMemBlockListSize)
418 mMemBlockFirstFree = (udword)-1;
419 while(mMemBlockList[++mMemBlockFirstFree] && (mMemBlockFirstFree<mMemBlockListSize));
424 return ((ubyte*)ptr) + sizeof(DebugBlock);
426 Log("Error: mallocDebug has been called in release!\n");
427 ASSERT(0);//Don't use debug malloc for release mode code!
432 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
434 void* DefaultAllocator::shrink(void* memory, udword size)
436 return null; // #ifdef ZERO_OVERHEAD_RELEASE
438 if(!memory) return null;
442 ubyte* SystemPointer = ((ubyte*)memory) - sizeof(DebugBlock);
444 DebugBlock* DB = (DebugBlock*)SystemPointer;
445 if(DB->mCheckValue!=DEBUG_IDENTIFIER)
447 // Not a valid memory block
452 // New size should be smaller!
456 // Try to shrink the block
457 void* Reduced = _expand(SystemPointer, size + sizeof(DebugBlock));
458 if(!Reduced) return null;
460 if(Reduced!=SystemPointer)
462 // Should not be possible?!
466 mNbAllocatedBytes -= DB->mSize;
467 mNbAllocatedBytes += size;
471 return memory; // The pointer should not have changed!
474 udword* SystemPointer = ((udword*)memory)-2;
475 if(SystemPointer[0]!=DEBUG_IDENTIFIER)
477 // Not a valid memory block
480 if(size>SystemPointer[1])
482 // New size should be smaller!
486 // Try to shrink the block
487 void* Reduced = _expand(SystemPointer, size+8);
488 if(!Reduced) return null;
490 if(Reduced!=SystemPointer)
492 // Should not be possible?!
496 mNbAllocatedBytes -= SystemPointer[1];
497 mNbAllocatedBytes += size;
499 SystemPointer[1] = size;
501 return memory; // The pointer should not have changed!
505 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
507 void* DefaultAllocator::realloc(void* memory, udword size)
509 // return ::realloc(memory, size);
515 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
517 void DefaultAllocator::free(void* memory)
522 // IceTrace("Warning: trying to free null pointer\n");
528 DebugBlock* DB = ((DebugBlock*)memory)-1;
530 // DebugBlock TmpDB = *DB; // Keep a local copy to have readable data when ::free() fails!
532 // Check we allocated it
533 if(DB->mCheckValue!=DEBUG_IDENTIFIER)
535 // IceTrace("Error: free unknown memory!!\n");
536 // ### should we really continue??
540 // Update global stats
541 mNbAllocatedBytes -= DB->mSize;
545 // Remove the block from the Memory block list
548 udword FreeSlot = DB->mSlotIndex;
549 ASSERT(mMemBlockList[FreeSlot]==DB);
551 udword NextFree = mFirstFree;
552 if(NextFree!=INVALID_ID)
558 mMemBlockList[FreeSlot] = (void*)NextFree;
559 mFirstFree = FreeSlot;
562 udword MemBlockFirstFree = DB->mSlotIndex; // The slot we are in
563 udword Line = DB->mLine;
564 const char* File = DB->mFilename;
566 // Remove the block from the Memory block list
569 ASSERT(mMemBlockList[MemBlockFirstFree]==DB);
570 mMemBlockList[MemBlockFirstFree] = null;
576 FreeString(DB->mClassName);
577 FreeString(DB->mFilename);
580 #ifdef FAST_BUFFER_SIZE
581 if(DB->mType==MEMORY_TEMP)
583 mNbFastBytes -= DB->mSize + sizeof(DebugBlock);
586 mFastBufferOffset = 0;
592 // ### should be useless since we'll release the memory just afterwards
593 DB->mCheckValue = DEBUG_DEALLOCATED;
595 DB->mClassName = null;
596 DB->mFilename = null;
597 DB->mSlotIndex = INVALID_ID;
598 DB->mLine = INVALID_ID;
603 #ifdef ZERO_OVERHEAD_RELEASE
605 // mNbAllocatedBytes -= ptr[1]; // ### use _msize() ?
611 udword* ptr = ((udword*)memory)-2;
612 if(ptr[0]!=DEBUG_IDENTIFIER)
615 IceTrace("Error: free unknown memory!!\n");
618 mNbAllocatedBytes -= ptr[1];
619 if(mNbAllocatedBytes<0)
622 IceTrace(_F("Oops (%d)\n", ptr[1]));
626 ptr[0]=DEBUG_DEALLOCATED;
634 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
636 inline_ bool ValidAddress(const void* addy)
639 return (addy && !(udword(addy)&1));
645 void DefaultAllocator::DumpCurrentMemoryState() const
648 // Scanning for memory leaks
649 if(mMemBlockList && mMemBlockUsed)
651 // IceTrace("\n\n----ALLOCATOR MEMORY DUMP:\n\n");
653 // We can't just use the "const char*" stored in the debug blocks because they're not guaranteed to
654 // be unique for similar strings. For example if a Container is allocated from two different DLLs,
655 // the "Container" character string will be duplicated (one per DLL). So we need to group similar
656 // strings together using the actual characters, not just the string address. We also have to do this
657 // without allocating any new memory, since it would add new entries to the memory debug structure.
659 // The good news is that we don't care about speed too much in this function, since it's not supposed
660 // to be called all the time.
669 static int SortCB(const void* elem1, const void* elem2)
671 const TmpStruct* s1 = (const TmpStruct*)elem1;
672 const TmpStruct* s2 = (const TmpStruct*)elem2;
673 return strcmp(s1->mName, s2->mName);
677 Local::TmpStruct* SortedStrings = (Local::TmpStruct*)LOCAL_MALLOC(sizeof(Local::TmpStruct)*mMemBlockListSize);
678 udword NbStrings = 0;
679 udword TotalSize = 0;
680 for(udword i=0;i<mMemBlockListSize;i++)
682 if(ValidAddress(mMemBlockList[i]))
684 const DebugBlock* DB = (const DebugBlock*)mMemBlockList[i];
687 SortedStrings[NbStrings].mName = DB->mClassName; // Memory by class
688 // SortedStrings[NbStrings].mName = DB->mFilename; // Memory by file
689 SortedStrings[NbStrings].mSize = DB->mSize;
690 TotalSize += DB->mSize;
695 qsort(SortedStrings, NbStrings, sizeof(Local::TmpStruct), Local::SortCB);
697 // Strings are now sorted. They might still be duplicated, i.e. we may have two strings for the same
698 // class. So now we parse the list and collect used memory for all classes. Then we sort this again,
699 // to report results in order of increasing memory.
702 udword* Classes = (udword*)LOCAL_MALLOC(sizeof(udword)*NbStrings);
703 udword* Sizes = (udword*)LOCAL_MALLOC(sizeof(udword)*NbStrings);
705 udword CurrentSize = SortedStrings[0].mSize;
706 const char* CurrentClass = SortedStrings[0].mName;
707 for(udword i=1;i<=NbStrings;i++) // One more time on purpose
709 const char* Current = null;
712 Current = SortedStrings[i].mName;
715 if(Current && strcmp(Current, CurrentClass)==0)
718 CurrentSize += SortedStrings[i].mSize;
724 // Store previous class
727 Classes[NbClasses] = (udword)CurrentClass; // We can store this pointer now because it's unique in our new array
728 Sizes[NbClasses++] = CurrentSize;
734 CurrentClass = Current;
735 CurrentSize = SortedStrings[i].mSize;
740 udword* Ranks0 = (udword*)LOCAL_MALLOC(sizeof(udword)*NbClasses);
741 udword* Ranks1 = (udword*)LOCAL_MALLOC(sizeof(udword)*NbClasses);
743 StackRadixSort(RS, Ranks0, Ranks1);
744 const udword* Sorted = RS.Sort(Sizes, NbClasses).GetRanks();
745 for(udword i=0;i<NbClasses;i++)
747 udword Index = Sorted[i];
749 sprintf(Buffer, "%s : %d\n", (const char*)Classes[Index], Sizes[Index]);
753 sprintf(Buffer, "Total size: %d\n", TotalSize);
762 LOCAL_FREE(SortedStrings);
767 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
769 // ### probably a bad idea to have a static var. Are we sure the "const char*" for class names/etc
770 // are still valid when this gets deleted?
772 static Allocator* gAllocator = null;
773 static DefaultAllocator gDefault;
774 //static DefaultAllocator* gDefault = null;
776 Allocator* IceCore::GetAllocator()
778 if(!gAllocator) gAllocator = &gDefault;
779 // if(!gAllocator) gAllocator = gDefault;
783 bool IceCore::SetAllocator(Allocator& allocator)
785 // ### make sure nothing has been allocated from the default one
786 gAllocator = &allocator;
790 void IceCore::DumpMemory()
792 gDefault.DumpCurrentMemoryState();
793 // if(gDefault) gDefault->DumpCurrentMemoryState();
796 void InitDefaultAllocator()
798 // gDefault = ::new DefaultAllocator;
801 void ReleaseDefaultAllocator()
803 // if(gDefault) ::delete gDefault;