1 // Written in the D programming language.
2 /**
3 The C heap allocator.
4 
5 Source: $(PHOBOSSRC std/experimental/allocator/mallocator.d)
6 */
7 module std.experimental.allocator.mallocator;
8 import std.experimental.allocator.common;
9 
10 /**
11    The C heap allocator.
12  */
13 struct Mallocator
14 {
15     version (StdUnittest) @system unittest { testAllocator!(() => Mallocator.instance); }
16 
17     /**
18     The alignment is a static constant equal to `platformAlignment`, which
19     ensures proper alignment for any D data type.
20     */
21     enum uint alignment = platformAlignment;
22 
23     /**
24     Standard allocator methods per the semantics defined above. The
25     `deallocate` and `reallocate` methods are `@system` because they
26     may move memory around, leaving dangling pointers in user code. Somewhat
27     paradoxically, `malloc` is `@safe` but that's only useful to safe
28     programs that can afford to leak memory allocated.
29     */
30     @trusted @nogc nothrow pure
31     void[] allocate(size_t bytes) shared const
32     {
33         import core.memory : pureMalloc;
34         if (!bytes) return null;
35         auto p = pureMalloc(bytes);
36         return p ? p[0 .. bytes] : null;
37     }
38 
39     /// Ditto
40     @system @nogc nothrow pure
41     bool deallocate(void[] b) shared const
42     {
43         import core.memory : pureFree;
44         pureFree(b.ptr);
45         return true;
46     }
47 
48     /// Ditto
49     @system @nogc nothrow pure
50     bool reallocate(ref void[] b, size_t s) shared const
51     {
52         import core.memory : pureRealloc;
53         if (!s)
54         {
55             // fuzzy area in the C standard, see https://stackoverflow.com/questions/6502077/malloc-and-realloc-functions
56             // so just deallocate and nullify the pointer
57             deallocate(b);
58             b = null;
59             return true;
60         }
61         auto p = cast(ubyte*) pureRealloc(b.ptr, s);
62         if (!p) return false;
63         b = p[0 .. s];
64         return true;
65     }
66 
67     @trusted @nogc nothrow pure
68     package void[] allocateZeroed()(size_t bytes) shared const
69     {
70         import core.memory : pureCalloc;
71         if (!bytes) return null;
72         auto p = pureCalloc(1, bytes);
73         return p ? p[0 .. bytes] : null;
74     }
75 
76     /**
77     Returns the global instance of this allocator type. The C heap allocator is
78     thread-safe, therefore all of its methods and `it` itself are
79     `shared`.
80     */
81     static shared Mallocator instance;
82 }
83 
84 ///
85 @nogc @system nothrow unittest
86 {
87     auto buffer = Mallocator.instance.allocate(1024 * 1024 * 4);
88     scope(exit) Mallocator.instance.deallocate(buffer);
89     //...
90 }
91 
92 @nogc @system nothrow pure unittest
93 {
94     @nogc nothrow pure
95     static void test(A)()
96     {
97         int* p = null;
98         p = cast(int*) A.instance.allocate(int.sizeof);
99         scope(exit) () nothrow @nogc { A.instance.deallocate(p[0 .. int.sizeof]); }();
100         *p = 42;
101         assert(*p == 42);
102     }
103     test!Mallocator();
104 }
105 
106 @nogc @system nothrow pure unittest
107 {
108     static void test(A)()
109     {
110         import std.experimental.allocator : make;
111         Object p = null;
112         p = A.instance.make!Object();
113         assert(p !is null);
114     }
115 
116     test!Mallocator();
117 }
118 
119 version (CRuntime_Microsoft)
120 {
121     @nogc nothrow private extern(C) void* _aligned_malloc(size_t, size_t);
122     @nogc nothrow private extern(C) void _aligned_free(void *memblock);
123     @nogc nothrow private extern(C) void* _aligned_realloc(void *, size_t, size_t);
124 }
125 
126 /**
127    Aligned allocator using OS-specific primitives, under a uniform API.
128  */
129 struct AlignedMallocator
130 {
131     version (StdUnittest) @system unittest { testAllocator!(() => typeof(this).instance); }
132 
133     /**
134     The default alignment is `platformAlignment`.
135     */
136     enum uint alignment = platformAlignment;
137 
138     /**
139     Forwards to $(D alignedAllocate(bytes, platformAlignment)).
140     */
141     @trusted @nogc nothrow
142     void[] allocate(size_t bytes) shared
143     {
144         if (!bytes) return null;
145         return alignedAllocate(bytes, alignment);
146     }
147 
148     /**
149     Uses $(HTTP man7.org/linux/man-pages/man3/posix_memalign.3.html,
150     `posix_memalign`) on Posix and
151     $(HTTP msdn.microsoft.com/en-us/library/8z34s9c6(v=vs.80).aspx,
152     `__aligned_malloc`) on Windows.
153     */
154     version (Posix)
155     @trusted @nogc nothrow
156     void[] alignedAllocate(size_t bytes, uint a) shared
157     {
158         import core.stdc.errno : ENOMEM, EINVAL;
159         import core.sys.posix.stdlib : posix_memalign;
160         assert(a.isGoodDynamicAlignment);
161         void* result;
162         auto code = posix_memalign(&result, a, bytes);
163 
164 version (OSX)
165 version (LDC_AddressSanitizer)
166 {
167         // The return value with AddressSanitizer may be -1 instead of ENOMEM
168         // or EINVAL. See https://bugs.llvm.org/show_bug.cgi?id=36510
169         if (code == -1)
170             return null;
171 }
172         if (code == ENOMEM)
173             return null;
174 
175         else if (code == EINVAL)
176         {
177             assert(0, "AlignedMallocator.alignment is not a power of two "
178                 ~"multiple of (void*).sizeof, according to posix_memalign!");
179         }
180         else if (code != 0)
181             assert(0, "posix_memalign returned an unknown code!");
182 
183         else
184             return result[0 .. bytes];
185     }
186     else version (Windows)
187     @trusted @nogc nothrow
188     void[] alignedAllocate(size_t bytes, uint a) shared
189     {
190         auto result = _aligned_malloc(bytes, a);
191         return result ? result[0 .. bytes] : null;
192     }
193     else static assert(0);
194 
195     /**
196     Calls `free(b.ptr)` on Posix and
197     $(HTTP msdn.microsoft.com/en-US/library/17b5h8td(v=vs.80).aspx,
198     `__aligned_free(b.ptr)`) on Windows.
199     */
200     version (Posix)
201     @system @nogc nothrow
202     bool deallocate(void[] b) shared
203     {
204         import core.stdc.stdlib : free;
205         free(b.ptr);
206         return true;
207     }
208     else version (Windows)
209     @system @nogc nothrow
210     bool deallocate(void[] b) shared
211     {
212         _aligned_free(b.ptr);
213         return true;
214     }
215     else static assert(0);
216 
217     /**
218     Forwards to $(D alignedReallocate(b, newSize, platformAlignment)).
219     Should be used with blocks obtained with `allocate` otherwise the custom
220     alignment passed with `alignedAllocate` can be lost.
221     */
222     @system @nogc nothrow
223     bool reallocate(ref void[] b, size_t newSize) shared
224     {
225         return alignedReallocate(b, newSize, alignment);
226     }
227 
228     /**
229     On Posix there is no `realloc` for aligned memory, so `alignedReallocate` emulates
230     the needed behavior by using `alignedAllocate` to get a new block. The existing
231     block is copied to the new block and then freed.
232     On Windows, calls $(HTTPS msdn.microsoft.com/en-us/library/y69db7sx.aspx,
233     $(D __aligned_realloc(b.ptr, newSize, a))).
234     */
235     version (Windows)
236     @system @nogc nothrow
237     bool alignedReallocate(ref void[] b, size_t s, uint a) shared
238     {
239         if (!s)
240         {
241             deallocate(b);
242             b = null;
243             return true;
244         }
245         auto p = cast(ubyte*) _aligned_realloc(b.ptr, s, a);
246         if (!p) return false;
247         b = p[0 .. s];
248         return true;
249     }
250 
251     /// ditto
252     version (Posix)
253     @system @nogc nothrow
254     bool alignedReallocate(ref void[] b, size_t s, uint a) shared
255     {
256         if (!s)
257         {
258             deallocate(b);
259             b = null;
260             return true;
261         }
262         auto p = alignedAllocate(s, a);
263         if (!p.ptr)
264         {
265             return false;
266         }
267         import std.algorithm.comparison : min;
268         const upTo = min(s, b.length);
269         p[0 .. upTo] = b[0 .. upTo];
270         deallocate(b);
271         b = p;
272         return true;
273     }
274 
275     /**
276     Returns the global instance of this allocator type. The C heap allocator is
277     thread-safe, therefore all of its methods and `instance` itself are
278     `shared`.
279     */
280     static shared AlignedMallocator instance;
281 }
282 
283 ///
284 @nogc @system nothrow unittest
285 {
286     auto buffer = AlignedMallocator.instance.alignedAllocate(1024 * 1024 * 4,
287         128);
288     scope(exit) AlignedMallocator.instance.deallocate(buffer);
289     //...
290 }
291 
292 version (Posix)
293 @nogc @system nothrow unittest
294 {
295     // https://issues.dlang.org/show_bug.cgi?id=16398
296     // test the "pseudo" alignedReallocate for Posix
297     void[] b = AlignedMallocator.instance.alignedAllocate(16, 32);
298     (cast(ubyte[]) b)[] = ubyte(1);
299     AlignedMallocator.instance.alignedReallocate(b, 32, 32);
300     ubyte[16] o;
301     o[] = 1;
302     assert((cast(ubyte[]) b)[0 .. 16] == o);
303     AlignedMallocator.instance.alignedReallocate(b, 4, 32);
304     assert((cast(ubyte[]) b)[0 .. 3] == o[0 .. 3]);
305     AlignedMallocator.instance.alignedReallocate(b, 128, 32);
306     assert((cast(ubyte[]) b)[0 .. 3] == o[0 .. 3]);
307     AlignedMallocator.instance.deallocate(b);
308 
309     void[] c;
310     AlignedMallocator.instance.alignedReallocate(c, 32, 32);
311     assert(c.ptr);
312 
313     version (LDC_AddressSanitizer) {} else // AddressSanitizer does not support such large memory allocations (0x10000000000 max)
314     version (DragonFlyBSD) {} else    /* FIXME: Malloc on DragonFly does not return NULL when allocating more than UINTPTR_MAX
315                                        * $(LINK: https://bugs.dragonflybsd.org/issues/3114, dragonfly bug report)
316                                        * $(LINK: https://github.com/dlang/druntime/pull/1999#discussion_r157536030, PR Discussion) */
317     assert(!AlignedMallocator.instance.alignedReallocate(c, size_t.max, 4096));
318     AlignedMallocator.instance.deallocate(c);
319 }