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 (D) void* allocmemoryNoFree(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 extern (D) void* allocmemory(size_t m_size) nothrow
199 {
200     version (GC)
201         if (mem.isGCEnabled)
202             return GC.malloc(m_size);
203 
204     return allocmemoryNoFree(m_size);
205 }
206 
207 version (DigitalMars)
208 {
209     enum OVERRIDE_MEMALLOC = true;
210 }
211 else version (LDC)
212 {
213     // Memory allocation functions gained weak linkage when the @weak attribute was introduced.
214     import ldc.attributes;
215     enum OVERRIDE_MEMALLOC = is(typeof(ldc.attributes.weak));
216 }
217 else version (GNU)
218 {
219     version (IN_GCC)
220         enum OVERRIDE_MEMALLOC = false;
221     else
222         enum OVERRIDE_MEMALLOC = true;
223 }
224 else
225 {
226     enum OVERRIDE_MEMALLOC = false;
227 }
228 
229 static if (OVERRIDE_MEMALLOC)
230 {
231     // Override the host druntime allocation functions in order to use the bump-
232     // pointer allocation scheme (`allocmemoryNoFree()` above) if the GC is disabled.
233     // That scheme is faster and comes with less memory overhead than using a
234     // disabled GC alone.
235 
236     extern (C) void* _d_allocmemory(size_t m_size) nothrow
237     {
238         return allocmemory(m_size);
239     }
240 
241     version (GC)
242     {
243         private void* allocClass(const ClassInfo ci) nothrow pure
244         {
245             alias BlkAttr = GC.BlkAttr;
246 
247             assert(!(ci.m_flags & TypeInfo_Class.ClassFlags.isCOMclass));
248 
249             BlkAttr attr = BlkAttr.NONE;
250             if (ci.m_flags & TypeInfo_Class.ClassFlags.hasDtor
251                 && !(ci.m_flags & TypeInfo_Class.ClassFlags.isCPPclass))
252                 attr |= BlkAttr.FINALIZE;
253             if (ci.m_flags & TypeInfo_Class.ClassFlags.noPointers)
254                 attr |= BlkAttr.NO_SCAN;
255             return GC.malloc(ci.initializer.length, attr, ci);
256         }
257 
258         extern (C) void* _d_newitemU(const TypeInfo ti) nothrow;
259     }
260 
261     extern (C) Object _d_newclass(const ClassInfo ci) nothrow
262     {
263         const initializer = ci.initializer;
264 
265         version (GC)
266             auto p = mem.isGCEnabled ? allocClass(ci) : allocmemoryNoFree(initializer.length);
267         else
268             auto p = allocmemoryNoFree(initializer.length);
269 
270         memcpy(p, initializer.ptr, initializer.length);
271         return cast(Object) p;
272     }
273 
274     version (LDC)
275     {
276         extern (C) Object _d_allocclass(const ClassInfo ci) nothrow
277         {
278             version (GC)
279                 if (mem.isGCEnabled)
280                     return cast(Object) allocClass(ci);
281 
282             return cast(Object) allocmemoryNoFree(ci.initializer.length);
283         }
284     }
285 
286     extern (C) void* _d_newitemT(TypeInfo ti) nothrow
287     {
288         version (GC)
289             auto p = mem.isGCEnabled ? _d_newitemU(ti) : allocmemoryNoFree(ti.tsize);
290         else
291             auto p = allocmemoryNoFree(ti.tsize);
292 
293         memset(p, 0, ti.tsize);
294         return p;
295     }
296 
297     extern (C) void* _d_newitemiT(TypeInfo ti) nothrow
298     {
299         version (GC)
300             auto p = mem.isGCEnabled ? _d_newitemU(ti) : allocmemoryNoFree(ti.tsize);
301         else
302             auto p = allocmemoryNoFree(ti.tsize);
303 
304         const initializer = ti.initializer;
305         memcpy(p, initializer.ptr, initializer.length);
306         return p;
307     }
308 
309     // TypeInfo.initializer for compilers older than 2.070
310     static if(!__traits(hasMember, TypeInfo, "initializer"))
311     private const(void[]) initializer(T : TypeInfo)(const T t)
312     nothrow pure @safe @nogc
313     {
314         return t.init;
315     }
316 }
317 
318 extern (C) pure @nogc nothrow
319 {
320     /**
321      * Pure variants of C's memory allocation functions `malloc`, `calloc`, and
322      * `realloc` and deallocation function `free`.
323      *
324      * UNIX 98 requires that errno be set to ENOMEM upon failure.
325      * https://linux.die.net/man/3/malloc
326      * However, this is irrelevant for DMD's purposes, and best practice
327      * protocol for using errno is to treat it as an `out` parameter, and not
328      * something with state that can be relied on across function calls.
329      * So, we'll ignore it.
330      *
331      * See_Also:
332      *     $(LINK2 https://dlang.org/spec/function.html#pure-functions, D's rules for purity),
333      *     which allow for memory allocation under specific circumstances.
334      */
335     pragma(mangle, "malloc") void* pureMalloc(size_t size) @trusted;
336 
337     /// ditto
338     pragma(mangle, "calloc") void* pureCalloc(size_t nmemb, size_t size) @trusted;
339 
340     /// ditto
341     pragma(mangle, "realloc") void* pureRealloc(void* ptr, size_t size) @system;
342 
343     /// ditto
344     pragma(mangle, "free") void pureFree(void* ptr) @system;
345 
346 }
347 
348 /**
349 Makes a null-terminated copy of the given string on newly allocated memory.
350 The null-terminator won't be part of the returned string slice. It will be
351 at position `n` where `n` is the length of the input string.
352 
353 Params:
354     s = string to copy
355 
356 Returns: A null-terminated copy of the input array.
357 */
358 extern (D) char[] xarraydup(const(char)[] s) pure nothrow
359 {
360     if (!s)
361         return null;
362 
363     auto p = cast(char*)mem.xmalloc_noscan(s.length + 1);
364     char[] a = p[0 .. s.length];
365     a[] = s[0 .. s.length];
366     p[s.length] = 0;    // preserve 0 terminator semantics
367     return a;
368 }
369 
370 ///
371 pure nothrow unittest
372 {
373     auto s1 = "foo";
374     auto s2 = s1.xarraydup;
375     s2[0] = 'b';
376     assert(s1 == "foo");
377     assert(s2 == "boo");
378     assert(*(s2.ptr + s2.length) == '\0');
379     string sEmpty;
380     assert(sEmpty.xarraydup is null);
381 }
382 
383 /**
384 Makes a copy of the given array on newly allocated memory.
385 
386 Params:
387     s = array to copy
388 
389 Returns: A copy of the input array.
390 */
391 extern (D) T[] arraydup(T)(const scope T[] s) pure nothrow
392 {
393     if (!s)
394         return null;
395 
396     const dim = s.length;
397     auto p = (cast(T*)mem.xmalloc(T.sizeof * dim))[0 .. dim];
398     p[] = s;
399     return p;
400 }
401 
402 ///
403 pure nothrow unittest
404 {
405     auto s1 = [0, 1, 2];
406     auto s2 = s1.arraydup;
407     s2[0] = 4;
408     assert(s1 == [0, 1, 2]);
409     assert(s2 == [4, 1, 2]);
410     string sEmpty;
411     assert(sEmpty.arraydup is null);
412 }
413 
414 // Define this to have Pool emit traces of objects allocated and disposed
415 //debug = Pool;
416 // Define this in addition to Pool to emit per-call traces (otherwise summaries are printed at the end).
417 //debug = PoolVerbose;
418 
419 /**
420 Defines a pool for class objects. Objects can be fetched from the pool with make() and returned to the pool with
421 dispose(). Using a reference that has been dispose()d has undefined behavior. make() may return memory that has been
422 previously dispose()d.
423 
424 Currently the pool has effect only if the GC is NOT used (i.e. either `version(GC)` or `mem.isGCEnabled` is false).
425 Otherwise `make` just forwards to `new` and `dispose` does nothing.
426 
427 Internally the implementation uses a singly-linked freelist with a global root. The "next" pointer is stored in the
428 first word of each disposed object.
429 */
430 struct Pool(T)
431 if (is(T == class))
432 {
433     /// The freelist's root
434     private static T root;
435 
436     private static void trace(string fun, string f, uint l)()
437     {
438         debug(Pool)
439         {
440             debug(PoolVerbose)
441             {
442                 fprintf(stderr, "%.*s(%u): bytes: %lu Pool!(%.*s)."~fun~"()\n",
443                     cast(int) f.length, f.ptr, l, T.classinfo.initializer.length,
444                     cast(int) T.stringof.length, T.stringof.ptr);
445             }
446             else
447             {
448                 static ulong calls;
449                 if (calls == 0)
450                 {
451                     // Plant summary printer
452                     static extern(C) void summarize()
453                     {
454                         fprintf(stderr, "%.*s(%u): bytes: %lu calls: %lu Pool!(%.*s)."~fun~"()\n",
455                             cast(int) f.length, f.ptr, l, ((T.classinfo.initializer.length + 15) & ~15) * calls,
456                             calls, cast(int) T.stringof.length, T.stringof.ptr);
457                     }
458                     atexit(&summarize);
459                 }
460                 ++calls;
461             }
462         }
463     }
464 
465     /**
466     Returns a reference to a new object in the same state as if created with new T(args).
467     */
468     static T make(string f = __FILE__, uint l = __LINE__, A...)(auto ref A args)
469     {
470         if (!root)
471         {
472             trace!("makeNew", f, l)();
473             return new T(args);
474         }
475         else
476         {
477             trace!("makeReuse", f, l)();
478             auto result = root;
479             root = *(cast(T*) root);
480             memcpy(cast(void*) result, T.classinfo.initializer.ptr, T.classinfo.initializer.length);
481             result.__ctor(args);
482             return result;
483         }
484     }
485 
486     /**
487     Signals to the pool that this object is no longer used, so it can recycle its memory.
488     */
489     static void dispose(string f = __FILE__, uint l = __LINE__, A...)(T goner)
490     {
491         version(GC)
492         {
493             if (mem.isGCEnabled) return;
494         }
495         trace!("dispose", f, l)();
496         debug
497         {
498             // Stomp the memory so as to maximize the chance of quick failure if used after dispose().
499             auto p = cast(ulong*) goner;
500             p[0 .. T.classinfo.initializer.length / ulong.sizeof] = 0xdeadbeef;
501         }
502         *(cast(T*) goner) = root;
503         root = goner;
504     }
505 }