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

Finalization support for large allocs. #310

Merged
merged 19 commits into from
Oct 13, 2023

Conversation

dsm9000
Copy link
Contributor

@dsm9000 dsm9000 commented Oct 12, 2023

Note: the extent side support split into #311

sdlib/d/gc/tcache.d Outdated Show resolved Hide resolved

unittest finalization {
// Faux destructor which simply records most recent kill:
static size_t lastKilledUsedCap = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

capacity

Copy link
Contributor

Choose a reason for hiding this comment

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

A cap is how many things you do before you stop and/or saturate.

sdlib/d/gc/tcache.d Outdated Show resolved Hide resolved
Comment on lines 380 to 401
void freeImpl(PageDescriptor pd, void* ptr) {
pd.arena.free(emap, pd, ptr);
}

void destroyImpl(PageDescriptor pd, void* ptr) {
auto e = pd.extent;
if (e is null) {
return;
}

if (pd.isSlab()) {
// Slab is not yet supported
return;
}

if (e.finalizer is null) {
return;
}

(cast(void function(void* body, size_t size)) e.finalizer)(
ptr, e.usedCapacity);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why this is destroyImpl rather than destroy itself. It has one callsite. Is it a generally useful abstraction?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@deadalnix it lets us avoid having to find the pd twice (destroy and then in free)

Copy link
Contributor

Choose a reason for hiding this comment

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

I still don't get why this isn't in the destroy method. What is the point of having two methods?

@@ -347,6 +377,29 @@ private:
return true;
}

void freeImpl(PageDescriptor pd, void* ptr) {
pd.arena.free(emap, pd, ptr);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure this is such a great idea, as it hides the fact this is using the emap. But the emap need to be initialized before it is used.

@@ -127,6 +173,7 @@ public:
if (isLargeSize(size)) {
auto npd = getPageDescriptor(newPtr);
npd.extent.setUsedCapacity(size);
npd.extent.setFinalizer(finalizer);
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't do finalization magic in realloc. this is not necessary at this time, and this is untested anyways.

freeImpl(pd, ptr);
}

bool setFinalizer(void* ptr, void* finalizer) {
Copy link
Contributor

Choose a reason for hiding this comment

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

You can't add a finalizer ex post facto because you don't know you have the room (you have it for large, but you can't know that from ptr).

You want to allocate with a finalizer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

https://github.com/snazzy-d/sdc/pull/311/files#diff-14fa859f3f1ce9782b36cdb23977f43bbae0b791fac441958f71ca167c5f52fbR403 is how I propose to handle it. idea is that we can set finalizer on any small allocs which are appendable. we'll have a allocFinalizable which calls allocAppendable and then sets the finalizer.

static size_t lastKilledUsedCapacity = 0;
static void* lastKilledAddress;
static uint destroyCount = 0;
static void destruct(void* body, size_t size) {
Copy link
Contributor

Choose a reason for hiding this comment

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

body used to be a keyword, and it poorly highlighted. ptr is better, this what it is everywhere else anyway.


auto e = pd.extent;
// Slab is not yet supported
if (e is null || pd.isSlab() || e.finalizer is null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Invert the condition so you don't need to call the same function twice.

return;
}

(cast(void function(void* ptr, size_t size)) e.finalizer)(
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't the accessor do the cast?

Copy link
Contributor Author

@dsm9000 dsm9000 Oct 13, 2023

Choose a reason for hiding this comment

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

Tried this earlier; any attempt to call a function pointer without a cast, e.g.

alias Finalizer = void function(void* ptr, size_t size);

e.finalizer(ptr, e.usedCapacity); in destroy(),

void* allocAppendable(size_t size, bool containsPointers, Finalizer finalizer = null)
etc.

... and using this as type for wherever finalizers are seen, bombs with core.exception.AssertError@src/d/common/type.d(375): Not a function. on sdc, with kilometres of llvm soup.

Edit: does work if not using alias.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Comment on lines 372 to 382
@property
void function(void* ptr, size_t size) finalizer() {
assert(isLarge(), "Finalizer accessed on non large!");
return cast(void function(void* ptr, size_t size))
_metadata.largeData.finalizer;
}

void setFinalizer(void* finalizer) {
assert(isLarge(), "Cannot set finalizer on a slab alloc!");
_metadata.largeData.finalizer = finalizer;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand what's up here.

It's like you put one foot forward and then don't realize that the second foot can move forward too.

If finalizer gives you a function, then setFinalizer should accept a fucntion, and the field itself almost certainly would benefit from being a function.

@@ -1,5 +1,6 @@
module d.gc.tcache;

import d.gc.extent;
Copy link
Contributor

Choose a reason for hiding this comment

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

This for one alias? A strong sign the alias isn't defined where it should be.

@deadalnix deadalnix merged commit 6317721 into snazzy-d:master Oct 13, 2023
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants