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 }