Skip to content

Commit

Permalink
Update GC interface to contain array management functions. (#20608)
Browse files Browse the repository at this point in the history
* Move array functions into conservative GC.

* Move array functions into conservative GC. Implement stubs for manual GC
and proto GC.

* Add note about trusted functions
  • Loading branch information
schveiguy authored Dec 30, 2024
1 parent fb07d99 commit b88ffc5
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 161 deletions.
69 changes: 69 additions & 0 deletions druntime/src/core/gc/gcinterface.d
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,73 @@ interface GC
* GC.stats().allocatedInCurrentThread, but faster.
*/
ulong allocatedInCurrentThread() nothrow;

// ARRAY FUNCTIONS
/**
* Get the current used capacity of an array block. Note that this is only
* needed if you are about to change the array used size and need to deal
* with the memory that is about to go away. For appending or shrinking
* arrays that have no destructors, you probably don't need this function.
* Params:
* ptr - The pointer to check. This can be an interior pointer, but if it
* is beyond the end of the used space, the return value may not be
* valid.
* atomic - If true, the value is fetched atomically (for shared arrays)
* Returns: Current array slice, or null if the pointer does not point to a
* valid appendable GC block.
*/
void[] getArrayUsed(void *ptr, bool atomic = false) nothrow;

/**
* Expand the array used size. Used for appending and expanding the length
* of the array slice. If the operation can be performed without
* reallocating, the function succeeds. Newly expanded data is not
* initialized.
*
* slices that do not point at expandable GC blocks cannot be affected, and
* this function will always return false.
* Params:
* slice - the slice to attempt expanding in place.
* newUsed - the size that should be stored as used.
* atomic - if true, the array may be shared between threads, and this
* operation should be done atomically.
* Returns: true if successful.
*/
bool expandArrayUsed(void[] slice, size_t newUsed, bool atomic = false) nothrow @safe;

/**
* Expand the array capacity. Used for reserving space that can be used for
* appending. If the operation can be performed without reallocating, the
* function succeeds. The used size is not changed.
*
* slices that do not point at expandable GC blocks cannot be affected, and
* this function will always return zero.
* Params:
* slice - the slice to attempt reserving capacity for.
* request - the requested size to expand to. Includes the existing data.
* Passing a value less than the current array size will result in no
* changes, but will return the current capacity.
* atomic - if true, the array may be shared between threads, and this
* operation should be done atomically.
* Returns: resulting capacity size, 0 if the operation could not be performed.
*/
size_t reserveArrayCapacity(void[] slice, size_t request, bool atomic = false) nothrow @safe;

/**
* Shrink used space of a slice. Unlike the other array functions, the
* array slice passed in is the target slice, and the existing used space
* is passed separately. This is to discourage code that ends up with a
* slice to dangling valid data.
* If slice.ptr[0 .. existingUsed] does not point to the end of a valid GC
* appendable slice, then the operation fails.
* Params:
* slice - The proposed valid slice data.
* existingUsed - The amount of data in the block (starting at slice.ptr)
* that is currently valid in the array. If this amount does not match
* the current used size, the operation fails.
* atomic - If true, the slice may be shared between threads, and the
* operation should be atomic.
* Returns: true if successful.
*/
bool shrinkArrayUsed(void[] slice, size_t existingUsed, bool atomic = false) nothrow;
}
187 changes: 187 additions & 0 deletions druntime/src/core/internal/gc/impl/conservative/gc.d
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,193 @@ class ConservativeGC : GC
stats.freeSize += freeListSize;
stats.allocatedInCurrentThread = bytesAllocated;
}

// ARRAY FUNCTIONS
void[] getArrayUsed(void *ptr, bool atomic = false) nothrow
{
import core.internal.gc.blockmeta;
import core.internal.gc.blkcache;
import core.internal.array.utils;

// lookup the block info, using the cache if possible.
auto bic = atomic ? null : __getBlkInfo(ptr);
auto info = bic ? *bic : query(ptr);

if (!(info.attr & BlkAttr.APPENDABLE))
// not appendable
return null;

assert(info.base); // sanity check.
if (!bic && !atomic)
// cache the lookup for next time
__insertBlkInfoCache(info, null);

auto usedSize = atomic ? __arrayAllocLengthAtomic(info) : __arrayAllocLength(info);
return __arrayStart(info)[0 .. usedSize];
}

/* NOTE about @trusted in these functions:
* These functions do a lot of pointer manipulation, and has writeable
* access to BlkInfo which is used to interface with other parts of the GC,
* including the block metadata and block cache. Marking these functions as
* @safe would mean that any modification of BlkInfo fields should be
* considered @safe, which is not the case. For example, it would be
* perfectly legal to change the BlkInfo size to some huge number, and then
* store it in the block cache to blow up later. The utility functions
* count on the BlkInfo representing the correct information inside the GC.
*
* In order to mark these @safe, we would need a BlkInfo that has
* restrictive access (i.e. @system only) to the information inside the
* BlkInfo. Until then any use of these structures needs to be @trusted,
* and therefore the entire functions are @trusted. The API is still @safe
* because the information is stored and looked up by the GC, not the
* caller.
*/
bool expandArrayUsed(void[] slice, size_t newUsed, bool atomic = false) nothrow @trusted
{
import core.internal.gc.blockmeta;
import core.internal.gc.blkcache;
import core.internal.array.utils;

if (newUsed < slice.length)
// cannot "expand" by shrinking.
return false;

// lookup the block info, using the cache if possible
auto bic = atomic ? null : __getBlkInfo(slice.ptr);
auto info = bic ? *bic : query(slice.ptr);

if (!(info.attr & BlkAttr.APPENDABLE))
// not appendable
return false;

assert(info.base); // sanity check.

immutable offset = slice.ptr - __arrayStart(info);
newUsed += offset;
auto existingUsed = slice.length + offset;

size_t typeInfoSize = (info.attr & BlkAttr.STRUCTFINAL) ? size_t.sizeof : 0;
if (__setArrayAllocLengthImpl(info, offset + newUsed, atomic, existingUsed, typeInfoSize))
{
// could expand without extending
if (!bic && !atomic)
// cache the lookup for next time
__insertBlkInfoCache(info, null);
return true;
}

// if we got here, just setting the used size did not work.
if (info.size < PAGESIZE)
// nothing else we can do
return false;

// try extending the block into subsequent pages.
immutable requiredExtension = newUsed - info.size - LARGEPAD;
auto extendedSize = extend(info.base, requiredExtension, requiredExtension, null);
if (extendedSize == 0)
// could not extend, can't satisfy the request
return false;

info.size = extendedSize;
if (bic)
*bic = info;
else if (!atomic)
__insertBlkInfoCache(info, null);

// this should always work.
return __setArrayAllocLengthImpl(info, newUsed, atomic, existingUsed, typeInfoSize);
}

bool shrinkArrayUsed(void[] slice, size_t existingUsed, bool atomic = false) nothrow
{
import core.internal.gc.blockmeta;
import core.internal.gc.blkcache;
import core.internal.array.utils;

if (existingUsed < slice.length)
// cannot "shrink" by growing.
return false;

// lookup the block info, using the cache if possible.
auto bic = atomic ? null : __getBlkInfo(slice.ptr);
auto info = bic ? *bic : query(slice.ptr);

if (!(info.attr & BlkAttr.APPENDABLE))
// not appendable
return false;

assert(info.base); // sanity check

immutable offset = slice.ptr - __arrayStart(info);
existingUsed += offset;
auto newUsed = slice.length + offset;

size_t typeInfoSize = (info.attr & BlkAttr.STRUCTFINAL) ? size_t.sizeof : 0;

if (__setArrayAllocLengthImpl(info, newUsed, atomic, existingUsed, typeInfoSize))
{
if (!bic && !atomic)
__insertBlkInfoCache(info, null);
return true;
}

return false;
}

size_t reserveArrayCapacity(void[] slice, size_t request, bool atomic = false) nothrow @trusted
{
import core.internal.gc.blockmeta;
import core.internal.gc.blkcache;
import core.internal.array.utils;

// lookup the block info, using the cache if possible.
auto bic = atomic ? null : __getBlkInfo(slice.ptr);
auto info = bic ? *bic : query(slice.ptr);

if (!(info.attr & BlkAttr.APPENDABLE))
// not appendable
return 0;

assert(info.base); // sanity check

immutable offset = slice.ptr - __arrayStart(info);
request += offset;
auto existingUsed = slice.length + offset;

// make sure this slice ends at the used space
auto blockUsed = atomic ? __arrayAllocLengthAtomic(info) : __arrayAllocLength(info);
if (existingUsed != blockUsed)
// not an expandable slice.
return 0;

// see if the capacity can contain the existing data
auto existingCapacity = __arrayAllocCapacity(info);
if (existingCapacity < request)
{
if (info.size < PAGESIZE)
// no possibility to extend
return 0;

immutable requiredExtension = request - existingCapacity;
auto extendedSize = extend(info.base, requiredExtension, requiredExtension, null);
if (extendedSize == 0)
// could not extend, can't satisfy the request
return 0;

info.size = extendedSize;

// update the block info cache if it was used
if (bic)
*bic = info;
else if (!atomic)
__insertBlkInfoCache(info, null);

existingCapacity = __arrayAllocCapacity(info);
}

return existingCapacity - offset;
}
}


Expand Down
20 changes: 20 additions & 0 deletions druntime/src/core/internal/gc/impl/manual/gc.d
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,24 @@ class ManualGC : GC
{
return typeof(return).init;
}

void[] getArrayUsed(void *ptr, bool atomic = false) nothrow
{
return null;
}

bool expandArrayUsed(void[] slice, size_t newUsed, bool atomic = false) nothrow @safe
{
return false;
}

size_t reserveArrayCapacity(void[] slice, size_t request, bool atomic = false) nothrow @safe
{
return 0;
}

bool shrinkArrayUsed(void[] slice, size_t existingUsed, bool atomic = false) nothrow
{
return false;
}
}
20 changes: 20 additions & 0 deletions druntime/src/core/internal/gc/impl/proto/gc.d
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,24 @@ class ProtoGC : GC
{
return stats().allocatedInCurrentThread;
}

void[] getArrayUsed(void *ptr, bool atomic = false) nothrow
{
return null;
}

bool expandArrayUsed(void[] slice, size_t newUsed, bool atomic = false) nothrow @safe
{
return false;
}

size_t reserveArrayCapacity(void[] slice, size_t request, bool atomic = false) nothrow @safe
{
return 0;
}

bool shrinkArrayUsed(void[] slice, size_t existingUsed, bool atomic = false) nothrow
{
return false;
}
}
20 changes: 20 additions & 0 deletions druntime/src/core/internal/gc/proxy.d
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,26 @@ extern (C)
return instance.allocatedInCurrentThread();
}

void[] gc_getArrayUsed(void *ptr, bool atomic) nothrow
{
return instance.getArrayUsed( ptr, atomic );
}

bool gc_expandArrayUsed(void[] slice, size_t newUsed, bool atomic) nothrow
{
return instance.expandArrayUsed( slice, newUsed, atomic );
}

size_t gc_reserveArrayCapacity(void[] slice, size_t request, bool atomic) nothrow
{
return instance.reserveArrayCapacity( slice, request, atomic );
}

bool gc_shrinkArrayUsed(void[] slice, size_t existingUsed, bool atomic) nothrow
{
return instance.shrinkArrayUsed( slice, existingUsed, atomic );
}

GC gc_getProxy() nothrow
{
return instance;
Expand Down
Loading

0 comments on commit b88ffc5

Please sign in to comment.