1 ///
2 module stdx.allocator.mallocator;
3 import stdx.allocator.common;
4 
5 /**
6    The C heap allocator.
7  */
8 struct Mallocator
9 {
10     @system unittest { testAllocator!(() => Mallocator.instance); }
11 
12     /**
13     The alignment is a static constant equal to $(D platformAlignment), which
14     ensures proper alignment for any D data type.
15     */
16     enum uint alignment = platformAlignment;
17 
18     /**
19     Standard allocator methods per the semantics defined above. The
20     $(D deallocate) and $(D reallocate) methods are $(D @system) because they
21     may move memory around, leaving dangling pointers in user code. Somewhat
22     paradoxically, $(D malloc) is $(D @safe) but that's only useful to safe
23     programs that can afford to leak memory allocated.
24     */
25     @trusted @nogc nothrow
26     static void[] allocate()(size_t bytes)
27     {
28         import core.stdc.stdlib : malloc;
29         if (!bytes) return null;
30         auto p = malloc(bytes);
31         return p ? p[0 .. bytes] : null;
32     }
33 
34     /// Ditto
35     @system @nogc nothrow
36     static bool deallocate()(void[] b)
37     {
38         import core.stdc.stdlib : free;
39         free(b.ptr);
40         return true;
41     }
42 
43     /// Ditto
44     @system @nogc nothrow
45     static bool reallocate()(ref void[] b, size_t s)
46     {
47         import core.stdc.stdlib : realloc;
48         if (!s)
49         {
50             // fuzzy area in the C standard, see http://goo.gl/ZpWeSE
51             // so just deallocate and nullify the pointer
52             deallocate(b);
53             b = null;
54             return true;
55         }
56         auto p = cast(ubyte*) realloc(b.ptr, s);
57         if (!p) return false;
58         b = p[0 .. s];
59         return true;
60     }
61 
62     /**
63     Returns the global instance of this allocator type. The C heap allocator is
64     thread-safe, therefore all of its methods are $(D static) and `instance` itself is
65     $(D shared).
66     */
67     enum Mallocator instance = Mallocator();
68 }
69 
70 ///
71 @nogc nothrow
72 @system unittest
73 {
74     auto buffer = Mallocator.instance.allocate(1024 * 1024 * 4);
75     scope(exit) Mallocator.instance.deallocate(buffer);
76     //...
77 }
78 
79 @nogc nothrow
80 @system unittest
81 {
82     @nogc nothrow
83     static void test(A)()
84     {
85         int* p = null;
86         p = cast(int*) A.instance.allocate(int.sizeof);
87         scope(exit) A.instance.deallocate(p[0 .. int.sizeof]);
88         *p = 42;
89         assert(*p == 42);
90     }
91     test!Mallocator();
92 }
93 
94 @nogc nothrow
95 @system unittest
96 {
97     static void test(A)()
98     {
99         import stdx.allocator : make;
100         Object p = null;
101         p = A.instance.make!Object();
102         assert(p !is null);
103     }
104 
105     test!Mallocator();
106 }
107 
108 version (Posix)
109 @nogc nothrow
110 private extern(C) int posix_memalign(void**, size_t, size_t);
111 
112 version (Windows)
113 {
114     // DMD Win 32 bit, DigitalMars C standard library misses the _aligned_xxx
115     // functions family (snn.lib)
116     version(CRuntime_DigitalMars)
117     {
118         // Helper to cast the infos written before the aligned pointer
119         // this header keeps track of the size (required to realloc) and of
120         // the base ptr (required to free).
121         private struct AlignInfo
122         {
123             void* basePtr;
124             size_t size;
125 
126             @nogc nothrow
127             static AlignInfo* opCall()(void* ptr)
128             {
129                 return cast(AlignInfo*) (ptr - AlignInfo.sizeof);
130             }
131         }
132 
133         @nogc nothrow
134         private void* _aligned_malloc()(size_t size, size_t alignment)
135         {
136             import core.stdc.stdlib : malloc;
137             size_t offset = alignment + size_t.sizeof * 2 - 1;
138 
139             // unaligned chunk
140             void* basePtr = malloc(size + offset);
141             if (!basePtr) return null;
142 
143             // get aligned location within the chunk
144             void* alignedPtr = cast(void**)((cast(size_t)(basePtr) + offset)
145                 & ~(alignment - 1));
146 
147             // write the header before the aligned pointer
148             AlignInfo* head = AlignInfo(alignedPtr);
149             head.basePtr = basePtr;
150             head.size = size;
151 
152             return alignedPtr;
153         }
154 
155         @nogc nothrow
156         private void* _aligned_realloc()(void* ptr, size_t size, size_t alignment)
157         {
158             import core.stdc.stdlib : free;
159             import core.stdc..string : memcpy;
160 
161             if (!ptr) return _aligned_malloc(size, alignment);
162 
163             // gets the header from the exising pointer
164             AlignInfo* head = AlignInfo(ptr);
165 
166             // gets a new aligned pointer
167             void* alignedPtr = _aligned_malloc(size, alignment);
168             if (!alignedPtr)
169             {
170                 //to https://msdn.microsoft.com/en-us/library/ms235462.aspx
171                 //see Return value: in this case the original block is unchanged
172                 return null;
173             }
174 
175             // copy exising data
176             memcpy(alignedPtr, ptr, head.size);
177             free(head.basePtr);
178 
179             return alignedPtr;
180         }
181 
182         @nogc nothrow
183         private void _aligned_free()(void *ptr)
184         {
185             import core.stdc.stdlib : free;
186             if (!ptr) return;
187             AlignInfo* head = AlignInfo(ptr);
188             free(head.basePtr);
189         }
190 
191     }
192     // DMD Win 64 bit, uses microsoft standard C library which implements them
193     else
194     {
195         @nogc nothrow private extern(C) void* _aligned_malloc(size_t, size_t);
196         @nogc nothrow private extern(C) void _aligned_free(void *memblock);
197         @nogc nothrow private extern(C) void* _aligned_realloc(void *, size_t, size_t);
198     }
199 }
200 
201 /**
202    Aligned allocator using OS-specific primitives, under a uniform API.
203  */
204 version (WebAssembly) {} else version = HasMemAlign;
205 
206 version (HasMemAlign)
207 struct AlignedMallocator
208 {
209     @system unittest { testAllocator!(() => typeof(this).instance); }
210 
211     /**
212     The default alignment is $(D platformAlignment).
213     */
214     enum uint alignment = platformAlignment;
215 
216     /**
217     Forwards to $(D alignedAllocate(bytes, platformAlignment)).
218     */
219     @trusted @nogc nothrow
220     static void[] allocate()(size_t bytes)
221     {
222         if (!bytes) return null;
223         return alignedAllocate(bytes, alignment);
224     }
225 
226     /**
227     Uses $(HTTP man7.org/linux/man-pages/man3/posix_memalign.3.html,
228     $(D posix_memalign)) on Posix and
229     $(HTTP msdn.microsoft.com/en-us/library/8z34s9c6(v=vs.80).aspx,
230     $(D __aligned_malloc)) on Windows.
231     */
232     version(Posix)
233     @trusted @nogc nothrow
234     static void[] alignedAllocate()(size_t bytes, uint a)
235     {
236         import core.stdc.errno : ENOMEM, EINVAL;
237         assert(a.isGoodDynamicAlignment);
238         void* result;
239         auto code = posix_memalign(&result, a, bytes);
240         if (code == ENOMEM)
241             return null;
242 
243         else if (code == EINVAL)
244         {
245             assert(0, "AlignedMallocator.alignment is not a power of two "
246                 ~"multiple of (void*).sizeof, according to posix_memalign!");
247         }
248         else if (code != 0)
249             assert(0, "posix_memalign returned an unknown code!");
250 
251         else
252             return result[0 .. bytes];
253     }
254     else version(Windows)
255     @trusted @nogc nothrow
256     static void[] alignedAllocate()(size_t bytes, uint a)
257     {
258         auto result = _aligned_malloc(bytes, a);
259         return result ? result[0 .. bytes] : null;
260     }
261     else static assert(0);
262 
263     /**
264     Calls $(D free(b.ptr)) on Posix and
265     $(HTTP msdn.microsoft.com/en-US/library/17b5h8td(v=vs.80).aspx,
266     $(D __aligned_free(b.ptr))) on Windows.
267     */
268     version (Posix)
269     @system @nogc nothrow
270     static bool deallocate()(void[] b)
271     {
272         import core.stdc.stdlib : free;
273         free(b.ptr);
274         return true;
275     }
276     else version (Windows)
277     @system @nogc nothrow
278     static bool deallocate()(void[] b)
279     {
280         _aligned_free(b.ptr);
281         return true;
282     }
283     else static assert(0);
284 
285     /**
286     On Posix, forwards to $(D realloc). On Windows, forwards to
287     $(D alignedReallocate(b, newSize, platformAlignment)).
288     */
289     version (Posix)
290     @system @nogc nothrow
291     static bool reallocate()(ref void[] b, size_t newSize)
292     {
293         return Mallocator.instance.reallocate(b, newSize);
294     }
295     version (Windows)
296     @system @nogc nothrow
297     static bool reallocate()(ref void[] b, size_t newSize)
298     {
299         return alignedReallocate(b, newSize, alignment);
300     }
301 
302     /**
303     On Posix, uses $(D alignedAllocate) and copies data around because there is
304     no realloc for aligned memory. On Windows, calls
305     $(HTTP msdn.microsoft.com/en-US/library/y69db7sx(v=vs.80).aspx,
306     $(D __aligned_realloc(b.ptr, newSize, a))).
307     */
308     version (Windows)
309     @system @nogc nothrow
310     static bool alignedReallocate()(ref void[] b, size_t s, uint a)
311     {
312         if (!s)
313         {
314             deallocate(b);
315             b = null;
316             return true;
317         }
318         auto p = cast(ubyte*) _aligned_realloc(b.ptr, s, a);
319         if (!p) return false;
320         b = p[0 .. s];
321         return true;
322     }
323 
324     /**
325     Returns the global instance of this allocator type. The C heap allocator is
326     thread-safe, therefore all of its methods are $(D static) and `instance` itself is
327     $(D shared).
328     */
329     enum AlignedMallocator instance = AlignedMallocator();
330 }
331 
332 ///
333 @nogc nothrow
334 @system unittest
335 {
336     auto buffer = AlignedMallocator.instance.alignedAllocate(1024 * 1024 * 4,
337         128);
338     scope(exit) AlignedMallocator.instance.deallocate(buffer);
339     //...
340 }
341 
342 version(unittest) version(CRuntime_DigitalMars)
343 @nogc nothrow
344 size_t addr(ref void* ptr) { return cast(size_t) ptr; }
345 
346 version(CRuntime_DigitalMars)
347 @nogc nothrow
348 @system unittest
349 {
350     void* m;
351 
352     m = _aligned_malloc(16, 0x10);
353     if (m)
354     {
355         assert((m.addr & 0xF) == 0);
356         _aligned_free(m);
357     }
358 
359     m = _aligned_malloc(16, 0x100);
360     if (m)
361     {
362         assert((m.addr & 0xFF) == 0);
363         _aligned_free(m);
364     }
365 
366     m = _aligned_malloc(16, 0x1000);
367     if (m)
368     {
369         assert((m.addr & 0xFFF) == 0);
370         _aligned_free(m);
371     }
372 
373     m = _aligned_malloc(16, 0x10);
374     if (m)
375     {
376         assert((cast(size_t) m & 0xF) == 0);
377         m = _aligned_realloc(m, 32, 0x10000);
378         if (m) assert((m.addr & 0xFFFF) == 0);
379         _aligned_free(m);
380     }
381 
382     m = _aligned_malloc(8, 0x10);
383     if (m)
384     {
385         *cast(ulong*) m = 0X01234567_89ABCDEF;
386         m = _aligned_realloc(m, 0x800, 0x1000);
387         if (m) assert(*cast(ulong*) m == 0X01234567_89ABCDEF);
388         _aligned_free(m);
389     }
390 }