-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Enable devirtualization in more scenarios on crossgen2 #53567
Enable devirtualization in more scenarios on crossgen2 #53567
Conversation
This is a difference between CoreCLR and the managed type system - the result of interface method resolution is going to be a canonical method on the CoreCLR side, but exact on the managed side. So this implementation is slightly different from how it looks like on the VM side. We slightly touched on the difference in dotnet#45744 but there was more to it.
… scenarios in which it is used
…ion override behavior - Enable use of these fixups for devirtualization scenarios - when --verify-type-and-field-layout is specified, the verification variant of the fixup is used - Update documentation and r2rdump utility
…that we won't devirtualize outside of the version bubble
… ok though, as I think type forwarders can make this R2R unsafe
…mentation and instantiated tables as the owner type is sufficient for now. - Also, set resolvedTokenDevirtualizedUnboxedMethod correctly
Use correct token
cced08c
to
e923066
Compare
…e type as a MethodImpl override
… it is used in such a way
… been determined via analysis
…pe signature generation, use it in the InstrumentationDataTableNode
…de on non-locally defined interfaces. - decl vs impl check done on exact result, not on canonicalized result
…e are implemented in crossgen2 - Provide devirtualization details for all possible ways devirt can fail in crossgen2 (This is more detailed than the crossgen output)
@dotnet/crossgen-contrib |
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.
Looked over the jit changes, which generally look good, and skimmed the rest, much of which I'm not going to be able to review effectively.
This will need a new jit guid.
There will need to be some updates to superpmi's handling of resolveVirtualMethod
; you can likely use the existing helpers for resolved tokens (eg SpmiRecordsHelper::StoreAgnostic_CORINFO_RESOLVED_TOKENout
) Seems like you might need a novel combo of the in and out helpers though, as in this case all the fields of the resolved token are outputs...?
src/coreclr/jit/fginline.cpp
Outdated
@@ -731,7 +731,7 @@ Compiler::fgWalkResult Compiler::fgLateDevirtualization(GenTree** pTree, fgWalkD | |||
CORINFO_CONTEXT_HANDLE context = nullptr; | |||
const bool isLateDevirtualization = true; | |||
bool explicitTailCall = (call->AsCall()->gtCallMoreFlags & GTF_CALL_M_EXPLICIT_TAILCALL) != 0; | |||
comp->impDevirtualizeCall(call, &method, &methodFlags, &context, nullptr, isLateDevirtualization, | |||
comp->impDevirtualizeCall(call, NULL, &method, &methodFlags, &context, nullptr, isLateDevirtualization, |
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.
comp->impDevirtualizeCall(call, NULL, &method, &methodFlags, &context, nullptr, isLateDevirtualization, | |
comp->impDevirtualizeCall(call, nullptr, &method, &methodFlags, &context, nullptr, isLateDevirtualization, |
@@ -20683,6 +20684,7 @@ bool Compiler::IsMathIntrinsic(GenTree* tree) | |||
// is not exactly known, and there is a plausible guess for the type. | |||
// | |||
void Compiler::impDevirtualizeCall(GenTreeCall* call, | |||
CORINFO_RESOLVED_TOKEN* pResolvedToken, |
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 you add a comment for the new arg to the block just above?
src/coreclr/jit/importer.cpp
Outdated
dvInfo.objClass = likelyClass; | ||
dvInfo.context = *pContextHandle; | ||
dvInfo.exactContext = *pContextHandle; | ||
dvInfo.pResolvedTokenVirtualMethod = NULL; |
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.
dvInfo.pResolvedTokenVirtualMethod = NULL; | |
dvInfo.pResolvedTokenVirtualMethod = nullptr; |
// jit interface logic after the logic that executes here. | ||
// | ||
// ImplType checking | ||
// 1. At all times the metadata definition of the implmenetation type must version with the application. |
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.
// 1. At all times the metadata definition of the implmenetation type must version with the application. | |
// 1. At all times the metadata definition of the implementation type must version with the application. |
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.
Looks beautiful to me, thank you!
sb.Append(":"); | ||
sb.Append(nameMangler.GetMangledTypeName(_implType)); | ||
sb.Append(":"); | ||
_implMethod.AppendMangledName(nameMangler, sb); |
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.
Should we apply a null check before calling _implMethod.AppendMangledName
?
if (result != 0) | ||
return result; | ||
|
||
return _implMethod.CompareTo(otherNode._implMethod, comparer); |
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.
dtto - null check on _implMethod
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, one more fun here that _implMethod
and otherNode._implMethod
being both null is probably a valid match.
|
||
public bool Equals(VirtualResolutionFixupSignatureFixupKey other) | ||
{ | ||
return FixupKind == other.FixupKind && DeclMethod.Equals(other.DeclMethod) && ImplType == other.ImplType && ImplMethod.Equals(other.ImplMethod); |
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 we need the null check here as well?
@@ -135,6 +135,11 @@ private void CreateNodeCaches() | |||
); | |||
}); | |||
|
|||
_virtualFunctionOverrideCache = new NodeCache<VirtualResolutionFixupSignature, ISymbolNode>(key => | |||
{ | |||
return new PrecodeHelperImport(_codegenNodeFactory,key); |
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.
Nit - missing space after the comma
@@ -399,6 +416,93 @@ private bool ComputeTypeVersionsWithCode(TypeDesc type) | |||
return ComputeInstantiationVersionsWithCode(type.Instantiation, type); | |||
} | |||
|
|||
private bool ComputeTypeReferenceVersionsWithCode(TypeDesc type) | |||
{ | |||
switch(type.Category) |
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.
Could we use type.IsPrimitive
here like we do below in ComputeInstantiationVersionsWithCode
? In fact, if this smallest set of "absolutely non-versionable types" means the same thing in both methods, could we consider making a helper method for it, something like IsPrimitiveObjectOrString
?
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.
Its not quite the same as ComputeInstantiationVersionsWithCode. This one includes Void, but I've used the IsPrimitive and IsVoid predicates, and added a comment.
@@ -247,6 +247,24 @@ public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) | |||
sb.Append("; UNBOXING"); | |||
} | |||
|
|||
public override string ToString() | |||
{ | |||
StringBuilder debuggingName = new StringBuilder(); |
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.
Could we implement ToString
by just calling AppendMangledName
on a local Utf8StringBuilder and returning its ToString
- i.o.w. is there any fundamental reason for the duplication?
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.
Its not quite the same, in particular, ToString() doesn't have access to a NameMangler.
if (fixupType == ReadyToRunFixupKind.Check_TypeLayout) | ||
builder.Append(" (Check_VirtualFunctionOverride)"); | ||
else | ||
builder.Append(" (Verify_VirtualFunctionOverride)"); |
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.
Nit - today R2RDump uses the "macro" casing for fixup type names. I don't have a strong opinion about this, perhaps it's better to stick with the constant names but at some point we should probably convert the rest to be consistent.
} | ||
|
||
SString fatalErrorString; | ||
fatalErrorString.Printf(W("Verify_VirtualFunctionOverride Decl Method '%s' on type '%s' is '%s'(actual) instead of expected '%s'(from compiler)"), |
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.
Beautiful, thank you for providing the rich diagnostic info upon failure, that's super useful for analyzing lab failures!
uint32_t updatedModuleIndex; | ||
IfFailThrow(sig.GetData(&updatedModuleIndex)); | ||
#ifdef FEATURE_MULTICOREJIT | ||
if (pZapSigContext->externalTokens == ZapSig::MulticoreJitTokens) |
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.
Just for my education - what is this used for? Is that something we should take into account in Crossgen2 itself?
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.
MulticoreJitTokens is used for the MulticoreJit feature, which is an odd profile guided optimization feature that works on a local machine. We don't need to worry about it in crossgen2.
public int _v1; | ||
public int _v2; | ||
|
||
public GenericClass() |
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 can find the original issue but it would be extremely helpful if you could add at least a three-line comment describing the way the test is supposed to work and how it detects bad behavior - in my recent work on fixing the various regression tests with Crossgen2 I've always had to spend quite a bit of time poring over the particular test to understand what it's supposed to be doing especially when it's exercising some subtle behavioral differences.
- Fix variant interface dispatch bug where the variant interface is implemented by another interface associated with the type. - Handle default interface methods - Except for variant dispatch, detect those, and fail the devirtualization
{ | ||
/// <summary> | ||
/// Gets the method whose body this <see cref="MethodIL"/> represents. | ||
/// Gets the method whose body this <see cref=""/> represents. |
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.
/// Gets the method whose body this <see cref=""/> represents. | |
/// Gets the method whose body this <see cref="MethodILScope"/> represents. |
using Internal.TypeSystem; | ||
|
||
using CORINFO_DEVIRTUALIZATION_DETAIL = Internal.JitInterface.CORINFO_DEVIRTUALIZATION_DETAIL; |
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 is technically a layering violation since DevirtualizationManager is the general purpose component that can be used with different codegen backends.
But I don't know if I really want to ask you to create a separate enum.
Fortunately this will not be a build break in NativeAOT because we already violated the layering (the ObjWriter uses JitInterface constants for stuff).
@@ -53,16 +54,16 @@ public virtual bool IsEffectivelySealed(MethodDesc method) | |||
/// Note that if <paramref name="implType"/> is a value type, the result of the resolution | |||
/// might have to be treated as an unboxing thunk by the caller. | |||
/// </remarks> | |||
public MethodDesc ResolveVirtualMethod(MethodDesc declMethod, TypeDesc implType) | |||
public MethodDesc ResolveVirtualMethod(MethodDesc declMethod, TypeDesc implType, ref CORINFO_DEVIRTUALIZATION_DETAIL devirtualizationDetail) |
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.
Should the new parameter be out
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.
Yes. Turns out not doing this was masking a bug in the devirtualilzation algorithm
{ | ||
// If we devirtualized into a default interface method on a generic type, we should actually return an | ||
// instantiating stub but this is not happening. | ||
// Making this work is tracked by https://github.com/dotnet/runtime/issues/9588 |
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 will potentially be a problem because we don't have instantiating stubs in the general type system, same as we don't have unboxing stubs.
We have JitInterface-local unboxing stubs, and I think that will be the solution we'll want to use for instantiating stubs as well, but we will not have access to those from here (we're not in JitInterface).
I don't have a solution, just pointing out if you have thoughts in that area.
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.
Well, its the same problem as devirtualizing to a struct method, which is something that @AndyAyersMS has actually made work fairly recently. I suspect that the comment is somewhat out of date, but I felt it was useful to have a matching comments in both crossgen2 and coreclr vm.
result.tokenContext = jitInterface.contextFromMethod(method); | ||
result.token = methodWithToken.Token.Token; | ||
if (methodWithToken.Token.TokenType != CorTokenType.mdtMethodDef) | ||
throw new NotSupportedException(); |
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 will tear down the compiler process. Do we want that to happen? Consider RequiresRuntimeJitException (not fatal) or CodeGenerationFailedException (that one can be fatal if we're not in the resilient mode).
@@ -11,7 +11,10 @@ | |||
|
|||
namespace Internal.IL | |||
{ | |||
public sealed partial class EcmaMethodIL : MethodIL | |||
// Marker interface implemented by EcmaMethodIL and EcmaMethodILScope | |||
public interface IEcmaMethodIL { } |
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.
Nit: might be faster to just do the two is
checks instead of testing for the interface. Both EcmaMethodIL and EcmaMethodILScope are sealed so an is
is quick.
…nning different tasks during outerloop
…anonicallly compatible vtable devirtualization, and go back to normal yml for the crossgen2 outerloop
Change dotnet#53567 introduced new functionality to the JIT-EE resolveVirtualMethod() API. This fixes the SuperPMI implementation in the case where the API returns `false`, where some data fields are not initialized. Fixes crossgen2 SuperPMI collections. Fixes dotnet#54310
Address deficiencies in current devirtualization infrastructure
READYTORUN_FIXUP_Check_VirtualFunctionOverride
andREADYTORUN_FIXUP_Verify_VirtualFunctionOverride
) these are used to validate that the behavior of the runtime and crossgen2 compiler is equivalent for a virtual resolution eventREADYTORUN_FIXUP_Check_VirtualFunctionOverride
will ensure that the virtual resolution decision is the same at crossgen2 time and runtime, and if the decision differs, any generated code affected by the decision will not be used.READYTORUN_FIXUP_Verify_VirtualFunctionOverride
will perform the same checks asREADYTORUN_FIXUP_Check_VirtualFunctionOverride
, but if it fails the check, the process will be terminated with a fail-fast. It is intended for use under the--verify-type-and-field-layout
stress mode.READYTORUN_FIXUP_Verify_VirtualFunctionOverride
is actually generated, and it is only generated when using the--verify-type-and-field-layout
switch to crossgen2. Future work will identify if there are scenarios where we need to generate theREADYTORUN_FIXUP_Check_VirtualFunctionOverride
flag. One area of possible concern is around covariant returns, another is around handling of type equivalence.ReadyToRunMethodSigFlags
.READYTORUN_METHOD_SIG_UpdateContext
will allow the method signature to internally specify the assembly which is associated with the method token, instead of relying on the ambient context.VersionsWithType
check on the implementation type, to a newVersionsWithTypeReference
check which can be used to validate that the type can be referred to, in combination with usingVersionsWithType
on the type definition.VersionsWithCode
, and it just needs a relationship to version matching code.resolveVirtualMethod
generate theCORINFO_RESOLVED_TOKEN
structures for the jit