-
Notifications
You must be signed in to change notification settings - Fork 201
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
[Runtime Async] Add support for shared generics #2755
[Runtime Async] Add support for shared generics #2755
Conversation
src/coreclr/vm/jitinterface.cpp
Outdated
// TODO: Is the VM ok with this being null temporarily while the JIT is | ||
// restoring state? Should we instead store the generic context (and | ||
// perhaps "this") in a pre-agreed upon place, and then pass it here? | ||
pCode->EmitLDC(0); | ||
numArgs++; |
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.
Any idea about this? I can imagine GC reporting might not be happy about seeing the null generic context in case we suspend before its value has been restored from the continuation inside the target method.
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 there is also a GC hole with collectible assemblies we'll have to solve in some way: the continuation does not necessarily have anything keeping the loader allocator for the generic context alive. It seems like for shared generics we'll have to add the loader allocator's rooting object for the generic context arg into the array of GC objects in the continuation. Even for unshared code we have to do this when emitting code into a collectible assembly.
This seems like it will probably require a helper call in the suspension case to get the loader allocator from the generic context/target method. The other alternative is that GC scanning could iterate all continuations and report their target methods/generic contexts, but that does not seem like a good solution (we really do not want to have to iterate all continuations outside the standard optimized GC paths).
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.
Any idea about this? I can imagine GC reporting might not be happy about seeing the null generic context in case we suspend before its value has been restored from the continuation inside the target method.
I think having null temporarily should be ok. As long as continuation is still reachable, which it should be until we restore.
It may hit some asserts and they may need to be tweaked. Like in
// Handle the case where the method is a static shared generic method and we need to keep the type
// of the generic parameters alive
if (paramContextType == GENERIC_PARAM_CONTEXT_METHODDESC)
{
MethodDesc *pMDReal = dac_cast<PTR_MethodDesc>(pCF->GetParamTypeArg());
_ASSERTE((pMDReal != NULL) || !pCF->IsFrameless()); <===
if (pMDReal != NULL)
{
GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMDReal->GetLoaderAllocator());
}
}
else if (paramContextType == GENERIC_PARAM_CONTEXT_METHODTABLE)
{
MethodTable *pMTReal = dac_cast<PTR_MethodTable>(pCF->GetParamTypeArg());
_ASSERTE((pMTReal != NULL) || !pCF->IsFrameless()); <===
if (pMTReal != NULL)
{
GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMTReal->GetLoaderAllocator());
}
}
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.
The other alternative is that GC scanning could iterate all continuations and report their target methods/generic contexts, but that does not seem like a good solution (we really do not want to have to iterate all continuations outside the standard optimized GC paths).
Right. It would be very expensive in Gen0 collections. Gen0 must be able to scan only a small (young) subset of continuations.
Adding the exposed object to the continuation seems the right way. Even if we do not use it for restoring anything - just as a keep-alive reference.
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.
It may hit some asserts and they may need to be tweaked
I think I will switch this over to load the generic context in the caller once we have the JIT reporting back the continuation layout to the EE. For now we can probably keep it like this.
I'm going to look into addressing the GC holes in a follow-up.
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.
Allowing the context arg to be NULL is not a big deal as long as we are safely preserving the exposed LoaderAllocator object somewhere. We would have to remove those asserts, but as you can see the code already handles the param being marked as NULL. In fact, if we can guarantee that the LoaderAllocator exposed object is kept live, the runtime has no need to require that the context as reported by the CrawlFrame ever be set. (Please don't do this, as it reduces the quality of the call stack, but a temporary time as NULL is not a meaningful problem).
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.
In fact, if we can guarantee that the LoaderAllocator exposed object is kept live, the runtime has no need to require that the context as reported by the CrawlFrame ever be set.
I think the regular stackwalk needs to report the Loader from the context, as there may be no other reference to the exposed object. It seems hard to prove that for a live frame with unloadable context there is always something else that keeps the loader alive.
The suspended frames are not stackwalked though, thus we need to keep the exposed object reachable as long as continuation is reachable. Stashing a reference to the exposed object in the continuation should be sufficient, I think.
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.
Right, that was what I was thinking about. We need to hold onto the continuation until the context is set in the method frame (and the continuation needs to hold the exposed object), but otherwise, we don't need the thing to be non-null until general code execution begins.
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 that sounds ok to you both then I will just keep it this way -- I've updated the assert in GC about the non-null generic context to allow async2 methods, and removed the TODO comment here.
This is missing some tests for the non-void case, which needs a bunch more generic signatures (for locals and calls to functions like |
Shared generic async2 methods with return values work now. What remains is the async2 -> async1 thunks synthesized when awaiting an async1 method from an async2 method. However, I have noticed that the current Roslyn prototype does not seem to generate calls to those at all. In fact, adding an always-failing assert to |
Seems like the support has also broken some old tests, e.g. |
I think that came from the early misunderstanding that async1 awaits async2 via a thunk (because there is no other way), but async2 awaits async1 in a regular way (which works, but would not be binary-compatible if the callee is changed to be async2) I will look into fixing this in the Roslyn prototype. |
This passes all async tests now. Can you take a look @VSadov (and @davidwrighton, potentially)? |
DWORD cSig; | ||
if (FAILED(GetMDImport()->GetSigOfMethodDef(GetMemberDef(), &cSig, &pSigRaw))) | ||
{ | ||
_ASSERTE(!"Loaded MethodDesc should not fail to get signature"); |
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.
(This assert is copied from MethodDesc::GetSigFromMetadata
)
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.
Thanks!!
0a10db8
into
dotnet:feature/async2-experiment
In the end this wasn't that hard (easier than it would have been a year ago -- the IL emitter has had support for generic signatures added in the meantime). Maybe there are some missing cases. I've also left some TODOs around things I am not fully clear on.
cc @agocke @davidwrighton @VSadov