1 module memutils.debugger; 2 import memutils.allocators; 3 import memutils.hashmap; 4 import memutils.dictionarylist; 5 import memutils.utils : Malloc; 6 import std.conv : emplace, to; 7 8 /** 9 * Another proxy allocator used to aggregate statistics and to enforce correct usage. 10 */ 11 final class DebugAllocator(Base : Allocator) : Allocator { 12 private { 13 static if (HasDictionaryDebugger) DictionaryListRef!(size_t, size_t, Malloc) m_blocks; 14 else HashMap!(size_t, size_t, Malloc) m_blocks; 15 size_t m_bytes; 16 size_t m_maxBytes; 17 void function(size_t) m_allocSizeCallback; 18 void function(size_t) m_freeSizeCallback; 19 } 20 package Base m_baseAlloc; 21 22 this() 23 { 24 version(TLSGC) { } else { 25 if (!mtx) mtx = new Mutex; 26 } 27 m_baseAlloc = getAllocator!Base(); 28 } 29 30 public { 31 void setAllocSizeCallbacks(void function(size_t) alloc_sz_cb, void function(size_t) free_sz_cb) { 32 m_allocSizeCallback = alloc_sz_cb; 33 m_freeSizeCallback = free_sz_cb; 34 } 35 @property size_t allocatedBlockCount() const { return m_blocks.length; } 36 @property size_t bytesAllocated() const { return m_bytes; } 37 @property size_t maxBytesAllocated() const { return m_maxBytes; } 38 void printMap() { 39 foreach(const ref size_t ptr, const ref size_t sz; m_blocks) { 40 logDebug(cast(void*)ptr, " sz ", sz); 41 } 42 } 43 static if (HasDictionaryDebugger) auto getMap() { 44 return m_blocks; 45 } 46 } 47 void[] alloc(size_t sz) 48 { 49 version(TLSGC) { } else { 50 mtx.lock_nothrow(); 51 scope(exit) mtx.unlock_nothrow(); 52 } 53 54 assert(sz > 0, "Cannot serve a zero-length allocation"); 55 56 //logTrace("Bytes allocated in ", Base.stringof, ": ", bytesAllocated()); 57 auto ret = m_baseAlloc.alloc(sz); 58 synchronized(this) { 59 assert(ret.length == sz, "base.alloc() returned block with wrong size."); 60 assert( cast(size_t)ret.ptr !in m_blocks, "Returning already allocated pointer"); 61 m_blocks[cast(size_t)ret.ptr] = sz; 62 m_bytes += sz; 63 if (m_allocSizeCallback) 64 m_allocSizeCallback(sz); 65 if( m_bytes > m_maxBytes ){ 66 m_maxBytes = m_bytes; 67 //logTrace("New allocation maximum: %d (%d blocks)", m_maxBytes, m_blocks.length); 68 } 69 } 70 //logDebug("Alloc ptr: ", ret.ptr, " sz: ", ret.length); 71 72 return ret; 73 } 74 75 void[] realloc(void[] mem, size_t new_size) 76 { 77 version(TLSGC) { } else { 78 mtx.lock_nothrow(); 79 scope(exit) mtx.unlock_nothrow(); 80 } 81 assert(new_size > 0 && mem.length > 0, "Cannot serve a zero-length reallocation"); 82 void[] ret; 83 size_t sz; 84 synchronized(this) { 85 sz = m_blocks.get(cast(size_t)mem.ptr, size_t.max); 86 assert(sz != size_t.max, "realloc() called with non-allocated pointer."); 87 assert(sz == mem.length, "realloc() called with block of wrong size."); 88 } 89 ret = m_baseAlloc.realloc(mem, new_size); 90 synchronized(this) { 91 //assert( cast(size_t)ret.ptr !in m_blocks, "Returning from realloc already allocated pointer"); 92 assert(ret.length == new_size, "base.realloc() returned block with wrong size."); 93 //assert(ret.ptr is mem.ptr || m_blocks.get(ret.ptr, size_t.max) == size_t.max, "base.realloc() returned block that is already allocated."); 94 m_bytes -= sz; 95 m_blocks.remove(cast(size_t)mem.ptr); 96 m_blocks[cast(size_t)ret.ptr] = new_size; 97 m_bytes += new_size; 98 if (m_freeSizeCallback) 99 m_freeSizeCallback(sz); 100 if (m_allocSizeCallback) 101 m_allocSizeCallback(new_size); 102 } 103 return ret; 104 } 105 106 void free(void[] mem) 107 { 108 version(TLSGC) { } else { 109 mtx.lock_nothrow(); 110 scope(exit) mtx.unlock_nothrow(); 111 } 112 assert(mem.length > 0, "Cannot serve a zero-length deallocation"); 113 114 size_t sz; 115 synchronized(this) { 116 sz = m_blocks.get(cast(const size_t)mem.ptr, size_t.max); 117 if (sz == size_t.max || sz != mem.length) logError("Debug Info: ", mem.ptr, " (", mem.length, " B) m_blocks len: ", m_blocks.length, " sz ", sz); 118 assert(sz != size_t.max, "free() called with non-allocated object."); 119 assert(sz == mem.length, "free() called with block of wrong size."); 120 121 } 122 123 //logDebug("free ptr: ", mem.ptr, " sz: ", mem.length); 124 m_baseAlloc.free(mem); 125 126 synchronized(this) { 127 m_bytes -= sz; 128 if (m_freeSizeCallback) 129 m_freeSizeCallback(sz); 130 m_blocks.remove(cast(size_t)mem.ptr); 131 } 132 } 133 134 package void ignore(void* ptr) { 135 synchronized(this) { 136 size_t sz = m_blocks.get(cast(const size_t) ptr, size_t.max); 137 m_bytes -= sz; 138 m_blocks.remove(cast(size_t)ptr); 139 } 140 } 141 }