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 (C) void* allocmemory(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 version (DigitalMars) 199 { 200 enum OVERRIDE_MEMALLOC = true; 201 } 202 else version (LDC) 203 { 204 // Memory allocation functions gained weak linkage when the @weak attribute was introduced. 205 import ldc.attributes; 206 enum OVERRIDE_MEMALLOC = is(typeof(ldc.attributes.weak)); 207 } 208 else version (GNU) 209 { 210 version (IN_GCC) 211 enum OVERRIDE_MEMALLOC = false; 212 else 213 enum OVERRIDE_MEMALLOC = true; 214 } 215 else 216 { 217 enum OVERRIDE_MEMALLOC = false; 218 } 219 220 static if (OVERRIDE_MEMALLOC) 221 { 222 // Override the host druntime allocation functions in order to use the bump- 223 // pointer allocation scheme (`allocmemory()` above) if the GC is disabled. 224 // That scheme is faster and comes with less memory overhead than using a 225 // disabled GC alone. 226 227 extern (C) void* _d_allocmemory(size_t m_size) nothrow 228 { 229 version (GC) 230 if (mem.isGCEnabled) 231 return GC.malloc(m_size); 232 233 return allocmemory(m_size); 234 } 235 236 version (GC) 237 { 238 private void* allocClass(const ClassInfo ci) nothrow pure 239 { 240 alias BlkAttr = GC.BlkAttr; 241 242 assert(!(ci.m_flags & TypeInfo_Class.ClassFlags.isCOMclass)); 243 244 BlkAttr attr = BlkAttr.NONE; 245 if (ci.m_flags & TypeInfo_Class.ClassFlags.hasDtor 246 && !(ci.m_flags & TypeInfo_Class.ClassFlags.isCPPclass)) 247 attr |= BlkAttr.FINALIZE; 248 if (ci.m_flags & TypeInfo_Class.ClassFlags.noPointers) 249 attr |= BlkAttr.NO_SCAN; 250 return GC.malloc(ci.initializer.length, attr, ci); 251 } 252 253 extern (C) void* _d_newitemU(const TypeInfo ti) nothrow; 254 } 255 256 extern (C) Object _d_newclass(const ClassInfo ci) nothrow 257 { 258 const initializer = ci.initializer; 259 260 version (GC) 261 auto p = mem.isGCEnabled ? allocClass(ci) : allocmemory(initializer.length); 262 else 263 auto p = allocmemory(initializer.length); 264 265 memcpy(p, initializer.ptr, initializer.length); 266 return cast(Object) p; 267 } 268 269 version (LDC) 270 { 271 extern (C) Object _d_allocclass(const ClassInfo ci) nothrow 272 { 273 version (GC) 274 if (mem.isGCEnabled) 275 return cast(Object) allocClass(ci); 276 277 return cast(Object) allocmemory(ci.initializer.length); 278 } 279 } 280 281 extern (C) void* _d_newitemT(TypeInfo ti) nothrow 282 { 283 version (GC) 284 auto p = mem.isGCEnabled ? _d_newitemU(ti) : allocmemory(ti.tsize); 285 else 286 auto p = allocmemory(ti.tsize); 287 288 memset(p, 0, ti.tsize); 289 return p; 290 } 291 292 extern (C) void* _d_newitemiT(TypeInfo ti) nothrow 293 { 294 version (GC) 295 auto p = mem.isGCEnabled ? _d_newitemU(ti) : allocmemory(ti.tsize); 296 else 297 auto p = allocmemory(ti.tsize); 298 299 const initializer = ti.initializer; 300 memcpy(p, initializer.ptr, initializer.length); 301 return p; 302 } 303 304 // TypeInfo.initializer for compilers older than 2.070 305 static if(!__traits(hasMember, TypeInfo, "initializer")) 306 private const(void[]) initializer(T : TypeInfo)(const T t) 307 nothrow pure @safe @nogc 308 { 309 return t.init; 310 } 311 } 312 313 extern (C) pure @nogc nothrow 314 { 315 /** 316 * Pure variants of C's memory allocation functions `malloc`, `calloc`, and 317 * `realloc` and deallocation function `free`. 318 * 319 * UNIX 98 requires that errno be set to ENOMEM upon failure. 320 * https://linux.die.net/man/3/malloc 321 * However, this is irrelevant for DMD's purposes, and best practice 322 * protocol for using errno is to treat it as an `out` parameter, and not 323 * something with state that can be relied on across function calls. 324 * So, we'll ignore it. 325 * 326 * See_Also: 327 * $(LINK2 https://dlang.org/spec/function.html#pure-functions, D's rules for purity), 328 * which allow for memory allocation under specific circumstances. 329 */ 330 pragma(mangle, "malloc") void* pureMalloc(size_t size) @trusted; 331 332 /// ditto 333 pragma(mangle, "calloc") void* pureCalloc(size_t nmemb, size_t size) @trusted; 334 335 /// ditto 336 pragma(mangle, "realloc") void* pureRealloc(void* ptr, size_t size) @system; 337 338 /// ditto 339 pragma(mangle, "free") void pureFree(void* ptr) @system; 340 341 } 342 343 /** 344 Makes a null-terminated copy of the given string on newly allocated memory. 345 The null-terminator won't be part of the returned string slice. It will be 346 at position `n` where `n` is the length of the input string. 347 348 Params: 349 s = string to copy 350 351 Returns: A null-terminated copy of the input array. 352 */ 353 extern (D) char[] xarraydup(const(char)[] s) pure nothrow 354 { 355 if (!s) 356 return null; 357 358 auto p = cast(char*)mem.xmalloc_noscan(s.length + 1); 359 char[] a = p[0 .. s.length]; 360 a[] = s[0 .. s.length]; 361 p[s.length] = 0; // preserve 0 terminator semantics 362 return a; 363 } 364 365 /// 366 pure nothrow unittest 367 { 368 auto s1 = "foo"; 369 auto s2 = s1.xarraydup; 370 s2[0] = 'b'; 371 assert(s1 == "foo"); 372 assert(s2 == "boo"); 373 assert(*(s2.ptr + s2.length) == '\0'); 374 string sEmpty; 375 assert(sEmpty.xarraydup is null); 376 } 377 378 /** 379 Makes a copy of the given array on newly allocated memory. 380 381 Params: 382 s = array to copy 383 384 Returns: A copy of the input array. 385 */ 386 extern (D) T[] arraydup(T)(const scope T[] s) pure nothrow 387 { 388 if (!s) 389 return null; 390 391 const dim = s.length; 392 auto p = (cast(T*)mem.xmalloc(T.sizeof * dim))[0 .. dim]; 393 p[] = s; 394 return p; 395 } 396 397 /// 398 pure nothrow unittest 399 { 400 auto s1 = [0, 1, 2]; 401 auto s2 = s1.arraydup; 402 s2[0] = 4; 403 assert(s1 == [0, 1, 2]); 404 assert(s2 == [4, 1, 2]); 405 string sEmpty; 406 assert(sEmpty.arraydup is null); 407 }