1 /**
2  * Allocate memory using `malloc` or the GC depending on the configuration.
3  *
4  * Copyright: Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved
5  * Authors:   Walter Bright, http://www.digitalmars.com
6  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:    $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/root/rmem.d, root/_rmem.d)
8  * Documentation:  https://dlang.org/phobos/dmd_root_rmem.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/rmem.d
10  */
11 
12 module dmd.root.rmem;
13 
14 import core.exception : onOutOfMemoryError;
15 import core.stdc.stdio;
16 import core.stdc.stdlib;
17 import core.stdc.string;
18 
19 version = GC;
20 
21 version (GC)
22 {
23     import core.memory : GC;
24 
25     enum isGCAvailable = true;
26 }
27 else
28     enum isGCAvailable = false;
29 
30 extern (C++) struct Mem
31 {
32     static char* xstrdup(const(char)* s) nothrow
33     {
34         version (GC)
35             if (isGCEnabled)
36                 return s ? s[0 .. strlen(s) + 1].dup.ptr : null;
37 
38         return s ? cast(char*)check(.strdup(s)) : null;
39     }
40 
41     static void xfree(void* p) pure nothrow
42     {
43         version (GC)
44             if (isGCEnabled)
45                 return GC.free(p);
46 
47         pureFree(p);
48     }
49 
50     static void* xmalloc(size_t size) pure nothrow
51     {
52         version (GC)
53             if (isGCEnabled)
54                 return size ? GC.malloc(size) : null;
55 
56         return size ? check(pureMalloc(size)) : null;
57     }
58 
59     static void* xmalloc_noscan(size_t size) pure nothrow
60     {
61         version (GC)
62             if (isGCEnabled)
63                 return size ? GC.malloc(size, GC.BlkAttr.NO_SCAN) : null;
64 
65         return size ? check(pureMalloc(size)) : null;
66     }
67 
68     static void* xcalloc(size_t size, size_t n) pure nothrow
69     {
70         version (GC)
71             if (isGCEnabled)
72                 return size * n ? GC.calloc(size * n) : null;
73 
74         return (size && n) ? check(pureCalloc(size, n)) : null;
75     }
76 
77     static void* xcalloc_noscan(size_t size, size_t n) pure nothrow
78     {
79         version (GC)
80             if (isGCEnabled)
81                 return size * n ? GC.calloc(size * n, GC.BlkAttr.NO_SCAN) : null;
82 
83         return (size && n) ? check(pureCalloc(size, n)) : null;
84     }
85 
86     static void* xrealloc(void* p, size_t size) pure nothrow
87     {
88         version (GC)
89             if (isGCEnabled)
90                 return GC.realloc(p, size);
91 
92         if (!size)
93         {
94             pureFree(p);
95             return null;
96         }
97 
98         return check(pureRealloc(p, size));
99     }
100 
101     static void* xrealloc_noscan(void* p, size_t size) pure nothrow
102     {
103         version (GC)
104             if (isGCEnabled)
105                 return GC.realloc(p, size, GC.BlkAttr.NO_SCAN);
106 
107         if (!size)
108         {
109             pureFree(p);
110             return null;
111         }
112 
113         return check(pureRealloc(p, size));
114     }
115 
116     static void* error() pure nothrow @nogc
117     {
118         onOutOfMemoryError();
119         assert(0);
120     }
121 
122     /**
123      * Check p for null. If it is, issue out of memory error
124      * and exit program.
125      * Params:
126      *  p = pointer to check for null
127      * Returns:
128      *  p if not null
129      */
130     static void* check(void* p) pure nothrow @nogc
131     {
132         return p ? p : error();
133     }
134 
135     version (GC)
136     {
137         __gshared bool _isGCEnabled = true;
138 
139         // fake purity by making global variable immutable (_isGCEnabled only modified before startup)
140         enum _pIsGCEnabled = cast(immutable bool*) &_isGCEnabled;
141 
142         static bool isGCEnabled() pure nothrow @nogc @safe
143         {
144             return *_pIsGCEnabled;
145         }
146 
147         static void disableGC() nothrow @nogc
148         {
149             _isGCEnabled = false;
150         }
151 
152         static void addRange(const(void)* p, size_t size) nothrow @nogc
153         {
154             if (isGCEnabled)
155                 GC.addRange(p, size);
156         }
157 
158         static void removeRange(const(void)* p) nothrow @nogc
159         {
160             if (isGCEnabled)
161                 GC.removeRange(p);
162         }
163     }
164 }
165 
166 extern (C++) const __gshared Mem mem;
167 
168 enum CHUNK_SIZE = (256 * 4096 - 64);
169 
170 __gshared size_t heapleft = 0;
171 __gshared void* heapp;
172 
173 extern (C) void* allocmemory(size_t m_size) nothrow @nogc
174 {
175     // 16 byte alignment is better (and sometimes needed) for doubles
176     m_size = (m_size + 15) & ~15;
177 
178     // The layout of the code is selected so the most common case is straight through
179     if (m_size <= heapleft)
180     {
181     L1:
182         heapleft -= m_size;
183         auto p = heapp;
184         heapp = cast(void*)(cast(char*)heapp + m_size);
185         return p;
186     }
187 
188     if (m_size > CHUNK_SIZE)
189     {
190         return Mem.check(malloc(m_size));
191     }
192 
193     heapleft = CHUNK_SIZE;
194     heapp = Mem.check(malloc(CHUNK_SIZE));
195     goto L1;
196 }
197 
198 version (DigitalMars)
199 {
200     enum OVERRIDE_MEMALLOC = true;
201 }
202 else version (LDC)
203 {
204     // Memory allocation functions gained weak linkage when the @weak attribute was introduced.
205     import ldc.attributes;
206     enum OVERRIDE_MEMALLOC = is(typeof(ldc.attributes.weak));
207 }
208 else version (GNU)
209 {
210     version (IN_GCC)
211         enum OVERRIDE_MEMALLOC = false;
212     else
213         enum OVERRIDE_MEMALLOC = true;
214 }
215 else
216 {
217     enum OVERRIDE_MEMALLOC = false;
218 }
219 
220 static if (OVERRIDE_MEMALLOC)
221 {
222     // Override the host druntime allocation functions in order to use the bump-
223     // pointer allocation scheme (`allocmemory()` above) if the GC is disabled.
224     // That scheme is faster and comes with less memory overhead than using a
225     // disabled GC alone.
226 
227     extern (C) void* _d_allocmemory(size_t m_size) nothrow
228     {
229         version (GC)
230             if (mem.isGCEnabled)
231                 return GC.malloc(m_size);
232 
233         return allocmemory(m_size);
234     }
235 
236     version (GC)
237     {
238         private void* allocClass(const ClassInfo ci) nothrow pure
239         {
240             alias BlkAttr = GC.BlkAttr;
241 
242             assert(!(ci.m_flags & TypeInfo_Class.ClassFlags.isCOMclass));
243 
244             BlkAttr attr = BlkAttr.NONE;
245             if (ci.m_flags & TypeInfo_Class.ClassFlags.hasDtor
246                 && !(ci.m_flags & TypeInfo_Class.ClassFlags.isCPPclass))
247                 attr |= BlkAttr.FINALIZE;
248             if (ci.m_flags & TypeInfo_Class.ClassFlags.noPointers)
249                 attr |= BlkAttr.NO_SCAN;
250             return GC.malloc(ci.initializer.length, attr, ci);
251         }
252 
253         extern (C) void* _d_newitemU(const TypeInfo ti) nothrow;
254     }
255 
256     extern (C) Object _d_newclass(const ClassInfo ci) nothrow
257     {
258         const initializer = ci.initializer;
259 
260         version (GC)
261             auto p = mem.isGCEnabled ? allocClass(ci) : allocmemory(initializer.length);
262         else
263             auto p = allocmemory(initializer.length);
264 
265         memcpy(p, initializer.ptr, initializer.length);
266         return cast(Object) p;
267     }
268 
269     version (LDC)
270     {
271         extern (C) Object _d_allocclass(const ClassInfo ci) nothrow
272         {
273             version (GC)
274                 if (mem.isGCEnabled)
275                     return cast(Object) allocClass(ci);
276 
277             return cast(Object) allocmemory(ci.initializer.length);
278         }
279     }
280 
281     extern (C) void* _d_newitemT(TypeInfo ti) nothrow
282     {
283         version (GC)
284             auto p = mem.isGCEnabled ? _d_newitemU(ti) : allocmemory(ti.tsize);
285         else
286             auto p = allocmemory(ti.tsize);
287 
288         memset(p, 0, ti.tsize);
289         return p;
290     }
291 
292     extern (C) void* _d_newitemiT(TypeInfo ti) nothrow
293     {
294         version (GC)
295             auto p = mem.isGCEnabled ? _d_newitemU(ti) : allocmemory(ti.tsize);
296         else
297             auto p = allocmemory(ti.tsize);
298 
299         const initializer = ti.initializer;
300         memcpy(p, initializer.ptr, initializer.length);
301         return p;
302     }
303 
304     // TypeInfo.initializer for compilers older than 2.070
305     static if(!__traits(hasMember, TypeInfo, "initializer"))
306     private const(void[]) initializer(T : TypeInfo)(const T t)
307     nothrow pure @safe @nogc
308     {
309         return t.init;
310     }
311 }
312 
313 extern (C) pure @nogc nothrow
314 {
315     /**
316      * Pure variants of C's memory allocation functions `malloc`, `calloc`, and
317      * `realloc` and deallocation function `free`.
318      *
319      * UNIX 98 requires that errno be set to ENOMEM upon failure.
320      * https://linux.die.net/man/3/malloc
321      * However, this is irrelevant for DMD's purposes, and best practice
322      * protocol for using errno is to treat it as an `out` parameter, and not
323      * something with state that can be relied on across function calls.
324      * So, we'll ignore it.
325      *
326      * See_Also:
327      *     $(LINK2 https://dlang.org/spec/function.html#pure-functions, D's rules for purity),
328      *     which allow for memory allocation under specific circumstances.
329      */
330     pragma(mangle, "malloc") void* pureMalloc(size_t size) @trusted;
331 
332     /// ditto
333     pragma(mangle, "calloc") void* pureCalloc(size_t nmemb, size_t size) @trusted;
334 
335     /// ditto
336     pragma(mangle, "realloc") void* pureRealloc(void* ptr, size_t size) @system;
337 
338     /// ditto
339     pragma(mangle, "free") void pureFree(void* ptr) @system;
340 
341 }
342 
343 /**
344 Makes a null-terminated copy of the given string on newly allocated memory.
345 The null-terminator won't be part of the returned string slice. It will be
346 at position `n` where `n` is the length of the input string.
347 
348 Params:
349     s = string to copy
350 
351 Returns: A null-terminated copy of the input array.
352 */
353 extern (D) char[] xarraydup(const(char)[] s) pure nothrow
354 {
355     if (!s)
356         return null;
357 
358     auto p = cast(char*)mem.xmalloc_noscan(s.length + 1);
359     char[] a = p[0 .. s.length];
360     a[] = s[0 .. s.length];
361     p[s.length] = 0;    // preserve 0 terminator semantics
362     return a;
363 }
364 
365 ///
366 pure nothrow unittest
367 {
368     auto s1 = "foo";
369     auto s2 = s1.xarraydup;
370     s2[0] = 'b';
371     assert(s1 == "foo");
372     assert(s2 == "boo");
373     assert(*(s2.ptr + s2.length) == '\0');
374     string sEmpty;
375     assert(sEmpty.xarraydup is null);
376 }
377 
378 /**
379 Makes a copy of the given array on newly allocated memory.
380 
381 Params:
382     s = array to copy
383 
384 Returns: A copy of the input array.
385 */
386 extern (D) T[] arraydup(T)(const scope T[] s) pure nothrow
387 {
388     if (!s)
389         return null;
390 
391     const dim = s.length;
392     auto p = (cast(T*)mem.xmalloc(T.sizeof * dim))[0 .. dim];
393     p[] = s;
394     return p;
395 }
396 
397 ///
398 pure nothrow unittest
399 {
400     auto s1 = [0, 1, 2];
401     auto s2 = s1.arraydup;
402     s2[0] = 4;
403     assert(s1 == [0, 1, 2]);
404     assert(s2 == [4, 1, 2]);
405     string sEmpty;
406     assert(sEmpty.arraydup is null);
407 }