1 ///
2 module std.experimental.allocator.mallocator;
3 import std.experimental.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
allocateMallocator26     void[] allocate(size_t bytes) shared
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
deallocateMallocator36     bool deallocate(void[] b) shared
37     {
38         import core.stdc.stdlib : free;
39         free(b.ptr);
40         return true;
41     }
42 
43     /// Ditto
44     @system @nogc nothrow
reallocateMallocator45     bool reallocate(ref void[] b, size_t s) shared
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 and `it` itself are
65     $(D shared).
66     */
67     static shared Mallocator instance;
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
test(A)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 {
test(A)97     static void test(A)()
98     {
99         import std.experimental.allocator : make;
100         Object p = null;
101         p = A.instance.make!Object();
102         assert(p !is null);
103     }
104 
105     test!Mallocator();
106 }
107 
version(Windows)108 version (Windows)
109 {
110     // DMD Win 32 bit, DigitalMars C standard library misses the _aligned_xxx
111     // functions family (snn.lib)
112     version (CRuntime_DigitalMars)
113     {
114         // Helper to cast the infos written before the aligned pointer
115         // this header keeps track of the size (required to realloc) and of
116         // the base ptr (required to free).
117         private struct AlignInfo
118         {
119             void* basePtr;
120             size_t size;
121 
122             @nogc nothrow
123             static AlignInfo* opCall(void* ptr)
124             {
125                 return cast(AlignInfo*) (ptr - AlignInfo.sizeof);
126             }
127         }
128 
129         @nogc nothrow
130         private void* _aligned_malloc(size_t size, size_t alignment)
131         {
132             import core.stdc.stdlib : malloc;
133             size_t offset = alignment + size_t.sizeof * 2 - 1;
134 
135             // unaligned chunk
136             void* basePtr = malloc(size + offset);
137             if (!basePtr) return null;
138 
139             // get aligned location within the chunk
140             void* alignedPtr = cast(void**)((cast(size_t)(basePtr) + offset)
141                 & ~(alignment - 1));
142 
143             // write the header before the aligned pointer
144             AlignInfo* head = AlignInfo(alignedPtr);
145             head.basePtr = basePtr;
146             head.size = size;
147 
148             return alignedPtr;
149         }
150 
151         @nogc nothrow
152         private void* _aligned_realloc(void* ptr, size_t size, size_t alignment)
153         {
154             import core.stdc.stdlib : free;
155             import core.stdc.string : memcpy;
156 
157             if (!ptr) return _aligned_malloc(size, alignment);
158 
159             // gets the header from the exising pointer
160             AlignInfo* head = AlignInfo(ptr);
161 
162             // gets a new aligned pointer
163             void* alignedPtr = _aligned_malloc(size, alignment);
164             if (!alignedPtr)
165             {
166                 //to https://msdn.microsoft.com/en-us/library/ms235462.aspx
167                 //see Return value: in this case the original block is unchanged
168                 return null;
169             }
170 
171             // copy exising data
172             memcpy(alignedPtr, ptr, head.size);
173             free(head.basePtr);
174 
175             return alignedPtr;
176         }
177 
178         @nogc nothrow
179         private void _aligned_free(void *ptr)
180         {
181             import core.stdc.stdlib : free;
182             if (!ptr) return;
183             AlignInfo* head = AlignInfo(ptr);
184             free(head.basePtr);
185         }
186 
187     }
188     // DMD Win 64 bit, uses microsoft standard C library which implements them
189     else
190     {
191         @nogc nothrow private extern(C) void* _aligned_malloc(size_t, size_t);
192         @nogc nothrow private extern(C) void _aligned_free(void *memblock);
193         @nogc nothrow private extern(C) void* _aligned_realloc(void *, size_t, size_t);
194     }
195 }
196 
197 /**
198    Aligned allocator using OS-specific primitives, under a uniform API.
199  */
200 struct AlignedMallocator
201 {
202     @system unittest { testAllocator!(() => typeof(this).instance); }
203 
204     /**
205     The default alignment is $(D platformAlignment).
206     */
207     enum uint alignment = platformAlignment;
208 
209     /**
210     Forwards to $(D alignedAllocate(bytes, platformAlignment)).
211     */
212     @trusted @nogc nothrow
allocateAlignedMallocator213     void[] allocate(size_t bytes) shared
214     {
215         if (!bytes) return null;
216         return alignedAllocate(bytes, alignment);
217     }
218 
219     /**
220     Uses $(HTTP man7.org/linux/man-pages/man3/posix_memalign.3.html,
221     $(D posix_memalign)) on Posix and
222     $(HTTP msdn.microsoft.com/en-us/library/8z34s9c6(v=vs.80).aspx,
223     $(D __aligned_malloc)) on Windows.
224     */
versionAlignedMallocator225     version (Posix)
226     @trusted @nogc nothrow
227     void[] alignedAllocate(size_t bytes, uint a) shared
228     {
229         import core.stdc.errno : ENOMEM, EINVAL;
230         import core.sys.posix.stdlib : posix_memalign;
231         assert(a.isGoodDynamicAlignment);
232         void* result;
233         auto code = posix_memalign(&result, a, bytes);
234         if (code == ENOMEM)
235             return null;
236 
237         else if (code == EINVAL)
238         {
239             assert(0, "AlignedMallocator.alignment is not a power of two "
240                 ~"multiple of (void*).sizeof, according to posix_memalign!");
241         }
242         else if (code != 0)
243             assert(0, "posix_memalign returned an unknown code!");
244 
245         else
246             return result[0 .. bytes];
247     }
versionAlignedMallocator248     else version (Windows)
249     @trusted @nogc nothrow
250     void[] alignedAllocate(size_t bytes, uint a) shared
251     {
252         auto result = _aligned_malloc(bytes, a);
253         return result ? result[0 .. bytes] : null;
254     }
255     else static assert(0);
256 
257     /**
258     Calls $(D free(b.ptr)) on Posix and
259     $(HTTP msdn.microsoft.com/en-US/library/17b5h8td(v=vs.80).aspx,
260     $(D __aligned_free(b.ptr))) on Windows.
261     */
versionAlignedMallocator262     version (Posix)
263     @system @nogc nothrow
264     bool deallocate(void[] b) shared
265     {
266         import core.stdc.stdlib : free;
267         free(b.ptr);
268         return true;
269     }
versionAlignedMallocator270     else version (Windows)
271     @system @nogc nothrow
272     bool deallocate(void[] b) shared
273     {
274         _aligned_free(b.ptr);
275         return true;
276     }
277     else static assert(0);
278 
279     /**
280     On Posix, forwards to $(D realloc). On Windows, forwards to
281     $(D alignedReallocate(b, newSize, platformAlignment)).
282     */
versionAlignedMallocator283     version (Posix)
284     @system @nogc nothrow
285     bool reallocate(ref void[] b, size_t newSize) shared
286     {
287         return Mallocator.instance.reallocate(b, newSize);
288     }
versionAlignedMallocator289     version (Windows)
290     @system @nogc nothrow
291     bool reallocate(ref void[] b, size_t newSize) shared
292     {
293         return alignedReallocate(b, newSize, alignment);
294     }
295 
296     /**
297     On Posix, uses $(D alignedAllocate) and copies data around because there is
298     no realloc for aligned memory. On Windows, calls
299     $(HTTP msdn.microsoft.com/en-US/library/y69db7sx(v=vs.80).aspx,
300     $(D __aligned_realloc(b.ptr, newSize, a))).
301     */
versionAlignedMallocator302     version (Windows)
303     @system @nogc nothrow
304     bool alignedReallocate(ref void[] b, size_t s, uint a) shared
305     {
306         if (!s)
307         {
308             deallocate(b);
309             b = null;
310             return true;
311         }
312         auto p = cast(ubyte*) _aligned_realloc(b.ptr, s, a);
313         if (!p) return false;
314         b = p[0 .. s];
315         return true;
316     }
317 
318     /**
319     Returns the global instance of this allocator type. The C heap allocator is
320     thread-safe, therefore all of its methods and `instance` itself are
321     $(D shared).
322     */
323     static shared AlignedMallocator instance;
324 }
325 
326 ///
327 @nogc nothrow
328 @system unittest
329 {
330     auto buffer = AlignedMallocator.instance.alignedAllocate(1024 * 1024 * 4,
331         128);
332     scope(exit) AlignedMallocator.instance.deallocate(buffer);
333     //...
334 }
335 
version(CRuntime_DigitalMars)336 version (unittest) version (CRuntime_DigitalMars)
337 @nogc nothrow
338 size_t addr(ref void* ptr) { return cast(size_t) ptr; }
339 
version(CRuntime_DigitalMars)340 version (CRuntime_DigitalMars)
341 @nogc nothrow
342 @system unittest
343 {
344     void* m;
345 
346     m = _aligned_malloc(16, 0x10);
347     if (m)
348     {
349         assert((m.addr & 0xF) == 0);
350         _aligned_free(m);
351     }
352 
353     m = _aligned_malloc(16, 0x100);
354     if (m)
355     {
356         assert((m.addr & 0xFF) == 0);
357         _aligned_free(m);
358     }
359 
360     m = _aligned_malloc(16, 0x1000);
361     if (m)
362     {
363         assert((m.addr & 0xFFF) == 0);
364         _aligned_free(m);
365     }
366 
367     m = _aligned_malloc(16, 0x10);
368     if (m)
369     {
370         assert((cast(size_t) m & 0xF) == 0);
371         m = _aligned_realloc(m, 32, 0x10000);
372         if (m) assert((m.addr & 0xFFFF) == 0);
373         _aligned_free(m);
374     }
375 
376     m = _aligned_malloc(8, 0x10);
377     if (m)
378     {
379         *cast(ulong*) m = 0X01234567_89ABCDEF;
380         m = _aligned_realloc(m, 0x800, 0x1000);
381         if (m) assert(*cast(ulong*) m == 0X01234567_89ABCDEF);
382         _aligned_free(m);
383     }
384 }
385