Generate fewer GenericMethodHashtable entries (#87466)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Wed, 14 Jun 2023 05:06:43 +0000 (14:06 +0900)
committerGitHub <noreply@github.com>
Wed, 14 Jun 2023 05:06:43 +0000 (14:06 +0900)
GenericMethodHashtable is a hashtable of all method generic dictionaries statically present in the image. In practice, we don't need a database of all of them - only those that are reflection-visible, or have a type loader template. This PR implements that.

Saves 0.2% on BasicMinimalApi. A bit less than I hoped for but since I already have this...

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDictionaryNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericMethodsHashtableEntryNode.cs [new file with mode: 0644]
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericMethodsHashtableNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs
src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj

index 3358175..6239f46 100644 (file)
@@ -196,7 +196,12 @@ namespace ILCompiler.DependencyAnalysis
         protected override TypeSystemContext Context => _owningMethod.Context;
         public override TypeSystemEntity OwningEntity => _owningMethod;
         public MethodDesc OwningMethod => _owningMethod;
-        public override bool HasConditionalStaticDependencies => false;
+        public override bool HasConditionalStaticDependencies => true;
+
+        public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory)
+        {
+            return factory.MetadataManager.GetConditionalDependenciesDueToGenericDictionary(factory, _owningMethod);
+        }
 
         protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
         {
@@ -206,8 +211,7 @@ namespace ILCompiler.DependencyAnalysis
             if (factory.CompilationModuleGroup.ContainsMethodBody(canonicalTarget, false))
                 dependencies.Add(GetDictionaryLayout(factory), "Layout");
 
-            // TODO-SIZE: We probably don't need to add these for all dictionaries
-            GenericMethodsHashtableNode.GetGenericMethodsHashtableDependenciesForMethod(ref dependencies, factory, _owningMethod);
+            factory.MetadataManager.GetDependenciesDueToGenericDictionary(ref dependencies, factory, _owningMethod);
 
             factory.InteropStubManager.AddMarshalAPIsGenericDependencies(ref dependencies, factory, _owningMethod);
 
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericMethodsHashtableEntryNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericMethodsHashtableEntryNode.cs
new file mode 100644 (file)
index 0000000..adab54e
--- /dev/null
@@ -0,0 +1,44 @@
+// 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.TypeSystem;
+
+using Debug = System.Diagnostics.Debug;
+
+namespace ILCompiler.DependencyAnalysis
+{
+    public class GenericMethodsHashtableEntryNode : DependencyNodeCore<NodeFactory>
+    {
+        private readonly MethodDesc _method;
+
+        public GenericMethodsHashtableEntryNode(MethodDesc method)
+        {
+            _method = method;
+        }
+
+        public MethodDesc Method => _method;
+
+        public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
+        {
+            DependencyList dependencies = null;
+            GenericMethodsHashtableNode.GetGenericMethodsHashtableDependenciesForMethod(ref dependencies, factory, _method);
+            Debug.Assert(dependencies != null);
+            return dependencies;
+        }
+        protected override string GetName(NodeFactory factory)
+        {
+            return "Generic methods hashtable entry: " + _method.ToString();
+        }
+
+        public override bool InterestingForDynamicDependencyAnalysis => false;
+        public override bool HasDynamicDependencies => false;
+        public override bool HasConditionalStaticDependencies => false;
+        public override bool StaticDependenciesAreComputed => true;
+        public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory) => null;
+        public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory factory) => null;
+    }
+}
index aeae01f..5a3ee23 100644 (file)
@@ -49,14 +49,8 @@ namespace ILCompiler.DependencyAnalysis
             Section nativeSection = nativeWriter.NewSection();
             nativeSection.Place(hashtable);
 
-            foreach (var dictionaryNode in factory.MetadataManager.GetCompiledGenericDictionaries())
+            foreach (MethodDesc method in factory.MetadataManager.GetGenericMethodHashtableEntries())
             {
-                MethodGenericDictionaryNode methodDictionary = dictionaryNode as MethodGenericDictionaryNode;
-                if (methodDictionary == null)
-                    continue;
-
-                MethodDesc method = methodDictionary.OwningMethod;
-
                 Debug.Assert(method.HasInstantiation && !method.IsCanonicalMethod(CanonicalFormKind.Any));
 
                 Vertex fullMethodSignature;
@@ -82,6 +76,7 @@ namespace ILCompiler.DependencyAnalysis
                 }
 
                 // Method's dictionary pointer
+                var dictionaryNode = factory.MethodGenericDictionary(method);
                 Vertex dictionaryVertex = nativeWriter.GetUnsignedConstant(_externalReferences.GetIndex(dictionaryNode));
 
                 Vertex entry = nativeWriter.GetTuple(dictionaryVertex, fullMethodSignature);
@@ -98,6 +93,8 @@ namespace ILCompiler.DependencyAnalysis
 
         public static void GetGenericMethodsHashtableDependenciesForMethod(ref DependencyList dependencies, NodeFactory factory, MethodDesc method)
         {
+            dependencies ??= new DependencyList();
+
             Debug.Assert(method.HasInstantiation && !method.IsCanonicalMethod(CanonicalFormKind.Any));
 
             // Method's containing type
@@ -115,9 +112,6 @@ namespace ILCompiler.DependencyAnalysis
             NativeLayoutVertexNode nameAndSig = factory.NativeLayout.MethodNameAndSignatureVertex(method.GetTypicalMethodDefinition());
             NativeLayoutSavedVertexNode placedNameAndSig = factory.NativeLayout.PlacedSignatureVertex(nameAndSig);
             dependencies.Add(new DependencyListEntry(placedNameAndSig, "GenericMethodsHashtable entry signature"));
-
-            ISymbolNode dictionaryNode = factory.MethodGenericDictionary(method);
-            dependencies.Add(new DependencyListEntry(dictionaryNode, "GenericMethodsHashtable entry dictionary"));
         }
 
         protected internal override int Phase => (int)ObjectNodePhase.Ordered;
index 5d73495..2b3d9f0 100644 (file)
@@ -298,6 +298,11 @@ namespace ILCompiler.DependencyAnalysis
                 return new GenericVirtualMethodImplNode(method);
             });
 
+            _genericMethodEntries = new NodeCache<MethodDesc, GenericMethodsHashtableEntryNode>(method =>
+            {
+                return new GenericMethodsHashtableEntryNode(method);
+            });
+
             _gvmTableEntries = new NodeCache<TypeDesc, TypeGVMEntriesNode>(type =>
             {
                 return new TypeGVMEntriesNode(type);
@@ -961,6 +966,12 @@ namespace ILCompiler.DependencyAnalysis
             return _gvmImpls.GetOrAdd(method);
         }
 
+        private NodeCache<MethodDesc, GenericMethodsHashtableEntryNode> _genericMethodEntries;
+        public GenericMethodsHashtableEntryNode GenericMethodsHashtableEntry(MethodDesc method)
+        {
+            return _genericMethodEntries.GetOrAdd(method);
+        }
+
         private NodeCache<TypeDesc, TypeGVMEntriesNode> _gvmTableEntries;
         internal TypeGVMEntriesNode TypeGVMEntries(TypeDesc type)
         {
index 26dc3ff..fcbf125 100644 (file)
@@ -14,6 +14,7 @@ using ReadyToRunSectionType = Internal.Runtime.ReadyToRunSectionType;
 using ReflectionMapBlob = Internal.Runtime.ReflectionMapBlob;
 using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.DependencyList;
 using CombinedDependencyList = System.Collections.Generic.List<ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.CombinedDependencyListEntry>;
+using CombinedDependencyListEntry = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.CombinedDependencyListEntry;
 using MethodIL = Internal.IL.MethodIL;
 using CustomAttributeValue = System.Reflection.Metadata.CustomAttributeValue<Internal.TypeSystem.TypeDesc>;
 
@@ -65,6 +66,7 @@ namespace ILCompiler
         private HashSet<NativeLayoutTemplateMethodSignatureVertexNode> _templateMethodEntries = new HashSet<NativeLayoutTemplateMethodSignatureVertexNode>();
         private readonly SortedSet<TypeDesc> _typeTemplates = new SortedSet<TypeDesc>(TypeSystemComparer.Instance);
         private readonly SortedSet<MetadataType> _typesWithGenericStaticBaseInfo = new SortedSet<MetadataType>(TypeSystemComparer.Instance);
+        private readonly SortedSet<MethodDesc> _genericMethodHashtableEntries = new SortedSet<MethodDesc>(TypeSystemComparer.Instance);
 
         private List<(DehydratableObjectNode Node, ObjectNode.ObjectData Data)> _dehydratableData = new List<(DehydratableObjectNode Node, ObjectNode.ObjectData data)>();
 
@@ -301,6 +303,11 @@ namespace ILCompiler
             {
                 _typesWithGenericStaticBaseInfo.Add(genericStaticBaseInfo.Type);
             }
+
+            if (obj is GenericMethodsHashtableEntryNode genericMethodsHashtableEntryNode)
+            {
+                _genericMethodHashtableEntries.Add(genericMethodsHashtableEntryNode.Method);
+            }
         }
 
         protected virtual bool AllMethodsCanBeReflectable => false;
@@ -379,6 +386,29 @@ namespace ILCompiler
             return true;
         }
 
+        public void GetDependenciesDueToGenericDictionary(ref DependencyList dependencies, NodeFactory factory, MethodDesc method)
+        {
+            MetadataCategory category = GetMetadataCategory(method.GetCanonMethodTarget(CanonicalFormKind.Specific));
+
+            if ((category & MetadataCategory.RuntimeMapping) != 0)
+            {
+                // If the method is visible from reflection, we need to keep track of this statically generated
+                // dictionary to make sure MakeGenericMethod works even without a type loader template
+                dependencies ??= new DependencyList();
+                dependencies.Add(factory.GenericMethodsHashtableEntry(method), "Reflection visible dictionary");
+            }
+        }
+
+        public IEnumerable<CombinedDependencyListEntry> GetConditionalDependenciesDueToGenericDictionary(NodeFactory factory, MethodDesc method)
+        {
+            // If there's a template for this method, we need to keep track of the dictionary so that we
+            // don't accidentally create a new dictionary for the same method at runtime.
+            yield return new CombinedDependencyListEntry(
+                factory.GenericMethodsHashtableEntry(method),
+                factory.NativeLayout.TemplateMethodEntry(method.GetCanonMethodTarget(CanonicalFormKind.Specific)),
+                "Runtime-constructable dictionary");
+        }
+
         /// <summary>
         /// This method is an extension point that can provide additional metadata-based dependencies to compiled method bodies.
         /// </summary>
@@ -734,6 +764,11 @@ namespace ILCompiler
             return _typesWithGenericStaticBaseInfo;
         }
 
+        public IEnumerable<MethodDesc> GetGenericMethodHashtableEntries()
+        {
+            return _genericMethodHashtableEntries;
+        }
+
         internal IEnumerable<IMethodBodyNode> GetCompiledMethodBodies()
         {
             return _methodBodiesGenerated;
index 1822619..99967af 100644 (file)
     <Compile Include="Compiler\DependencyAnalysis\ExternSymbolsImportedNodeProvider.cs" />
     <Compile Include="Compiler\DependencyAnalysis\FieldRvaDataNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\FunctionPointerMapNode.cs" />
+    <Compile Include="Compiler\DependencyAnalysis\GenericMethodsHashtableEntryNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\GenericStaticBaseInfoNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\GenericVarianceNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\GenericVirtualMethodImplNode.cs" />