-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Support dynamically creating tailored continuation layouts #120411
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
base: main
Are you sure you want to change the base?
Support dynamically creating tailored continuation layouts #120411
Conversation
Add ability for the VM to dynamically create continuation layout types and for the JIT to request such types to be created.
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
src/coreclr/vm/jitinterface.cpp
Outdated
|
||
AllocMemTracker amTracker; | ||
// TODO: table to share/deduplicate these | ||
result = (CORINFO_CLASS_HANDLE)m_pMethodBeingCompiled->GetModule()->CreateContinuationMethodTable((unsigned)dataSize, objRefs, dataOffsets, &amTracker); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these should be created on the loader allocator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean place the method on another class? CreateContinuationMethodTable
uses the high frequency heap of the module's loader allocator for the allocation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definition module loader allocator != method loader allocator.
Consider MethodInNonCollectibleAssembly<CollectibleType>()
: This method instantiation should be allocated on collectible loader allocator. As written, it will be allocated on non-collectible loader allocator of MethodInNonCollectibleAssembly and the collectability of the instantiation argument won't be taken into account. We will have a memory leak.
Loader allocator is the scope of unloading. If you are allocating something collectible, loader allocator is the right place to attach it to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes a lot of sense. Will change this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do we expect pMT->GetLoaderModule()
/pMT->GetModule()
to return for these types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want these to return something meaningful:
pMT->GetLoaderModule()
should return m_pMethodBeingCompiled->GetLoaderModule()
pMT->GetModule()
is used as a context for interpreting metadata tokens and for reflection. I assume that these types won't have metadata token and won't be visible to reflection, so pMT->GetModule()
can be anything. Returning S.P.Corelib as pMT->GetModule()
would help with sharing.
Alternatively, we may want to make sure that these methods are never called on these types. If these methods are called on the continuation types, it is a likely a bug. It may take a while to chase down all places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, we may want to make sure that these methods are never called on these types. If these methods are called on the continuation types, it is a likely a bug. It may take a while to chase down all places.
That does seem best. For now I have set the fields as you suggested above, but will look into asserting that we don't access them for the continuation types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing that is already confused is SOS. !dumpobj
, !dumpmt
and !verifyheap
report invalid objects/MT when the continuation method tables are involved. Hopefully that's a simple check somewhere.
src/coreclr/vm/methodtable.h
Outdated
inline BOOL IsContinuation() | ||
{ | ||
LIMITED_METHOD_DAC_CONTRACT; | ||
return GetFlag(enum_flag_IsContinuationType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this check whether the parent type is a CLASS__CONTINUATION
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that seems better, no need to add this flag
Debug.Assert(continuationContext != null); | ||
return continuationContext; | ||
// Only the special continuation sub types are expected. | ||
Debug.Assert(GetType() != typeof(Continuation)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this type abstract instead of having this assert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We currently create an instance of Continuation
inside FinalizeTaskReturningThunk
, though I think with change and some more refactoring we can probably get rid of that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, we also create one in AwaitAwaiter
and UnsafeAwaitAwaiter
. That one is harder to remove.
Now that we store directly into the ThunkTask's result we do not need to allocate a tail continuation at all.
src/coreclr/vm/jitinterface.cpp
Outdated
LoaderAllocator* allocator = m_pMethodBeingCompiled->GetLoaderAllocator(); | ||
AsyncContinuationsManager* asyncConts = allocator->GetAsyncContinuationsManager(); | ||
AllocMemTracker amTracker; | ||
result = (CORINFO_CLASS_HANDLE)asyncConts->LookupOrCreateContinuationMethodTable((unsigned)dataSize, objRefs, dataOffsets, m_pMethodBeingCompiled, &amTracker); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strictly speaking we can delay this call until the continuation needs to be created. But in any case we need to persist some description of the continuation somewhere. However, we would be able to store that description compressed until it is needed.
I don't have good intuition about the expense of creating these or the number of them that we are going to be creating (likely a lot), so it is hard to know if this will be good enough or if it will show up on the radar once we start measuring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think this will be a problem (with de-duping of the layout in place). Also, the de-duping can be helped by sorting the fields, so that there is higher probability of finding dups.
public uint State; | ||
public delegate*<Continuation, ref byte, Continuation?> Resume; | ||
public CorInfoContinuationFlags Flags; | ||
public int State; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't actually need this field in Continuation
, however this part is padding on 64-bit anyway. For 32-bit we could save space in methods with 1 suspension point by not having it at all, but then we make the 64-bit continuations with State
fields in them larger. And doing one thing in 64-bit and another in 32-bit is also more complex than I figured was worth it.
src/coreclr/vm/jitinterface.cpp
Outdated
|
||
LoaderAllocator* allocator = m_pMethodBeingCompiled->GetLoaderAllocator(); | ||
AsyncContinuationsManager* asyncConts = allocator->GetAsyncContinuationsManager(); | ||
AllocMemTracker amTracker; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AllocMemTracker should be created around Create
part of LookupOrCreate.
src/coreclr/vm/methodtable.cpp
Outdated
return (pCanonMT->GetClass() == pClass); | ||
else | ||
return (pCanonMT == this) || IsArray(); | ||
return (pCanonMT == this) || IsArray() || IsContinuation(); // TODO -- allocate an EEClass instead? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allocate an EEClass instead?
I do not think we want to do that to minimize overhead. These fake types will be special.
Add ability for the VM to dynamically create continuation layout types and for the JIT to request such types to be created.