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 }