From af90704913246920a67253f2679e605b07f43f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 9 Jul 2026 14:33:05 +0900 Subject: [PATCH 1/3] FindSlotDefiningMethodForVirtualMethod doesn't work for instantiated methods --- .../Compiler/DependencyAnalysis/GVMDependenciesNode.cs | 2 +- .../Common/Compiler/GenericCycleDetection/GraphBuilder.cs | 2 +- .../TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs | 2 ++ .../tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs | 8 +++++++- .../DependencyAnalysis/ReflectionVirtualInvokeMapNode.cs | 6 +++--- .../Compiler/UsageBasedMetadataManager.cs | 3 ++- .../DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs | 4 ++-- 7 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/GVMDependenciesNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/GVMDependenciesNode.cs index da679b0644406a..4e869939d7c913 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/GVMDependenciesNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/GVMDependenciesNode.cs @@ -32,7 +32,7 @@ public GVMDependenciesNode(MethodDesc method) Debug.Assert(method.GetCanonMethodTarget(CanonicalFormKind.Specific) == method); Debug.Assert(method.HasInstantiation); Debug.Assert(method.IsVirtual); - Debug.Assert(MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method) == method); + Debug.Assert(MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method.GetMethodDefinition()) == method.GetMethodDefinition()); _method = method; } diff --git a/src/coreclr/tools/Common/Compiler/GenericCycleDetection/GraphBuilder.cs b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/GraphBuilder.cs index c29e48b03a5220..81decb34755444 100644 --- a/src/coreclr/tools/Common/Compiler/GenericCycleDetection/GraphBuilder.cs +++ b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/GraphBuilder.cs @@ -270,7 +270,7 @@ private void LookForVirtualOverrides(EcmaMethod method) // of the implementation to the generic parameters of the declaration - any call to the // declaration will be modeled as if the declaration was calling into the implementation. - var decl = (EcmaMethod)MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method).GetTypicalMethodDefinition(); + var decl = (EcmaMethod)MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method.GetMethodDefinition()).GetTypicalMethodDefinition(); if (decl != method) { RecordBinding(this, decl.Instantiation, method.Instantiation); diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs index b1444f1afe5077..9f09ecff151bd4 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs @@ -441,6 +441,8 @@ public static MethodDesc FindSlotDefiningMethodForVirtualMethod(MethodDesc metho if (method == null) return method; + Debug.Assert(method.GetMethodDefinition() == method); + DefType currentType = method.OwningType.BaseType; // Loop until a newslot method is found diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs index ddcab414eb80d5..9b0ab44e373b99 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs @@ -123,7 +123,13 @@ public DelegateCreationInfo GetDelegateCtor(TypeDesc delegateType, MethodDesc ta followVirtualDispatch = false; if (followVirtualDispatch) - target = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(target); + { + MethodDesc originalTarget = target; + MethodDesc targetDefinition = target.GetMethodDefinition(); + target = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(targetDefinition); + if (originalTarget != targetDefinition) + target = target.MakeInstantiatedMethod(originalTarget.Instantiation); + } return DelegateCreationInfo.Create(delegateType, target, constrainedType, NodeFactory, followVirtualDispatch); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionVirtualInvokeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionVirtualInvokeMapNode.cs index b55ca1bdacaad4..51371d4fa06f54 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionVirtualInvokeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionVirtualInvokeMapNode.cs @@ -86,11 +86,11 @@ public static void GetVirtualInvokeMapDependencies(ref DependencyList dependenci factory.NecessaryTypeSymbol(method.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific)), "Reflection virtual invoke owning type"); - MethodDesc slotDefiningMethod = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method); + MethodDesc methodDefinition = method.GetMethodDefinition(); + MethodDesc slotDefiningMethod = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(methodDefinition); if (method.HasInstantiation) { - // FindSlotDefiningMethod might uninstantiate. We might want to fix the method not to do that. - if (slotDefiningMethod.IsMethodDefinition) + if (method != methodDefinition) slotDefiningMethod = factory.TypeSystemContext.GetInstantiatedMethod(slotDefiningMethod, method.Instantiation); dependencies.Add(factory.GVMDependencies(slotDefiningMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)), "GVM callable reflectable method"); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index c4b05b20c524c4..1476031e388e00 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -631,7 +631,8 @@ public override void GetDependenciesDueToDelegateCreation(ref CombinedDependency public override void GetDependenciesForOverridingMethod(ref CombinedDependencyList dependencies, NodeFactory factory, MethodDesc decl, MethodDesc impl) { - Debug.Assert(decl.IsVirtual && MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(decl) == decl); + Debug.Assert(decl.IsVirtual + && MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(decl.GetMethodDefinition()) == decl.GetMethodDefinition()); // If a virtual method slot is a target of a delegate, all implementations become reflection visible // to support Delegate.GetMethodInfo(). diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs index 23830d98e02f2f..34178500977b3c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -154,8 +154,8 @@ public GVMDependenciesNode GVMDependencies(MethodDesc method) { Debug.Assert(method.IsVirtual); MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific); - canonMethod = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(canonMethod); - return _gvmDependenciesNode.GetOrAdd(canonMethod); + MethodDesc canonSlotMethodDefinition = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(canonMethod.GetMethodDefinition()); + return _gvmDependenciesNode.GetOrAdd(canonSlotMethodDefinition.MakeInstantiatedMethod(canonMethod.Instantiation)); } private NodeCache _virtualMethodUseNodes; From cd80ee0044d99a465835047589683ef110b15dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 9 Jul 2026 14:33:46 +0900 Subject: [PATCH 2/3] FindVirtualFunctionTargetMethodOnObjectType doesn't accept uninstantiated generic method --- .../Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs index 9f09ecff151bd4..dd8e1a23841142 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs @@ -239,6 +239,7 @@ private static MethodDesc FindVirtualFunctionTargetMethodOnObjectType(MethodDesc // Step 2, convert targetMethod to method in type hierarchy of uninstantiated form targetMethod = targetMethod.GetMethodDefinition(); + MethodDesc initialTargetMethodDefinition = targetMethod; if (uninstantiatedType != objectType) { targetMethod = uninstantiatedType.FindMethodOnTypeWithMatchingTypicalMethod(targetMethod); @@ -259,7 +260,7 @@ private static MethodDesc FindVirtualFunctionTargetMethodOnObjectType(MethodDesc { resolutionTarget = objectType.FindMethodOnTypeWithMatchingTypicalMethod(resolutionTarget); } - if (initialTargetMethod.HasInstantiation) + if (initialTargetMethod != initialTargetMethodDefinition) { resolutionTarget = resolutionTarget.MakeInstantiatedMethod(initialTargetMethod.Instantiation); } From 5492e20b6d0bcf2d4d8e4e967d732c34ba672bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 9 Jul 2026 14:37:04 +0900 Subject: [PATCH 3/3] Faster virtual method resolution Compiling DynamicGenerics.obj went from taking 2.44 seconds to 2.16 seconds. --- .../Compiler/CachingVirtualMethodAlgorithm.cs | 86 +++++++++++++++++++ .../CompilerTypeSystemContext.Async.cs | 2 +- .../ILCompiler.Compiler.csproj | 1 + .../ILCompiler.ReadyToRun.csproj | 1 + 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/coreclr/tools/Common/Compiler/CachingVirtualMethodAlgorithm.cs diff --git a/src/coreclr/tools/Common/Compiler/CachingVirtualMethodAlgorithm.cs b/src/coreclr/tools/Common/Compiler/CachingVirtualMethodAlgorithm.cs new file mode 100644 index 00000000000000..5b30f5f7ec1456 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/CachingVirtualMethodAlgorithm.cs @@ -0,0 +1,86 @@ +// 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.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; + +using Internal.TypeSystem; + +namespace ILCompiler +{ + public class CachingVirtualMethodAlgorithm : MetadataVirtualMethodAlgorithm + { + private readonly ConcurrentDictionary _vtableCache + = new ConcurrentDictionary(); + + private readonly Func _vtableCreator; + + public CachingVirtualMethodAlgorithm() + => _vtableCreator = ComputeVtable; + + private (MethodDesc Slot, MethodDesc Implementation)[] ComputeVtable(TypeDesc type) + { + // Do not recompute the same things for all the generics + Debug.Assert(type.IsTypeDefinition); + + var result = new ArrayBuilder<(MethodDesc Slot, MethodDesc Implementation)>(3 /* At least Equals/GetHashCode/ToString */); + + foreach (MethodDesc slotDecl in base.ComputeAllVirtualSlots(type)) + { + result.Add((slotDecl, base.FindVirtualFunctionTargetMethodOnObjectType(slotDecl, type))); + } + + return result.ToArray(); + } + + public override IEnumerable ComputeAllVirtualSlots(TypeDesc type) + { + // This just enumerates virtual methods, not worth caching. + if (type.IsInterface) + return base.ComputeAllVirtualSlots(type); + + return GetCachedSlots(this, type); + + static IEnumerable GetCachedSlots(CachingVirtualMethodAlgorithm thisObj, TypeDesc type) + { + foreach ((MethodDesc slot, _) in thisObj._vtableCache.GetOrAdd(type.GetTypeDefinition(), thisObj._vtableCreator)) + { + yield return type.FindMethodOnTypeWithMatchingTypicalMethod(slot); + } + } + } + + public override MethodDesc FindVirtualFunctionTargetMethodOnObjectType(MethodDesc targetMethod, TypeDesc objectType) + { + MetadataType uninstantiatedType = (MetadataType)objectType.GetTypeDefinition(); + MethodDesc targetMethodDefinition = targetMethod.GetMethodDefinition(); + + MethodDesc slotDefiningMethod = targetMethodDefinition; + if (uninstantiatedType != objectType) + { + slotDefiningMethod = uninstantiatedType.FindMethodOnTypeWithMatchingTypicalMethod(slotDefiningMethod); + } + slotDefiningMethod = FindSlotDefiningMethodForVirtualMethod(slotDefiningMethod); + + foreach (var kvp in _vtableCache.GetOrAdd(objectType.GetTypeDefinition(), _vtableCreator)) + { + if (kvp.Slot == slotDefiningMethod) + { + MethodDesc result = kvp.Implementation; + + if (uninstantiatedType != objectType) + result = objectType.FindMethodOnTypeWithMatchingTypicalMethod(result); + + if (targetMethod != targetMethodDefinition) + result = result.MakeInstantiatedMethod(targetMethod.Instantiation); + + return result; + } + } + + return null; + } + } +} diff --git a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs index a1c8177e5a09d4..5d5494eef686e2 100644 --- a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs +++ b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs @@ -21,7 +21,7 @@ public static IEnumerable GetAllMethodsAndAsyncVariants(this TypeDes public partial class CompilerTypeSystemContext { - private sealed class AsyncAwareVirtualMethodResolutionAlgorithm : MetadataVirtualMethodAlgorithm + private sealed class AsyncAwareVirtualMethodResolutionAlgorithm : CachingVirtualMethodAlgorithm { private readonly CompilerTypeSystemContext _context; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index ff91bfe0b5263b..545b0574f7d025 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -36,6 +36,7 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 7ad1e765e46e72..24d173ecbacbf5 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -64,6 +64,7 @@ +