1 module evael.lib.memory;
2 
3 import std.experimental.allocator.mallocator;
4 import std.conv : emplace;
5 import std.traits : isImplicitlyConvertible;
6 
7 public
8 {
9     import evael.lib.memory.no_gc_class;
10 }
11 
12 debug
13 {
14     import std.experimental.allocator.building_blocks.stats_collector;
15 
16     alias CustomStatsCollector = StatsCollector!(Mallocator, 
17         Options.all, // Global stats
18         Options.all  // Per call stats
19     );
20 
21     __gshared CustomStatsCollector defaultAllocator;
22 
23     static ~this()
24     {
25         import std.stdio : writefln;
26         if (defaultAllocator.bytesUsed != 0)
27         {
28             writefln("StatsCollector: There are still %d bytes used, you probably have a memory leak.",
29                 defaultAllocator.bytesUsed);
30         }
31     }
32 }
33 else
34 {
35     __gshared Mallocator defaultAllocator;
36 }
37 
38 /// From druntime. Used by MemoryHelper.dispose(...).
39 extern (C)
40 private void _d_monitordelete(Object h, bool det) @nogc nothrow pure;
41 
42 /**
43  * Static helper that provides @nogc new and delete.
44  */
45 static class MemoryHelper
46 {
47     /** 
48      * @nogc new for classes.
49      * Only classes that inherit from NoGCClass can be allocated with this function.
50      */
51     @nogc
52     public static T create(T, Args...)(Args args, string file = __FILE__, int line =__LINE__) if (is(T == class))
53     {
54         static if (!isImplicitlyConvertible!(T, NoGCClass))
55         {
56             static assert(false, "Your class must inerith from NoGCClass if you want to use create.");
57         }
58 
59         enum size = __traits(classInstanceSize, T);
60 
61         auto memory = defaultAllocator.allocate(size);
62         (cast(void*) memory)[0..size] = typeid(T).initializer[];
63 
64         T ret = emplace!(T, Args)(memory, args);
65         ret.instantiatedWithGC = false;
66 
67         return ret;
68     }
69 
70     /** 
71      * @nogc delete for classes and interfaces.
72      *
73      * - If this function is called on classes that inherit from NoGCClass / NoGCInterface (and are instantiated with @nogc new),
74      * 	 this functon will free memory and call object's destructor.
75      *
76      * - If it is called on classes allocated with GC, only the destructor will be called.
77      */
78     @nogc
79     public static void dispose(T)(ref T instance, string file = __FILE__, int line =__LINE__) if (is(T == class) || is(T == interface))
80     {
81         if (instance is null)
82             return;
83 
84         enum isNoGCClass = isImplicitlyConvertible!(T, NoGCClass) || isImplicitlyConvertible!(T, NoGCInterface);
85 
86         static if (isNoGCClass)
87         {
88             bool instantiatedWithGC = (cast(NoGCClass) instance).instantiatedWithGC;
89         }
90 
91         auto support = MemoryHelper.finalize(instance);
92 
93         static if(isNoGCClass)
94         {
95             if (instantiatedWithGC == false && support !is null)
96             {
97                 defaultAllocator.deallocate(support);
98             }
99         }
100 
101         instance = null;
102     }
103 
104     /**
105      * Calls the destructor of an object. 
106      * This code has been taken from the druntime repository.
107      */
108     @nogc
109     private static void[] finalize(T)(ref T instance)
110     {
111         auto obj = cast(Object) instance;
112         auto p = cast(void*) obj;
113         auto ppv = cast(void**) p;
114 
115         if (!p || !*ppv)
116             return null;
117 
118         auto pc = cast(ClassInfo*) *ppv;
119         auto c = *pc;
120 
121         do
122         {
123             if (c.destructor)
124             {
125                 alias DtorType = void function(Object) @nogc;
126                 (cast(DtorType) c.destructor)(obj);
127             }
128         }
129         while ((c = c.base) !is null);
130 
131         if (ppv[1]) // if monitor is not null
132             _d_monitordelete(obj, true);
133         
134         auto support = p[0..typeid(obj).initializer.length];
135 
136         *ppv = null;
137 
138         return support;
139     }
140 
141     /**
142      * @nogc new for structs.
143      */
144     @nogc
145     public static T* create(T, Args...)(Args args, string file = __FILE__, int line =__LINE__) if (is(T == struct))
146     {
147         enum size = T.sizeof;
148 
149         auto memory = defaultAllocator.allocate(size);
150 
151         return emplace!(T, Args)(memory, args);
152     }
153 
154     /**
155      * @nogc delete for structs.
156      */
157     @nogc
158     public static void dispose(T)(ref T* instance, string file = __FILE__, int line =__LINE__) if (is(T == struct))
159     {
160         destroy(instance);
161 
162         defaultAllocator.deallocate((cast(void*) instance)[0..T.sizeof]);
163         instance = null;
164     }
165 
166     /**
167      * @nogc delete for structs that have not been allocated with @nogc new.
168      */
169     @nogc
170     public static void dispose(T)(ref T instance, string file = __FILE__, int line =__LINE__) if (is(T == struct))
171     {
172         destroy(instance);
173     }
174 }