Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update GC interface to contain array management functions. #20608

Merged
merged 3 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Comment on lines +219 to +221
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation generator should have yelled at you here. = not -

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* 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;
Comment on lines +227 to +243
Copy link
Member

@Geod24 Geod24 Dec 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally can we format those a bit better ? A one-liner title / summary at the top, followed by an extended description, and an empty line between sections ? In this case:

Suggested change
/**
* 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;
/**
* Expand the array capacity in place.
*
* 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 the array may be shared between threads, and this
* operation should be done atomically.
*
* Returns:
* Resulting capacity size or 0 if the operation could not be performed.
*/

Also made some minor edits.


/**
* 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
schveiguy marked this conversation as resolved.
Show resolved Hide resolved
{
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
schveiguy marked this conversation as resolved.
Show resolved Hide resolved
{
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
Loading