| /** |
| * Allocate memory using `malloc` or the GC depending on the configuration. |
| * |
| * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved |
| * Authors: Walter Bright, https://www.digitalmars.com |
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) |
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/root/rmem.d, root/_rmem.d) |
| * Documentation: https://dlang.org/phobos/dmd_root_rmem.html |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/rmem.d |
| */ |
| |
| module dmd.root.rmem; |
| |
| import core.exception : onOutOfMemoryError; |
| import core.stdc.stdio; |
| import core.stdc.stdlib; |
| import core.stdc.string; |
| |
| import core.memory : GC; |
| |
| extern (C++) struct Mem |
| { |
| static char* xstrdup(const(char)* s) nothrow |
| { |
| if (isGCEnabled) |
| return s ? s[0 .. strlen(s) + 1].dup.ptr : null; |
| |
| return s ? cast(char*)check(.strdup(s)) : null; |
| } |
| |
| static void xfree(void* p) pure nothrow |
| { |
| if (isGCEnabled) |
| return GC.free(p); |
| |
| pureFree(p); |
| } |
| |
| static void* xmalloc(size_t size) pure nothrow |
| { |
| if (isGCEnabled) |
| return size ? GC.malloc(size) : null; |
| |
| return size ? check(pureMalloc(size)) : null; |
| } |
| |
| static void* xmalloc_noscan(size_t size) pure nothrow |
| { |
| if (isGCEnabled) |
| return size ? GC.malloc(size, GC.BlkAttr.NO_SCAN) : null; |
| |
| return size ? check(pureMalloc(size)) : null; |
| } |
| |
| static void* xcalloc(size_t size, size_t n) pure nothrow |
| { |
| if (isGCEnabled) |
| return size * n ? GC.calloc(size * n) : null; |
| |
| return (size && n) ? check(pureCalloc(size, n)) : null; |
| } |
| |
| static void* xcalloc_noscan(size_t size, size_t n) pure nothrow |
| { |
| if (isGCEnabled) |
| return size * n ? GC.calloc(size * n, GC.BlkAttr.NO_SCAN) : null; |
| |
| return (size && n) ? check(pureCalloc(size, n)) : null; |
| } |
| |
| static void* xrealloc(void* p, size_t size) pure nothrow |
| { |
| if (isGCEnabled) |
| return GC.realloc(p, size); |
| |
| if (!size) |
| { |
| pureFree(p); |
| return null; |
| } |
| |
| return check(pureRealloc(p, size)); |
| } |
| |
| static void* xrealloc_noscan(void* p, size_t size) pure nothrow |
| { |
| if (isGCEnabled) |
| return GC.realloc(p, size, GC.BlkAttr.NO_SCAN); |
| |
| if (!size) |
| { |
| pureFree(p); |
| return null; |
| } |
| |
| return check(pureRealloc(p, size)); |
| } |
| |
| static void* error() pure nothrow @nogc @safe |
| { |
| onOutOfMemoryError(); |
| assert(0); |
| } |
| |
| /** |
| * Check p for null. If it is, issue out of memory error |
| * and exit program. |
| * Params: |
| * p = pointer to check for null |
| * Returns: |
| * p if not null |
| */ |
| static void* check(void* p) pure nothrow @nogc |
| { |
| return p ? p : error(); |
| } |
| |
| __gshared bool _isGCEnabled = true; |
| |
| // fake purity by making global variable immutable (_isGCEnabled only modified before startup) |
| enum _pIsGCEnabled = cast(immutable bool*) &_isGCEnabled; |
| |
| static bool isGCEnabled() pure nothrow @nogc @safe |
| { |
| return *_pIsGCEnabled; |
| } |
| |
| static void disableGC() nothrow @nogc |
| { |
| _isGCEnabled = false; |
| } |
| |
| static void addRange(const(void)* p, size_t size) nothrow @nogc |
| { |
| if (isGCEnabled) |
| GC.addRange(p, size); |
| } |
| |
| static void removeRange(const(void)* p) nothrow @nogc |
| { |
| if (isGCEnabled) |
| GC.removeRange(p); |
| } |
| } |
| |
| extern (C++) const __gshared Mem mem; |
| |
| enum CHUNK_SIZE = (256 * 4096 - 64); |
| |
| __gshared size_t heapleft = 0; |
| __gshared void* heapp; |
| |
| extern (D) void* allocmemoryNoFree(size_t m_size) nothrow @nogc |
| { |
| // 16 byte alignment is better (and sometimes needed) for doubles |
| m_size = (m_size + 15) & ~15; |
| |
| // The layout of the code is selected so the most common case is straight through |
| if (m_size <= heapleft) |
| { |
| L1: |
| heapleft -= m_size; |
| auto p = heapp; |
| heapp = cast(void*)(cast(char*)heapp + m_size); |
| return p; |
| } |
| |
| if (m_size > CHUNK_SIZE) |
| { |
| return Mem.check(malloc(m_size)); |
| } |
| |
| heapleft = CHUNK_SIZE; |
| heapp = Mem.check(malloc(CHUNK_SIZE)); |
| goto L1; |
| } |
| |
| extern (D) void* allocmemory(size_t m_size) nothrow |
| { |
| if (mem.isGCEnabled) |
| return GC.malloc(m_size); |
| |
| return allocmemoryNoFree(m_size); |
| } |
| |
| version (DigitalMars) |
| { |
| enum OVERRIDE_MEMALLOC = true; |
| } |
| else version (LDC) |
| { |
| // Memory allocation functions gained weak linkage when the @weak attribute was introduced. |
| import ldc.attributes; |
| enum OVERRIDE_MEMALLOC = is(typeof(ldc.attributes.weak)); |
| } |
| else version (GNU) |
| { |
| version (IN_GCC) |
| enum OVERRIDE_MEMALLOC = false; |
| else |
| enum OVERRIDE_MEMALLOC = true; |
| } |
| else |
| { |
| enum OVERRIDE_MEMALLOC = false; |
| } |
| |
| static if (OVERRIDE_MEMALLOC) |
| { |
| // Override the host druntime allocation functions in order to use the bump- |
| // pointer allocation scheme (`allocmemoryNoFree()` above) if the GC is disabled. |
| // That scheme is faster and comes with less memory overhead than using a |
| // disabled GC alone. |
| |
| extern (C) void* _d_allocmemory(size_t m_size) nothrow |
| { |
| return allocmemory(m_size); |
| } |
| |
| private void* allocClass(const ClassInfo ci) nothrow pure |
| { |
| alias BlkAttr = GC.BlkAttr; |
| |
| assert(!(ci.m_flags & TypeInfo_Class.ClassFlags.isCOMclass)); |
| |
| BlkAttr attr = BlkAttr.NONE; |
| if (ci.m_flags & TypeInfo_Class.ClassFlags.hasDtor |
| && !(ci.m_flags & TypeInfo_Class.ClassFlags.isCPPclass)) |
| attr |= BlkAttr.FINALIZE; |
| if (ci.m_flags & TypeInfo_Class.ClassFlags.noPointers) |
| attr |= BlkAttr.NO_SCAN; |
| return GC.malloc(ci.initializer.length, attr, ci); |
| } |
| |
| extern (C) void* _d_newitemU(const TypeInfo ti) nothrow; |
| |
| extern (C) Object _d_newclass(const ClassInfo ci) nothrow |
| { |
| const initializer = ci.initializer; |
| |
| auto p = mem.isGCEnabled ? allocClass(ci) : allocmemoryNoFree(initializer.length); |
| memcpy(p, initializer.ptr, initializer.length); |
| return cast(Object) p; |
| } |
| |
| version (LDC) |
| { |
| extern (C) Object _d_allocclass(const ClassInfo ci) nothrow |
| { |
| if (mem.isGCEnabled) |
| return cast(Object) allocClass(ci); |
| |
| return cast(Object) allocmemoryNoFree(ci.initializer.length); |
| } |
| } |
| |
| extern (C) void* _d_newitemT(TypeInfo ti) nothrow |
| { |
| auto p = mem.isGCEnabled ? _d_newitemU(ti) : allocmemoryNoFree(ti.tsize); |
| memset(p, 0, ti.tsize); |
| return p; |
| } |
| |
| extern (C) void* _d_newitemiT(TypeInfo ti) nothrow |
| { |
| auto p = mem.isGCEnabled ? _d_newitemU(ti) : allocmemoryNoFree(ti.tsize); |
| const initializer = ti.initializer; |
| memcpy(p, initializer.ptr, initializer.length); |
| return p; |
| } |
| |
| // TypeInfo.initializer for compilers older than 2.070 |
| static if(!__traits(hasMember, TypeInfo, "initializer")) |
| private const(void[]) initializer(T : TypeInfo)(const T t) |
| nothrow pure @safe @nogc |
| { |
| return t.init; |
| } |
| } |
| |
| extern (C) pure @nogc nothrow |
| { |
| /** |
| * Pure variants of C's memory allocation functions `malloc`, `calloc`, and |
| * `realloc` and deallocation function `free`. |
| * |
| * UNIX 98 requires that errno be set to ENOMEM upon failure. |
| * https://linux.die.net/man/3/malloc |
| * However, this is irrelevant for DMD's purposes, and best practice |
| * protocol for using errno is to treat it as an `out` parameter, and not |
| * something with state that can be relied on across function calls. |
| * So, we'll ignore it. |
| * |
| * See_Also: |
| * $(LINK2 https://dlang.org/spec/function.html#pure-functions, D's rules for purity), |
| * which allow for memory allocation under specific circumstances. |
| */ |
| pragma(mangle, "malloc") void* pureMalloc(size_t size) @trusted; |
| |
| /// ditto |
| pragma(mangle, "calloc") void* pureCalloc(size_t nmemb, size_t size) @trusted; |
| |
| /// ditto |
| pragma(mangle, "realloc") void* pureRealloc(void* ptr, size_t size) @system; |
| |
| /// ditto |
| pragma(mangle, "free") void pureFree(void* ptr) @system; |
| |
| } |
| |
| /** |
| Makes a null-terminated copy of the given string on newly allocated memory. |
| The null-terminator won't be part of the returned string slice. It will be |
| at position `n` where `n` is the length of the input string. |
| |
| Params: |
| s = string to copy |
| |
| Returns: A null-terminated copy of the input array. |
| */ |
| extern (D) char[] xarraydup(const(char)[] s) pure nothrow |
| { |
| if (!s) |
| return null; |
| |
| auto p = cast(char*)mem.xmalloc_noscan(s.length + 1); |
| char[] a = p[0 .. s.length]; |
| a[] = s[0 .. s.length]; |
| p[s.length] = 0; // preserve 0 terminator semantics |
| return a; |
| } |
| |
| /// |
| pure nothrow unittest |
| { |
| auto s1 = "foo"; |
| auto s2 = s1.xarraydup; |
| s2[0] = 'b'; |
| assert(s1 == "foo"); |
| assert(s2 == "boo"); |
| assert(*(s2.ptr + s2.length) == '\0'); |
| string sEmpty; |
| assert(sEmpty.xarraydup is null); |
| } |
| |
| /** |
| Makes a copy of the given array on newly allocated memory. |
| |
| Params: |
| s = array to copy |
| |
| Returns: A copy of the input array. |
| */ |
| extern (D) T[] arraydup(T)(const scope T[] s) pure nothrow |
| { |
| if (!s) |
| return null; |
| |
| const dim = s.length; |
| auto p = (cast(T*)mem.xmalloc(T.sizeof * dim))[0 .. dim]; |
| p[] = s; |
| return p; |
| } |
| |
| /// |
| pure nothrow unittest |
| { |
| auto s1 = [0, 1, 2]; |
| auto s2 = s1.arraydup; |
| s2[0] = 4; |
| assert(s1 == [0, 1, 2]); |
| assert(s2 == [4, 1, 2]); |
| string sEmpty; |
| assert(sEmpty.arraydup is null); |
| } |