Skip to content

Commit

Permalink
Merge pull request #20698 from schveiguy/gcfinalizerarrays
Browse files Browse the repository at this point in the history
Move all array and finalizer functionality into the GC
  • Loading branch information
thewilsonator authored Jan 14, 2025
2 parents 4485b0d + ed08053 commit 93ca467
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 360 deletions.
40 changes: 24 additions & 16 deletions druntime/src/core/internal/array/construction.d
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ T[] _d_newarrayU(T)(size_t length, bool isShared=false) @trusted
{
import core.exception : onOutOfMemoryError;
import core.internal.traits : Unqual;
import core.internal.array.utils : __arrayStart, __setArrayAllocLength, __arrayAlloc;
import core.internal.array.utils : __arrayAlloc;

alias UnqT = Unqual!T;

Expand Down Expand Up @@ -395,16 +395,11 @@ Loverflow:
assert(0);

Lcontinue:
auto info = __arrayAlloc!UnqT(arraySize);
if (!info.base)
auto arr = __arrayAlloc!UnqT(arraySize);
if (!arr.ptr)
goto Loverflow;
debug(PRINTF) printf("p = %p\n", info.base);

auto arrstart = __arrayStart(info);

__setArrayAllocLength!UnqT(info, arraySize, isShared);

return (cast(T*) arrstart)[0 .. length];
debug(PRINTF) printf("p = %p\n", arr.ptr);
return (cast(T*) arr.ptr)[0 .. length];
}

/// ditto
Expand Down Expand Up @@ -522,7 +517,7 @@ Tarr _d_newarraymTX(Tarr : U[], T, U)(size_t[] dims, bool isShared=false) @trust

void[] __allocateInnerArray(size_t[] dims)
{
import core.internal.array.utils : __arrayStart, __setArrayAllocLength, __arrayAlloc;
import core.internal.array.utils : __arrayAlloc;

auto dim = dims[0];

Expand All @@ -535,15 +530,13 @@ Tarr _d_newarraymTX(Tarr : U[], T, U)(size_t[] dims, bool isShared=false) @trust

auto allocSize = (void[]).sizeof * dim;
// the array-of-arrays holds pointers! Don't use UnqT here!
auto info = __arrayAlloc!(void[])(allocSize);
__setArrayAllocLength!(void[])(info, allocSize, isShared);
auto p = __arrayStart(info)[0 .. dim];
auto arr = __arrayAlloc!(void[])(allocSize);

foreach (i; 0..dim)
{
(cast(void[]*)p.ptr)[i] = __allocateInnerArray(dims[1..$]);
(cast(void[]*)arr.ptr)[i] = __allocateInnerArray(dims[1..$]);
}
return p;
return arr.ptr[0 .. dim];
}

auto result = __allocateInnerArray(dims);
Expand Down Expand Up @@ -580,6 +573,21 @@ unittest
}
}

// Test 3-level array allocation (this uses different code paths).
unittest
{
int[][][] a = _d_newarraymTX!(int[][][], int)([3, 4, 5]);
int[5] zeros = 0;

assert(a.length == 3);
foreach(l2; a)
{
assert(l2.length == 4);
foreach(l3; l2)
assert(l3 == zeros[]);
}
}

// https://issues.dlang.org/show_bug.cgi?id=24436
@system unittest
{
Expand Down
115 changes: 7 additions & 108 deletions druntime/src/core/internal/array/utils.d
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,8 @@ module core.internal.array.utils;
import core.internal.traits : Parameters;
import core.memory : GC;

alias BlkInfo = GC.BlkInfo;
alias BlkAttr = GC.BlkAttr;

private
{
enum : size_t
{
PAGESIZE = 4096,
BIGLENGTHMASK = ~(PAGESIZE - 1),
SMALLPAD = 1,
MEDPAD = ushort.sizeof,
LARGEPREFIX = 16, // 16 bytes padding at the front of the array
LARGEPAD = LARGEPREFIX + 1,
MAXSMALLSIZE = 256-SMALLPAD,
MAXMEDSIZE = (PAGESIZE / 2) - MEDPAD
}
}

auto gcStatsPure() nothrow pure
{
import core.memory : GC;
Expand Down Expand Up @@ -156,55 +140,21 @@ template isPostblitNoThrow(T) {
}

/**
* Clear padding that might not be zeroed by the GC (it assumes it is within the
* requested size from the start, but it is actually at the end of the allocated
* block).
*
* Params:
* info = array allocation data
* arrSize = size of the array in bytes
* padSize = size of the padding in bytes
*/
void __arrayClearPad()(ref BlkInfo info, size_t arrSize, size_t padSize) nothrow pure
{
import core.stdc.string;
if (padSize > MEDPAD && !(info.attr & BlkAttr.NO_SCAN) && info.base)
{
if (info.size < PAGESIZE)
memset(info.base + arrSize, 0, padSize);
else
memset(info.base, 0, LARGEPREFIX);
}
}

/**
* Allocate an array memory block by applying the proper padding and assigning
* block attributes if not inherited from the existing block.
* Allocate a memory block with appendable capabilities for array usage.
*
* Params:
* arrSize = size of the allocated array in bytes
* Returns:
* `BlkInfo` with allocation metadata
* `void[]` matching requested size on success, `null` on failure.
*/
BlkInfo __arrayAlloc(T)(size_t arrSize) @trusted
void[] __arrayAlloc(T)(size_t arrSize) @trusted
{
import core.checkedint;
import core.lifetime : TypeInfoSize;
import core.internal.traits : hasIndirections;

enum typeInfoSize = TypeInfoSize!T;
BlkAttr attr = BlkAttr.APPENDABLE;

size_t padSize = arrSize > MAXMEDSIZE ?
LARGEPAD :
((arrSize > MAXSMALLSIZE ? MEDPAD : SMALLPAD) + typeInfoSize);

bool overflow;
auto paddedSize = addu(arrSize, padSize, overflow);

if (overflow)
return BlkInfo();

/* `extern(C++)` classes don't have a classinfo pointer in their vtable,
* so the GC can't finalize them.
*/
Expand All @@ -213,59 +163,8 @@ BlkInfo __arrayAlloc(T)(size_t arrSize) @trusted
static if (!hasIndirections!T)
attr |= BlkAttr.NO_SCAN;

auto bi = GC.qalloc(paddedSize, attr, typeid(T));
__arrayClearPad(bi, arrSize, padSize);
return bi;
}

/**
* Get the start of the array for the given block.
*
* Params:
* info = array metadata
* Returns:
* pointer to the start of the array
*/
void *__arrayStart()(return scope BlkInfo info) nothrow pure
{
return info.base + ((info.size & BIGLENGTHMASK) ? LARGEPREFIX : 0);
}

/**
* Set the allocated length of the array block. This is called when an array
* is appended to or its length is set.
*
* The allocated block looks like this for blocks < PAGESIZE:
* `|elem0|elem1|elem2|...|elemN-1|emptyspace|N*elemsize|`
*
* The size of the allocated length at the end depends on the block size:
* a block of 16 to 256 bytes has an 8-bit length.
* a block with 512 to pagesize/2 bytes has a 16-bit length.
*
* For blocks >= pagesize, the length is a size_t and is at the beginning of the
* block. The reason we have to do this is because the block can extend into
* more pages, so we cannot trust the block length if it sits at the end of the
* block, because it might have just been extended. If we can prove in the
* future that the block is unshared, we may be able to change this, but I'm not
* sure it's important.
*
* In order to do put the length at the front, we have to provide 16 bytes
* buffer space in case the block has to be aligned properly. In x86, certain
* SSE instructions will only work if the data is 16-byte aligned. In addition,
* we need the sentinel byte to prevent accidental pointers to the next block.
* Because of the extra overhead, we only do this for page size and above, where
* the overhead is minimal compared to the block size.
*
* So for those blocks, it looks like:
* `|N*elemsize|padding|elem0|elem1|...|elemN-1|emptyspace|sentinelbyte|``
*
* where `elem0` starts 16 bytes after the first byte.
*/
bool __setArrayAllocLength(T)(ref BlkInfo info, size_t newLength, bool isShared, size_t oldLength = ~0)
{
import core.lifetime : TypeInfoSize;
import core.internal.gc.blockmeta : __setArrayAllocLengthImpl, __setBlockFinalizerInfo;
static if (TypeInfoSize!T)
__setBlockFinalizerInfo(info, typeid(T));
return __setArrayAllocLengthImpl(info, newLength, isShared, oldLength, TypeInfoSize!T);
auto ptr = GC.malloc(arrSize, attr, typeid(T));
if (ptr)
return ptr[0 .. arrSize];
return null;
}
79 changes: 67 additions & 12 deletions druntime/src/core/internal/gc/blockmeta.d
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,11 @@ size_t structTypeInfoSize(const TypeInfo ti) pure nothrow @nogc
where elem0 starts 16 bytes after the first byte.
*/
bool __setArrayAllocLength(ref BlkInfo info, size_t newlength, bool isshared, const TypeInfo tinext, size_t oldlength = ~0) pure nothrow
bool __setArrayAllocLength(ref BlkInfo info, size_t newlength, bool isshared, const TypeInfo tinext, size_t oldlength = size_t.max) pure nothrow
{
size_t typeInfoSize = (info.attr & BlkAttr.STRUCTFINAL) ? size_t.sizeof : 0;
if (typeInfoSize)
__setBlockFinalizerInfo(info, tinext);
__setBlockFinalizerInfo(info, tinext);

size_t typeInfoSize = (info.attr & BlkAttr.STRUCTFINAL) ? size_t.sizeof : 0;
return __setArrayAllocLengthImpl(info, newlength, isshared, oldlength, typeInfoSize);
}

Expand All @@ -97,7 +96,7 @@ bool __setArrayAllocLengthImpl(ref BlkInfo info, size_t newlength, bool isshared
return false;

auto length = cast(ubyte *)(info.base + info.size - typeInfoSize - SMALLPAD);
if (oldlength != ~0)
if (oldlength != size_t.max)
{
if (isshared)
{
Expand All @@ -123,7 +122,7 @@ bool __setArrayAllocLengthImpl(ref BlkInfo info, size_t newlength, bool isshared
// new size does not fit inside block
return false;
auto length = cast(ushort *)(info.base + info.size - typeInfoSize - MEDPAD);
if (oldlength != ~0)
if (oldlength != size_t.max)
{
if (isshared)
{
Expand All @@ -149,7 +148,7 @@ bool __setArrayAllocLengthImpl(ref BlkInfo info, size_t newlength, bool isshared
// new size does not fit inside block
return false;
auto length = cast(size_t *)(info.base);
if (oldlength != ~0)
if (oldlength != size_t.max)
{
if (isshared)
{
Expand All @@ -176,24 +175,49 @@ bool __setArrayAllocLengthImpl(ref BlkInfo info, size_t newlength, bool isshared
The block finalizer info is set separately from the array length, as that is
only needed on the initial setup of the block. No shared is needed, since
this should only happen when the block is new.
If the STRUCTFINAL bit is not set, no finalizer is stored (but if needed the
slot is zeroed)
*/
void __setBlockFinalizerInfo(ref BlkInfo info, const TypeInfo ti) pure nothrow
{
if ((info.attr & BlkAttr.APPENDABLE) && info.size >= PAGESIZE)
{
// if the structfinal bit is not set, we don't have a finalizer. But we
// should still zero out the finalizer slot.
auto context = (info.attr & BlkAttr.STRUCTFINAL) ? cast(void*)ti : null;

// array used size goes at the beginning. We can stuff the typeinfo
// right after it, as we need to use 16 bytes anyway.
auto typeInfo = cast(TypeInfo*)(info.base + size_t.sizeof);
*typeInfo = cast() ti;
//
auto typeInfo = cast(void**)info.base + 1;
*typeInfo = context;
version (D_LP64) {} else
{
// zero out the extra padding
(cast(size_t*)info.base)[2 .. 4] = 0;
}
}
else
else if(info.attr & BlkAttr.STRUCTFINAL)
{
// all other cases the typeinfo gets put at the end of the block
auto typeInfo = cast(TypeInfo*)(info.base + info.size - size_t.sizeof);
*typeInfo = cast() ti;
auto typeInfo = cast(void**)(info.base + info.size) - 1;
*typeInfo = cast(void*) ti;
}
}

/**
Get the finalizer info from the block (typeinfo).
Must be called on a block with STRUCTFINAL set.
*/
const(TypeInfo) __getBlockFinalizerInfo(ref BlkInfo info) pure nothrow
{
bool isLargeArray = (info.attr & BlkAttr.APPENDABLE) && info.size >= PAGESIZE;
auto typeInfo = isLargeArray ?
info.base + size_t.sizeof :
info.base + info.size - size_t.sizeof;
return *cast(TypeInfo*)typeInfo;
}

/**
get the used size of the array for the given block
*/
Expand Down Expand Up @@ -251,3 +275,34 @@ size_t __arrayPad(size_t size, const TypeInfo tinext) nothrow pure @trusted
{
return size > MAXMEDSIZE ? LARGEPAD : ((size > MAXSMALLSIZE ? MEDPAD : SMALLPAD) + structTypeInfoSize(tinext));
}

/**
get the padding required to allocate size bytes, use the bits to determine
which metadata must be stored.
*/
size_t __allocPad(size_t size, uint bits) nothrow pure @trusted
{
auto finalizerSize = (bits & BlkAttr.STRUCTFINAL) ? (void*).sizeof : 0;
if (bits & BlkAttr.APPENDABLE)
{
if (size > MAXMEDSIZE - finalizerSize)
return LARGEPAD;
auto pad = (size > MAXSMALLSIZE - finalizerSize) ? MEDPAD : SMALLPAD;
return pad + finalizerSize;
}

return finalizerSize;
}

/**
* Get the start of the array for the given block.
*
* Params:
* info = array metadata
* Returns:
* pointer to the start of the array
*/
void *__arrayStart()(return scope BlkInfo info) nothrow pure
{
return info.base + ((info.size & BIGLENGTHMASK) ? LARGEPREFIX : 0);
}
Loading

0 comments on commit 93ca467

Please sign in to comment.