From dc8058b5160797879962e61ac594f78003bfb53d Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 26 Nov 2025 17:23:53 -0800 Subject: [PATCH 01/18] Create MethodGenericDictionary for calls to GVMs --- .../tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index 8bb7c1f6eedd6c..c5fa24eb2b096b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -746,6 +746,13 @@ private void ImportCall(ILOpcode opcode, int token) else { _dependencies.Add(_factory.RuntimeMethodHandle(methodToLookup), reason); + + MethodDesc concreteMethod = targetMethod; + targetMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); + if (targetMethod.RequiresInstMethodDescArg()) + { + _dependencies.Add(_factory.MethodGenericDictionary(concreteMethod), reason); + } } _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GVMLookupForSlot), reason); From 036aded91fc10d080699c50285f6c85585f3c0b9 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 5 Dec 2025 12:45:37 -0800 Subject: [PATCH 02/18] Revert "Create MethodGenericDictionary for calls to GVMs" This reverts commit dc8058b5160797879962e61ac594f78003bfb53d. --- .../tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index c5fa24eb2b096b..8bb7c1f6eedd6c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -746,13 +746,6 @@ private void ImportCall(ILOpcode opcode, int token) else { _dependencies.Add(_factory.RuntimeMethodHandle(methodToLookup), reason); - - MethodDesc concreteMethod = targetMethod; - targetMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); - if (targetMethod.RequiresInstMethodDescArg()) - { - _dependencies.Add(_factory.MethodGenericDictionary(concreteMethod), reason); - } } _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GVMLookupForSlot), reason); From 74c00722b8d76720366b05e8032128eefc30766d Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 5 Dec 2025 15:39:01 -0800 Subject: [PATCH 03/18] Create ShadowConcreteMethod for GVMs --- .../Compiler/DependencyAnalysis/RuntimeMethodHandleNode.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/RuntimeMethodHandleNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/RuntimeMethodHandleNode.cs index 7f5f3035ccbc70..3c3e14f174f281 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/RuntimeMethodHandleNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/RuntimeMethodHandleNode.cs @@ -55,6 +55,11 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact MethodDesc canonMethod = _targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); dependencies.Add(factory.GVMDependencies(canonMethod), "GVM dependencies for runtime method handle"); + if (_targetMethod != canonMethod) + { + dependencies.Add(factory.ShadowConcreteMethod(_targetMethod), "GVM concrete method dependencies"); + } + // GVM analysis happens on canonical forms, but this is potentially injecting new genericness // into the system. Ensure reflection analysis can still see this. if (_targetMethod.IsAbstract) From bfc47f26dce3ab2dad14bd597191388807b5e81d Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 9 Dec 2025 11:55:50 -0800 Subject: [PATCH 04/18] Revert "Create ShadowConcreteMethod for GVMs" This reverts commit 74c00722b8d76720366b05e8032128eefc30766d. --- .../Compiler/DependencyAnalysis/RuntimeMethodHandleNode.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/RuntimeMethodHandleNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/RuntimeMethodHandleNode.cs index 3c3e14f174f281..7f5f3035ccbc70 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/RuntimeMethodHandleNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/RuntimeMethodHandleNode.cs @@ -55,11 +55,6 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact MethodDesc canonMethod = _targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); dependencies.Add(factory.GVMDependencies(canonMethod), "GVM dependencies for runtime method handle"); - if (_targetMethod != canonMethod) - { - dependencies.Add(factory.ShadowConcreteMethod(_targetMethod), "GVM concrete method dependencies"); - } - // GVM analysis happens on canonical forms, but this is potentially injecting new genericness // into the system. Ensure reflection analysis can still see this. if (_targetMethod.IsAbstract) From 91418927186082098bfb1c41b2388ac7db4bf197 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 9 Dec 2025 11:58:33 -0800 Subject: [PATCH 05/18] Add ShadowGenericMethod for partially instantiated methods --- ...thodNode.cs => ShadowGenericMethodNode.cs} | 19 ++++++++------- .../GenericDictionaryNode.cs | 4 ++-- .../DependencyAnalysis/GenericLookupResult.cs | 23 ++++++++++++++++++ .../GenericVirtualMethodImplNode.cs | 2 ++ .../DependencyAnalysis/NodeFactory.cs | 24 +++++++++---------- .../ReadyToRunGenericHelperNode.cs | 4 ++++ .../ReflectionInvokeMapNode.cs | 1 + .../ShadowConcreteUnboxingThunkNode.cs | 2 +- .../ILCompiler.Compiler/Compiler/ILScanner.cs | 4 ++-- .../Compiler/MetadataManager.cs | 2 +- .../IL/ILImporter.Scanner.cs | 2 +- .../ILCompiler.Compiler.csproj | 2 +- .../ILCompiler.ReadyToRun.csproj | 2 +- .../Compiler/RyuJitCompilation.cs | 4 ++-- 14 files changed, 64 insertions(+), 31 deletions(-) rename src/coreclr/tools/Common/Compiler/DependencyAnalysis/{ShadowConcreteMethodNode.cs => ShadowGenericMethodNode.cs} (88%) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowConcreteMethodNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs similarity index 88% rename from src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowConcreteMethodNode.cs rename to src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs index 4480f9d3941035..1d0abbd5a9790a 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowConcreteMethodNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs @@ -13,13 +13,13 @@ namespace ILCompiler.DependencyAnalysis { /// - /// Represents a concrete method on a generic type (or a generic method) that doesn't + /// Represents a generic method on a generic type (or a generic method) that doesn't /// have code emitted in the executable because it's physically backed by a canonical - /// method body. The purpose of this node is to track the dependencies of the concrete + /// method body. The purpose of this node is to track the dependencies of the generic /// method body, as if it was generated. The node acts as a symbol for the canonical /// method for convenience. /// - public class ShadowConcreteMethodNode : DependencyNodeCore, IMethodNode, ISymbolNodeWithLinkage + public class ShadowGenericMethodNode : DependencyNodeCore, IMethodNode, ISymbolNodeWithLinkage { /// /// Gets the canonical method body that defines the dependencies of this node. @@ -27,7 +27,7 @@ public class ShadowConcreteMethodNode : DependencyNodeCore, IMethod public IMethodNode CanonicalMethodNode { get; } /// - /// Gets the concrete method represented by this node. + /// Gets the generic method represented by this node. /// public MethodDesc Method { get; } @@ -42,9 +42,12 @@ public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) public override bool StaticDependenciesAreComputed => CanonicalMethodNode.StaticDependenciesAreComputed; - public ShadowConcreteMethodNode(MethodDesc method, IMethodNode canonicalMethod) + public ShadowGenericMethodNode(MethodDesc method, IMethodNode canonicalMethod) { - Debug.Assert(!method.IsSharedByGenericInstantiations); + if (method.ToString().Contains("MyRepro")) + { + System.Console.Error.WriteLine("ShadowGenericMethodNode: " + method.ToString()); + } Debug.Assert(!method.IsRuntimeDeterminedExactMethod); Debug.Assert(canonicalMethod.Method.IsSharedByGenericInstantiations); Debug.Assert(canonicalMethod.Method == method.GetCanonMethodTarget(CanonicalFormKind.Specific)); @@ -121,11 +124,11 @@ public sealed override IEnumerable GetConditionalSt int ISortableNode.CompareToImpl(ISortableNode other, CompilerComparer comparer) { - var compare = comparer.Compare(Method, ((ShadowConcreteMethodNode)other).Method); + var compare = comparer.Compare(Method, ((ShadowGenericMethodNode)other).Method); if (compare != 0) return compare; - return comparer.Compare(CanonicalMethodNode, ((ShadowConcreteMethodNode)other).CanonicalMethodNode); + return comparer.Compare(CanonicalMethodNode, ((ShadowGenericMethodNode)other).CanonicalMethodNode); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDictionaryNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDictionaryNode.cs index 6239f46779fc17..f609251eb7cf8d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDictionaryNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDictionaryNode.cs @@ -157,7 +157,7 @@ public override IEnumerable GetConditionalStaticDep // If a canonical method body was compiled, we need to track the dictionary // dependencies in the context of the concrete type that owns this dictionary. yield return new CombinedDependencyListEntry( - factory.ShadowConcreteMethod(method), + factory.ShadowGenericMethod(method), factory.MethodEntrypoint(method.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Generic dictionary dependency"); } @@ -241,7 +241,7 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact } // Make sure the dictionary can also be populated - dependencies.Add(factory.ShadowConcreteMethod(_owningMethod), "Dictionary contents"); + dependencies.Add(factory.ShadowGenericMethod(_owningMethod), "Dictionary contents"); return dependencies; } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs index ea7849f02c3405..e502ad11e28b93 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs @@ -72,6 +72,17 @@ public abstract class GenericLookupResult { protected abstract int ClassCode { get; } public abstract ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary); + + /// + /// Returns true if this lookup result would produce a concrete (non-canonical) result + /// when instantiated with the given context. This is used to filter out dependencies + /// that would still be canonical after instantiation. + /// + public virtual bool LookupResultIsConcreteAfterInstantiation(GenericLookupResultContext dictionary) + { + return true; + } + public abstract void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb); public abstract override string ToString(); protected abstract int CompareToImpl(GenericLookupResult other, TypeSystemComparer comparer); @@ -504,6 +515,12 @@ public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultCo return factory.MethodGenericDictionary(instantiatedMethod); } + public override bool LookupResultIsConcreteAfterInstantiation(GenericLookupResultContext dictionary) + { + var instantiatedMethod = _method.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); + return !instantiatedMethod.IsSharedByGenericInstantiations; + } + public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append("MethodDictionary_"u8); @@ -685,6 +702,12 @@ public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultCo return factory.TypeNonGCStaticsSymbol(instantiatedType); } + public override bool LookupResultIsConcreteAfterInstantiation(GenericLookupResultContext dictionary) + { + var instantiatedType = _type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); + return !instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any); + } + public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append("NonGCStaticBase_"u8); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs index 6912c9358a18dc..45ae0771b2f3e1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs @@ -61,6 +61,8 @@ public override IEnumerable GetStaticDependencies(NodeFacto { dependencies.Add(factory.ExactMethodInstantiationsHashtableEntry(_method), "GVM Dependency - runtime lookups"); } + + dependencies.Add(factory.ShadowGenericMethod(_method), "GVM Dependency - shadow generic method"); } return dependencies; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 8cea4b6dee9aa9..746ce5ab28a249 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -400,7 +400,7 @@ private void CreateNodeCaches() return new ObjectGetTypeFlowDependenciesNode(type); }); - _shadowConcreteMethods = new ShadowConcreteMethodHashtable(this); + _shadowGenericMethods = new ShadowGenericMethodHashtable(this); _shadowConcreteUnboxingMethods = new NodeCache(method => { @@ -1141,7 +1141,7 @@ public IMethodNode CanonicalEntrypoint(MethodDesc method, bool isUnboxingStub = { MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific); if (method != canonMethod) - return ShadowConcreteMethod(method, isUnboxingStub); + return ShadowGenericMethod(method, isUnboxingStub); else return MethodEntrypoint(method, isUnboxingStub); } @@ -1249,26 +1249,26 @@ internal ObjectGetTypeFlowDependenciesNode ObjectGetTypeFlowDependencies(Metadat return _objectGetTypeFlowDependencies.GetOrAdd(type); } - private sealed class ShadowConcreteMethodHashtable : LockFreeReaderHashtable + private sealed class ShadowGenericMethodHashtable : LockFreeReaderHashtable { private readonly NodeFactory _factory; - public ShadowConcreteMethodHashtable(NodeFactory factory) => _factory = factory; - protected override bool CompareKeyToValue(MethodDesc key, ShadowConcreteMethodNode value) => key == value.Method; - protected override bool CompareValueToValue(ShadowConcreteMethodNode value1, ShadowConcreteMethodNode value2) => value1.Method == value2.Method; - protected override ShadowConcreteMethodNode CreateValueFromKey(MethodDesc key) => - new ShadowConcreteMethodNode(key, _factory.MethodEntrypoint(key.GetCanonMethodTarget(CanonicalFormKind.Specific))); + public ShadowGenericMethodHashtable(NodeFactory factory) => _factory = factory; + protected override bool CompareKeyToValue(MethodDesc key, ShadowGenericMethodNode value) => key == value.Method; + protected override bool CompareValueToValue(ShadowGenericMethodNode value1, ShadowGenericMethodNode value2) => value1.Method == value2.Method; + protected override ShadowGenericMethodNode CreateValueFromKey(MethodDesc key) => + new ShadowGenericMethodNode(key, _factory.MethodEntrypoint(key.GetCanonMethodTarget(CanonicalFormKind.Specific))); protected override int GetKeyHashCode(MethodDesc key) => key.GetHashCode(); - protected override int GetValueHashCode(ShadowConcreteMethodNode value) => value.Method.GetHashCode(); + protected override int GetValueHashCode(ShadowGenericMethodNode value) => value.Method.GetHashCode(); } - private ShadowConcreteMethodHashtable _shadowConcreteMethods; + private ShadowGenericMethodHashtable _shadowGenericMethods; private NodeCache _shadowConcreteUnboxingMethods; - public IMethodNode ShadowConcreteMethod(MethodDesc method, bool isUnboxingStub = false) + public IMethodNode ShadowGenericMethod(MethodDesc method, bool isUnboxingStub = false) { if (isUnboxingStub) return _shadowConcreteUnboxingMethods.GetOrAdd(method); else - return _shadowConcreteMethods.GetOrCreateValue(method); + return _shadowGenericMethods.GetOrCreateValue(method); } private static readonly string[][] s_helperEntrypointNames = new string[][] { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index 3ca290ed29b359..e440f1c71bd834 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -131,6 +131,10 @@ public IEnumerable InstantiateDependencies(NodeFactory fact DependencyList result = new DependencyList(); var lookupContext = new GenericLookupResultContext(_dictionaryOwner, typeInstantiation, methodInstantiation); + if (!_lookupSignature.LookupResultIsConcreteAfterInstantiation(lookupContext)) + { + return result.ToArray(); + } switch (_id) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs index f74267f9c26be4..3046ad606af7cf 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs @@ -65,6 +65,7 @@ public static void AddDependenciesDueToReflectability(ref DependencyList depende if (!method.IsAbstract) { + dependencies.Add(factory.ShadowGenericMethod(method), "Shadow generic reflectable method"); dependencies.Add(factory.AddressTakenMethodEntrypoint(method), "Body of a reflectable method"); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ShadowConcreteUnboxingThunkNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ShadowConcreteUnboxingThunkNode.cs index 3cc6c0ab953394..731bdff8c5eac6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ShadowConcreteUnboxingThunkNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ShadowConcreteUnboxingThunkNode.cs @@ -55,7 +55,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto dependencies.Add(new DependencyListEntry(_canonicalThunk, "Canonical body")); // Make sure the target of the thunk gets modeled as a dependency - dependencies.Add(new DependencyListEntry(factory.ShadowConcreteMethod(Method), "Unboxing thunk target")); + dependencies.Add(new DependencyListEntry(factory.ShadowGenericMethod(Method), "Unboxing thunk target")); return dependencies; } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs index adca2e9baa475c..55bbf83810e0e0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs @@ -63,8 +63,8 @@ protected override void ComputeDependencyNodeDependencies(List obj) _reflectableMethods.Add(methodNode.Method); } - methodNode ??= obj as ShadowConcreteMethodNode; + methodNode ??= obj as ShadowGenericMethodNode; if (methodNode != null) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index 8bb7c1f6eedd6c..10092cccf51b44 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -662,7 +662,7 @@ private void ImportCall(ILOpcode opcode, int token) _dependencies.Add(instParam, reason); } - _dependencies.Add(_factory.CanonicalEntrypoint(targetMethod), reason); + _dependencies.Add(_factory.ShadowGenericMethod(concreteMethod), reason); } else { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 86886d93c2baed..143225eb3aeb13 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -300,7 +300,7 @@ - + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 3251ff2660ebe3..e8d3381baa4e82 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -82,7 +82,7 @@ - + diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs index 98b214e2b4785a..8cc4486e68c845 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs @@ -132,8 +132,8 @@ protected override void ComputeDependencyNodeDependencies(List Date: Tue, 9 Dec 2025 11:59:26 -0800 Subject: [PATCH 06/18] Add testcase --- .../SmokeTests/UnitTests/Generics.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs index 5850a7343e1201..c52926aec56d28 100644 --- a/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs +++ b/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs @@ -55,6 +55,7 @@ internal static int Run() TestRecursionThroughGenericLookups.Run(); TestRecursionInFields.Run(); TestGvmLookupDependency.Run(); + TestGvmBaseCallDependencies.Run(); Test99198Regression.Run(); Test102259Regression.Run(); Test104913Regression.Run(); @@ -3824,6 +3825,44 @@ public static void Run() throw new Exception(); } } + + class TestGvmBaseCallDependencies + { + class Container + { + public static int MyStaticField; + } + + abstract class Base + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void TryInvoke() + { + Container.MyStaticField = 3; + } + } + + sealed class Derived : Base + { + public override void TryInvoke() + { + base.TryInvoke(); + } + } + + interface IFoo { } + + [MethodImpl(MethodImplOptions.NoInlining)] + static Base Get() => new Derived(); + + public static void Run() + { + // Testing for compilation failures due to missing GVM dependencies. + // See: https://github.com/dotnet/runtime/issues/120847 + Container.MyStaticField = 3; + Get().TryInvoke(); + } + } } static class Ext From 5d9b2bceee1f2afc0ce46560c653aaeb60cc5ce2 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 9 Dec 2025 16:48:49 -0800 Subject: [PATCH 07/18] Use existing InstantiateDependencies for generic lookups to create shadow nodes --- .../ShadowGenericMethodNode.cs | 5 - .../DependencyAnalysis/GenericLookupResult.cs | 22 ----- .../ReadyToRunGenericHelperNode.cs | 91 ++++++++++++++++++- .../ReflectionInvokeMapNode.cs | 5 +- .../IL/ILImporter.Scanner.cs | 2 +- .../Compiler/RyuJitCompilation.cs | 8 ++ 6 files changed, 102 insertions(+), 31 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs index 1d0abbd5a9790a..e08dd28da9e0c7 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs @@ -44,12 +44,7 @@ public override bool StaticDependenciesAreComputed public ShadowGenericMethodNode(MethodDesc method, IMethodNode canonicalMethod) { - if (method.ToString().Contains("MyRepro")) - { - System.Console.Error.WriteLine("ShadowGenericMethodNode: " + method.ToString()); - } Debug.Assert(!method.IsRuntimeDeterminedExactMethod); - Debug.Assert(canonicalMethod.Method.IsSharedByGenericInstantiations); Debug.Assert(canonicalMethod.Method == method.GetCanonMethodTarget(CanonicalFormKind.Specific)); Method = method; CanonicalMethodNode = canonicalMethod; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs index e502ad11e28b93..f6aedbcaa7ed39 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs @@ -73,16 +73,6 @@ public abstract class GenericLookupResult protected abstract int ClassCode { get; } public abstract ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary); - /// - /// Returns true if this lookup result would produce a concrete (non-canonical) result - /// when instantiated with the given context. This is used to filter out dependencies - /// that would still be canonical after instantiation. - /// - public virtual bool LookupResultIsConcreteAfterInstantiation(GenericLookupResultContext dictionary) - { - return true; - } - public abstract void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb); public abstract override string ToString(); protected abstract int CompareToImpl(GenericLookupResult other, TypeSystemComparer comparer); @@ -515,12 +505,6 @@ public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultCo return factory.MethodGenericDictionary(instantiatedMethod); } - public override bool LookupResultIsConcreteAfterInstantiation(GenericLookupResultContext dictionary) - { - var instantiatedMethod = _method.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); - return !instantiatedMethod.IsSharedByGenericInstantiations; - } - public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append("MethodDictionary_"u8); @@ -702,12 +686,6 @@ public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultCo return factory.TypeNonGCStaticsSymbol(instantiatedType); } - public override bool LookupResultIsConcreteAfterInstantiation(GenericLookupResultContext dictionary) - { - var instantiatedType = _type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); - return !instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any); - } - public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append("NonGCStaticBase_"u8); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index e440f1c71bd834..7090ca26a62324 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -10,6 +10,7 @@ using Internal.TypeSystem; using ILCompiler.DependencyAnalysisFramework; +using Internal.ReadyToRunConstants; namespace ILCompiler.DependencyAnalysis { @@ -126,14 +127,100 @@ private bool TriggersLazyStaticConstructor(NodeFactory factory) return factory.PreinitializationManager.HasLazyStaticConstructor(type.ConvertToCanonForm(CanonicalFormKind.Specific)); } + private static bool ContainsCanonicalTypes(Instantiation instantiation) + { + foreach (TypeDesc arg in instantiation) + { + if (arg.IsCanonicalSubtype(CanonicalFormKind.Any)) + return true; + } + return false; + } + public IEnumerable InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation) { DependencyList result = new DependencyList(); var lookupContext = new GenericLookupResultContext(_dictionaryOwner, typeInstantiation, methodInstantiation); - if (!_lookupSignature.LookupResultIsConcreteAfterInstantiation(lookupContext)) + + // Check if the instantiation is not fully concrete (contains canonical types like __Canon) + bool isNotConcreteInstantiation = ContainsCanonicalTypes(typeInstantiation) || ContainsCanonicalTypes(methodInstantiation); + + if (isNotConcreteInstantiation) { - return result.ToArray(); + switch (_id) + { + case ReadyToRunHelperId.MethodHandle: + case ReadyToRunHelperId.MethodDictionary: + case ReadyToRunHelperId.VirtualDispatchCell: + case ReadyToRunHelperId.MethodEntry: + { + MethodDesc instantiatedMethod = ((MethodDesc)_target).GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(typeInstantiation, methodInstantiation); + if (instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)) + { + if (!instantiatedMethod.IsAbstract) + { + result.Add(new DependencyListEntry( + factory.ShadowGenericMethod(instantiatedMethod), + "Partially instantiated generic dictionary dependencies")); + } + return result.ToArray(); + } + } + break; + + case ReadyToRunHelperId.TypeHandle: + case ReadyToRunHelperId.NecessaryTypeHandle: + case ReadyToRunHelperId.MetadataTypeHandle: + case ReadyToRunHelperId.TypeHandleForCasting: + case ReadyToRunHelperId.GetGCStaticBase: + case ReadyToRunHelperId.GetNonGCStaticBase: + case ReadyToRunHelperId.GetThreadStaticBase: + case ReadyToRunHelperId.DefaultConstructor: + case ReadyToRunHelperId.ObjectAllocator: + { + TypeDesc instantiatedType = ((TypeDesc)_target).GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(typeInstantiation, methodInstantiation); + if (instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return result.ToArray(); + } + } + break; + + case ReadyToRunHelperId.FieldHandle: + { + FieldDesc field = (FieldDesc)_target; + TypeDesc instantiatedOwningType = field.OwningType.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(typeInstantiation, methodInstantiation); + if (instantiatedOwningType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return result.ToArray(); + } + } + break; + + case ReadyToRunHelperId.DelegateCtor: + { + DelegateCreationInfo createInfo = (DelegateCreationInfo)_target; + MethodDesc instantiatedMethod = createInfo.PossiblyUnresolvedTargetMethod.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(typeInstantiation, methodInstantiation); + if (instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)) + { + return result.ToArray(); + } + } + break; + + case ReadyToRunHelperId.ConstrainedDirectCall: + { + ConstrainedCallInfo callInfo = (ConstrainedCallInfo)_target; + MethodDesc instantiatedMethod = callInfo.Method.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(typeInstantiation, methodInstantiation); + TypeDesc instantiatedConstrainedType = callInfo.ConstrainedType.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(typeInstantiation, methodInstantiation); + if (instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any) || instantiatedConstrainedType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return result.ToArray(); + } + } + break; + } } switch (_id) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs index 3046ad606af7cf..a75cf15a02aa6b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs @@ -65,7 +65,10 @@ public static void AddDependenciesDueToReflectability(ref DependencyList depende if (!method.IsAbstract) { - dependencies.Add(factory.ShadowGenericMethod(method), "Shadow generic reflectable method"); + if (method.IsSharedByGenericInstantiations) + { + dependencies.Add(factory.ShadowGenericMethod(method), "Shadow generic reflectable method"); + } dependencies.Add(factory.AddressTakenMethodEntrypoint(method), "Body of a reflectable method"); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index 10092cccf51b44..8bb7c1f6eedd6c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -662,7 +662,7 @@ private void ImportCall(ILOpcode opcode, int token) _dependencies.Add(instParam, reason); } - _dependencies.Add(_factory.ShadowGenericMethod(concreteMethod), reason); + _dependencies.Add(_factory.CanonicalEntrypoint(targetMethod), reason); } else { diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs index 8cc4486e68c845..7f2681745823f1 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs @@ -124,6 +124,7 @@ protected override void ComputeDependencyNodeDependencies(List(); var canonicalMethodsToCompile = new HashSet(); + var methodNodesToCompile = new HashSet(); foreach (DependencyNodeCore dependency in obj) { @@ -144,6 +145,13 @@ protected override void ComputeDependencyNodeDependencies(List Date: Wed, 10 Dec 2025 11:58:43 -0800 Subject: [PATCH 08/18] More tests --- .../SmokeTests/UnitTests/Generics.cs | 201 ++++++++++++++---- 1 file changed, 162 insertions(+), 39 deletions(-) diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs index c52926aec56d28..f153144f506923 100644 --- a/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs +++ b/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs @@ -55,7 +55,11 @@ internal static int Run() TestRecursionThroughGenericLookups.Run(); TestRecursionInFields.Run(); TestGvmLookupDependency.Run(); - TestGvmBaseCallDependencies.Run(); + TestGvmInliningDependencies.Run(); + TestGvmInliningWithAbstract.Run(); + TestGenericInliningWithReflection.Run(); + TestGenericInliningMethodGenericsOnly.Run(); + TestGenericInliningTypeGenericsOnly.Run(); Test99198Regression.Run(); Test102259Regression.Run(); Test104913Regression.Run(); @@ -3515,6 +3519,163 @@ public static void Run() } } + class TestGvmInliningDependencies + { + class Container + { + public static int MyStaticField; + } + + abstract class Base + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void VirtualMethod() + { + Container.MyStaticField = 3; + } + } + + sealed class Derived : Base + { + public override void VirtualMethod() + { + base.VirtualMethod(); + } + } + + interface IFoo { } + + [MethodImpl(MethodImplOptions.NoInlining)] + static Base Get() => new Derived(); + + public static void Run() + { + // Testing for compilation failures due to missing GVM dependencies. + // See: https://github.com/dotnet/runtime/issues/120847 + Container.MyStaticField = 3; + Get().VirtualMethod(); + } + } + + // Additional testcases for GVM inlining dependencies with different inheritance patterns. + // See: https://github.com/dotnet/runtime/issues/120847 + class TestGvmInliningWithAbstract + { + class Container + { + public static int MyStaticField; + } + + abstract class Base + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public abstract void VirtualMethod(); + } + + class Mid : Base + { + public override void VirtualMethod() + { + Container.MyStaticField = 3; + } + } + + sealed class Derived : Mid + { + public override void VirtualMethod() + { + base.VirtualMethod(); + } + } + + interface IFoo { } + + [MethodImpl(MethodImplOptions.NoInlining)] + static Base Get() => new Derived(); + + public static void Run() + { + Container.MyStaticField = 5; + Get().VirtualMethod(); + } + } + + // Testcases for generic inlining with reflection. + // See: https://github.com/dotnet/runtime/issues/120847 + class TestGenericInliningWithReflection + { + class Container { } + + class Base + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string BaseMethod() => new Container().ToString(); + } + + class Derived : Base + { + public string DerivedMethod() where T : class => BaseMethod(); + } + + static Type s_stringType = typeof(string); + + public static void Run() + { + object r = typeof(Derived).GetMethod(nameof(Derived.DerivedMethod)).MakeGenericMethod(s_stringType).Invoke(new Derived(), []); + if (r is null || !r.ToString().Contains("Container")) + throw new Exception(); + } + } + + class TestGenericInliningMethodGenericsOnly + { + class Container { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string CalleeMethod() => new Container().ToString(); + + public static string CallerMethod() => CalleeMethod(); + + static Type s_stringType = typeof(string); + + public static void Run() + { + var r = typeof(TestGenericInliningMethodGenericsOnly).GetMethod(nameof(CallerMethod)).MakeGenericMethod(s_stringType).Invoke(null, []); + if (r is null || !r.ToString().Contains("Container")) + throw new Exception(); + } + } + + class TestGenericInliningTypeGenericsOnly + { + class Container { } + + class Base + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string BaseMethod() + { + return new Container().ToString(); + } + } + + class Derived : Base + { + public string DerivedMethod() => base.BaseMethod(); + } + + static Type s_stringType = typeof(string); + + public static void Run() + { + var type = typeof(Derived<>).MakeGenericType(s_stringType); + var instance = Activator.CreateInstance(type); + var r = type.GetMethod("DerivedMethod").Invoke(instance, null); + if (r is null || !r.ToString().Contains("Container")) + throw new Exception(); + } + } + class Test99198Regression { delegate void Set(ref T t, IFoo ifoo); @@ -3825,44 +3986,6 @@ public static void Run() throw new Exception(); } } - - class TestGvmBaseCallDependencies - { - class Container - { - public static int MyStaticField; - } - - abstract class Base - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public virtual void TryInvoke() - { - Container.MyStaticField = 3; - } - } - - sealed class Derived : Base - { - public override void TryInvoke() - { - base.TryInvoke(); - } - } - - interface IFoo { } - - [MethodImpl(MethodImplOptions.NoInlining)] - static Base Get() => new Derived(); - - public static void Run() - { - // Testing for compilation failures due to missing GVM dependencies. - // See: https://github.com/dotnet/runtime/issues/120847 - Container.MyStaticField = 3; - Get().TryInvoke(); - } - } } static class Ext From 34787ec98e74cc7de6e78591c58a1aacd130efd5 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 12 Dec 2025 17:31:50 -0800 Subject: [PATCH 09/18] Track ShadowGenericMethod for direct call To fix case without method generic parameters --- .../DependencyAnalysis/NodeFactory.cs | 38 ++++++++++++++----- .../ReadyToRunGenericHelperNode.cs | 9 +++++ .../IL/ILImporter.Scanner.cs | 2 +- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 746ce5ab28a249..df4bc1b2d85b55 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -1137,13 +1137,13 @@ public IMethodNode ExactCallableAddressTakenAddress(MethodDesc method, bool isUn return AddressTakenMethodEntrypoint(method, isUnboxingStub); } - public IMethodNode CanonicalEntrypoint(MethodDesc method, bool isUnboxingStub = false) + public IMethodNode CanonicalEntrypoint(MethodDesc method) { MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific); if (method != canonMethod) - return ShadowGenericMethod(method, isUnboxingStub); + return ShadowGenericMethod(method); else - return MethodEntrypoint(method, isUnboxingStub); + return MethodEntrypoint(method); } private NodeCache _gvmDependenciesNode; @@ -1255,20 +1255,38 @@ private sealed class ShadowGenericMethodHashtable : LockFreeReaderHashtable _factory = factory; protected override bool CompareKeyToValue(MethodDesc key, ShadowGenericMethodNode value) => key == value.Method; protected override bool CompareValueToValue(ShadowGenericMethodNode value1, ShadowGenericMethodNode value2) => value1.Method == value2.Method; - protected override ShadowGenericMethodNode CreateValueFromKey(MethodDesc key) => - new ShadowGenericMethodNode(key, _factory.MethodEntrypoint(key.GetCanonMethodTarget(CanonicalFormKind.Specific))); + protected override ShadowGenericMethodNode CreateValueFromKey(MethodDesc key) + { + // Duplicate the normalization logic from CreateMethodEntrypointNode + if (key.IsInternalCall) + { + if (_factory.TypeSystemContext.IsSpecialUnboxingThunkTargetMethod(key)) + { + return _factory.ShadowGenericMethod(_factory.TypeSystemContext.GetRealSpecialUnboxingThunkTargetMethod(key)); + } + else if (_factory.TypeSystemContext.IsDefaultInterfaceMethodImplementationThunkTargetMethod(key)) + { + return _factory.ShadowGenericMethod(_factory.TypeSystemContext.GetRealDefaultInterfaceMethodImplementationThunkTargetMethod(key)); + } + else if (key.IsArrayAddressMethod()) + { + return _factory.ShadowGenericMethod(((ArrayType)key.OwningType).GetArrayMethod(ArrayMethodKind.AddressWithHiddenArg)); + } + } + + MethodDesc canonMethod = key.GetCanonMethodTarget(CanonicalFormKind.Specific); + var entry = _factory.MethodEntrypoint(canonMethod); + return new ShadowGenericMethodNode(key, entry); + } protected override int GetKeyHashCode(MethodDesc key) => key.GetHashCode(); protected override int GetValueHashCode(ShadowGenericMethodNode value) => value.Method.GetHashCode(); } private ShadowGenericMethodHashtable _shadowGenericMethods; private NodeCache _shadowConcreteUnboxingMethods; - public IMethodNode ShadowGenericMethod(MethodDesc method, bool isUnboxingStub = false) + public ShadowGenericMethodNode ShadowGenericMethod(MethodDesc method) { - if (isUnboxingStub) - return _shadowConcreteUnboxingMethods.GetOrAdd(method); - else - return _shadowGenericMethods.GetOrCreateValue(method); + return _shadowGenericMethods.GetOrCreateValue(method); } private static readonly string[][] s_helperEntrypointNames = new string[][] { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index 7090ca26a62324..00ea85268c83b9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -160,9 +160,18 @@ public IEnumerable InstantiateDependencies(NodeFactory fact { if (!instantiatedMethod.IsAbstract) { + try + { + factory.TypeSystemContext.DetectGenericCycles(lookupContext.Context, instantiatedMethod); result.Add(new DependencyListEntry( factory.ShadowGenericMethod(instantiatedMethod), "Partially instantiated generic dictionary dependencies")); + } + catch (TypeSystemException) + { + // It's fine to continue here - we will generate a bad slot helper when + // instantiating the dependencies of any concrete instantiations. + } } return result.ToArray(); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index 8bb7c1f6eedd6c..10092cccf51b44 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -662,7 +662,7 @@ private void ImportCall(ILOpcode opcode, int token) _dependencies.Add(instParam, reason); } - _dependencies.Add(_factory.CanonicalEntrypoint(targetMethod), reason); + _dependencies.Add(_factory.ShadowGenericMethod(concreteMethod), reason); } else { From f2a2fcc0470e138a51acdcc9a01a119a831b8740 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 12 Dec 2025 18:08:53 -0800 Subject: [PATCH 10/18] Revert normalization change No longer needed since the conversion of generic unboxing thunks to the underlying target method no longer happens here. --- .../Compiler/DependencyAnalysis/NodeFactory.cs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 153ecf1495302f..259cac546903ec 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -1271,21 +1271,8 @@ private sealed class ShadowGenericMethodHashtable : LockFreeReaderHashtable _factory = factory; protected override bool CompareKeyToValue(MethodDesc key, ShadowGenericMethodNode value) => key == value.Method; protected override bool CompareValueToValue(ShadowGenericMethodNode value1, ShadowGenericMethodNode value2) => value1.Method == value2.Method; - protected override ShadowGenericMethodNode CreateValueFromKey(MethodDesc key) - { - // Duplicate the normalization logic from CreateMethodEntrypointNode - if (key.IsInternalCall) - { - if (key.IsArrayAddressMethod()) - { - return _factory.ShadowGenericMethod(((ArrayType)key.OwningType).GetArrayMethod(ArrayMethodKind.AddressWithHiddenArg)); - } - } - - MethodDesc canonMethod = key.GetCanonMethodTarget(CanonicalFormKind.Specific); - var entry = _factory.MethodEntrypoint(canonMethod); - return new ShadowGenericMethodNode(key, entry); - } + protected override ShadowGenericMethodNode CreateValueFromKey(MethodDesc key) => + new ShadowGenericMethodNode(key, _factory.MethodEntrypoint(key.GetCanonMethodTarget(CanonicalFormKind.Specific))); protected override int GetKeyHashCode(MethodDesc key) => key.GetHashCode(); protected override int GetValueHashCode(ShadowGenericMethodNode value) => value.Method.GetHashCode(); } From 4d6fbdc8211cc8b3abee2c75f7e14ebc898e12dc Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 16 Dec 2025 11:13:16 -0800 Subject: [PATCH 11/18] Cleanup --- .../ShadowGenericMethodNode.cs | 1 + .../DependencyAnalysis/GenericLookupResult.cs | 1 - .../GenericVirtualMethodImplNode.cs | 2 +- .../ReadyToRunGenericHelperNode.cs | 18 ++++++++++++------ .../ILCompiler.Compiler/Compiler/ILScanner.cs | 4 ++-- .../Compiler/RyuJitCompilation.cs | 12 ++---------- 6 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs index e08dd28da9e0c7..b77490663dcd26 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs @@ -46,6 +46,7 @@ public ShadowGenericMethodNode(MethodDesc method, IMethodNode canonicalMethod) { Debug.Assert(!method.IsRuntimeDeterminedExactMethod); Debug.Assert(canonicalMethod.Method == method.GetCanonMethodTarget(CanonicalFormKind.Specific)); + Debug.Assert(canonicalMethod.Method.IsSharedByGenericInstantiations); Method = method; CanonicalMethodNode = canonicalMethod; } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs index f6aedbcaa7ed39..ea7849f02c3405 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs @@ -72,7 +72,6 @@ public abstract class GenericLookupResult { protected abstract int ClassCode { get; } public abstract ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary); - public abstract void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb); public abstract override string ToString(); protected abstract int CompareToImpl(GenericLookupResult other, TypeSystemComparer comparer); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs index 45ae0771b2f3e1..3e99424b598fc6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs @@ -56,13 +56,13 @@ public override IEnumerable GetStaticDependencies(NodeFacto { dependencies.Add(factory.NativeLayout.TemplateMethodEntry(_method), "GVM Dependency - Template entry"); dependencies.Add(factory.NativeLayout.TemplateMethodLayout(_method), "GVM Dependency - Template"); + dependencies.Add(factory.ShadowGenericMethod(_method), "GVM Dependency - shadow generic method"); } else { dependencies.Add(factory.ExactMethodInstantiationsHashtableEntry(_method), "GVM Dependency - runtime lookups"); } - dependencies.Add(factory.ShadowGenericMethod(_method), "GVM Dependency - shadow generic method"); } return dependencies; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index 00ea85268c83b9..7b1ad5d293e09e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -143,11 +143,13 @@ public IEnumerable InstantiateDependencies(NodeFactory fact var lookupContext = new GenericLookupResultContext(_dictionaryOwner, typeInstantiation, methodInstantiation); - // Check if the instantiation is not fully concrete (contains canonical types like __Canon) bool isNotConcreteInstantiation = ContainsCanonicalTypes(typeInstantiation) || ContainsCanonicalTypes(methodInstantiation); if (isNotConcreteInstantiation) { + // If this instantiation is not fully concrete, we may still need to track dependencies if the result + // of a generic substitution is concrete. Check the substitution for each helper type and if it is + // still canonical, we don't need to track any dependencies, except for shadow generic methods. switch (_id) { case ReadyToRunHelperId.MethodHandle: @@ -158,19 +160,20 @@ public IEnumerable InstantiateDependencies(NodeFactory fact MethodDesc instantiatedMethod = ((MethodDesc)_target).GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(typeInstantiation, methodInstantiation); if (instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)) { + // The substituted method is still not concrete, but it may have concrete dependencies + // so we track it as a shadow node to ensure its dependencies are discovered. if (!instantiatedMethod.IsAbstract) { try { factory.TypeSystemContext.DetectGenericCycles(lookupContext.Context, instantiatedMethod); - result.Add(new DependencyListEntry( - factory.ShadowGenericMethod(instantiatedMethod), - "Partially instantiated generic dictionary dependencies")); + result.Add(new DependencyListEntry( + factory.ShadowGenericMethod(instantiatedMethod), + "Partially instantiated generic dictionary dependencies")); } catch (TypeSystemException) { - // It's fine to continue here - we will generate a bad slot helper when - // instantiating the dependencies of any concrete instantiations. + // Don't track any dependencies if we hit a generic cycle. } } return result.ToArray(); @@ -229,6 +232,9 @@ public IEnumerable InstantiateDependencies(NodeFactory fact } } break; + + default: + throw new NotImplementedException(); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs index a9f4a0cb02bd56..67099f9c90d614 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs @@ -63,8 +63,8 @@ protected override void ComputeDependencyNodeDependencies(List(); var canonicalMethodsToCompile = new HashSet(); - var methodNodesToCompile = new HashSet(); foreach (DependencyNodeCore dependency in obj) { @@ -133,8 +132,8 @@ protected override void ComputeDependencyNodeDependencies(List Date: Tue, 16 Dec 2025 11:59:11 -0800 Subject: [PATCH 12/18] Update src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs index 3e99424b598fc6..d85dd16d6a300a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs @@ -62,7 +62,6 @@ public override IEnumerable GetStaticDependencies(NodeFacto { dependencies.Add(factory.ExactMethodInstantiationsHashtableEntry(_method), "GVM Dependency - runtime lookups"); } - } return dependencies; From 7b519341309ea284f97fef5cad0d1bc160368a76 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 17 Dec 2025 15:43:03 -0800 Subject: [PATCH 13/18] Feedback - Split ShadowGenericMethodNode into: - common base class ShadowMethodNode - existing ShadowConcreteMethodNode - new ShadowNonConcreteMethodNode with specific asserts in the derived classes - fix comment - pass isConcreteInstantiation through InstantiateDependencies and GetTarget - limit creation of ShadowNonConcreteMethodNode to where AcquiresInstMethodTableFromThis is true - move instantiation logic for non-concrete instantiations into GetTarget implementations --- .../INodeWithRuntimeDeterminedDependencies.cs | 2 +- .../ShadowConcreteMethodNode.cs | 38 ++++ ...nericMethodNode.cs => ShadowMethodNode.cs} | 24 +-- .../ShadowNonConcreteMethodNode.cs | 38 ++++ .../Compiler/Dataflow/HandleCallAction.cs | 6 +- .../DataflowAnalyzedMethodNode.cs | 2 +- .../GenericDictionaryNode.cs | 4 +- .../DependencyAnalysis/GenericLookupResult.cs | 196 +++++++++++++++--- .../GenericVirtualMethodImplNode.cs | 2 +- .../DependencyAnalysis/NodeFactory.cs | 44 ++-- .../ReadyToRunGenericHelperNode.cs | 102 +-------- .../ReflectionInvokeMapNode.cs | 2 +- .../ILCompiler.Compiler/Compiler/ILScanner.cs | 2 +- .../Compiler/MetadataManager.cs | 2 +- .../IL/ILImporter.Scanner.cs | 6 +- .../ILCompiler.Compiler.csproj | 4 +- .../ILCompiler.ReadyToRun.csproj | 3 +- .../Compiler/RyuJitCompilation.cs | 2 +- 18 files changed, 308 insertions(+), 171 deletions(-) create mode 100644 src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowConcreteMethodNode.cs rename src/coreclr/tools/Common/Compiler/DependencyAnalysis/{ShadowGenericMethodNode.cs => ShadowMethodNode.cs} (87%) create mode 100644 src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowNonConcreteMethodNode.cs diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/INodeWithRuntimeDeterminedDependencies.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/INodeWithRuntimeDeterminedDependencies.cs index 645835a365d4cd..d6e5f5445c37a2 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/INodeWithRuntimeDeterminedDependencies.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/INodeWithRuntimeDeterminedDependencies.cs @@ -17,6 +17,6 @@ public interface INodeWithRuntimeDeterminedDependencies /// /// Instantiates runtime determined dependencies of this node using the supplied generic context. /// - IEnumerable InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation); + IEnumerable InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation, bool isConcreteInstantiation); } } diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowConcreteMethodNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowConcreteMethodNode.cs new file mode 100644 index 00000000000000..d30a2b0831ba44 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowConcreteMethodNode.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +using ILCompiler.DependencyAnalysisFramework; + +using Internal.Text; +using Internal.TypeSystem; + +using Debug = System.Diagnostics.Debug; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// Represents a concrete method (fully instantiated) for the purpose of + /// tracking dependencies. + /// + public class ShadowConcreteMethodNode : ShadowMethodNode, IMethodNode, ISymbolNodeWithLinkage + { + public ShadowConcreteMethodNode(MethodDesc method, IMethodNode canonicalMethod) + : base(method, canonicalMethod) + { + Debug.Assert(!method.IsSharedByGenericInstantiations); + } + + protected override int ClassCode => -1440570971; + + protected override int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + var compare = comparer.Compare(Method, ((ShadowConcreteMethodNode)other).Method); + if (compare != 0) + return compare; + + return comparer.Compare(CanonicalMethodNode, ((ShadowConcreteMethodNode)other).CanonicalMethodNode); + } + } +} diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowMethodNode.cs similarity index 87% rename from src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs rename to src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowMethodNode.cs index b77490663dcd26..ba0b8280fb0b30 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowGenericMethodNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowMethodNode.cs @@ -13,13 +13,13 @@ namespace ILCompiler.DependencyAnalysis { /// - /// Represents a generic method on a generic type (or a generic method) that doesn't + /// Represents a method on a generic type (or a generic method) that doesn't /// have code emitted in the executable because it's physically backed by a canonical /// method body. The purpose of this node is to track the dependencies of the generic /// method body, as if it was generated. The node acts as a symbol for the canonical /// method for convenience. /// - public class ShadowGenericMethodNode : DependencyNodeCore, IMethodNode, ISymbolNodeWithLinkage + public abstract class ShadowMethodNode : DependencyNodeCore, IMethodNode, ISymbolNodeWithLinkage { /// /// Gets the canonical method body that defines the dependencies of this node. @@ -42,7 +42,7 @@ public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) public override bool StaticDependenciesAreComputed => CanonicalMethodNode.StaticDependenciesAreComputed; - public ShadowGenericMethodNode(MethodDesc method, IMethodNode canonicalMethod) + public ShadowMethodNode(MethodDesc method, IMethodNode canonicalMethod) { Debug.Assert(!method.IsRuntimeDeterminedExactMethod); Debug.Assert(canonicalMethod.Method == method.GetCanonMethodTarget(CanonicalFormKind.Specific)); @@ -76,7 +76,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto var runtimeDep = canonDep.Node as INodeWithRuntimeDeterminedDependencies; if (runtimeDep != null) { - dependencies.AddRange(runtimeDep.InstantiateDependencies(factory, typeInst, methodInst)); + dependencies.AddRange(runtimeDep.InstantiateDependencies(factory, typeInst, methodInst, isConcreteInstantiation: !Method.IsSharedByGenericInstantiations)); } } } @@ -101,13 +101,14 @@ public sealed override IEnumerable GetConditionalSt var node = canonDep.Node; if (node is INodeWithRuntimeDeterminedDependencies runtimeDeterminedNode) { - foreach (var nodeInner in runtimeDeterminedNode.InstantiateDependencies(factory, typeInst, methodInst)) + foreach (var nodeInner in runtimeDeterminedNode.InstantiateDependencies(factory, typeInst, methodInst, isConcreteInstantiation: !Method.IsSharedByGenericInstantiations)) yield return new CombinedDependencyListEntry(nodeInner.Node, canonDep.OtherReasonNode, nodeInner.Reason); } } } } + protected override string GetName(NodeFactory factory) => $"{Method} backed by {CanonicalMethodNode.GetMangledName(factory.NameMangler)}"; public sealed override bool HasConditionalStaticDependencies => CanonicalMethodNode.HasConditionalStaticDependencies; @@ -116,15 +117,12 @@ public sealed override IEnumerable GetConditionalSt public sealed override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory factory) => null; - int ISortableNode.ClassCode => -1440570971; + int ISortableNode.ClassCode => ClassCode; - int ISortableNode.CompareToImpl(ISortableNode other, CompilerComparer comparer) - { - var compare = comparer.Compare(Method, ((ShadowGenericMethodNode)other).Method); - if (compare != 0) - return compare; + protected abstract int ClassCode { get; } - return comparer.Compare(CanonicalMethodNode, ((ShadowGenericMethodNode)other).CanonicalMethodNode); - } + int ISortableNode.CompareToImpl(ISortableNode other, CompilerComparer comparer) => CompareToImpl(other, comparer); + + protected abstract int CompareToImpl(ISortableNode other, CompilerComparer comparer); } } diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowNonConcreteMethodNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowNonConcreteMethodNode.cs new file mode 100644 index 00000000000000..2163f0093eff7b --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowNonConcreteMethodNode.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +using ILCompiler.DependencyAnalysisFramework; + +using Internal.Text; +using Internal.TypeSystem; + +using Debug = System.Diagnostics.Debug; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// Represents a non-concrete method (not fully instantiated) for the purpose of + /// tracking dependencies. + /// + public class ShadowNonConcreteMethodNode : ShadowMethodNode, IMethodNode, ISymbolNodeWithLinkage + { + public ShadowNonConcreteMethodNode(MethodDesc method, IMethodNode canonicalMethod) + : base(method, canonicalMethod) + { + Debug.Assert(method.IsSharedByGenericInstantiations); + } + + protected override int ClassCode => 2120942405; + + protected override int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + var compare = comparer.Compare(Method, ((ShadowNonConcreteMethodNode)other).Method); + if (compare != 0) + return compare; + + return comparer.Compare(CanonicalMethodNode, ((ShadowNonConcreteMethodNode)other).CanonicalMethodNode); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs index 95895cacff11d8..71271952e2b11c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs @@ -749,8 +749,9 @@ private sealed class MakeGenericMethodSite : INodeWithRuntimeDeterminedDependenc public MakeGenericMethodSite(MethodDesc method) => _method = method; - public IEnumerable.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation) + public IEnumerable.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation, bool isConcreteInstantiation) { + Debug.Assert(isConcreteInstantiation); var list = new DependencyList(); MethodDesc instantiatedMethod = _method.InstantiateSignature(typeInstantiation, methodInstantiation); if (instantiatedMethod.CheckConstraints(new InstantiationContext(typeInstantiation, methodInstantiation))) @@ -765,8 +766,9 @@ private sealed class MakeGenericTypeSite : INodeWithRuntimeDeterminedDependencie public MakeGenericTypeSite(TypeDesc type) => _type = type; - public IEnumerable.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation) + public IEnumerable.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation, bool isConcreteInstantiotion) { + Debug.Assert(isConcreteInstantiotion); var list = new DependencyList(); TypeDesc instantiatedType = _type.InstantiateSignature(typeInstantiation, methodInstantiation); if (instantiatedType.CheckConstraints(new InstantiationContext(typeInstantiation, methodInstantiation))) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs index 6a6e2e0b9fb697..026b9a54028948 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs @@ -61,7 +61,7 @@ public override IEnumerable SearchDynamicDependenci // Instantiate all runtime dependencies for the found generic specialization foreach (var n in _runtimeDependencies) { - foreach (var d in n.InstantiateDependencies(factory, method.OwningType.Instantiation, method.Instantiation)) + foreach (var d in n.InstantiateDependencies(factory, method.OwningType.Instantiation, method.Instantiation, isConcreteInstantiation: !method.IsSharedByGenericInstantiations)) { yield return new CombinedDependencyListEntry(d.Node, null, d.Reason); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDictionaryNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDictionaryNode.cs index 4247a163e109f4..be8571870a5c53 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDictionaryNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDictionaryNode.cs @@ -157,7 +157,7 @@ public override IEnumerable GetConditionalStaticDep // If a canonical method body was compiled, we need to track the dictionary // dependencies in the context of the concrete type that owns this dictionary. yield return new CombinedDependencyListEntry( - factory.ShadowGenericMethod(method), + factory.ShadowConcreteMethod(method), factory.MethodEntrypoint(method.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Generic dictionary dependency"); } @@ -241,7 +241,7 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact } // Make sure the dictionary can also be populated - dependencies.Add(factory.ShadowGenericMethod(_owningMethod), "Dictionary contents"); + dependencies.Add(factory.ShadowConcreteMethod(_owningMethod), "Dictionary contents"); return dependencies; } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs index ea7849f02c3405..df04eee0b09ab2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs @@ -71,7 +71,7 @@ public GenericLookupResultContext(TypeSystemEntity canonicalOwner, Instantiation public abstract class GenericLookupResult { protected abstract int ClassCode { get; } - public abstract ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary); + public abstract ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation); public abstract void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb); public abstract override string ToString(); protected abstract int CompareToImpl(GenericLookupResult other, TypeSystemComparer comparer); @@ -92,12 +92,33 @@ public sealed override int GetHashCode() return ClassCode * 31 + GetHashCodeImpl(); } + private static bool InstantiationIsConcrete(Instantiation instantiation) + { + if (instantiation.IsNull || instantiation.Length == 0) + { + return true; + } + + for (int i = 0; i < instantiation.Length; i++) + { + TypeDesc argument = instantiation[i]; + if (argument.IsRuntimeDeterminedSubtype || argument.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return false; + } + } + + return true; + } + public virtual void EmitDictionaryEntry(ref ObjectDataBuilder builder, NodeFactory factory, GenericLookupResultContext dictionary, GenericDictionaryNode dictionaryNode) { ISymbolNode target; try { - target = GetTarget(factory, dictionary); + Debug.Assert(InstantiationIsConcrete(dictionary.TypeInstantiation)); + Debug.Assert(InstantiationIsConcrete(dictionary.MethodInstantiation)); + target = GetTarget(factory, dictionary, isConcreteInstantiation: true); } catch (TypeSystemException) { @@ -175,11 +196,15 @@ public TypeHandleGenericLookupResult(TypeDesc type) _type = type; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { // We are getting a maximally constructable type symbol because this might be something passed to newobj. TypeDesc instantiatedType = _type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); - + if (!isConcreteInstantiation && instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return null; + } + Debug.Assert(!instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)); factory.TypeSystemContext.DetectGenericCycles(dictionary.Context, instantiatedType); return factory.MaximallyConstructableType(instantiatedType); @@ -230,10 +255,14 @@ public NecessaryTypeHandleGenericLookupResult(TypeDesc type) _type = type; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { TypeDesc instantiatedType = _type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); - + if (!isConcreteInstantiation && instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return null; + } + Debug.Assert(!instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)); factory.TypeSystemContext.DetectGenericCycles(dictionary.Context, instantiatedType); return factory.NecessaryTypeSymbol(instantiatedType); @@ -284,10 +313,14 @@ public MetadataTypeHandleGenericLookupResult(TypeDesc type) _type = type; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { TypeDesc instantiatedType = _type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); - + if (!isConcreteInstantiation && instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return null; + } + Debug.Assert(!instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)); factory.TypeSystemContext.DetectGenericCycles(dictionary.Context, instantiatedType); return factory.MetadataTypeSymbol(instantiatedType); @@ -338,7 +371,7 @@ public UnwrapNullableTypeHandleGenericLookupResult(TypeDesc type) _type = type; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { TypeDesc instantiatedType = _type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); @@ -346,6 +379,12 @@ public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultCo if (instantiatedType.IsNullable) instantiatedType = instantiatedType.Instantiation[0]; + if (!isConcreteInstantiation && instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return null; + } + Debug.Assert(!instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)); + // We are getting a constructed type symbol because this might be something passed to newobj. return factory.ConstructedTypeSymbol(instantiatedType); } @@ -395,10 +434,25 @@ public MethodHandleGenericLookupResult(MethodDesc method) _method = method; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { MethodDesc instantiatedMethod = _method.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); - return factory.RuntimeMethodHandle(instantiatedMethod); + if (isConcreteInstantiation || !instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)) + { + return factory.RuntimeMethodHandle(instantiatedMethod); + } + else + { + Debug.Assert(instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)); + // The substituted method is still not concrete, but it may have concrete dependencies + // so we track it as a shadow node to ensure its dependencies are discovered. + if (!instantiatedMethod.IsAbstract) + { + factory.TypeSystemContext.DetectGenericCycles(dictionary.Context, instantiatedMethod); + return factory.ShadowNonConcreteMethod(instantiatedMethod); + } + return null; + } } public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) @@ -445,9 +499,15 @@ public FieldHandleGenericLookupResult(FieldDesc field) _field = field; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { FieldDesc instantiatedField = _field.GetNonRuntimeDeterminedFieldFromRuntimeDeterminedFieldViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); + if (!isConcreteInstantiation && instantiatedField.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return null; + } + Debug.Assert(!instantiatedField.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any)); + return factory.RuntimeFieldHandle(instantiatedField); } @@ -495,13 +555,23 @@ public MethodDictionaryGenericLookupResult(MethodDesc method) _method = method; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { MethodDesc instantiatedMethod = _method.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); - factory.TypeSystemContext.DetectGenericCycles(dictionary.Context, instantiatedMethod); - - return factory.MethodGenericDictionary(instantiatedMethod); + if (isConcreteInstantiation || !instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)) + { + return factory.MethodGenericDictionary(instantiatedMethod); + } + else + { + Debug.Assert(instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)); + if (!instantiatedMethod.IsAbstract) + { + return factory.ShadowNonConcreteMethod(instantiatedMethod); + } + return null; + } } public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) @@ -551,11 +621,24 @@ public MethodEntryGenericLookupResult(MethodDesc method, bool isUnboxingThunk) _isUnboxingThunk = isUnboxingThunk; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { MethodDesc instantiatedMethod = _method.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); - // TODO-SIZE: this is address taken only in the delegate target case - return factory.FatAddressTakenFunctionPointer(instantiatedMethod, _isUnboxingThunk); + if (isConcreteInstantiation || !instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)) + { + // TODO-SIZE: this is address taken only in the delegate target case + return factory.FatAddressTakenFunctionPointer(instantiatedMethod, _isUnboxingThunk); + } + else + { + Debug.Assert(instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)); + if (!instantiatedMethod.IsAbstract) + { + factory.TypeSystemContext.DetectGenericCycles(dictionary.Context, instantiatedMethod); + return factory.ShadowNonConcreteMethod(instantiatedMethod); + } + return null; + } } public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) @@ -621,17 +704,29 @@ public VirtualDispatchCellGenericLookupResult(MethodDesc method) _method = method; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext context) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext context, bool isConcreteInstantiation) { MethodDesc instantiatedMethod = _method.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(context.TypeInstantiation, context.MethodInstantiation); + if (isConcreteInstantiation || !instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)) + { + TypeSystemEntity contextOwner = context.Context; + GenericDictionaryNode dictionary = + contextOwner is TypeDesc ? + (GenericDictionaryNode)factory.TypeGenericDictionary((TypeDesc)contextOwner) : + (GenericDictionaryNode)factory.MethodGenericDictionary((MethodDesc)contextOwner); - TypeSystemEntity contextOwner = context.Context; - GenericDictionaryNode dictionary = - contextOwner is TypeDesc ? - (GenericDictionaryNode)factory.TypeGenericDictionary((TypeDesc)contextOwner) : - (GenericDictionaryNode)factory.MethodGenericDictionary((MethodDesc)contextOwner); - - return factory.InterfaceDispatchCell(instantiatedMethod, dictionary); + return factory.InterfaceDispatchCell(instantiatedMethod, dictionary); + } + else + { + Debug.Assert(instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)); + if (!instantiatedMethod.IsAbstract) + { + factory.TypeSystemContext.DetectGenericCycles(context.Context, instantiatedMethod); + return factory.ShadowNonConcreteMethod(instantiatedMethod); + } + return null; + } } public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) @@ -679,9 +774,15 @@ public TypeNonGCStaticBaseGenericLookupResult(TypeDesc type) _type = (MetadataType)type; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { var instantiatedType = (MetadataType)_type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); + if (!isConcreteInstantiation && instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return null; + } + Debug.Assert(!instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)); + return factory.TypeNonGCStaticsSymbol(instantiatedType); } @@ -730,9 +831,14 @@ public TypeThreadStaticBaseIndexGenericLookupResult(TypeDesc type) _type = (MetadataType)type; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { var instantiatedType = (MetadataType)_type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); + if (!isConcreteInstantiation && instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return null; + } + Debug.Assert(!instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)); return factory.TypeThreadStaticIndex(instantiatedType); } @@ -781,9 +887,14 @@ public TypeGCStaticBaseGenericLookupResult(TypeDesc type) _type = (MetadataType)type; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { var instantiatedType = (MetadataType)_type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); + if (!isConcreteInstantiation && instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return null; + } + Debug.Assert(!instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)); return factory.TypeGCStaticsSymbol(instantiatedType); } @@ -832,9 +943,14 @@ public ObjectAllocatorGenericLookupResult(TypeDesc type) _type = type; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { TypeDesc instantiatedType = _type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); + if (!isConcreteInstantiation && instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return null; + } + Debug.Assert(!instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)); return factory.ExternFunctionSymbol(JitHelper.GetNewObjectHelperForType(instantiatedType)); } @@ -879,9 +995,14 @@ public DefaultConstructorLookupResult(TypeDesc type) _type = type; } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { TypeDesc instantiatedType = _type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); + if (!isConcreteInstantiation && instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return null; + } + Debug.Assert(!instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)); MethodDesc defaultCtor = Compilation.GetConstructorForCreateInstanceIntrinsic(instantiatedType); return instantiatedType.IsValueType ? factory.ExactCallableAddress(defaultCtor) : factory.CanonicalEntrypoint(defaultCtor); } @@ -949,10 +1070,19 @@ public override IEnumerable> NonRelocDependencie } } - public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary, bool isConcreteInstantiation) { MethodDesc instantiatedConstrainedMethod = _constrainedMethod.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); TypeDesc instantiatedConstraintType = _constraintType.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); + if (!isConcreteInstantiation && + (instantiatedConstrainedMethod.IsCanonicalMethod(CanonicalFormKind.Any) || + instantiatedConstraintType.IsCanonicalSubtype(CanonicalFormKind.Any))) + { + return null; + } + Debug.Assert(!instantiatedConstrainedMethod.IsCanonicalMethod(CanonicalFormKind.Any)); + Debug.Assert(!instantiatedConstraintType.IsCanonicalSubtype(CanonicalFormKind.Any)); + MethodDesc implMethod; MethodDesc instantiatedConstrainedMethodDefinition = instantiatedConstrainedMethod.GetMethodDefinition(); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs index d85dd16d6a300a..98615a90ce980b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodImplNode.cs @@ -56,7 +56,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto { dependencies.Add(factory.NativeLayout.TemplateMethodEntry(_method), "GVM Dependency - Template entry"); dependencies.Add(factory.NativeLayout.TemplateMethodLayout(_method), "GVM Dependency - Template"); - dependencies.Add(factory.ShadowGenericMethod(_method), "GVM Dependency - shadow generic method"); + dependencies.Add(factory.ShadowNonConcreteMethod(_method), "GVM Dependency - shadow generic method"); } else { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 259cac546903ec..3fa80161992a65 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -408,7 +408,9 @@ private void CreateNodeCaches() return new ObjectGetTypeFlowDependenciesNode(type); }); - _shadowGenericMethods = new ShadowGenericMethodHashtable(this); + _shadowConcreteMethods = new ShadowConcreteMethodHashtable(this); + + _shadowNonConcreteMethods = new ShadowNonConcreteMethodHashtable(this); _virtMethods = new VirtualMethodUseHashtable(this); @@ -1157,7 +1159,7 @@ public IMethodNode CanonicalEntrypoint(MethodDesc method) { MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific); if (method != canonMethod) - return ShadowGenericMethod(method); + return ShadowConcreteMethod(method); else return MethodEntrypoint(method); } @@ -1265,22 +1267,40 @@ internal ObjectGetTypeFlowDependenciesNode ObjectGetTypeFlowDependencies(Metadat return _objectGetTypeFlowDependencies.GetOrAdd(type); } - private sealed class ShadowGenericMethodHashtable : LockFreeReaderHashtable + private sealed class ShadowConcreteMethodHashtable : LockFreeReaderHashtable + { + private readonly NodeFactory _factory; + public ShadowConcreteMethodHashtable(NodeFactory factory) => _factory = factory; + protected override bool CompareKeyToValue(MethodDesc key, ShadowConcreteMethodNode value) => key == value.Method; + protected override bool CompareValueToValue(ShadowConcreteMethodNode value1, ShadowConcreteMethodNode value2) => value1.Method == value2.Method; + protected override ShadowConcreteMethodNode CreateValueFromKey(MethodDesc key) => + new ShadowConcreteMethodNode(key, _factory.MethodEntrypoint(key.GetCanonMethodTarget(CanonicalFormKind.Specific))); + protected override int GetKeyHashCode(MethodDesc key) => key.GetHashCode(); + protected override int GetValueHashCode(ShadowConcreteMethodNode value) => value.Method.GetHashCode(); + } + + private ShadowConcreteMethodHashtable _shadowConcreteMethods; + public ShadowConcreteMethodNode ShadowConcreteMethod(MethodDesc method) + { + return _shadowConcreteMethods.GetOrCreateValue(method); + } + + private sealed class ShadowNonConcreteMethodHashtable : LockFreeReaderHashtable { private readonly NodeFactory _factory; - public ShadowGenericMethodHashtable(NodeFactory factory) => _factory = factory; - protected override bool CompareKeyToValue(MethodDesc key, ShadowGenericMethodNode value) => key == value.Method; - protected override bool CompareValueToValue(ShadowGenericMethodNode value1, ShadowGenericMethodNode value2) => value1.Method == value2.Method; - protected override ShadowGenericMethodNode CreateValueFromKey(MethodDesc key) => - new ShadowGenericMethodNode(key, _factory.MethodEntrypoint(key.GetCanonMethodTarget(CanonicalFormKind.Specific))); + public ShadowNonConcreteMethodHashtable(NodeFactory factory) => _factory = factory; + protected override bool CompareKeyToValue(MethodDesc key, ShadowNonConcreteMethodNode value) => key == value.Method; + protected override bool CompareValueToValue(ShadowNonConcreteMethodNode value1, ShadowNonConcreteMethodNode value2) => value1.Method == value2.Method; + protected override ShadowNonConcreteMethodNode CreateValueFromKey(MethodDesc key) => + new ShadowNonConcreteMethodNode(key, _factory.MethodEntrypoint(key.GetCanonMethodTarget(CanonicalFormKind.Specific))); protected override int GetKeyHashCode(MethodDesc key) => key.GetHashCode(); - protected override int GetValueHashCode(ShadowGenericMethodNode value) => value.Method.GetHashCode(); + protected override int GetValueHashCode(ShadowNonConcreteMethodNode value) => value.Method.GetHashCode(); } - private ShadowGenericMethodHashtable _shadowGenericMethods; - public ShadowGenericMethodNode ShadowGenericMethod(MethodDesc method) + private ShadowNonConcreteMethodHashtable _shadowNonConcreteMethods; + public ShadowNonConcreteMethodNode ShadowNonConcreteMethod(MethodDesc method) { - return _shadowGenericMethods.GetOrCreateValue(method); + return _shadowNonConcreteMethods.GetOrCreateValue(method); } private static readonly string[][] s_helperEntrypointNames = new string[][] { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index 7b1ad5d293e09e..108e5b920926da 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -137,107 +137,11 @@ private static bool ContainsCanonicalTypes(Instantiation instantiation) return false; } - public IEnumerable InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation) + public IEnumerable InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation, bool isConcreteInstantiation) { DependencyList result = new DependencyList(); - var lookupContext = new GenericLookupResultContext(_dictionaryOwner, typeInstantiation, methodInstantiation); - bool isNotConcreteInstantiation = ContainsCanonicalTypes(typeInstantiation) || ContainsCanonicalTypes(methodInstantiation); - - if (isNotConcreteInstantiation) - { - // If this instantiation is not fully concrete, we may still need to track dependencies if the result - // of a generic substitution is concrete. Check the substitution for each helper type and if it is - // still canonical, we don't need to track any dependencies, except for shadow generic methods. - switch (_id) - { - case ReadyToRunHelperId.MethodHandle: - case ReadyToRunHelperId.MethodDictionary: - case ReadyToRunHelperId.VirtualDispatchCell: - case ReadyToRunHelperId.MethodEntry: - { - MethodDesc instantiatedMethod = ((MethodDesc)_target).GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(typeInstantiation, methodInstantiation); - if (instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)) - { - // The substituted method is still not concrete, but it may have concrete dependencies - // so we track it as a shadow node to ensure its dependencies are discovered. - if (!instantiatedMethod.IsAbstract) - { - try - { - factory.TypeSystemContext.DetectGenericCycles(lookupContext.Context, instantiatedMethod); - result.Add(new DependencyListEntry( - factory.ShadowGenericMethod(instantiatedMethod), - "Partially instantiated generic dictionary dependencies")); - } - catch (TypeSystemException) - { - // Don't track any dependencies if we hit a generic cycle. - } - } - return result.ToArray(); - } - } - break; - - case ReadyToRunHelperId.TypeHandle: - case ReadyToRunHelperId.NecessaryTypeHandle: - case ReadyToRunHelperId.MetadataTypeHandle: - case ReadyToRunHelperId.TypeHandleForCasting: - case ReadyToRunHelperId.GetGCStaticBase: - case ReadyToRunHelperId.GetNonGCStaticBase: - case ReadyToRunHelperId.GetThreadStaticBase: - case ReadyToRunHelperId.DefaultConstructor: - case ReadyToRunHelperId.ObjectAllocator: - { - TypeDesc instantiatedType = ((TypeDesc)_target).GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(typeInstantiation, methodInstantiation); - if (instantiatedType.IsCanonicalSubtype(CanonicalFormKind.Any)) - { - return result.ToArray(); - } - } - break; - - case ReadyToRunHelperId.FieldHandle: - { - FieldDesc field = (FieldDesc)_target; - TypeDesc instantiatedOwningType = field.OwningType.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(typeInstantiation, methodInstantiation); - if (instantiatedOwningType.IsCanonicalSubtype(CanonicalFormKind.Any)) - { - return result.ToArray(); - } - } - break; - - case ReadyToRunHelperId.DelegateCtor: - { - DelegateCreationInfo createInfo = (DelegateCreationInfo)_target; - MethodDesc instantiatedMethod = createInfo.PossiblyUnresolvedTargetMethod.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(typeInstantiation, methodInstantiation); - if (instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any)) - { - return result.ToArray(); - } - } - break; - - case ReadyToRunHelperId.ConstrainedDirectCall: - { - ConstrainedCallInfo callInfo = (ConstrainedCallInfo)_target; - MethodDesc instantiatedMethod = callInfo.Method.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(typeInstantiation, methodInstantiation); - TypeDesc instantiatedConstrainedType = callInfo.ConstrainedType.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(typeInstantiation, methodInstantiation); - if (instantiatedMethod.IsCanonicalMethod(CanonicalFormKind.Any) || instantiatedConstrainedType.IsCanonicalSubtype(CanonicalFormKind.Any)) - { - return result.ToArray(); - } - } - break; - - default: - throw new NotImplementedException(); - } - } - switch (_id) { case ReadyToRunHelperId.GetGCStaticBase: @@ -249,7 +153,7 @@ public IEnumerable InstantiateDependencies(NodeFactory fact { result.Add( new DependencyListEntry( - factory.GenericLookup.TypeNonGCStaticBase((TypeDesc)_target).GetTarget(factory, lookupContext), + factory.GenericLookup.TypeNonGCStaticBase((TypeDesc)_target).GetTarget(factory, lookupContext, isConcreteInstantiation), "Dictionary dependency")); } } @@ -279,7 +183,7 @@ public IEnumerable InstantiateDependencies(NodeFactory fact { // All generic lookups depend on the thing they point to result.Add(new DependencyListEntry( - _lookupSignature.GetTarget(factory, lookupContext), + _lookupSignature.GetTarget(factory, lookupContext, isConcreteInstantiation), "Dictionary dependency")); } catch (TypeSystemException) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs index a75cf15a02aa6b..310da2dd794634 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionInvokeMapNode.cs @@ -67,7 +67,7 @@ public static void AddDependenciesDueToReflectability(ref DependencyList depende { if (method.IsSharedByGenericInstantiations) { - dependencies.Add(factory.ShadowGenericMethod(method), "Shadow generic reflectable method"); + dependencies.Add(factory.ShadowNonConcreteMethod(method), "Shadow generic reflectable method"); } dependencies.Add(factory.AddressTakenMethodEntrypoint(method), "Body of a reflectable method"); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs index 67099f9c90d614..598a5dec7233a5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs @@ -63,7 +63,7 @@ protected override void ComputeDependencyNodeDependencies(List obj) _reflectableMethods.Add(methodNode.Method); } - methodNode ??= obj as ShadowGenericMethodNode; + methodNode ??= obj as ShadowConcreteMethodNode; if (methodNode != null) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index 4ca98714e94e4a..55cd78fa3d4669 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -790,13 +790,17 @@ private void ImportCall(ILOpcode opcode, int token) { instParam = GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.OwningType); } + else if (targetMethod.AcquiresInstMethodTableFromThis()) + { + _dependencies.Add(_factory.ShadowConcreteMethod(concreteMethod), reason); + } if (instParam != null) { _dependencies.Add(instParam, reason); } - _dependencies.Add(_factory.ShadowGenericMethod(concreteMethod), reason); + _dependencies.Add(_factory.CanonicalEntrypoint(targetMethod), reason); } else { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 620a9d5335cb59..41045f576e0eb4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -310,7 +310,9 @@ - + + + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index e935f39339f367..f287ea0d002d4b 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -89,7 +89,8 @@ - + + diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs index 648bb41f662d94..5c635bf69e3b4c 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs @@ -132,7 +132,7 @@ protected override void ComputeDependencyNodeDependencies(List Date: Thu, 18 Dec 2025 12:09:31 -0800 Subject: [PATCH 14/18] Handle null GetTarget and non-concrete MakeGeneric --- .../Compiler/Dataflow/HandleCallAction.cs | 11 +++++++++-- .../ReadyToRunGenericHelperNode.cs | 19 +++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs index 71271952e2b11c..bb5a6939cda1ce 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs @@ -751,8 +751,12 @@ private sealed class MakeGenericMethodSite : INodeWithRuntimeDeterminedDependenc public IEnumerable.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation, bool isConcreteInstantiation) { - Debug.Assert(isConcreteInstantiation); var list = new DependencyList(); + if (!isConcreteInstantiation) + { + return list; + } + MethodDesc instantiatedMethod = _method.InstantiateSignature(typeInstantiation, methodInstantiation); if (instantiatedMethod.CheckConstraints(new InstantiationContext(typeInstantiation, methodInstantiation))) RootingHelpers.TryGetDependenciesForReflectedMethod(ref list, factory, instantiatedMethod, "MakeGenericMethod"); @@ -768,8 +772,11 @@ private sealed class MakeGenericTypeSite : INodeWithRuntimeDeterminedDependencie public IEnumerable.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation, bool isConcreteInstantiotion) { - Debug.Assert(isConcreteInstantiotion); var list = new DependencyList(); + if (!isConcreteInstantiotion) + { + return list; + } TypeDesc instantiatedType = _type.InstantiateSignature(typeInstantiation, methodInstantiation); if (instantiatedType.CheckConstraints(new InstantiationContext(typeInstantiation, methodInstantiation))) RootingHelpers.TryGetDependenciesForReflectedType(ref list, factory, instantiatedType, "MakeGenericType"); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index 108e5b920926da..7c108e58b671fa 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -151,10 +151,11 @@ public IEnumerable InstantiateDependencies(NodeFactory fact // because that's where the class constructor context is. if (TriggersLazyStaticConstructor(factory)) { - result.Add( - new DependencyListEntry( - factory.GenericLookup.TypeNonGCStaticBase((TypeDesc)_target).GetTarget(factory, lookupContext, isConcreteInstantiation), - "Dictionary dependency")); + var lookupTarget = factory.GenericLookup.TypeNonGCStaticBase((TypeDesc)_target).GetTarget(factory, lookupContext, isConcreteInstantiation); + if (lookupTarget != null) + { + result.Add(new DependencyListEntry(lookupTarget, "Dictionary dependency")); + } } } break; @@ -181,10 +182,12 @@ public IEnumerable InstantiateDependencies(NodeFactory fact try { - // All generic lookups depend on the thing they point to - result.Add(new DependencyListEntry( - _lookupSignature.GetTarget(factory, lookupContext, isConcreteInstantiation), - "Dictionary dependency")); + var lookupTarget = _lookupSignature.GetTarget(factory, lookupContext, isConcreteInstantiation); + if (lookupTarget != null) + { + // All generic lookups depend on the thing they point to + result.Add(new DependencyListEntry(lookupTarget, "Dictionary dependency")); + } } catch (TypeSystemException) { From 9dd909ea6b5f57bff301f02daa9293d293f4b471 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 18 Dec 2025 14:36:43 -0800 Subject: [PATCH 15/18] Revert MakeGeneric logic InstantiateDependencies here could be called for non-concrete instantiations even before these changes. --- .../Compiler/Dataflow/HandleCallAction.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs index bb5a6939cda1ce..207e5993ff8221 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs @@ -752,11 +752,6 @@ private sealed class MakeGenericMethodSite : INodeWithRuntimeDeterminedDependenc public IEnumerable.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation, bool isConcreteInstantiation) { var list = new DependencyList(); - if (!isConcreteInstantiation) - { - return list; - } - MethodDesc instantiatedMethod = _method.InstantiateSignature(typeInstantiation, methodInstantiation); if (instantiatedMethod.CheckConstraints(new InstantiationContext(typeInstantiation, methodInstantiation))) RootingHelpers.TryGetDependenciesForReflectedMethod(ref list, factory, instantiatedMethod, "MakeGenericMethod"); @@ -773,10 +768,6 @@ private sealed class MakeGenericTypeSite : INodeWithRuntimeDeterminedDependencie public IEnumerable.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation, bool isConcreteInstantiotion) { var list = new DependencyList(); - if (!isConcreteInstantiotion) - { - return list; - } TypeDesc instantiatedType = _type.InstantiateSignature(typeInstantiation, methodInstantiation); if (instantiatedType.CheckConstraints(new InstantiationContext(typeInstantiation, methodInstantiation))) RootingHelpers.TryGetDependenciesForReflectedType(ref list, factory, instantiatedType, "MakeGenericType"); From 2f39adcc0436ebd81a94224954ef3a0301352cc9 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 18 Dec 2025 14:37:32 -0800 Subject: [PATCH 16/18] Update src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs --- .../ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs index 207e5993ff8221..610a1a84a038c9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs @@ -765,7 +765,7 @@ private sealed class MakeGenericTypeSite : INodeWithRuntimeDeterminedDependencie public MakeGenericTypeSite(TypeDesc type) => _type = type; - public IEnumerable.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation, bool isConcreteInstantiotion) + public IEnumerable.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation, bool isConcreteInstantiation) { var list = new DependencyList(); TypeDesc instantiatedType = _type.InstantiateSignature(typeInstantiation, methodInstantiation); From cb7fc96fcd43202c25e437fa1e59ff9d0ca47a34 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 19 Dec 2025 11:23:21 -0800 Subject: [PATCH 17/18] Fix node types And limit direct call shadow nodes We already create concrete nodes inside CanonicalEntrypoint --- .../tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs | 2 +- .../tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs index 598a5dec7233a5..0980e21a5c2f0e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs @@ -63,7 +63,7 @@ protected override void ComputeDependencyNodeDependencies(List Date: Fri, 2 Jan 2026 10:01:56 -0800 Subject: [PATCH 18/18] PR feedback - Remove dead code - Remove redundant check - Improve comment --- .../DependencyAnalysis/ShadowConcreteMethodNode.cs | 2 +- .../DependencyAnalysis/ReadyToRunGenericHelperNode.cs | 11 ----------- .../aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowConcreteMethodNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowConcreteMethodNode.cs index d30a2b0831ba44..1ce5973c387d38 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowConcreteMethodNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ShadowConcreteMethodNode.cs @@ -14,7 +14,7 @@ namespace ILCompiler.DependencyAnalysis { /// /// Represents a concrete method (fully instantiated) for the purpose of - /// tracking dependencies. + /// tracking dependencies of inlinees. /// public class ShadowConcreteMethodNode : ShadowMethodNode, IMethodNode, ISymbolNodeWithLinkage { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index 7c108e58b671fa..536c347ba3fdf4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -10,7 +10,6 @@ using Internal.TypeSystem; using ILCompiler.DependencyAnalysisFramework; -using Internal.ReadyToRunConstants; namespace ILCompiler.DependencyAnalysis { @@ -127,16 +126,6 @@ private bool TriggersLazyStaticConstructor(NodeFactory factory) return factory.PreinitializationManager.HasLazyStaticConstructor(type.ConvertToCanonForm(CanonicalFormKind.Specific)); } - private static bool ContainsCanonicalTypes(Instantiation instantiation) - { - foreach (TypeDesc arg in instantiation) - { - if (arg.IsCanonicalSubtype(CanonicalFormKind.Any)) - return true; - } - return false; - } - public IEnumerable InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation, bool isConcreteInstantiation) { DependencyList result = new DependencyList(); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index cdd3baad6e0978..f2d7ae64358786 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -791,7 +791,7 @@ private void ImportCall(ILOpcode opcode, int token) { instParam = GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.OwningType); } - else if (targetMethod.AcquiresInstMethodTableFromThis() && concreteMethod.IsSharedByGenericInstantiations) + else if (targetMethod.AcquiresInstMethodTableFromThis()) { _dependencies.Add(_factory.ShadowNonConcreteMethod(concreteMethod), reason); }