1 /* 2 Copyright (c) 2015-2021 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 /** 30 * Tools for manual memory management 31 * 32 * New/Delete for classes, structs and arrays. It utilizes dlib.memory 33 * for actual memory allocation, so it is possible to switch allocator that is 34 * being used. By default, dlib.memory.mallocator.Mallocator is used. 35 * 36 * Module includes a simple memory profiler that can be turned on with enableMemoryProfiler 37 * function. If active, it will store information about every allocation (type and size), 38 * and will mark those which are leaked (haven't been deleted). 39 * 40 * Copyright: Timur Gafarov 2015-2021. 41 * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). 42 * Authors: Timur Gafarov 43 */ 44 module dlib.core.memory; 45 46 import std.stdio; 47 import std.conv; 48 import std.traits; 49 import std.datetime; 50 import std.algorithm; 51 import core.stdc.stdlib; 52 import core.exception: onOutOfMemoryError; 53 54 import dlib.memory; 55 56 private __gshared ulong _allocatedMemory = 0; 57 58 /** 59 * Returns current amount of allocated memory in bytes. This is 0 at program start 60 */ 61 ulong allocatedMemory() 62 { 63 return _allocatedMemory; 64 } 65 66 private __gshared Mallocator _defaultGlobalAllocator; 67 private __gshared Allocator _globalAllocator; 68 69 /** 70 * Returns current global Allocator that is used by New and Delete 71 */ 72 Allocator globalAllocator() 73 { 74 if (_globalAllocator is null) 75 { 76 if (_defaultGlobalAllocator is null) 77 _defaultGlobalAllocator = Mallocator.instance; 78 _globalAllocator = _defaultGlobalAllocator; 79 } 80 return _globalAllocator; 81 } 82 83 /** 84 * Sets global Allocator that is used by New and Delete 85 */ 86 void globalAllocator(Allocator a) 87 { 88 _globalAllocator = a; 89 } 90 91 struct AllocationRecord 92 { 93 string type; 94 size_t size; 95 string file; 96 int line; 97 ulong id; 98 bool deleted; 99 } 100 101 private 102 { 103 __gshared bool memoryProfilerEnabled = false; 104 __gshared AllocationRecord[ulong] records; 105 __gshared ulong counter = 0; 106 107 void addRecord(void* p, string type, size_t size, string file = "<undefined>", int line = 0) 108 { 109 records[cast(ulong)p] = AllocationRecord(type, size, file, line, counter, false); 110 counter++; 111 } 112 113 void markDeleted(void* p) 114 { 115 ulong k = cast(ulong)p - psize; 116 records[k].deleted = true; 117 } 118 } 119 120 /** 121 * Enables or disables memory profiler 122 */ 123 void enableMemoryProfiler(bool mode) 124 { 125 memoryProfilerEnabled = mode; 126 } 127 128 /** 129 * Prints full allocation list if memory profiler is enabled, otherwise does nothing 130 */ 131 void printMemoryLog() 132 { 133 writeln("----------------------------------------------------"); 134 writeln(" Memory allocation log "); 135 writeln("----------------------------------------------------"); 136 auto keys = records.keys; 137 sort!((a, b) => records[a].id < records[b].id)(keys); 138 foreach(k; keys) 139 { 140 AllocationRecord r = records[k]; 141 if (r.deleted) 142 writefln(" %s - %s byte(s) in %s(%s)", r.type, r.size, r.file, r.line); 143 else 144 writefln("REMAINS: %s - %s byte(s) in %s(%s)", r.type, r.size, r.file, r.line); 145 } 146 writeln("----------------------------------------------------"); 147 writefln("Total amount of allocated memory: %s byte(s)", _allocatedMemory); 148 writeln("----------------------------------------------------"); 149 } 150 151 /** 152 * Prints leaked allocations if memory profiler is enabled, otherwise does nothing 153 */ 154 void printMemoryLeaks() 155 { 156 writeln("----------------------------------------------------"); 157 writeln(" Memory leaks "); 158 writeln("----------------------------------------------------"); 159 auto keys = records.keys; 160 sort!((a, b) => records[a].id < records[b].id)(keys); 161 foreach(k; keys) 162 { 163 AllocationRecord r = records[k]; 164 if (!r.deleted) 165 writefln("%s - %s byte(s) in %s(%s)", r.type, r.size, r.file, r.line); 166 } 167 writeln("----------------------------------------------------"); 168 writefln("Total amount of leaked memory: %s byte(s)", _allocatedMemory); 169 writeln("----------------------------------------------------"); 170 } 171 172 interface Freeable 173 { 174 void free(); 175 } 176 177 enum psize = 8; 178 179 static if (__VERSION__ >= 2079) 180 { 181 T allocate(T, A...) (A args, string file = __FILE__, int line = __LINE__) if (is(T == class)) 182 { 183 enum size = __traits(classInstanceSize, T); 184 void* p = globalAllocator.allocate(size+psize).ptr; 185 if (!p) 186 onOutOfMemoryError(); 187 auto memory = p[psize..psize+size]; 188 *cast(size_t*)p = size; 189 _allocatedMemory += size; 190 if (memoryProfilerEnabled) 191 { 192 addRecord(p, T.stringof, size, file, line); 193 } 194 auto res = emplace!(T, A)(memory, args); 195 return res; 196 } 197 198 T* allocate(T, A...) (A args, string file = __FILE__, int line = __LINE__) if (is(T == struct)) 199 { 200 enum size = T.sizeof; 201 void* p = globalAllocator.allocate(size+psize).ptr; 202 if (!p) 203 onOutOfMemoryError(); 204 auto memory = p[psize..psize+size]; 205 *cast(size_t*)p = size; 206 _allocatedMemory += size; 207 if (memoryProfilerEnabled) 208 { 209 addRecord(p, T.stringof, size, file, line); 210 } 211 return emplace!(T, A)(memory, args); 212 } 213 214 T allocate(T) (size_t length, string file = __FILE__, int line = __LINE__) if (isArray!T) 215 { 216 alias AT = ForeachType!T; 217 size_t size = length * AT.sizeof; 218 auto mem = globalAllocator.allocate(size+psize).ptr; 219 if (!mem) 220 onOutOfMemoryError(); 221 T arr = cast(T)mem[psize..psize+size]; 222 foreach(ref v; arr) 223 v = v.init; 224 *cast(size_t*)mem = size; 225 _allocatedMemory += size; 226 if (memoryProfilerEnabled) 227 { 228 addRecord(mem, T.stringof, size, file, line); 229 } 230 return arr; 231 } 232 } 233 else 234 { 235 T allocate(T, A...) (A args) if (is(T == class)) 236 { 237 enum size = __traits(classInstanceSize, T); 238 void* p = globalAllocator.allocate(size+psize).ptr; 239 if (!p) 240 onOutOfMemoryError(); 241 auto memory = p[psize..psize+size]; 242 *cast(size_t*)p = size; 243 _allocatedMemory += size; 244 if (memoryProfilerEnabled) 245 { 246 addRecord(p, T.stringof, size); 247 } 248 auto res = emplace!(T, A)(memory, args); 249 return res; 250 } 251 252 T* allocate(T, A...) (A args) if (is(T == struct)) 253 { 254 enum size = T.sizeof; 255 void* p = globalAllocator.allocate(size+psize).ptr; 256 if (!p) 257 onOutOfMemoryError(); 258 auto memory = p[psize..psize+size]; 259 *cast(size_t*)p = size; 260 _allocatedMemory += size; 261 if (memoryProfilerEnabled) 262 { 263 addRecord(p, T.stringof, size); 264 } 265 return emplace!(T, A)(memory, args); 266 } 267 268 T allocate(T) (size_t length) if (isArray!T) 269 { 270 alias AT = ForeachType!T; 271 size_t size = length * AT.sizeof; 272 auto mem = globalAllocator.allocate(size+psize).ptr; 273 if (!mem) 274 onOutOfMemoryError(); 275 T arr = cast(T)mem[psize..psize+size]; 276 foreach(ref v; arr) 277 v = v.init; 278 *cast(size_t*)mem = size; 279 _allocatedMemory += size; 280 if (memoryProfilerEnabled) 281 { 282 addRecord(mem, T.stringof, size); 283 } 284 return arr; 285 } 286 } 287 288 void deallocate(T)(ref T obj) if (isArray!T) 289 { 290 void* p = cast(void*)obj.ptr; 291 size_t size = *cast(size_t*)(p - psize); 292 globalAllocator.deallocate((p - psize)[0..size+psize]); 293 _allocatedMemory -= size; 294 if (memoryProfilerEnabled) 295 { 296 markDeleted(p); 297 } 298 obj.length = 0; 299 } 300 301 void deallocate(T)(T obj) if (is(T == class) || is(T == interface)) 302 { 303 Object o = cast(Object)obj; 304 void* p = cast(void*)o; 305 size_t size = *cast(size_t*)(p - psize); 306 destroy(obj); 307 globalAllocator.deallocate((p - psize)[0..size+psize]); 308 _allocatedMemory -= size; 309 if (memoryProfilerEnabled) 310 { 311 markDeleted(p); 312 } 313 } 314 315 void deallocate(T)(T* obj) 316 { 317 void* p = cast(void*)obj; 318 size_t size = *cast(size_t*)(p - psize); 319 destroy(obj); 320 globalAllocator.deallocate((p - psize)[0..size+psize]); 321 _allocatedMemory -= size; 322 if (memoryProfilerEnabled) 323 { 324 markDeleted(p); 325 } 326 } 327 328 /** 329 Creates an object of type T and calls its constructor if necessary. 330 331 Description: 332 This is an equivalent for D's new opetator. It allocates arrays, 333 classes and structs on a heap using currently set globalAllocator. 334 Arguments to this function are passed to constructor. 335 336 Examples: 337 ---- 338 MyClass c = New!MyClass(10, 4, 5); 339 int[] arr = New!(int[])(100); 340 assert(arr.length == 100); 341 MyStruct* s = New!MyStruct; 342 Delete(c); 343 Delete(arr); 344 Delete(s); 345 ---- 346 */ 347 alias New = allocate; 348 349 /** 350 Destroys an object of type T previously created by New and calls 351 its destructor if necessary. 352 353 Examples: 354 ---- 355 MyClass c = New!MyClass(10, 4, 5); 356 int[] arr = New!(int[])(100); 357 assert(arr.length == 100); 358 MyStruct* s = New!MyStruct; 359 Delete(c); 360 Delete(arr); 361 Delete(s); 362 ---- 363 */ 364 alias Delete = deallocate; 365 366 unittest 367 { 368 auto mem = allocatedMemory(); 369 int[] arr = New!(int[])(100); 370 assert(arr.length == 100); 371 assert(allocatedMemory() - mem == uint.sizeof * 100); 372 Delete(arr); 373 assert(arr.length == 0); 374 375 struct Foo { int a; } 376 Foo* foo = New!Foo(10); 377 assert(foo.a == 10); 378 Delete(foo); 379 }