diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs index 8b878afcc0c061..ee3f3830ce3b00 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs @@ -572,6 +572,11 @@ public override MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(Me return ResolveVariantInterfaceMethodToVirtualMethodOnType(interfaceMethod, (MetadataType)currentType); } + public override MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType, Func inVersionBubble) + { + return ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, (MetadataType)currentType, inVersionBubble); + } + //////////////////////// INTERFACE RESOLUTION //Interface function resolution // Interface function resolution follows the following rules @@ -826,5 +831,125 @@ public static IEnumerable EnumAllVirtualSlots(MetadataType type) } while (type != null); } } + + /// + /// Try to resolve a given virtual static interface method on a given constrained type and its base types. + /// + /// Interface method to resolve + /// Type to attempt virtual static method resolution on + /// MethodDesc of the resolved virtual static method, null when not found (runtime lookup must be used) + public static MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType, Func inVersionBubble) + { + TypeDesc interfaceType = interfaceMethod.OwningType; + if (!inVersionBubble(interfaceType)) + { + return null; + } + + // Search for match on a per-level in the type hierarchy + for (TypeDesc typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.BaseType) + { + if (!inVersionBubble(typeToCheck)) + { + return null; + } + + MethodDesc resolvedMethodOnType = TryResolveVirtualStaticMethodOnThisType(typeToCheck, interfaceMethod); + if (resolvedMethodOnType != null) + { + return resolvedMethodOnType; + } + + // Variant interface dispatch + foreach (DefType runtimeInterfaceType in typeToCheck.RuntimeInterfaces) + { + if (runtimeInterfaceType == interfaceType) + { + // This is the variant interface check logic, skip this + continue; + } + + if (!runtimeInterfaceType.HasSameTypeDefinition(interfaceType)) + { + // Variance matches require a typedef match + // Equivalence isn't sufficient, and is uninteresting as equivalent interfaces cannot have static virtuals. + continue; + } + + if (runtimeInterfaceType.CanCastTo(interfaceType)) + { + if (!inVersionBubble(runtimeInterfaceType)) + { + // Fail the resolution if a candidate variant interface match is outside of the version bubble + return null; + } + + // Attempt to resolve on variance matched interface + MethodDesc runtimeInterfaceMethod = TryResolveInterfaceMethodOnVariantCompatibleInterface(runtimeInterfaceType, interfaceMethod); + + if (runtimeInterfaceMethod != null) + { + resolvedMethodOnType = TryResolveVirtualStaticMethodOnThisType(typeToCheck, runtimeInterfaceMethod); + } + if (resolvedMethodOnType != null) + { + return resolvedMethodOnType; + } + } + } + } + return null; + } + + private static MethodDesc TryResolveInterfaceMethodOnVariantCompatibleInterface(TypeDesc compatibleInterfaceType, MethodDesc interfaceMethod) + { + Debug.Assert(compatibleInterfaceType.CanCastTo(interfaceMethod.OwningType)); + if (compatibleInterfaceType is MetadataType mdType) + { + foreach (MethodDesc runtimeInterfaceMethod in mdType.GetVirtualMethods()) + { + if (runtimeInterfaceMethod.Name == interfaceMethod.Name && + runtimeInterfaceMethod.Signature == interfaceMethod.Signature) + { + return runtimeInterfaceMethod; + } + } + } + return null; + } + + /// + /// Try to resolve a given virtual static interface method on a given constrained type and return the resolved method or null when not found. + /// + /// Type to attempt method resolution on + /// Interface declaring the method + /// Method to resolve + /// MethodDesc of the resolved method or null when not found (runtime lookup must be used) + private static MethodDesc TryResolveVirtualStaticMethodOnThisType(TypeDesc constrainedType, MethodDesc interfaceMethod) + { + if (constrainedType is MetadataType mdType) + { + foreach (MethodImplRecord methodImpl in mdType.FindMethodsImplWithMatchingDeclName(interfaceMethod.Name) ?? Array.Empty()) + { + if (methodImpl.Decl == interfaceMethod) + { + MethodDesc resolvedMethodImpl = methodImpl.Body; + if (resolvedMethodImpl.OwningType != mdType) + { + ThrowHelper.ThrowMissingMethodException(constrainedType, resolvedMethodImpl.Name, resolvedMethodImpl.Signature); + } + if (interfaceMethod.HasInstantiation || methodImpl.Body.HasInstantiation || constrainedType.HasInstantiation) + { + resolvedMethodImpl = resolvedMethodImpl.InstantiateSignature(constrainedType.Instantiation, interfaceMethod.Instantiation); + } + if (resolvedMethodImpl != null) + { + return resolvedMethodImpl; + } + } + } + } + return null; + } } } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs index 3732586a45e75a..5e8fe65669fd3a 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs @@ -288,6 +288,11 @@ public static MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(this return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveVariantInterfaceMethodToVirtualMethodOnType(interfaceMethod, type); } + public static MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(this TypeDesc type, MethodDesc interfaceMethod, Func inVersionBubble) + { + return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, type, inVersionBubble); + } + public static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultImplementationOnType(this TypeDesc type, MethodDesc interfaceMethod, out MethodDesc implMethod) { return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, type, out implMethod); diff --git a/src/coreclr/tools/Common/TypeSystem/Common/VirtualMethodAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/VirtualMethodAlgorithm.cs index e13e61d3862da6..19cb2ee7ad12a5 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/VirtualMethodAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/VirtualMethodAlgorithm.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; namespace Internal.TypeSystem @@ -24,6 +25,8 @@ public abstract class VirtualMethodAlgorithm public abstract MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType); + public abstract MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType, Func inVersionBubble); + public abstract DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultImplementationOnType(MethodDesc interfaceMethod, TypeDesc currentType, out MethodDesc impl); /// diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index e3da2dc734fef5..4eafab65a9f35b 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -939,11 +939,13 @@ private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RE private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken, MethodDesc methodDesc, out object context, ref TypeDesc constrainedType) { - if (methodDesc != null && (_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(methodDesc) + if (methodDesc != null && !methodDesc.IsAbstract && + (_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(methodDesc) || (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod) || methodDesc.IsPInvoke)) { - if ((CorTokenType)(unchecked((uint)pResolvedToken.token) & 0xFF000000u) == CorTokenType.mdtMethodDef && + CorTokenType corTokenType = (CorTokenType)(unchecked((uint)pResolvedToken.token) & 0xFF000000u); + if ((corTokenType == CorTokenType.mdtMethodDef || corTokenType == CorTokenType.mdtMemberRef) && methodDesc?.GetTypicalMethodDefinition() is EcmaMethod ecmaMethod) { mdToken token = (mdToken)MetadataTokens.GetToken(ecmaMethod.Handle); @@ -1448,22 +1450,21 @@ private void ceeInfoGetCallInfo( } callerModule = ((EcmaMethod)callerMethod.GetTypicalMethodDefinition()).Module; + bool isCallVirt = (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0; + bool isLdftn = (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0; + bool isStaticVirtual = (originalMethod.Signature.IsStatic && originalMethod.IsVirtual); // Spec says that a callvirt lookup ignores static methods. Since static methods // can't have the exact same signature as instance methods, a lookup that found // a static method would have never found an instance method. - if (originalMethod.Signature.IsStatic && (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0) + if (originalMethod.Signature.IsStatic && isCallVirt) { ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramCallVirtStatic, originalMethod); } exactType = type; - constrainedType = null; - if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0 && pConstrainedResolvedToken != null) - { - constrainedType = HandleToObject(pConstrainedResolvedToken->hClass); - } + constrainedType = (pConstrainedResolvedToken != null ? HandleToObject(pConstrainedResolvedToken->hClass) : null); bool resolvedConstraint = false; bool forceUseRuntimeLookup = false; @@ -1490,7 +1491,19 @@ private void ceeInfoGetCallInfo( originalMethod = methodOnUnderlyingType; } - MethodDesc directMethod = constrainedType.TryResolveConstraintMethodApprox(exactType, originalMethod, out forceUseRuntimeLookup); + MethodDesc directMethod; + if (isStaticVirtual) + { + directMethod = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(originalMethod, _compilation.CompilationModuleGroup.VersionsWithType); + if (directMethod == null) + { + throw new RequiresRuntimeJitException(originalMethod.ToString()); + } + } + else + { + directMethod = constrainedType.TryResolveConstraintMethodApprox(exactType, originalMethod, out forceUseRuntimeLookup); + } if (directMethod != null) { // Either @@ -1513,6 +1526,10 @@ private void ceeInfoGetCallInfo( exactType = constrainedType; } + else if (isStaticVirtual) + { + pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_NO_THIS_TRANSFORM; + } else if (constrainedType.IsValueType) { pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_BOX_THIS; @@ -1562,17 +1579,20 @@ private void ceeInfoGetCallInfo( bool resolvedCallVirt = false; bool callVirtCrossingVersionBubble = false; - if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0) + if (isLdftn) { directCall = true; } - else - if (targetMethod.Signature.IsStatic) + else if (isStaticVirtual && !resolvedConstraint) + { + // Don't use direct calls for static virtual method calls unresolved at compile time + } + else if (targetMethod.Signature.IsStatic) { // Static methods are always direct calls directCall = true; } - else if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) == 0 || resolvedConstraint) + else if (!isCallVirt || resolvedConstraint) { directCall = true; } @@ -1631,16 +1651,41 @@ private void ceeInfoGetCallInfo( if (directCall) { + bool isVirtualBehaviorUnresolved = (isCallVirt && !resolvedCallVirt || isStaticVirtual && !resolvedConstraint); + // Direct calls to abstract methods are not allowed if (targetMethod.IsAbstract && // Compensate for always treating delegates as direct calls above - !(((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0) && ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0) && !resolvedCallVirt)) + !(isLdftn && isVirtualBehaviorUnresolved)) { ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramCallAbstractMethod, targetMethod); } bool allowInstParam = (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_ALLOWINSTPARAM) != 0; + // If the target method is resolved via constrained static virtual dispatch + // And it requires an instParam, we do not have the generic dictionary infrastructure + // to load the correct generic context arg via EmbedGenericHandle. + // Instead, force the call to go down the CORINFO_CALL_CODE_POINTER code path + // which should have somewhat inferior performance. This should only actually happen in the case + // of shared generic code calling a shared generic implementation method, which should be rare. + // + // An alternative design would be to add a new generic dictionary entry kind to hold the MethodDesc + // of the constrained target instead, and use that in some circumstances; however, implementation of + // that design requires refactoring variuos parts of the JIT interface as well as + // TryResolveConstraintMethodApprox. In particular we would need to be abled to embed a constrained lookup + // via EmbedGenericHandle, as well as decide in TryResolveConstraintMethodApprox if the call can be made + // via a single use of CORINFO_CALL_CODE_POINTER, or would be better done with a CORINFO_CALL + embedded + // constrained generic handle, or if there is a case where we would want to use both a CORINFO_CALL and + // embedded constrained generic handle. Given the current expected high performance use case of this feature + // which is generic numerics which will always resolve to exact valuetypes, it is not expected that + // the complexity involved would be worth the risk. Other scenarios are not expected to be as performance + // sensitive. + if (isStaticVirtual && pResult->exactContextNeedsRuntimeLookup) + { + allowInstParam = false; + } + if (!allowInstParam && canonMethod != null && canonMethod.RequiresInstArg()) { useInstantiatingStub = true; @@ -1687,11 +1732,16 @@ private void ceeInfoGetCallInfo( { pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL_CODE_POINTER; - // For reference types, the constrained type does not affect method resolution - DictionaryEntryKind entryKind = (constrainedType != null && constrainedType.IsValueType + // For reference types, the constrained type does not affect instance virtual method resolution + DictionaryEntryKind entryKind = (constrainedType != null && (constrainedType.IsValueType || !isCallVirt) ? DictionaryEntryKind.ConstrainedMethodEntrySlot : DictionaryEntryKind.MethodEntrySlot); + if (isStaticVirtual && exactType.HasInstantiation) + { + useInstantiatingStub = true; + } + ComputeRuntimeLookupForSharedGenericToken(entryKind, ref pResolvedToken, pConstrainedResolvedToken, originalMethod, ref pResult->codePointerOrStubLookup); } } @@ -1713,6 +1763,21 @@ private void ceeInfoGetCallInfo( } pResult->nullInstanceCheck = resolvedCallVirt; } + else if (isStaticVirtual) + { + pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL; + pResult->nullInstanceCheck = false; + + // Always use an instantiating stub for unresolved constrained SVM calls as we cannot + // always tell at compile time that a given SVM resolves to a method on a generic base + // class and not requesting the instantiating stub makes the runtime transform the + // owning type to its canonical equivalent that would need different codegen + // (supplying the instantiation argument). + if (!resolvedConstraint && !originalMethod.IsSharedByGenericInstantiations) + { + useInstantiatingStub = true; + } + } // All virtual calls which take method instantiations must // currently be implemented by an indirect call via a runtime-lookup // function pointer @@ -1841,13 +1906,6 @@ private void VerifyMethodSignatureIsStable(MethodSignature methodSig) private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, CORINFO_METHOD_STRUCT_* callerHandle, CORINFO_CALLINFO_FLAGS flags, CORINFO_CALL_INFO* pResult) { - if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) == 0 && pConstrainedResolvedToken != null) - { - // Defer constrained call / ldftn instructions used for static virtual methods - // to runtime resolution. - throw new RequiresRuntimeJitException("SVM"); - } - MethodDesc methodToCall; MethodDesc targetMethod; TypeDesc constrainedType;