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.
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;
// 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())
}
}
- 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)
// 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;
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;
--- /dev/null
+// 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;
+ }
+}
return new ReflectableFieldNode(field);
});
+ _genericStaticBaseInfos = new NodeCache<MetadataType, GenericStaticBaseInfoNode>(type =>
+ {
+ return new GenericStaticBaseInfoNode(type);
+ });
+
_objectGetTypeFlowDependencies = new NodeCache<MetadataType, ObjectGetTypeFlowDependenciesNode>(type =>
{
return new ObjectGetTypeFlowDependenciesNode(type);
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)
{
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Collections.Generic;
using Internal.Text;
using Internal.TypeSystem;
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)
ModuleUseBasedDependencyAlgorithm.AddDependenciesDueToModuleUse(ref dependencyList, factory, _type.Module);
- EETypeNode.AddDependenciesForStaticsNode(factory, _type, ref dependencyList);
-
return dependencyList;
}
{
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))
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Diagnostics;
using Internal.Text;
using Internal.TypeSystem;
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");
}
}
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)
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));
}
}
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)
public TypePreinit.TypePreinitializationPolicy GetPreinitializationPolicy()
{
- return new ScannedPreinitializationPolicy(MarkedNodes);
+ return new ScannedPreinitializationPolicy(_factory.PreinitializationManager, MarkedNodes);
}
private sealed class ScannedVTableProvider : VTableSliceProvider
{
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)
{
{
_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));
+ }
}
}
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)>();
{
_frozenObjects.Add(frozenStr);
}
+
+ if (obj is GenericStaticBaseInfoNode genericStaticBaseInfo)
+ {
+ _typesWithGenericStaticBaseInfo.Add(genericStaticBaseInfo.Type);
+ }
}
protected virtual bool AllMethodsCanBeReflectable => false;
return _frozenObjects;
}
+ public IEnumerable<MetadataType> GetTypesWithGenericStaticBaseInfos()
+ {
+ return _typesWithGenericStaticBaseInfo;
+ }
+
internal IEnumerable<IMethodBodyNode> GetCompiledMethodBodies()
{
return _methodBodiesGenerated;
<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" />
TestByRefReturnInvoke.Run();
TestAssemblyLoad.Run();
#endif
- TestEntryPoint.Run();
+ TestBaseOnlyUsedFromCode.Run();
+ TestEntryPoint.Run();
return 100;
}
}
}
+ 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);