1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 /**
6  * This module contains the interface for implementing custom allocators.
7  *
8  * Allocators are classes encapsulating memory allocation strategy. This allows
9  * to decouple memory management from the algorithms and the data. 
10  *
11  * Copyright: Eugene Wissner 2016-2020.
12  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
13  *                  Mozilla Public License, v. 2.0).
14  * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
15  * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/allocator.d,
16  *                 tanya/memory/allocator.d)
17  */
18 module tanya.memory.allocator;
19 
20 import tanya.memory.lifetime;
21 import tanya.meta.trait;
22 
23 /**
24  * Abstract class implementing a basic allocator.
25  */
26 interface Allocator
27 {
28     /**
29      * Returns: Alignment offered.
30      */
31     @property uint alignment() const shared pure nothrow @safe @nogc;
32 
33     /**
34      * Allocates $(D_PARAM size) bytes of memory.
35      *
36      * Params:
37      *  size = Amount of memory to allocate.
38      *
39      * Returns: Pointer to the new allocated memory.
40      */
41     void[] allocate(size_t size) shared pure nothrow @nogc;
42 
43     /**
44      * Deallocates a memory block.
45      *
46      * Params:
47      *  p = A pointer to the memory block to be freed.
48      *
49      * Returns: Whether the deallocation was successful.
50      */
51     bool deallocate(void[] p) shared pure nothrow @nogc;
52 
53     /**
54      * Increases or decreases the size of a memory block.
55      *
56      * Params:
57      *  p    = A pointer to the memory block.
58      *  size = Size of the reallocated block.
59      *
60      * Returns: Pointer to the allocated memory.
61      */
62     bool reallocate(ref void[] p, size_t size) shared pure nothrow @nogc;
63 
64     /**
65      * Reallocates a memory block in place if possible or returns
66      * $(D_KEYWORD false). This function cannot be used to allocate or
67      * deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
68      * $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
69      *
70      * Params:
71      *  p    = A pointer to the memory block.
72      *  size = Size of the reallocated block.
73      *
74      * Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
75      */
76     bool reallocateInPlace(ref void[] p, size_t size)
77     shared pure nothrow @nogc;
78 }
79 
80 package template GetPureInstance(T : Allocator)
81 {
82     alias GetPureInstance = shared(T) function()
83                             pure nothrow @nogc;
84 }
85 
86 /**
87  * The mixin generates common methods for classes and structs using
88  * allocators. It provides a protected member, constructor and a read-only property,
89  * that checks if an allocator was already set and sets it to the default
90  * one, if not (useful for structs which don't have a default constructor).
91  */
92 mixin template DefaultAllocator()
93 {
94     /// Allocator.
95     protected shared Allocator allocator_;
96 
97     /**
98      * Params:
99      *  allocator = The allocator should be used.
100      *
101      * Precondition: $(D_INLINECODE allocator_ !is null)
102      */
103     this(shared Allocator allocator) @nogc nothrow pure @safe
104     in
105     {
106         assert(allocator !is null);
107     }
108     do
109     {
110         this.allocator_ = allocator;
111     }
112 
113     /**
114      * This property checks if the allocator was set in the constructor
115      * and sets it to the default one, if not.
116      *
117      * Returns: Used allocator.
118      *
119      * Postcondition: $(D_INLINECODE allocator !is null)
120      */
121     @property shared(Allocator) allocator() @nogc nothrow pure @safe
122     out (allocator)
123     {
124         assert(allocator !is null);
125     }
126     do
127     {
128         if (allocator_ is null)
129         {
130             allocator_ = defaultAllocator;
131         }
132         return allocator_;
133     }
134 
135     /// ditto
136     @property shared(Allocator) allocator() const @nogc nothrow pure @trusted
137     out (allocator)
138     {
139         assert(allocator !is null);
140     }
141     do
142     {
143         if (allocator_ is null)
144         {
145             return defaultAllocator;
146         }
147         return cast(shared Allocator) allocator_;
148     }
149 }
150 
151 shared Allocator allocator;
152 
153 private shared(Allocator) getAllocatorInstance() @nogc nothrow
154 {
155     if (allocator is null)
156     {
157         version (TanyaNative)
158         {
159             import tanya.memory.mmappool : MmapPool;
160             defaultAllocator = MmapPool.instance;
161         }
162         else
163         {
164             import tanya.memory.mallocator : Mallocator;
165             defaultAllocator = Mallocator.instance;
166         }
167     }
168     return allocator;
169 }
170 
171 /**
172  * Returns: Default allocator.
173  *
174  * Postcondition: $(D_INLINECODE allocator !is null).
175  */
176 @property shared(Allocator) defaultAllocator() @nogc nothrow pure @trusted
177 out (allocator)
178 {
179     assert(allocator !is null);
180 }
181 do
182 {
183     return (cast(GetPureInstance!Allocator) &getAllocatorInstance)();
184 }
185 
186 /**
187  * Sets the default allocator.
188  *
189  * Params:
190  *  allocator = $(D_PSYMBOL Allocator) instance.
191  *
192  * Precondition: $(D_INLINECODE allocator !is null).
193  */
194 @property void defaultAllocator(shared(Allocator) allocator) @nogc nothrow @safe
195 in
196 {
197     assert(allocator !is null);
198 }
199 do
200 {
201     .allocator = allocator;
202 }
203 
204 /**
205  * Params:
206  *  size      = Raw size.
207  *  alignment = Alignment.
208  *
209  * Returns: Aligned size.
210  */
211 size_t alignedSize(const size_t size, const size_t alignment = 8)
212 pure nothrow @safe @nogc
213 {
214     return (size - 1) / alignment * alignment + alignment;
215 }
216 
217 /**
218  * Error thrown if memory allocation fails.
219  */
220 final class OutOfMemoryError : Error
221 {
222     /**
223      * Constructs new error.
224      *
225      * Params:
226      *  msg  = The message for the exception.
227      *  file = The file where the exception occurred.
228      *  line = The line number where the exception occurred.
229      *  next = The previous exception in the chain of exceptions, if any.
230      */
231     this(string msg = "Out of memory",
232          string file = __FILE__,
233          size_t line = __LINE__,
234          Throwable next = null) @nogc nothrow pure @safe
235     {
236         super(msg, file, line, next);
237     }
238 
239     /// ditto
240     this(string msg,
241          Throwable next,
242          string file = __FILE__,
243          size_t line = __LINE__) @nogc nothrow pure @safe
244     {
245         super(msg, file, line, next);
246     }
247 }
248 
249 /**
250  * Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T).
251  * It is assumed the respective entities had been allocated with the same
252  * allocator.
253  *
254  * Params:
255  *  T         = Type of $(D_PARAM p).
256  *  allocator = Allocator the $(D_PARAM p) was allocated with.
257  *  p         = Object or array to be destroyed.
258  */
259 void dispose(T)(shared Allocator allocator, auto ref T p)
260 {
261     () @trusted { allocator.deallocate(finalize(p)); }();
262     p = null;
263 }
264 
265 /**
266  * Constructs a new class instance of type $(D_PARAM T) using $(D_PARAM args)
267  * as the parameter list for the constructor of $(D_PARAM T).
268  *
269  * Params:
270  *  T         = Class type.
271  *  A         = Types of the arguments to the constructor of $(D_PARAM T).
272  *  allocator = Allocator.
273  *  args      = Constructor arguments of $(D_PARAM T).
274  *
275  * Returns: Newly created $(D_PSYMBOL T).
276  *
277  * Precondition: $(D_INLINECODE allocator !is null)
278  */
279 T make(T, A...)(shared Allocator allocator, auto ref A args)
280 if (is(T == class))
281 in
282 {
283     assert(allocator !is null);
284 }
285 do
286 {
287     auto mem = (() @trusted => allocator.allocate(stateSize!T))();
288     if (mem is null)
289     {
290         onOutOfMemoryError();
291     }
292     scope (failure)
293     {
294         () @trusted { allocator.deallocate(mem); }();
295     }
296 
297     return emplace!T(mem[0 .. stateSize!T], args);
298 }
299 
300 /**
301  * Constructs a value object of type $(D_PARAM T) using $(D_PARAM args)
302  * as the parameter list for the constructor of $(D_PARAM T) and returns a
303  * pointer to the new object.
304  *
305  * Params:
306  *  T         = Object type.
307  *  A         = Types of the arguments to the constructor of $(D_PARAM T).
308  *  allocator = Allocator.
309  *  args      = Constructor arguments of $(D_PARAM T).
310  *
311  * Returns: Pointer to the created object.
312  *
313  * Precondition: $(D_INLINECODE allocator !is null)
314  */
315 T* make(T, A...)(shared Allocator allocator, auto ref A args)
316 if (!isPolymorphicType!T && !isAssociativeArray!T && !isArray!T)
317 in
318 {
319     assert(allocator !is null);
320 }
321 do
322 {
323     auto mem = (() @trusted => allocator.allocate(stateSize!T))();
324     if (mem is null)
325     {
326         onOutOfMemoryError();
327     }
328     scope (failure)
329     {
330         () @trusted { allocator.deallocate(mem); }();
331     }
332     return emplace!T(mem[0 .. stateSize!T], args);
333 }
334 
335 ///
336 @nogc nothrow pure @safe unittest
337 {
338     int* i = defaultAllocator.make!int(5);
339     assert(*i == 5);
340     defaultAllocator.dispose(i);
341 }
342 
343 /**
344  * Constructs a new array with $(D_PARAM n) elements.
345  *
346  * Params:
347  *  T         = Array type.
348  *  E         = Array element type.
349  *  allocator = Allocator.
350  *  n         = Array size.
351  *
352  * Returns: Newly created array.
353  *
354  * Precondition: $(D_INLINECODE allocator !is null
355  *                           && n <= size_t.max / E.sizeof)
356  */
357 T make(T : E[], E)(shared Allocator allocator, size_t n)
358 in
359 {
360     assert(allocator !is null);
361     assert(n <= size_t.max / E.sizeof);
362 }
363 do
364 {
365     auto ret = allocator.resize!E(null, n);
366 
367     static if (hasElaborateDestructor!E)
368     {
369         for (auto range = ret; range.length != 0; range = range[1 .. $])
370         {
371             emplace!E(cast(void[]) range[0 .. 1], E.init);
372         }
373     }
374     else
375     {
376         ret[] = E.init;
377     }
378 
379     return ret;
380 }
381 
382 ///
383 @nogc nothrow pure @safe unittest
384 {
385     int[] i = defaultAllocator.make!(int[])(2);
386     assert(i.length == 2);
387     assert(i[0] == int.init && i[1] == int.init);
388     defaultAllocator.dispose(i);
389 }
390 
391 /*
392  * Destroys the object.
393  * Returns the memory should be freed.
394  */
395 package void[] finalize(T)(ref T* p)
396 {
397     if (p is null)
398     {
399         return null;
400     }
401     static if (hasElaborateDestructor!T)
402     {
403         destroy(*p);
404     }
405     return (cast(void*) p)[0 .. T.sizeof];
406 }
407 
408 package void[] finalize(T)(ref T p)
409 if (isPolymorphicType!T)
410 {
411     if (p is null)
412     {
413         return null;
414     }
415     static if (is(T == interface))
416     {
417         version(Windows)
418         {
419             import core.sys.windows.unknwn : IUnknown;
420             static assert(!is(T : IUnknown), "COM interfaces can't be destroyed in "
421                                            ~ __PRETTY_FUNCTION__);
422         }
423         auto ob = cast(Object) p;
424     }
425     else
426     {
427         alias ob = p;
428     }
429     auto ptr = cast(void*) ob;
430     auto support = ptr[0 .. typeid(ob).initializer.length];
431 
432     auto ppv = cast(void**) ptr;
433     if (!*ppv)
434     {
435         return null;
436     }
437     auto pc = cast(ClassInfo*) *ppv;
438     scope (exit)
439     {
440         *ppv = null;
441     }
442 
443     auto c = *pc;
444     do
445     {
446         // Assume the destructor is @nogc. Leave it nothrow since the destructor
447         // shouldn't throw and if it does, it is an error anyway.
448         if (c.destructor)
449         {
450             alias DtorType = void function(Object) pure nothrow @safe @nogc;
451             (cast(DtorType) c.destructor)(ob);
452         }
453     }
454     while ((c = c.base) !is null);
455 
456     if (ppv[1]) // if monitor is not null
457     {
458         _d_monitordelete(cast(Object) ptr, true);
459     }
460     return support;
461 }
462 
463 package void[] finalize(T)(ref T[] p)
464 {
465     destroyAllImpl!(T[], T)(p);
466     return p;
467 }
468 
469 /**
470  * Allocates $(D_PSYMBOL OutOfMemoryError) in a static storage and throws it.
471  *
472  * Params:
473  *  msg = Custom error message.
474  *
475  * Throws: $(D_PSYMBOL OutOfMemoryError).
476  */
477 void onOutOfMemoryError(string msg = "Out of memory")
478 @nogc nothrow pure @trusted
479 {
480     static ubyte[stateSize!OutOfMemoryError] memory;
481     alias PureType = OutOfMemoryError function(string) @nogc nothrow pure;
482     throw (cast(PureType) () => emplace!OutOfMemoryError(memory))(msg);
483 }
484 
485 // From druntime
486 extern (C)
487 private void _d_monitordelete(Object h, bool det) @nogc nothrow pure;
488 
489 /*
490  * Internal function used to create, resize or destroy a dynamic array. It
491  * may throw $(D_PSYMBOL OutOfMemoryError). The new
492  * allocated part of the array isn't initialized. This function can be trusted
493  * only in the data structures that can ensure that the array is
494  * allocated/rellocated/deallocated with the same allocator.
495  *
496  * Params:
497  *  T         = Element type of the array being created.
498  *  allocator = The allocator used for getting memory.
499  *  array     = A reference to the array being changed.
500  *  length    = New array length.
501  *
502  * Returns: $(D_PARAM array).
503  */
504 package(tanya) T[] resize(T)(shared Allocator allocator,
505                              auto ref T[] array,
506                              const size_t length) @trusted
507 {
508     if (length == 0)
509     {
510         if (allocator.deallocate(array))
511         {
512             return null;
513         }
514         else
515         {
516             onOutOfMemoryError();
517         }
518     }
519 
520     void[] buf = array;
521     if (!allocator.reallocate(buf, length * T.sizeof))
522     {
523         onOutOfMemoryError();
524     }
525     // Casting from void[] is unsafe, but we know we cast to the original type.
526     array = cast(T[]) buf;
527 
528     return array;
529 }