Skip to content

Conversation

jakobbotsch
Copy link
Member

Add ability for the VM to dynamically create continuation layout types and for the JIT to request such types to be created.

Add ability for the VM to dynamically create continuation layout types
and for the JIT to request such types to be created.
@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Oct 4, 2025
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.


AllocMemTracker amTracker;
// TODO: table to share/deduplicate these
result = (CORINFO_CLASS_HANDLE)m_pMethodBeingCompiled->GetModule()->CreateContinuationMethodTable((unsigned)dataSize, objRefs, dataOffsets, &amTracker);
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member Author

@jakobbotsch jakobbotsch Oct 6, 2025

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?

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

Choose a reason for hiding this comment

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

The parts of the system that will likely get in trouble are if we (via reflection) get a System.Type of one of these, and the various debugger and profiler apis which examine the heap and various values. Please get @tommcdon or @noahfalk to take a look at this before we signoff.

Copy link
Member Author

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.

inline BOOL IsContinuation()
{
LIMITED_METHOD_DAC_CONTRACT;
return GetFlag(enum_flag_IsContinuationType);
Copy link
Member

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?

Copy link
Member Author

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));
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member Author

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.

LoaderAllocator* allocator = m_pMethodBeingCompiled->GetLoaderAllocator();
AsyncContinuationsManager* asyncConts = allocator->GetAsyncContinuationsManager();
AllocMemTracker amTracker;
result = (CORINFO_CLASS_HANDLE)asyncConts->LookupOrCreateContinuationMethodTable((unsigned)dataSize, objRefs, dataOffsets, m_pMethodBeingCompiled, &amTracker);
Copy link
Member Author

@jakobbotsch jakobbotsch Oct 7, 2025

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.

Copy link
Member

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;
Copy link
Member Author

@jakobbotsch jakobbotsch Oct 7, 2025

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.


LoaderAllocator* allocator = m_pMethodBeingCompiled->GetLoaderAllocator();
AsyncContinuationsManager* asyncConts = allocator->GetAsyncContinuationsManager();
AllocMemTracker amTracker;
Copy link
Member

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.

return (pCanonMT->GetClass() == pClass);
else
return (pCanonMT == this) || IsArray();
return (pCanonMT == this) || IsArray() || IsContinuation(); // TODO -- allocate an EEClass instead?
Copy link
Member

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.

@davidwrighton davidwrighton requested a review from noahfalk October 7, 2025 23:22
@davidwrighton davidwrighton requested a review from tommcdon October 7, 2025 23:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants