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 }