1 /**
2   This module provide support for aligned memory.
3  */
4 module gfm.core.memory;
5 
6 import core.memory : GC;
7 import core.exception : onOutOfMemoryError;
8 
9 import std.c.stdlib : malloc, free, realloc;
10 import std.conv : emplace;
11 import std.traits;
12 
13 
14 static if( __VERSION__ < 2066 ) private enum nogc = 1;
15 
16 /// Returns: next pointer aligned with alignment bytes.
17 @nogc void* nextAlignedPointer(void* start, size_t alignment) pure nothrow
18 {
19     return cast(void*)nextMultipleOf(cast(size_t)(start), alignment);
20 }
21 
22 /// Allocates an aligned memory chunk.
23 /// Functionally equivalent to Visual C++ _aligned_malloc.
24 @nogc void* alignedMalloc(size_t size, size_t alignment) nothrow
25 {
26     if (size == 0)
27         return null;
28 
29     size_t request = requestedSize(size, alignment);
30     void* raw = malloc(request);
31 
32     static if( __VERSION__ > 2067 ) // onOutOfMemoryError wasn't nothrow before July 2014
33     {
34         if (request > 0 && raw == null) // malloc(0) can validly return anything
35             onOutOfMemoryError();
36     }
37 
38     return storeRawPointerAndReturnAligned(raw, alignment);
39 }
40 
41 /// Frees aligned memory allocated by alignedMalloc or alignedRealloc.
42 /// Functionally equivalent to Visual C++ _aligned_free.
43 @nogc void alignedFree(void* aligned) nothrow
44 {
45     // support for free(NULL)
46     if (aligned is null)
47         return;
48 
49     void** rawLocation = cast(void**)(cast(char*)aligned - size_t.sizeof);
50     free(*rawLocation);
51 }
52 
53 /// Reallocates an aligned memory chunk allocated by alignedMalloc or alignedRealloc.
54 /// Functionally equivalent to Visual C++ _aligned_realloc.
55 @nogc void* alignedRealloc(void* aligned, size_t size, size_t alignment) nothrow
56 {
57     if (aligned is null)
58         return alignedMalloc(size, alignment);
59 
60     if (size == 0)
61     {
62         alignedFree(aligned);
63         return null;
64     }
65 
66     void* raw = *cast(void**)(cast(char*)aligned - size_t.sizeof);
67 
68     size_t request = requestedSize(size, alignment);
69     void* newRaw = realloc(raw, request);
70 
71     static if( __VERSION__ > 2067 ) // onOutOfMemoryError wasn't nothrow before July 2014
72     {
73         if (request > 0 && newRaw == null) // realloc(0) can validly return anything
74             onOutOfMemoryError();
75     }
76 
77     // if newRaw is raw, nothing to do
78     if (raw is newRaw)
79         return aligned;
80 
81     // else write raw at the new location
82     return storeRawPointerAndReturnAligned(newRaw, alignment);
83 }
84 
85 private
86 {
87     // Returns number of bytes to actually allocate when asking
88     // for a particular alignement
89     @nogc size_t requestedSize(size_t askedSize, size_t alignment) pure nothrow
90     {
91         enum size_t pointerSize = size_t.sizeof;
92         return askedSize + alignment - 1 + pointerSize;
93     }
94 
95     @nogc void* storeRawPointerAndReturnAligned(void* raw, size_t alignment) nothrow
96     {
97         enum size_t pointerSize = size_t.sizeof;
98         char* start = cast(char*)raw + pointerSize;
99         void* aligned = nextAlignedPointer(start, alignment);
100         void** rawLocation = cast(void**)(cast(char*)aligned - pointerSize);
101         *rawLocation = raw;
102         return aligned;
103     }
104 
105     // Returns: x, multiple of powerOfTwo, so that x >= n.
106     @nogc size_t nextMultipleOf(size_t n, size_t powerOfTwo) pure nothrow
107     {
108         // check power-of-two
109         assert( (powerOfTwo != 0) && ((powerOfTwo & (powerOfTwo - 1)) == 0));
110 
111         size_t mask = ~(powerOfTwo - 1);
112         return (n + powerOfTwo - 1) & mask;
113     }
114 }
115 
116 unittest
117 {
118     {
119         void* p = alignedMalloc(23, 16);
120         assert(p !is null);
121         assert(((cast(size_t)p) & 0xf) == 0);
122 
123         alignedFree(p);
124     }
125 
126     assert(alignedMalloc(0, 16) == null);
127     alignedFree(null);
128 
129     {
130         void* p = alignedRealloc(null, 100, 16);
131         p = alignedRealloc(p, 200, 16);
132         p = alignedRealloc(p, 0, 16);
133     }
134 }
135 
136 
137 /// Allocates and construct a struct or class object.
138 /// Returns: Newly allocated object.
139 auto mallocEmplace(T, Args...)(Args args)
140 {
141     static if (is(T == class))
142         immutable size_t allocSize = __traits(classInstanceSize, T);
143     else
144         immutable size_t allocSize = T.sizeof;
145 
146     void* rawMemory = malloc(allocSize);
147     if (!rawMemory)
148         onOutOfMemoryError();
149 
150     static if (is(T == class))
151     {
152         T obj = emplace!T(rawMemory[0 .. allocSize], args);
153     }
154     else
155     {
156         T* obj = cast(T*)rawMemory;
157         emplace!T(obj, args);
158     }
159 
160     static if (hasIndirections!T)
161         GC.addRange(rawMemory, allocSize);
162 
163     return obj;
164 }
165 
166 /// Destroys and frees a class object created with $(D mallocEmplace).
167 void destroyFree(T)(T p) if (is(T == class))
168 {
169     if (p !is null)
170     {
171         destroy(p);
172 
173         static if (hasIndirections!T)
174             GC.removeRange(cast(void*)p);
175 
176         free(cast(void*)p);
177     }
178 }
179 
180 /// Destroys and frees a non-class object created with $(D mallocEmplace).
181 void destroyFree(T)(T* p) if (!is(T == class))
182 {
183     if (p !is null)
184     {
185         destroy(p);
186 
187         static if (hasIndirections!T)
188             GC.removeRange(cast(void*)p);
189 
190         free(cast(void*)p);
191     }
192 }
193 
194 unittest
195 {
196     class A
197     {
198         int _i;
199         this(int i)
200         {
201             _i = i;
202         }
203     }
204 
205     struct B
206     {
207         int i;
208     }
209 
210     void testMallocEmplace()
211     {
212         A a = mallocEmplace!A(4);
213         destroyFree(a);
214 
215         B* b = mallocEmplace!B(5);
216         destroyFree(b);
217     }
218 
219     testMallocEmplace();
220 }
221 
222 version( D_InlineAsm_X86 )
223 {
224     version = AsmX86;
225 }
226 else version( D_InlineAsm_X86_64 )
227 {
228     version = AsmX86;
229 }
230 
231 /// Inserts a breakpoint instruction. useful to trigger the debugger.
232 void debugBreak() nothrow @nogc
233 {
234     version( AsmX86 )
235     {
236         static if( __VERSION__ >= 2067 )
237         {
238             mixin("asm nothrow @nogc { int 3; }");
239         }
240         else
241         {
242             mixin("asm { int 3; }");
243         }
244     }
245     else
246     {
247         // TODO: implement debugBreak() for GDC
248     }
249 }
250 
251 auto assumeNoGC(T) (T t) if (isFunctionPointer!T || isDelegate!T)
252 {
253     enum attrs = functionAttributes!T | FunctionAttribute.nogc;
254     return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
255 }
256 
257 auto assumeNothrowNoGC(T) (T t) if (isFunctionPointer!T || isDelegate!T)
258 {
259     enum attrs = functionAttributes!T | FunctionAttribute.nogc | FunctionAttribute.nothrow_;
260     return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
261 }
262 
263 unittest
264 {
265     void funcThatDoesGC()
266     {
267         throw new Exception("hello!");
268     }
269 
270     void anotherFunction() nothrow @nogc
271     {
272         assumeNothrowNoGC( (){ funcThatDoesGC(); } )();
273     }
274 
275     void aThirdFunction() @nogc
276     {
277         assumeNoGC( () { funcThatDoesGC(); } )();
278     }
279 }