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

[RuntimeAsync] Keep LoaderAllocator objects alive from continuations #2812

Merged

Conversation

jakobbotsch
Copy link
Member

@jakobbotsch jakobbotsch commented Nov 26, 2024

Add logic to make sure we keep the exposed loader allocator object alive when a runtime async function suspends. This adds two new JIT helpers CORINFO_HELP_ALLOC_CONTINUATION_CLASS and
CORINFO_HELP_ALLOC_CONTINUATION_METHOD that store the LoaderAllocator associated with a class or method into the allocated continuation before returning it back to the JIT.

There are a few cases where the JIT has to call the new helpers:

  1. Any function loaded into a collectible ALC must keep the LoaderAllocator associated with the method being compiled alive
  2. A shared generic function taking a MethodDesc* as the generic context must keep the LoaderAllocator assocated with that MethodDesc* alive
  3. A shared generic function taking a MethodTable* as the generic context must similarly keep that one alive.

Fix #2759

Add logic to make sure we keep the exposed loader allocator object alive
when a runtime async function suspends. This adds two new JIT helpers
`CORINFO_HELP_ALLOC_CONTINUATION_CLASS` and
`CORINFO_HELP_ALLOC_CONTINUATION_METHOD` that store the
`LoaderAllocator` associated with a class or method into the allocated
continuation before returning it back to the JIT.

There are a few cases where the JIT has to call the new helpers:
1. Any function loaded into a collectible ALC must keep the
   `LoaderAllocator` associated with the method being compiled alive
2. A shared generic function taking a `MethodDesc*` as the generic
   context must keep the `LoaderAllocator` assocated with that
   `MethodDesc*` alive
3. A shared generic function taking a `MethodTable*` as the generic
   context must similarly keep that one alive.
Comment on lines 1115 to 1117
// TODO: Do we also need to keep the method handle's loader allocator
// alive here in case the shared generic method is inside a collectible
// ALC?
Copy link
Member Author

Choose a reason for hiding this comment

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

@davidwrighton @VSadov Any idea? Is it sufficient to keep the LoaderAllocator associated with the MethodTable* generic context alive here? I see that MethodDesc::GetLoaderAllocator gets it via GetLoaderModule()->GetLoaderAllocator(), but MethodDesc::GetLoaderModule() seemingly may not always match GetMethodTable()->GetLoaderModule() based on this code:

https://github.com/dotnet/runtime/blob/0170d78578a04206542f32e4db3eaa88a5384973/src/coreclr/vm/method.cpp#L1479-L1492

Copy link
Member

Choose a reason for hiding this comment

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

Is it sufficient to keep the LoaderAllocator associated with the MethodTable* generic context alive here?

I do not think so. You can have void MyMethod<T>() instantiated as void MyMethod<UnloadableType>. The MethodTable won't know anything about the UnloadableType.

Copy link
Member

Choose a reason for hiding this comment

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

Ignore my comment. I have missed that this path is for the MethodTable* generic context case.

It is sufficient to keep the LoaderAllocator associated with the MethodTable* generic context alive here.

@jakobbotsch jakobbotsch marked this pull request as ready for review November 26, 2024 14:37
@jakobbotsch
Copy link
Member Author

PTAL @VSadov @davidwrighton

@jakobbotsch jakobbotsch changed the title Keep LoaderAllocator objects alive from continuations [RuntimeAsync] Keep LoaderAllocator objects alive from continuations Nov 27, 2024
@jakobbotsch jakobbotsch added the area-async Runtime generate async state machines label Nov 27, 2024
GenTree* dataSizeNode = m_comp->gtNewIconNode((ssize_t)dataSize, TYP_I_IMPL);
// If VM requests that we report the method handle, or if we have a shared generic context method handle
// that is live here, then we need to call a different helper.
GenTree* methodHandleArg = nullptr;
Copy link
Member

Choose a reason for hiding this comment

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

If I read this correctly we will use an alternative continuation allocators unconditionally, if we have generic contexts and allocator will sort it out if there is an object to hold on to.

I guess we cannot know statically if we will have loader allocator or not. Not a big deal, I think.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I think all shared generics need to go through these new helpers. We cannot know if the generic context was instantiated with an unloadable type during JIT.

For the continuationsNeedMethodHandle case I assume the loader allocator will always be non-null (since it's only set when the ALC is collectible).

@VSadov
Copy link
Member

VSadov commented Nov 27, 2024

Should we have a test, at least for the continuationsNeedMethodHandle scenario?

We are probably not ready to stress-test this for GC holes, but maybe for code coverage - to make sure the alternative code paths are functional.

Copy link
Member

@VSadov VSadov left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks!
If not too involved, perhaps add a test.

@jakobbotsch
Copy link
Member Author

jakobbotsch commented Nov 28, 2024

Should we have a test, at least for the continuationsNeedMethodHandle scenario?

The shared-generic test does exercise the two new helpers. I can add one that uses collectible ALCs to also exercise the collectible ALC one.

@jakobbotsch
Copy link
Member Author

I pushed a test and verified that it fails without the change (with AV from trying to resume in unloaded code) and succeeds with it.

@jakobbotsch jakobbotsch merged commit 4daa761 into dotnet:feature/async2-experiment Nov 28, 2024
1 of 7 checks passed
@jakobbotsch jakobbotsch deleted the root-loader-allocator branch November 28, 2024 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-async Runtime generate async state machines
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants