Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomáš Rylek <trylek@microsoft.com>2021-05-15 01:05:16 +0300
committerGitHub <noreply@github.com>2021-05-15 01:05:16 +0300
commit88211b9a1fe17819e9cabf78bfd86f6d62228034 (patch)
tree12c30921d0e42580e9a8b450782509561653a237 /src/coreclr/vm/methodtable.cpp
parent268d9516a8b7fd2a82d78b87687423ab5a30cae4 (diff)
Runtime support for static virtual interface methods (#52173)
This change implements initial CoreCLR runtime support for static virtual interface methods and adds over 1700 test cases covering various aspects of this new runtime feature. 1) In the JIT, we are relaxing the assumption that ".constrained" must be always followed by a "callvirt" by allowing two more IL instructions to be constrained, "call" and "ldftn". 2) In the JIT interface, we're adding bits of code to CEEInfo::getCallInfo to properly handle the new static virtual methods. We're extending constrained method resolution to cater for the static virtual method case in a new method "ResolveStaticVirtualMethod". 3) In our work on the implementation we found a pre-existing JIT interface issue - the interplay between getCallInfo and embedGenericHandle doesn't work well in case of constrained calls as the existing API surface doesn't provide any means for communicating compile-time resolution of the constraint from getCallInfo to embedGenericHandle. In this change we're working around this by deferring to runtime resolution of the constraint in the presence of shared generics. 4) In the method table builder, we're newly tracking a flag saying whether we're implementing an interface declaring static virtual methods (fHasVirtualStaticMethods) that is used to speed up the runtime checks by skipping irrelevant interfaces. 5) For Crossgen / Crossgen2, this change only adds minimalistic support in the form of bailing out in getCallInfo whenever we hit a call to a static virtual method, cancelling AOT compilation of the caller and deferring it to runtime JIT.
Diffstat (limited to 'src/coreclr/vm/methodtable.cpp')
-rw-r--r--src/coreclr/vm/methodtable.cpp272
1 files changed, 272 insertions, 0 deletions
diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp
index 9f112a4e8d4..58e64187bc4 100644
--- a/src/coreclr/vm/methodtable.cpp
+++ b/src/coreclr/vm/methodtable.cpp
@@ -5671,6 +5671,22 @@ void MethodTable::DoFullyLoad(Generics::RecursionGraph * const pVisited, const
}
else
{
+ // Validate implementation of virtual static methods on all implemented interfaces unless:
+ // 1) The type resides in the system module (System.Private.CoreLib); we own this module and ensure
+ // its consistency by other means not requiring runtime checks;
+ // 2) There are no virtual static methods defined on any of the interfaces implemented by this type;
+ // 3) The method is abstract in which case it's allowed to leave some virtual static methods unimplemented
+ // akin to equivalent behavior of virtual instance method overriding in abstract classes;
+ // 4) The type is a shared generic in which case we generally don't have enough information to perform
+ // the validation.
+ if (!GetModule()->IsSystem() &&
+ HasVirtualStaticMethods() &&
+ !IsAbstract() &&
+ !IsSharedByGenericInstantiations())
+ {
+ VerifyThatAllVirtualStaticMethodsAreImplemented();
+ }
+
// Finally, mark this method table as fully loaded
SetIsFullyLoaded();
}
@@ -9183,6 +9199,250 @@ MethodDesc *MethodTable::GetDefaultConstructor(BOOL forceBoxedEntryPoint /* = FA
}
//==========================================================================================
+// Finds the (non-unboxing) MethodDesc that implements the interface virtual static method pInterfaceMD.
+MethodDesc *
+MethodTable::ResolveVirtualStaticMethod(MethodTable* pInterfaceType, MethodDesc* pInterfaceMD, BOOL allowNullResult, BOOL checkDuplicates, BOOL allowVariantMatches)
+{
+ if (!pInterfaceMD->IsSharedByGenericMethodInstantiations() && !pInterfaceType->IsSharedByGenericInstantiations())
+ {
+ // Check that there is no implementation of the interface on this type which is the canonical interface for a shared generic. If so, that indicates that
+ // we cannot exactly compute a target method result, as even if there is an exact match in the type hierarchy
+ // it isn't guaranteed that we will always find the right result, as we may find a match on a base type when we should find the match
+ // on a more derived type.
+
+ MethodTable *pInterfaceTypeCanonical = pInterfaceType->GetCanonicalMethodTable();
+ bool canonicalEquivalentFound = false;
+ if (pInterfaceType != pInterfaceTypeCanonical)
+ {
+ InterfaceMapIterator it = IterateInterfaceMap();
+ while (it.Next())
+ {
+ if (pInterfaceTypeCanonical == it.GetInterface())
+ {
+ canonicalEquivalentFound = true;
+ break;
+ return NULL;
+ }
+ }
+ }
+
+ if (!canonicalEquivalentFound)
+ {
+ // Search for match on a per-level in the type hierarchy
+ for (MethodTable* pMT = this; pMT != nullptr; pMT = pMT->GetParentMethodTable())
+ {
+ MethodDesc* pMD = pMT->TryResolveVirtualStaticMethodOnThisType(pInterfaceType, pInterfaceMD, checkDuplicates);
+ if (pMD != nullptr)
+ {
+ return pMD;
+ }
+
+ if (pInterfaceType->HasVariance() || pInterfaceType->HasTypeEquivalence())
+ {
+ // Variant interface dispatch
+ MethodTable::InterfaceMapIterator it = IterateInterfaceMap();
+ while (it.Next())
+ {
+ if (it.GetInterface() == pInterfaceType)
+ {
+ // This is the variant interface check logic, skip the
+ continue;
+ }
+
+ if (!it.GetInterface()->HasSameTypeDefAs(pInterfaceType))
+ {
+ // Variance matches require a typedef match
+ // Equivalence isn't sufficient, and is uninteresting as equivalent interfaces cannot have static virtuals.
+ continue;
+ }
+
+ BOOL equivalentOrVariantCompatible;
+
+ if (allowVariantMatches)
+ {
+ equivalentOrVariantCompatible = it.GetInterface()->CanCastTo(pInterfaceType, NULL);
+ }
+ else
+ {
+ // When performing override checking to ensure that a concrete type is valid, require the implementation
+ // actually implement the exact or equivalent interface.
+ equivalentOrVariantCompatible = it.GetInterface()->IsEquivalentTo(pInterfaceType);
+ }
+
+ if (equivalentOrVariantCompatible)
+ {
+ // Variant or equivalent matching interface found
+ // Attempt to resolve on variance matched interface
+ pMD = pMT->TryResolveVirtualStaticMethodOnThisType(it.GetInterface(), pInterfaceMD, checkDuplicates);
+ if (pMD != nullptr)
+ {
+ return pMD;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (allowNullResult)
+ return NULL;
+ else
+ COMPlusThrow(kTypeLoadException, E_NOTIMPL);
+}
+
+//==========================================================================================
+// Try to locate the appropriate MethodImpl matching a given interface static virtual method.
+// Returns nullptr on failure.
+MethodDesc*
+MethodTable::TryResolveVirtualStaticMethodOnThisType(MethodTable* pInterfaceType, MethodDesc* pInterfaceMD, BOOL checkDuplicates)
+{
+ HRESULT hr = S_OK;
+ IMDInternalImport* pMDInternalImport = GetMDImport();
+ HENUMInternalMethodImplHolder hEnumMethodImpl(pMDInternalImport);
+ hr = hEnumMethodImpl.EnumMethodImplInitNoThrow(GetCl());
+ SigTypeContext sigTypeContext(this);
+
+ if (FAILED(hr))
+ {
+ COMPlusThrow(kTypeLoadException, hr);
+ }
+
+ // This gets the count out of the metadata interface.
+ uint32_t dwNumberMethodImpls = hEnumMethodImpl.EnumMethodImplGetCount();
+ MethodDesc* pPrevMethodImpl = nullptr;
+
+ // Iterate through each MethodImpl declared on this class
+ for (uint32_t i = 0; i < dwNumberMethodImpls; i++)
+ {
+ mdToken methodBody;
+ mdToken methodDecl;
+ hr = hEnumMethodImpl.EnumMethodImplNext(&methodBody, &methodDecl);
+ if (FAILED(hr))
+ {
+ COMPlusThrow(kTypeLoadException, hr);
+ }
+ if (hr == S_FALSE)
+ {
+ // In the odd case that the enumerator fails before we've reached the total reported
+ // entries, let's reset the count and just break out. (Should we throw?)
+ break;
+ }
+ mdToken tkParent;
+ hr = pMDInternalImport->GetParentToken(methodDecl, &tkParent);
+ if (FAILED(hr))
+ {
+ COMPlusThrow(kTypeLoadException, hr);
+ }
+ MethodTable *pInterfaceMT = ClassLoader::LoadTypeDefOrRefOrSpecThrowing(
+ GetModule(),
+ tkParent,
+ &sigTypeContext,
+ ClassLoader::ThrowIfNotFound,
+ ClassLoader::FailIfUninstDefOrRef,
+ ClassLoader::LoadTypes,
+ CLASS_LOAD_EXACTPARENTS)
+ .GetMethodTable();
+ if (pInterfaceMT != pInterfaceType)
+ {
+ continue;
+ }
+ MethodDesc *pMethodDecl = MemberLoader::GetMethodDescFromMemberDefOrRefOrSpec(
+ GetModule(),
+ methodDecl,
+ &sigTypeContext,
+ /* strictMetadataChecks */ FALSE,
+ /* allowInstParam */ FALSE,
+ /* owningTypeLoadLevel */ CLASS_LOAD_EXACTPARENTS);
+ if (pMethodDecl == nullptr)
+ {
+ COMPlusThrow(kTypeLoadException, E_FAIL);
+ }
+ if (!pMethodDecl->HasSameMethodDefAs(pInterfaceMD))
+ {
+ continue;
+ }
+
+ // Spec requires that all body token for MethodImpls that refer to static virtual implementation methods must be MethodDef tokens.
+ if (TypeFromToken(methodBody) != mdtMethodDef)
+ {
+ COMPlusThrow(kTypeLoadException, E_FAIL);
+ }
+
+ MethodDesc *pMethodImpl = MemberLoader::GetMethodDescFromMemberDefOrRefOrSpec(
+ GetModule(),
+ methodBody,
+ &sigTypeContext,
+ /* strictMetadataChecks */ FALSE,
+ /* allowInstParam */ FALSE,
+ /* owningTypeLoadLevel */ CLASS_LOAD_EXACTPARENTS);
+ if (pMethodImpl == nullptr)
+ {
+ COMPlusThrow(kTypeLoadException, E_FAIL);
+ }
+
+ // Spec requires that all body token for MethodImpls that refer to static virtual implementation methods must to methods
+ // defined on the same type that defines the MethodImpl
+ if (!HasSameTypeDefAs(pMethodImpl->GetMethodTable()))
+ {
+ COMPlusThrow(kTypeLoadException, E_FAIL);
+ }
+
+ if (pInterfaceMD->HasMethodInstantiation() || pMethodImpl->HasMethodInstantiation() || HasInstantiation())
+ {
+ pMethodImpl = pMethodImpl->FindOrCreateAssociatedMethodDesc(
+ pMethodImpl,
+ this,
+ FALSE,
+ pInterfaceMD->GetMethodInstantiation(),
+ /* allowInstParam */ FALSE,
+ /* forceRemotableMethod */ FALSE,
+ /* allowCreate */ TRUE,
+ /* level */ CLASS_LOAD_EXACTPARENTS);
+ }
+ if (pMethodImpl != nullptr)
+ {
+ if (!checkDuplicates)
+ {
+ return pMethodImpl;
+ }
+ if (pPrevMethodImpl != nullptr)
+ {
+ // Two MethodImpl records found for the same virtual static interface method
+ COMPlusThrow(kTypeLoadException, E_FAIL);
+ }
+ pPrevMethodImpl = pMethodImpl;
+ }
+ }
+
+ return pPrevMethodImpl;
+}
+
+void
+MethodTable::VerifyThatAllVirtualStaticMethodsAreImplemented()
+{
+ InterfaceMapIterator it = IterateInterfaceMap();
+ while (it.Next())
+ {
+ MethodTable *pInterfaceMT = it.GetInterface();
+ if (pInterfaceMT->HasVirtualStaticMethods())
+ {
+ for (MethodIterator it(pInterfaceMT); it.IsValid(); it.Next())
+ {
+ MethodDesc *pMD = it.GetMethodDesc();
+ if (pMD->IsVirtual() &&
+ pMD->IsStatic() &&
+ !ResolveVirtualStaticMethod(pInterfaceMT, pMD, /* allowNullResult */ TRUE, /* checkDuplicates */ TRUE, /* allowVariantMatches */ FALSE))
+ {
+ IMDInternalImport* pInternalImport = GetModule()->GetMDImport();
+ GetModule()->GetAssembly()->ThrowTypeLoadException(pInternalImport, GetCl(), pMD->GetName(), IDS_CLASSLOAD_STATICVIRTUAL_NOTIMPL);
+ }
+ }
+ }
+ }
+}
+
+//==========================================================================================
// Finds the (non-unboxing) MethodDesc that implements the interface method pInterfaceMD.
//
// Note our ability to resolve constraint methods is affected by the degree of code sharing we are
@@ -9201,6 +9461,18 @@ MethodTable::TryResolveConstraintMethodApprox(
GC_TRIGGERS;
} CONTRACTL_END;
+ if (pInterfaceMD->IsStatic())
+ {
+ _ASSERTE(!thInterfaceType.IsTypeDesc());
+ _ASSERTE(thInterfaceType.IsInterface());
+ MethodDesc *result = ResolveVirtualStaticMethod(thInterfaceType.GetMethodTable(), pInterfaceMD, pfForceUseRuntimeLookup != NULL);
+ if (result == NULL)
+ {
+ *pfForceUseRuntimeLookup = TRUE;
+ }
+ return result;
+ }
+
// We can't resolve constraint calls effectively for reference types, and there's
// not a lot of perf. benefit in doing it anyway.
//