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