1 /**
2 * Allocate memory using `malloc` or the GC depending on the configuration.
3 *
4 * Copyright: Copyright (C) 1999-2021 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 import core.memory : GC;
20
21 extern (C++) struct Mem
22 {
xstrdupMem23 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
xfreeMem31 static void xfree(void* p) pure nothrow
32 {
33 if (isGCEnabled)
34 return GC.free(p);
35
36 pureFree(p);
37 }
38
xmallocMem39 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
xmalloc_noscanMem47 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
xcallocMem55 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
xcalloc_noscanMem63 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
xreallocMem71 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
xrealloc_noscanMem85 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
errorMem99 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 */
checkMem113 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
isGCEnabledMem123 static bool isGCEnabled() pure nothrow @nogc @safe
124 {
125 return *_pIsGCEnabled;
126 }
127
disableGCMem128 static void disableGC() nothrow @nogc
129 {
130 _isGCEnabled = false;
131 }
132
addRangeMem133 static void addRange(const(void)* p, size_t size) nothrow @nogc
134 {
135 if (isGCEnabled)
136 GC.addRange(p, size);
137 }
138
removeRangeMem139 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
allocmemoryNoFree(size_t m_size)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
allocmemory(size_t m_size)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
version(DigitalMars)186 version (DigitalMars)
187 {
188 enum OVERRIDE_MEMALLOC = true;
189 }
version(LDC)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 }
version(GNU)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
_d_allocmemory(size_t m_size)215 extern (C) void* _d_allocmemory(size_t m_size) nothrow
216 {
217 return allocmemory(m_size);
218 }
219
allocClass(const ClassInfo ci)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
_d_newclass(const ClassInfo ci)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
version(LDC)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
_d_newitemT(TypeInfo ti)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
_d_newitemiT(TypeInfo ti)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 */
xarraydup(const (char)[]s)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 */
arraydup(T)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 }
376