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 }