protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType implType, out CORINFO_DEVIRTUALIZATION_DETAIL devirtualizationDetail) { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN; MethodDesc impl; if (declMethod.OwningType.IsInterface) { if (declMethod.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any) || implType.IsCanonicalSubtype(CanonicalFormKind.Any)) { DefType[] implTypeRuntimeInterfaces = implType.RuntimeInterfaces; int canonicallyMatchingInterfacesFound = 0; DefType canonicalInterfaceType = (DefType)declMethod.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific); for (int i = 0; i < implTypeRuntimeInterfaces.Length; i++) { DefType runtimeInterface = implTypeRuntimeInterfaces[i]; if (canonicalInterfaceType.HasSameTypeDefinition(runtimeInterface) && runtimeInterface.ConvertToCanonForm(CanonicalFormKind.Specific) == canonicalInterfaceType) { canonicallyMatchingInterfacesFound++; if (canonicallyMatchingInterfacesFound > 1) { // We cannot resolve the interface as we don't know with exact enough detail which interface // of multiple possible interfaces is being called. devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_MULTIPLE_IMPL; return(null); } } } } if (!implType.CanCastTo(declMethod.OwningType)) { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CAST; return(null); } impl = implType.ResolveInterfaceMethodTargetWithVariance(declMethod); if (impl != null) { impl = implType.FindVirtualFunctionTargetMethodOnObjectType(impl); } else { MethodDesc dimMethod = null; // This isn't the correct lookup algorithm for variant default interface methods // but as we will drop any results we find in any case, it doesn't matter much. // Non-variant dispatch can simply use ResolveInterfaceMethodToDefaultImplementationOnType // but that implementation currently cannot handle variance. MethodDesc defaultInterfaceDispatchDeclMethod = null; foreach (TypeDesc iface in implType.RuntimeInterfaces) { if (iface == declMethod.OwningType) { defaultInterfaceDispatchDeclMethod = declMethod; break; } if (iface.HasSameTypeDefinition(declMethod.OwningType) && iface.CanCastTo(declMethod.OwningType)) { defaultInterfaceDispatchDeclMethod = iface.FindMethodOnTypeWithMatchingTypicalMethod(declMethod); // Prefer to find the exact match, so don't break immediately } } if (defaultInterfaceDispatchDeclMethod != null) { switch (implType.ResolveInterfaceMethodToDefaultImplementationOnType(defaultInterfaceDispatchDeclMethod, out dimMethod)) { case DefaultInterfaceMethodResolution.Diamond: case DefaultInterfaceMethodResolution.Reabstraction: devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_DIM; return(null); case DefaultInterfaceMethodResolution.DefaultImplementation: if (dimMethod.OwningType.HasInstantiation || (declMethod != defaultInterfaceDispatchDeclMethod)) { // 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 // In addition, we fail here for variant default interface dispatch devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_DIM; return(null); } else { impl = dimMethod; } break; } } } } else { // The derived class should be a subclass of the base class. // this check is perfomed via typedef checking instead of casting, as we accept canon methods calling exact types TypeDesc checkType; for (checkType = implType; checkType != null && !checkType.HasSameTypeDefinition(declMethod.OwningType); checkType = checkType.BaseType) { } if ((checkType == null) || (checkType.ConvertToCanonForm(CanonicalFormKind.Specific) != declMethod.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific))) { // The derived class should be a subclass of the base class. devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_SUBCLASS; return(null); } else { // At this point, the decl method may be only canonically compatible, but not an exact match to a method in the type hierarchy // Convert it to an exact match. (Or if it is an exact match, the FindMethodOnTypeWithMatchingTypicalMethod will be a no-op) declMethod = checkType.FindMethodOnTypeWithMatchingTypicalMethod(declMethod); } impl = implType.FindVirtualFunctionTargetMethodOnObjectType(declMethod); if (impl != null && (impl != declMethod)) { MethodDesc slotDefiningMethodImpl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(impl); MethodDesc slotDefiningMethodDecl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(declMethod); if (slotDefiningMethodImpl != slotDefiningMethodDecl) { // If the derived method's slot does not match the vtable slot, // bail on devirtualization, as the method was installed into // the vtable slot via an explicit override and even if the // method is final, the slot may not be. // // Note the jit could still safely devirtualize if it had an exact // class, but such cases are likely rare. devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_SLOT; impl = null; } } } return(impl); }
public MethodDesc ResolveVirtualMethod(MethodDesc declMethod, TypeDesc implType, out CORINFO_DEVIRTUALIZATION_DETAIL devirtualizationDetail) { return(_devirtualizationManager.ResolveVirtualMethod(declMethod, implType, out devirtualizationDetail)); }
/// <summary> /// Attempts to resolve the <paramref name="declMethod"/> virtual method into /// a method on <paramref name="implType"/> that implements the declaring method. /// Returns null if this is not possible. /// </summary> /// <remarks> /// 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, out CORINFO_DEVIRTUALIZATION_DETAIL devirtualizationDetail) { Debug.Assert(declMethod.IsVirtual); // We're operating on virtual methods. This means that if implType is an array, we need // to get the type that has all the virtual methods provided by the class library. return(ResolveVirtualMethod(declMethod, implType.GetClosestDefType(), out devirtualizationDetail)); }
protected override MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType implType, out CORINFO_DEVIRTUALIZATION_DETAIL devirtualizationDetail) { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN; // Versioning resiliency rules here are complex // Decl method checking // 1. If the declMethod is a class method, then we do not need to check if it is within the version bubble with a VersionsWithCode check // but the metadata for the open definition must be within the bubble, or the decl method is in the direct parent type // of a type which is in the version bubble relative to the implType. // 2. If the declMethod is an interface method, we can allow it if interface type is defined within the version // bubble, or if the implementation type hierarchy is entirely within the version bubble (excluding System.Object and System.ValueType). // 3. At all times the declMethod must be representable as a token. That check is handled internally in the // jit interface logic after the logic that executes here. // // ImplType checking // 1. At all times the metadata definition of the implementation type must version with the application. // 2. Additionally, the exact implementation type must be representable within the R2R image (this is checked via VersionsWithTypeReference // // Result method checking // 1. Ensure that the resolved result versions with the code, or is the decl method // 2. Devirtualizing to a default interface method is not currently considered to be useful, and how to check for version // resilience has not yet been analyzed. // 3. When checking that the resolved result versions with the code, validate that all of the types // From implType to the owning type of resolved result method also version with the code. bool declMethodCheckFailed; var firstTypeInImplTypeHierarchyNotInVersionBubble = FindVersionBubbleEdge(_compilationModuleGroup, implType, out TypeDesc lastTypeInHierarchyInVersionBubble); if (!declMethod.OwningType.IsInterface) { if (_compilationModuleGroup.VersionsWithType(declMethod.OwningType.GetTypeDefinition())) { declMethodCheckFailed = false; } else { if (firstTypeInImplTypeHierarchyNotInVersionBubble != declMethod.OwningType) { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE_CLASS_DECL; declMethodCheckFailed = true; } else { declMethodCheckFailed = false; } } } else { if (_compilationModuleGroup.VersionsWithType(declMethod.OwningType.GetTypeDefinition())) { declMethodCheckFailed = false; } else { if (firstTypeInImplTypeHierarchyNotInVersionBubble == null || implType.IsValueType || firstTypeInImplTypeHierarchyNotInVersionBubble.IsObject) { declMethodCheckFailed = false; } else { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE_INTERFACE_DECL; declMethodCheckFailed = true; } } } if (declMethodCheckFailed) { return(null); } // Impl type check if (!_compilationModuleGroup.VersionsWithType(implType.GetTypeDefinition())) { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE_IMPL; return(null); } if (!_compilationModuleGroup.VersionsWithTypeReference(implType)) { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE_IMPL_NOT_REFERENCEABLE; return(null); } /** * It is possible for us to hit a scenario where a type implements * the same interface more than once due to generic instantiations. * * In some instances of those cases, the VirtualMethodAlgorithm * does not produce identical output as CoreCLR would, leading to * behavioral differences in compiled outputs. * * Instead of fixing the algorithm (in which the work to fix it is * tracked in https://github.com/dotnet/corert/issues/208), the * following duplication detection algorithm will detect the case and * refuse to devirtualize for those scenarios. */ if (declMethod.OwningType.IsInterface) { DefType[] implTypeRuntimeInterfaces = implType.RuntimeInterfaces; for (int i = 0; i < implTypeRuntimeInterfaces.Length; i++) { for (int j = i + 1; j < implTypeRuntimeInterfaces.Length; j++) { if (implTypeRuntimeInterfaces[i] == implTypeRuntimeInterfaces[j]) { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_DUPLICATE_INTERFACE; return(null); } } } } if (declMethod.OwningType.IsInterface) { // Check for ComImport class, as we don't support devirtualization of ComImport classes // Run this check on all platforms, to avoid possible future versioning problems if we implement // COM on other architectures. if (!implType.IsObject) { TypeDesc typeThatDerivesFromObject = implType; while (!typeThatDerivesFromObject.BaseType.IsObject) { typeThatDerivesFromObject = typeThatDerivesFromObject.BaseType; } if (typeThatDerivesFromObject is Internal.TypeSystem.Ecma.EcmaType ecmaType) { if ((ecmaType.Attributes & System.Reflection.TypeAttributes.Import) != 0) { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_COM; return(null); } } } } MethodDesc resolvedVirtualMethod = base.ResolveVirtualMethod(declMethod, implType, out devirtualizationDetail); if (resolvedVirtualMethod != null) { // Validate that the inheritance chain for resolution is within version bubble // The rule is somewhat tricky here. // If the resolved method is the declMethod, then only types which derive from the // OwningType of the decl method need to be within the version bubble. // // If not, then all the types from the implType to the Owning type of the resolved // virtual method must be within the version bubble. if (firstTypeInImplTypeHierarchyNotInVersionBubble == null) { // The entire type hierarchy of the implType is within the version bubble, and there is no more to check return(resolvedVirtualMethod); } if (declMethod == resolvedVirtualMethod && firstTypeInImplTypeHierarchyNotInVersionBubble == declMethod.OwningType) { // Exact match for use of decl method check return(resolvedVirtualMethod); } // Ensure that declMethod is implemented on a type within the type hierarchy that is within the version bubble for (TypeDesc typeExamine = resolvedVirtualMethod.OwningType; typeExamine != null; typeExamine = typeExamine.BaseType) { if (typeExamine == lastTypeInHierarchyInVersionBubble) { return(resolvedVirtualMethod); } } devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE; } // Cannot devirtualize, as we can't resolve to a target. return(null);