From 0247e76fe2f7228fb2f06970e8b441bca06fc4c8 Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 15 Jan 2026 02:12:44 +0900 Subject: [PATCH 01/11] Devirtualize non-shared GVMs in R2R --- src/coreclr/jit/gentree.h | 9 ++++++-- .../Compiler/DevirtualizationManager.cs | 23 +++++++++++++++++++ .../tools/Common/JitInterface/CorInfoImpl.cs | 7 ------ .../Common/TypeSystem/Common/Instantiation.cs | 16 +++++++++++++ 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 4560fb0f2fe1a4..485e51a2b6d4a6 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -5269,8 +5269,13 @@ struct GenTreeCall final : public GenTree } bool IsGenericVirtual(Compiler* compiler) const { - return (gtCallType == CT_INDIRECT && (gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_VIRTUAL_FUNC_PTR) || - gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_GVMLOOKUP_FOR_SLOT))); + return (gtCallType == CT_INDIRECT && + (gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_VIRTUAL_FUNC_PTR) || + gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_GVMLOOKUP_FOR_SLOT) +#ifdef FEATURE_READYTORUN + || gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR) +#endif + )); } bool IsDevirtualizationCandidate(Compiler* compiler) const; diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs index f704807c0c0841..76e42f19fdc477 100644 --- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs +++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs @@ -100,6 +100,14 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType } impl = implType.ResolveInterfaceMethodTargetWithVariance(declMethod); + + // If we end up with a generic method definition, we need to bring the instantiation back + // so that we can try devirtualizing this generic virtual method + if (impl.IsGenericMethodDefinition && declMethod.HasInstantiation) + { + impl = impl.MakeInstantiatedMethod(declMethod.Instantiation); + } + if (impl != null) { impl = implType.FindVirtualFunctionTargetMethodOnObjectType(impl); @@ -178,6 +186,14 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType } impl = implType.FindVirtualFunctionTargetMethodOnObjectType(declMethod); + + // If we end up with a generic method definition, we need to bring the instantiation back + // so that we can try devirtualizing this generic virtual method + if (impl.IsGenericMethodDefinition && declMethod.HasInstantiation) + { + impl = impl.MakeInstantiatedMethod(declMethod.Instantiation); + } + if (impl != null && (impl != declMethod)) { MethodDesc slotDefiningMethodImpl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(impl); @@ -198,6 +214,13 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType } } + if (impl != null && impl.HasInstantiation && (!impl.Instantiation.IsConstructed || declMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Specific))) + { + // We don't support devirtualization of shared generic virtual methods yet. + devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; + impl = null; + } + return impl; } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 85d416c833878b..cba502bde11375 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1340,13 +1340,6 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) // Transform from the unboxing thunk to the normal method decl = decl.IsUnboxingThunk() ? decl.GetUnboxedMethod() : decl; - if (decl.HasInstantiation) - { - // We cannot devirtualize generic virtual methods in AOT yet - info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_GENERIC_VIRTUAL; - return false; - } - if ((info->context != null) && decl.OwningType.IsInterface) { TypeDesc ownerTypeDesc = typeFromContext(info->context); diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Instantiation.cs b/src/coreclr/tools/Common/TypeSystem/Common/Instantiation.cs index 7d798253f59192..7f4484c0fb6b6d 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Instantiation.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/Instantiation.cs @@ -50,6 +50,22 @@ public bool IsNull } } + public bool IsConstructed + { + get + { + for (int i = 0; i < _genericParameters.Length; i++) + { + if (_genericParameters[i].IsGenericParameter) + { + return false; + } + } + + return true; + } + } + public static readonly Instantiation Empty = new Instantiation(TypeDesc.EmptyTypes); public Enumerator GetEnumerator() From 54a601f500ac423fcb83ea0e8bba209a5bd86bc8 Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 15 Jan 2026 02:22:10 +0900 Subject: [PATCH 02/11] Remove unused failure mode --- src/coreclr/inc/corinfo.h | 1 - src/coreclr/inc/jiteeversionguid.h | 10 +++++----- src/coreclr/jit/compiler.cpp | 2 -- src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs | 1 - 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index f6922867abcfee..23941ec9f42e1b 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1557,7 +1557,6 @@ enum CORINFO_DEVIRTUALIZATION_DETAIL CORINFO_DEVIRTUALIZATION_FAILED_DUPLICATE_INTERFACE, // crossgen2 virtual method algorithm and runtime algorithm differ in the presence of duplicate interface implementations CORINFO_DEVIRTUALIZATION_FAILED_DECL_NOT_REPRESENTABLE, // Decl method cannot be represented in R2R image CORINFO_DEVIRTUALIZATION_FAILED_TYPE_EQUIVALENCE, // Support for type equivalence in devirtualization is not yet implemented in crossgen2 - CORINFO_DEVIRTUALIZATION_FAILED_GENERIC_VIRTUAL, // Devirtualization of generic virtual methods is not yet implemented in crossgen2 CORINFO_DEVIRTUALIZATION_COUNT, // sentinel for maximum value }; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 5f7f639550fb31..c0614ed35209d0 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 976a4d6d-d1b2-4096-a3c9-46ddcae71196 */ - 0x976a4d6d, - 0xd1b2, - 0x4096, - {0xa3, 0xc9, 0x46, 0xdd, 0xca, 0xe7, 0x11, 0x96} +constexpr GUID JITEEVersionIdentifier = { /* c10e2acf-7c94-4c61-b895-5178602e0e1e */ + 0xc10e2acf, + 0x7c94, + 0x4c61, + {0xb8, 0x95, 0x51, 0x78, 0x60, 0x2e, 0x0e, 0x1e} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index bba3971a975759..dbad71a35a1399 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -10510,8 +10510,6 @@ const char* Compiler::devirtualizationDetailToString(CORINFO_DEVIRTUALIZATION_DE return "Decl method cannot be represented in R2R image"; case CORINFO_DEVIRTUALIZATION_FAILED_TYPE_EQUIVALENCE: return "Support for type equivalence in devirtualization is not yet implemented in crossgen2"; - case CORINFO_DEVIRTUALIZATION_FAILED_GENERIC_VIRTUAL: - return "Devirtualization of generic virtual methods is not yet implemented in crossgen2"; default: return "undefined"; } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 2ae65728f235e5..59a4c56eeb7153 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1138,7 +1138,6 @@ public enum CORINFO_DEVIRTUALIZATION_DETAIL CORINFO_DEVIRTUALIZATION_FAILED_DUPLICATE_INTERFACE, // crossgen2 virtual method algorithm and runtime algorithm differ in the presence of duplicate interface implementations CORINFO_DEVIRTUALIZATION_FAILED_DECL_NOT_REPRESENTABLE, // Decl method cannot be represented in R2R image CORINFO_DEVIRTUALIZATION_FAILED_TYPE_EQUIVALENCE, // Support for type equivalence in devirtualization is not yet implemented in crossgen2 - CORINFO_DEVIRTUALIZATION_FAILED_GENERIC_VIRTUAL, // Devirtualization of generic virtual methods is not yet implemented in crossgen2 CORINFO_DEVIRTUALIZATION_COUNT, // sentinel for maximum value } From 5b2e74b7c299bdd0983a6a48a34da6a9d85bdbfb Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 15 Jan 2026 22:40:25 +0900 Subject: [PATCH 03/11] Check null for impl first --- .../Common/Compiler/DevirtualizationManager.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs index 76e42f19fdc477..61a726ade181a8 100644 --- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs +++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs @@ -101,15 +101,15 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType impl = implType.ResolveInterfaceMethodTargetWithVariance(declMethod); - // If we end up with a generic method definition, we need to bring the instantiation back - // so that we can try devirtualizing this generic virtual method - if (impl.IsGenericMethodDefinition && declMethod.HasInstantiation) - { - impl = impl.MakeInstantiatedMethod(declMethod.Instantiation); - } - if (impl != null) { + // If we end up with a generic method definition, we need to bring the instantiation back + // so that we can try devirtualizing this generic virtual method + if (impl.IsGenericMethodDefinition && declMethod.HasInstantiation) + { + impl = impl.MakeInstantiatedMethod(declMethod.Instantiation); + } + impl = implType.FindVirtualFunctionTargetMethodOnObjectType(impl); } else From a935bb74c562a60db8fcc379f787e402bf733fac Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 15 Jan 2026 22:40:55 +0900 Subject: [PATCH 04/11] Use IsGenericMethodDefinition --- .../Common/Compiler/DevirtualizationManager.cs | 2 +- .../Common/TypeSystem/Common/Instantiation.cs | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs index 61a726ade181a8..0fdcb3a271227f 100644 --- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs +++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs @@ -214,7 +214,7 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType } } - if (impl != null && impl.HasInstantiation && (!impl.Instantiation.IsConstructed || declMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Specific))) + if (impl != null && impl.HasInstantiation && (impl.IsGenericMethodDefinition || declMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Specific))) { // We don't support devirtualization of shared generic virtual methods yet. devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Instantiation.cs b/src/coreclr/tools/Common/TypeSystem/Common/Instantiation.cs index 7f4484c0fb6b6d..7d798253f59192 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Instantiation.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/Instantiation.cs @@ -50,22 +50,6 @@ public bool IsNull } } - public bool IsConstructed - { - get - { - for (int i = 0; i < _genericParameters.Length; i++) - { - if (_genericParameters[i].IsGenericParameter) - { - return false; - } - } - - return true; - } - } - public static readonly Instantiation Empty = new Instantiation(TypeDesc.EmptyTypes); public Enumerator GetEnumerator() From 2d109f19ff5c28b84b4a6efbba219a629dbd2967 Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 15 Jan 2026 23:30:34 +0900 Subject: [PATCH 05/11] More fixes --- .../Compiler/DevirtualizationManager.cs | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs index 0fdcb3a271227f..a60ff301cd64bf 100644 --- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs +++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs @@ -66,6 +66,7 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN; + MethodDesc originalDeclMethod = declMethod; MethodDesc impl; if (declMethod.OwningType.IsInterface) @@ -103,11 +104,13 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType if (impl != null) { - // If we end up with a generic method definition, we need to bring the instantiation back - // so that we can try devirtualizing this generic virtual method - if (impl.IsGenericMethodDefinition && declMethod.HasInstantiation) + // We need to bring the original instantiation back so that we can still try devirtualizing + // when the method is a generic virtual method + if (originalDeclMethod.HasInstantiation) { - impl = impl.MakeInstantiatedMethod(declMethod.Instantiation); + // We may end up with a method that has subsituted type parameters, so we need to instantiate + // on the method definition + impl = impl.GetMethodDefinition().MakeInstantiatedMethod(originalDeclMethod.Instantiation); } impl = implType.FindVirtualFunctionTargetMethodOnObjectType(impl); @@ -187,34 +190,40 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType impl = implType.FindVirtualFunctionTargetMethodOnObjectType(declMethod); - // If we end up with a generic method definition, we need to bring the instantiation back - // so that we can try devirtualizing this generic virtual method - if (impl.IsGenericMethodDefinition && declMethod.HasInstantiation) - { - impl = impl.MakeInstantiatedMethod(declMethod.Instantiation); - } - - if (impl != null && (impl != declMethod)) + if (impl != null) { - MethodDesc slotDefiningMethodImpl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(impl); - MethodDesc slotDefiningMethodDecl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(declMethod); + // We need to bring the original instantiation back so that we can still try devirtualizing + // when the method is a generic virtual method + if (originalDeclMethod.HasInstantiation) + { + // We may end up with a method that has subsituted type parameters, so we need to instantiate + // on the method definition + impl = impl.GetMethodDefinition().MakeInstantiatedMethod(originalDeclMethod.Instantiation); + } - if (slotDefiningMethodImpl != slotDefiningMethodDecl) + if (impl != declMethod) { - // 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; + MethodDesc slotDefiningMethodImpl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(impl.GetMethodDefinition()); + 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; + } + } } } - if (impl != null && impl.HasInstantiation && (impl.IsGenericMethodDefinition || declMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Specific))) + if (impl != null && impl.HasInstantiation && originalDeclMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Specific)) { // We don't support devirtualization of shared generic virtual methods yet. devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; From 607b05a32e8b6e6fecf43afa093f15eca0cc55b3 Mon Sep 17 00:00:00 2001 From: Steven He Date: Fri, 16 Jan 2026 00:11:10 +0900 Subject: [PATCH 06/11] Update comments --- src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 59a4c56eeb7153..c05adcbba87037 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1122,7 +1122,7 @@ public enum CORINFO_DEVIRTUALIZATION_DETAIL { CORINFO_DEVIRTUALIZATION_UNKNOWN, // no details available CORINFO_DEVIRTUALIZATION_SUCCESS, // devirtualization was successful - CORINFO_DEVIRTUALIZATION_FAILED_CANON, // object class was canonical + CORINFO_DEVIRTUALIZATION_FAILED_CANON, // object class or method was canonical CORINFO_DEVIRTUALIZATION_FAILED_COM, // object class was com CORINFO_DEVIRTUALIZATION_FAILED_CAST, // object class could not be cast to interface class CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP, // interface method could not be found From 11139976846e4844d06bdc51cf96123d88381be5 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sat, 17 Jan 2026 04:01:41 +0900 Subject: [PATCH 07/11] Use typical method definition for R2R generic virtual method verification --- src/coreclr/vm/jitinterface.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index d4169cb86d69d5..ac8e4e386fa940 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -14334,6 +14334,12 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, } } + // Strip off method instantiation for comparison if the method is generic virtual. + if (pImplMethodCompiler != NULL && pDeclMethod->HasMethodInstantiation()) + { + pImplMethodCompiler = pImplMethodCompiler->StripMethodInstantiation(); + } + if (pImplMethodRuntime != pImplMethodCompiler) { if (kind == READYTORUN_FIXUP_Check_VirtualFunctionOverride) From 515f39c5f6e4b20827a0161aa25366bf11375d42 Mon Sep 17 00:00:00 2001 From: Steven He Date: Fri, 23 Jan 2026 01:02:00 +0900 Subject: [PATCH 08/11] Update devirtualization detail message --- src/coreclr/jit/compiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index dbad71a35a1399..ccaf6acea576dd 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -10476,7 +10476,7 @@ const char* Compiler::devirtualizationDetailToString(CORINFO_DEVIRTUALIZATION_DE case CORINFO_DEVIRTUALIZATION_SUCCESS: return "success"; case CORINFO_DEVIRTUALIZATION_FAILED_CANON: - return "object class was canonical"; + return "object class or method was canonical"; case CORINFO_DEVIRTUALIZATION_FAILED_COM: return "object class was com"; case CORINFO_DEVIRTUALIZATION_FAILED_CAST: From c896f153f602ac7645d5628d736be133e81e5cf0 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sat, 24 Jan 2026 00:47:49 +0900 Subject: [PATCH 09/11] Reduce nesting per review feedback --- .../Compiler/DevirtualizationManager.cs | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs index a60ff301cd64bf..1786359f04aedf 100644 --- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs +++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs @@ -190,35 +190,31 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType impl = implType.FindVirtualFunctionTargetMethodOnObjectType(declMethod); - if (impl != null) + // We need to bring the original instantiation back so that we can still try devirtualizing + // when the method is a generic virtual method + if (impl != null && originalDeclMethod.HasInstantiation) { - // We need to bring the original instantiation back so that we can still try devirtualizing - // when the method is a generic virtual method - if (originalDeclMethod.HasInstantiation) - { - // We may end up with a method that has subsituted type parameters, so we need to instantiate - // on the method definition - impl = impl.GetMethodDefinition().MakeInstantiatedMethod(originalDeclMethod.Instantiation); - } - - if (impl != declMethod) - { - MethodDesc slotDefiningMethodImpl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(impl.GetMethodDefinition()); - MethodDesc slotDefiningMethodDecl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(declMethod); + // We may end up with a method that has subsituted type parameters, so we need to instantiate + // on the method definition + impl = impl.GetMethodDefinition().MakeInstantiatedMethod(originalDeclMethod.Instantiation); + } - 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; - } + if (impl != null && (impl != declMethod)) + { + MethodDesc slotDefiningMethodImpl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(impl.GetMethodDefinition()); + 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; } } } From ef005bfce2139475e554e04d5807709b6575b1e1 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sat, 24 Jan 2026 00:53:24 +0900 Subject: [PATCH 10/11] Nit --- .../tools/Common/Compiler/DevirtualizationManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs index 1786359f04aedf..f2247e571bc8bd 100644 --- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs +++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs @@ -104,16 +104,16 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType if (impl != null) { + impl = implType.FindVirtualFunctionTargetMethodOnObjectType(impl); + // We need to bring the original instantiation back so that we can still try devirtualizing // when the method is a generic virtual method - if (originalDeclMethod.HasInstantiation) + if (impl != null && originalDeclMethod.HasInstantiation) { // We may end up with a method that has subsituted type parameters, so we need to instantiate // on the method definition impl = impl.GetMethodDefinition().MakeInstantiatedMethod(originalDeclMethod.Instantiation); } - - impl = implType.FindVirtualFunctionTargetMethodOnObjectType(impl); } else { From 3317ecef8296f16f6d12b2ae1524e15e41e536db Mon Sep 17 00:00:00 2001 From: Steve Date: Sat, 31 Jan 2026 17:03:27 +0900 Subject: [PATCH 11/11] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../tools/Common/Compiler/DevirtualizationManager.cs | 6 +++--- src/coreclr/vm/jitinterface.cpp | 12 ++++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs index f2247e571bc8bd..ac544ed02e3fea 100644 --- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs +++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs @@ -110,7 +110,7 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType // when the method is a generic virtual method if (impl != null && originalDeclMethod.HasInstantiation) { - // We may end up with a method that has subsituted type parameters, so we need to instantiate + // We may end up with a method that has substituted type parameters, so we need to instantiate // on the method definition impl = impl.GetMethodDefinition().MakeInstantiatedMethod(originalDeclMethod.Instantiation); } @@ -194,7 +194,7 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType // when the method is a generic virtual method if (impl != null && originalDeclMethod.HasInstantiation) { - // We may end up with a method that has subsituted type parameters, so we need to instantiate + // We may end up with a method that has substituted type parameters, so we need to instantiate // on the method definition impl = impl.GetMethodDefinition().MakeInstantiatedMethod(originalDeclMethod.Instantiation); } @@ -219,7 +219,7 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType } } - if (impl != null && impl.HasInstantiation && originalDeclMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Specific)) + if (impl != null && impl.HasInstantiation && impl.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Specific)) { // We don't support devirtualization of shared generic virtual methods yet. devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 166a990352e847..d93d1d588fcbf3 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -14299,9 +14299,17 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, } // Strip off method instantiation for comparison if the method is generic virtual. - if (pImplMethodCompiler != NULL && pDeclMethod->HasMethodInstantiation()) + if (pDeclMethod->HasMethodInstantiation()) { - pImplMethodCompiler = pImplMethodCompiler->StripMethodInstantiation(); + if (pImplMethodRuntime != NULL) + { + pImplMethodRuntime = pImplMethodRuntime->StripMethodInstantiation(); + } + + if (pImplMethodCompiler != NULL) + { + pImplMethodCompiler = pImplMethodCompiler->StripMethodInstantiation(); + } } if (pImplMethodRuntime != pImplMethodCompiler)