Add more precise tracking of static bases (#80093)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Fri, 6 Jan 2023 08:00:10 +0000 (17:00 +0900)
committerGitHub <noreply@github.com>
Fri, 6 Jan 2023 08:00:10 +0000 (17:00 +0900)
Under some circumstances, the compiler needs to generate information about the bases where static fields of a type are placed.

The existing logic was simple: if a static base was accessed, generate the owning type. If owning type was generated, generate all of it's static bases. At object emission, go over all generated types and preserve information about their bases.

We couldn't do better at the time this was implemented.

The information about static bases is needed in two cases:

1. A static field on the type is reflectable. We now know when that happens and it doesn't happen very often.
2. The generic type whose static base we generated has a template and we could build the type at runtime. This also doesn't happen very often anymore.

This PR teaches the compiler to only emit the information for these two cases. It prevents generating unnecessary EETypes, static bases, and .cctors.

Also adding a test case for scenario 2 above since we didn't have coverage for the scenario. Scenario 1 is covered.

Saves 0.2% on a hello world. Not much, but at least it's easier to reason about why this is generated.

12 files changed:
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticsNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericStaticBaseInfoNode.cs [new file with mode: 0644]
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NonGCStaticsNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectableFieldNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/StaticsInfoHashtableNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ThreadStaticsNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs
src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs

index ce9b4eb..01bf228 100644 (file)
@@ -317,6 +317,15 @@ namespace ILCompiler.DependencyAnalysis
                 return result;
             }
 
+            TypeDesc canonOwningType = _type.ConvertToCanonForm(CanonicalFormKind.Specific);
+            if (_type.IsDefType && _type != canonOwningType)
+            {
+                result.Add(new CombinedDependencyListEntry(
+                    factory.GenericStaticBaseInfo((MetadataType)_type),
+                    factory.NativeLayout.TemplateTypeLayout(canonOwningType),
+                    "Information about static bases for type with template"));
+            }
+
             if (!EmitVirtualSlotsAndInterfaces)
                 return result;
 
@@ -493,9 +502,6 @@ namespace ILCompiler.DependencyAnalysis
             // emitting it.
             dependencies.Add(new DependencyListEntry(_optionalFieldsNode, "Optional fields"));
 
-            // TODO-SIZE: We probably don't need to add these for all EETypes
-            StaticsInfoHashtableNode.AddStaticsInfoDependencies(ref dependencies, factory, _type);
-
             if (EmitVirtualSlotsAndInterfaces)
             {
                 if (!_type.IsArrayTypeWithoutGenericInterfaces())
@@ -1182,28 +1188,6 @@ namespace ILCompiler.DependencyAnalysis
             }
         }
 
-        public static void AddDependenciesForStaticsNode(NodeFactory factory, TypeDesc type, ref DependencyList dependencies)
-        {
-            // To ensure that the behvior of FieldInfo.GetValue/SetValue remains correct,
-            // if a type may be reflectable, and it is generic, if a canonical instantiation of reflection
-            // can exist which can refer to the associated type of this static base, ensure that type
-            // has an MethodTable. (Which will allow the static field lookup logic to find the right type)
-            if (type.HasInstantiation && !factory.MetadataManager.IsReflectionBlocked(type))
-            {
-                // TODO-SIZE: This current implementation is slightly generous, as it does not attempt to restrict
-                // the created types to the maximum extent by investigating reflection data and such. Here we just
-                // check if we support use of a canonically equivalent type to perform reflection.
-                // We don't check to see if reflection is enabled on the type.
-                if (factory.TypeSystemContext.SupportsUniversalCanon
-                    || (factory.TypeSystemContext.SupportsCanon && (type != type.ConvertToCanonForm(CanonicalFormKind.Specific))))
-                {
-                    dependencies ??= new DependencyList();
-
-                    dependencies.Add(factory.NecessaryTypeSymbol(type), "Static block owning type is necessary for canonically equivalent reflection");
-                }
-            }
-        }
-
         protected static void AddDependenciesForUniversalGVMSupport(NodeFactory factory, TypeDesc type, ref DependencyList dependencies)
         {
             if (factory.TypeSystemContext.SupportsUniversalCanon)
index 4bceb9b..5b07a13 100644 (file)
@@ -1,6 +1,8 @@
 // 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 Internal.Text;
 using Internal.TypeSystem;
 
@@ -62,11 +64,24 @@ namespace ILCompiler.DependencyAnalysis
             dependencyList.Add(factory.GCStaticsRegion, "GCStatics Region");
 
             dependencyList.Add(factory.GCStaticIndirection(_type), "GC statics indirection");
-            EETypeNode.AddDependenciesForStaticsNode(factory, _type, ref dependencyList);
 
             return dependencyList;
         }
 
+        public override bool HasConditionalStaticDependencies => _type.ConvertToCanonForm(CanonicalFormKind.Specific) != _type;
+
+        public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory)
+        {
+            // If we have a type loader template for this type, we need to keep track of the generated
+            // bases in the type info hashtable. The type symbol node does such accounting.
+            return new CombinedDependencyListEntry[]
+            {
+                new CombinedDependencyListEntry(factory.NecessaryTypeSymbol(_type),
+                    factory.NativeLayout.TemplateTypeLayout(_type.ConvertToCanonForm(CanonicalFormKind.Specific)),
+                    "Keeping track of template-constructable type static bases"),
+            };
+        }
+
         public override bool StaticDependenciesAreComputed => true;
 
         public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.DataSection;
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericStaticBaseInfoNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericStaticBaseInfoNode.cs
new file mode 100644 (file)
index 0000000..9820371
--- /dev/null
@@ -0,0 +1,46 @@
+// 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 System.Diagnostics;
+
+using ILCompiler.DependencyAnalysisFramework;
+
+using Internal.TypeSystem;
+
+namespace ILCompiler.DependencyAnalysis
+{
+    /// <summary>
+    /// Represents an entry in a hashtable that contains information about static bases of generic types.
+    /// </summary>
+    internal sealed class GenericStaticBaseInfoNode : DependencyNodeCore<NodeFactory>
+    {
+        public MetadataType Type { get; }
+
+        public GenericStaticBaseInfoNode(MetadataType type)
+        {
+            Debug.Assert(!type.IsCanonicalSubtype(CanonicalFormKind.Any));
+            Debug.Assert(type.HasInstantiation);
+            Type = type;
+        }
+
+        public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
+        {
+            var dependencies = new DependencyList();
+            StaticsInfoHashtableNode.AddStaticsInfoDependencies(ref dependencies, factory, Type);
+            return dependencies;
+        }
+
+        protected override string GetName(NodeFactory factory)
+        {
+            return "Static base info: " + Type.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 7d8fb9e..be70251 100644 (file)
@@ -287,6 +287,11 @@ namespace ILCompiler.DependencyAnalysis
                 return new ReflectableFieldNode(field);
             });
 
+            _genericStaticBaseInfos = new NodeCache<MetadataType, GenericStaticBaseInfoNode>(type =>
+            {
+                return new GenericStaticBaseInfoNode(type);
+            });
+
             _objectGetTypeFlowDependencies = new NodeCache<MetadataType, ObjectGetTypeFlowDependenciesNode>(type =>
             {
                 return new ObjectGetTypeFlowDependenciesNode(type);
@@ -888,6 +893,12 @@ namespace ILCompiler.DependencyAnalysis
             return _reflectableFields.GetOrAdd(field);
         }
 
+        private NodeCache<MetadataType, GenericStaticBaseInfoNode> _genericStaticBaseInfos;
+        internal GenericStaticBaseInfoNode GenericStaticBaseInfo(MetadataType type)
+        {
+            return _genericStaticBaseInfos.GetOrAdd(type);
+        }
+
         private NodeCache<MetadataType, ObjectGetTypeFlowDependenciesNode> _objectGetTypeFlowDependencies;
         internal ObjectGetTypeFlowDependenciesNode ObjectGetTypeFlowDependencies(MetadataType type)
         {
index e29c8a1..57659ca 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Collections.Generic;
 
 using Internal.Text;
 using Internal.TypeSystem;
@@ -118,6 +119,20 @@ namespace ILCompiler.DependencyAnalysis
             return target.PointerSize;
         }
 
+        public override bool HasConditionalStaticDependencies => _type.ConvertToCanonForm(CanonicalFormKind.Specific) != _type;
+
+        public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory)
+        {
+            // If we have a type loader template for this type, we need to keep track of the generated
+            // bases in the type info hashtable. The type symbol node does such accounting.
+            return new CombinedDependencyListEntry[]
+            {
+                new CombinedDependencyListEntry(factory.NecessaryTypeSymbol(_type),
+                    factory.NativeLayout.TemplateTypeLayout(_type.ConvertToCanonForm(CanonicalFormKind.Specific)),
+                    "Keeping track of template-constructable type static bases"),
+            };
+        }
+
         public override bool StaticDependenciesAreComputed => true;
 
         protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
@@ -131,8 +146,6 @@ namespace ILCompiler.DependencyAnalysis
 
             ModuleUseBasedDependencyAlgorithm.AddDependenciesDueToModuleUse(ref dependencyList, factory, _type.Module);
 
-            EETypeNode.AddDependenciesForStaticsNode(factory, _type, ref dependencyList);
-
             return dependencyList;
         }
 
index 3fc33b7..d7d7e03 100644 (file)
@@ -80,6 +80,13 @@ namespace ILCompiler.DependencyAnalysis
                 {
                     dependencies.Add(factory.TypeNonGCStaticsSymbol((MetadataType)_field.OwningType), "CCtor context");
                 }
+
+                // For generic types, the reflection mapping table only keeps track of information about offsets
+                // from the static bases. To locate the static base, we need the GenericStaticBaseInfo hashtable.
+                if (_field.OwningType.HasInstantiation)
+                {
+                    dependencies.Add(factory.GenericStaticBaseInfo((MetadataType)_field.OwningType), "Field on a generic type");
+                }
             }
 
             if (!_field.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any))
index 013837e..bfa05c4 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Diagnostics;
 
 using Internal.Text;
 using Internal.TypeSystem;
@@ -38,36 +39,32 @@ namespace ILCompiler.DependencyAnalysis
         public override bool StaticDependenciesAreComputed => true;
         protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);
 
-
         /// <summary>
         /// Helper method to compute the dependencies that would be needed by a hashtable entry for statics info lookup.
         /// This helper is used by EETypeNode, which is used by the dependency analysis to compute the statics hashtable
         /// entries for the compiled types.
         /// </summary>
-        public static void AddStaticsInfoDependencies(ref DependencyList dependencies, NodeFactory factory, TypeDesc type)
+        public static void AddStaticsInfoDependencies(ref DependencyList dependencies, NodeFactory factory, MetadataType metadataType)
         {
-            if (type is MetadataType && type.HasInstantiation && !type.IsCanonicalSubtype(CanonicalFormKind.Any))
-            {
-                MetadataType metadataType = (MetadataType)type;
+            Debug.Assert(metadataType.HasInstantiation && !metadataType.IsCanonicalSubtype(CanonicalFormKind.Any));
 
-                // The StaticsInfoHashtable entries only exist for static fields on generic types.
+            // The StaticsInfoHashtable entries only exist for static fields on generic types.
 
-                if (metadataType.GCStaticFieldSize.AsInt > 0)
-                {
-                    dependencies.Add(factory.TypeGCStaticsSymbol(metadataType), "GC statics indirection for StaticsInfoHashtable");
-                }
+            if (metadataType.GCStaticFieldSize.AsInt > 0)
+            {
+                dependencies.Add(factory.TypeGCStaticsSymbol(metadataType), "GC statics indirection for StaticsInfoHashtable");
+            }
 
-                if (metadataType.NonGCStaticFieldSize.AsInt > 0 || NonGCStaticsNode.TypeHasCctorContext(factory.PreinitializationManager, metadataType))
-                {
-                    // The entry in the StaticsInfoHashtable points at the beginning of the static fields data, rather than the cctor
-                    // context offset.
-                    dependencies.Add(factory.TypeNonGCStaticsSymbol(metadataType), "Non-GC statics indirection for StaticsInfoHashtable");
-                }
+            if (metadataType.NonGCStaticFieldSize.AsInt > 0 || NonGCStaticsNode.TypeHasCctorContext(factory.PreinitializationManager, metadataType))
+            {
+                // The entry in the StaticsInfoHashtable points at the beginning of the static fields data, rather than the cctor
+                // context offset.
+                dependencies.Add(factory.TypeNonGCStaticsSymbol(metadataType), "Non-GC statics indirection for StaticsInfoHashtable");
+            }
 
-                if (metadataType.ThreadGcStaticFieldSize.AsInt > 0)
-                {
-                    dependencies.Add(factory.TypeThreadStaticIndex(metadataType), "Threadstatics indirection for StaticsInfoHashtable");
-                }
+            if (metadataType.ThreadGcStaticFieldSize.AsInt > 0)
+            {
+                dependencies.Add(factory.TypeThreadStaticIndex(metadataType), "Threadstatics indirection for StaticsInfoHashtable");
             }
         }
 
@@ -83,15 +80,8 @@ namespace ILCompiler.DependencyAnalysis
 
             section.Place(hashtable);
 
-            foreach (var type in factory.MetadataManager.GetTypesWithConstructedEETypes())
+            foreach (var metadataType in factory.MetadataManager.GetTypesWithGenericStaticBaseInfos())
             {
-                if (!type.HasInstantiation || type.IsCanonicalSubtype(CanonicalFormKind.Any) || type.IsGenericDefinition)
-                    continue;
-
-                MetadataType metadataType = type as MetadataType;
-                if (metadataType == null)
-                    continue;
-
                 VertexBag bag = new VertexBag();
 
                 if (metadataType.GCStaticFieldSize.AsInt > 0)
@@ -109,10 +99,10 @@ namespace ILCompiler.DependencyAnalysis
 
                 if (bag.ElementsCount > 0)
                 {
-                    uint typeId = _externalReferences.GetIndex(factory.NecessaryTypeSymbol(type));
+                    uint typeId = _externalReferences.GetIndex(factory.NecessaryTypeSymbol(metadataType));
                     Vertex staticsInfo = writer.GetTuple(writer.GetUnsignedConstant(typeId), bag);
 
-                    hashtable.Append((uint)type.GetHashCode(), section.Place(staticsInfo));
+                    hashtable.Append((uint)metadataType.GetHashCode(), section.Place(staticsInfo));
                 }
             }
 
index b1b8580..4c48b63 100644 (file)
@@ -64,10 +64,23 @@ namespace ILCompiler.DependencyAnalysis
 
             ModuleUseBasedDependencyAlgorithm.AddDependenciesDueToModuleUse(ref result, factory, _type.Module);
 
-            EETypeNode.AddDependenciesForStaticsNode(factory, _type, ref result);
             return result;
         }
 
+        public override bool HasConditionalStaticDependencies => _type.ConvertToCanonForm(CanonicalFormKind.Specific) != _type;
+
+        public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory)
+        {
+            // If we have a type loader template for this type, we need to keep track of the generated
+            // bases in the type info hashtable. The type symbol node does such accounting.
+            return new CombinedDependencyListEntry[]
+            {
+                new CombinedDependencyListEntry(factory.NecessaryTypeSymbol(_type),
+                    factory.NativeLayout.TemplateTypeLayout(_type.ConvertToCanonForm(CanonicalFormKind.Specific)),
+                    "Keeping track of template-constructable type static bases"),
+            };
+        }
+
         public override bool StaticDependenciesAreComputed => true;
 
         public override void EncodeData(ref ObjectDataBuilder builder, NodeFactory factory, bool relocsOnly)
index 342a160..b644ba6 100644 (file)
@@ -249,7 +249,7 @@ namespace ILCompiler
 
         public TypePreinit.TypePreinitializationPolicy GetPreinitializationPolicy()
         {
-            return new ScannedPreinitializationPolicy(MarkedNodes);
+            return new ScannedPreinitializationPolicy(_factory.PreinitializationManager, MarkedNodes);
         }
 
         private sealed class ScannedVTableProvider : VTableSliceProvider
@@ -639,7 +639,7 @@ namespace ILCompiler
         {
             private readonly HashSet<TypeDesc> _canonFormsWithCctorChecks = new HashSet<TypeDesc>();
 
-            public ScannedPreinitializationPolicy(ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
+            public ScannedPreinitializationPolicy(PreinitializationManager preinitManager, ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
             {
                 foreach (var markedNode in markedNodes)
                 {
@@ -664,6 +664,15 @@ namespace ILCompiler
                     {
                         _canonFormsWithCctorChecks.Add(nonGCStatics.Type.ConvertToCanonForm(CanonicalFormKind.Specific));
                     }
+
+                    // Also look at EETypes to cover the cases when the non-GC static base wasn't generated.
+                    // This makes assert around CanPreinitializeAllConcreteFormsForCanonForm happy.
+                    if (markedNode is EETypeNode eeType
+                        && eeType.Type.ConvertToCanonForm(CanonicalFormKind.Specific) != eeType.Type
+                        && preinitManager.HasLazyStaticConstructor(eeType.Type))
+                    {
+                        _canonFormsWithCctorChecks.Add(eeType.Type.ConvertToCanonForm(CanonicalFormKind.Specific));
+                    }
                 }
             }
 
index ce19577..3af62e8 100644 (file)
@@ -64,6 +64,7 @@ namespace ILCompiler
         private readonly SortedSet<DefType> _typesWithStructMarshalling = new SortedSet<DefType>(TypeSystemComparer.Instance);
         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 List<(DehydratableObjectNode Node, ObjectNode.ObjectData Data)> _dehydratableData = new List<(DehydratableObjectNode Node, ObjectNode.ObjectData data)>();
 
@@ -295,6 +296,11 @@ namespace ILCompiler
             {
                 _frozenObjects.Add(frozenStr);
             }
+
+            if (obj is GenericStaticBaseInfoNode genericStaticBaseInfo)
+            {
+                _typesWithGenericStaticBaseInfo.Add(genericStaticBaseInfo.Type);
+            }
         }
 
         protected virtual bool AllMethodsCanBeReflectable => false;
@@ -727,6 +733,11 @@ namespace ILCompiler
             return _frozenObjects;
         }
 
+        public IEnumerable<MetadataType> GetTypesWithGenericStaticBaseInfos()
+        {
+            return _typesWithGenericStaticBaseInfo;
+        }
+
         internal IEnumerable<IMethodBodyNode> GetCompiledMethodBodies()
         {
             return _methodBodiesGenerated;
index cdec153..5dd83b8 100644 (file)
     <Compile Include="Compiler\DependencyAnalysis\DynamicDependencyAttributeAlgorithm.cs" />
     <Compile Include="Compiler\DependencyAnalysis\ExternSymbolsImportedNodeProvider.cs" />
     <Compile Include="Compiler\DependencyAnalysis\FieldRvaDataNode.cs" />
+    <Compile Include="Compiler\DependencyAnalysis\GenericStaticBaseInfoNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\IndirectionExtensions.cs" />
     <Compile Include="Compiler\DependencyAnalysis\InterfaceDispatchCellSectionNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\MethodExceptionHandlingInfoNode.cs" />
index e7e20d1..31d923d 100644 (file)
@@ -81,7 +81,8 @@ internal static class ReflectionTest
         TestByRefReturnInvoke.Run();
         TestAssemblyLoad.Run();
 #endif
-               TestEntryPoint.Run();
+        TestBaseOnlyUsedFromCode.Run();
+        TestEntryPoint.Run();
         return 100;
     }
 
@@ -1508,6 +1509,33 @@ internal static class ReflectionTest
         }
     }
 
+    class TestBaseOnlyUsedFromCode
+    {
+        class SomeReferenceType { }
+
+        class SomeGenericClass<T>
+        {
+            public static string Cookie;
+        }
+
+        class OtherGenericClass<T>
+        {
+            public override string ToString() => SomeGenericClass<T>.Cookie;
+        }
+
+        public static void Run()
+        {
+            SomeGenericClass<SomeReferenceType>.Cookie = "Hello";
+
+            var inst = Activator.CreateInstance(typeof(OtherGenericClass<>).MakeGenericType(GetSomeReferenceType()));
+
+            if (inst.ToString() != "Hello")
+                throw new Exception();
+
+            static Type GetSomeReferenceType() => typeof(SomeReferenceType);
+        }
+    }
+
     class TestRuntimeLab929Regression
     {
         static Type s_atom = typeof(Atom);