]>
Commit | Line | Data |
---|---|---|
5fee5ec3 IB |
1 | /** |
2 | * Allocate memory using `malloc` or the GC depending on the configuration. | |
3 | * | |
5470a9b1 | 4 | * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved |
c43b5909 IB |
5 | * Authors: Walter Bright, https://www.digitalmars.com |
6 | * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) | |
5fee5ec3 IB |
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 | */ | |
d6679fa2 | 113 | static void* check(void* p) pure nothrow @nogc @safe |
5fee5ec3 IB |
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 | } |