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 104 return ret; 105 } 106 107 void free(void[] mem) 108 { 109 version(TLSGC) { } else { 110 mtx.lock_nothrow(); 111 scope(exit) mtx.unlock_nothrow(); 112 } 113 assert(mem.length > 0, "Cannot serve a zero-length deallocation"); 114 115 size_t sz; 116 synchronized(this) { 117 sz = m_blocks.get(cast(const size_t)mem.ptr, size_t.max); 118 119 assert(sz != size_t.max, "free() called with non-allocated object. "~ mem.ptr.to!string ~ " (" ~ mem.length.to!string ~" B) m_blocks len: "~ m_blocks.length.to!string); 120 assert(sz == mem.length, "free() called with block of wrong size: got " ~ mem.length.to!string ~ "B but should be " ~ sz.to!string ~ "B"); 121 122 } 123 124 //logDebug("free ptr: ", mem.ptr, " sz: ", mem.length); 125 m_baseAlloc.free(mem); 126 127 synchronized(this) { 128 m_bytes -= sz; 129 if (m_freeSizeCallback) 130 m_freeSizeCallback(sz); 131 m_blocks.remove(cast(size_t)mem.ptr); 132 } 133 } 134 135 package void ignore(void* ptr) { 136 synchronized(this) { 137 size_t sz = m_blocks.get(cast(const size_t) ptr, size_t.max); 138 m_bytes -= sz; 139 m_blocks.remove(cast(size_t)ptr); 140 } 141 } 142 }