Skip to content

Commit f54c15a

Browse files
Precisely track reflected on fields (#70546)
The AOT compiler used simple logic to decide what fields should be kept/generated: if a type is considered constructed, generate all fields. This works, but it's not very efficient. With this change I'm introducing precise tracking of each field that should be accessible from reflection at runtime. The fields are represented by new nodes within the dependency graph. We track fields on individual generic instantiations and ensure we end up in a consistent state where if e.g. we decided `Class<int>.Foo` should be reflection accessible and `Class<double>` is used elsewhere in the program, `Class<double>.Foo` is also reflection accessible. This matches how IL Linker thinks about reflectability where genericness doesn't matter. We could be more optimal here, but various suppressions in framework rely on this logic. Additional reflectable fields only cost tens of bytes. I had to update various places within the compiler that didn't bother specifying field dependencies because they didn't matter in the past. Added a couple new tests that test the various invariants. This saves 2% in size on a `dotnet new webapi` template project with IlcTrimMetadata on. It is a small regression with `IlcTrimMetadata` off because we actually now track more things for the reflectable field: previously we would not make sure the field is reflection-accessible at runtime. We need a `TypeHandle` for the field type for it to be usable. As a potential future optimization, we could look into allowing reflection to see "unconstructed" `TypeHandles` (and use that one). Reflection is currently not allowed to see unconstructed `TypeHandles` to prevent people falling into a `RuntimeHelpers.AllocateUninitializedObject(someField.FieldType.TypeHandle)` trap that has a bad failure mode right now. Once we fix the failure mode, we could potentially allow it.
1 parent f1f940f commit f54c15a

20 files changed

+442
-184
lines changed

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ internal void MarkStaticConstructor(in MessageOrigin origin, TypeDesc type)
161161
{
162162
if (!type.IsGenericDefinition && !type.ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true) && type.HasStaticConstructor)
163163
{
164-
_dependencies.Add(_factory.CanonicalEntrypoint(type.GetStaticConstructor()), "RunClassConstructor reference");
164+
// Mark the GC static base - it contains a pointer to the class constructor, but also info
165+
// about whether the class constructor already executed and it's what is looked at at runtime.
166+
_dependencies.Add(_factory.TypeNonGCStaticsSymbol((MetadataType)type), "RunClassConstructor reference");
165167
}
166168
}
167169

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ private static bool AddDependenciesFromCustomAttributeBlob(DependencyList depend
168168
{
169169
if (decodedArgument.Kind == CustomAttributeNamedArgumentKind.Field)
170170
{
171-
// This is an instance field. We don't track them right now.
171+
if (!AddDependenciesFromField(dependencies, factory, attributeType, decodedArgument.Name))
172+
return false;
172173
}
173174
else
174175
{
@@ -186,6 +187,29 @@ private static bool AddDependenciesFromCustomAttributeBlob(DependencyList depend
186187
return true;
187188
}
188189

190+
private static bool AddDependenciesFromField(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, string fieldName)
191+
{
192+
FieldDesc field = attributeType.GetField(fieldName);
193+
if (field is not null)
194+
{
195+
if (factory.MetadataManager.IsReflectionBlocked(field))
196+
return false;
197+
198+
dependencies.Add(factory.ReflectableField(field), "Custom attribute blob");
199+
200+
return true;
201+
}
202+
203+
// Haven't found it in current type. Check the base type.
204+
TypeDesc baseType = attributeType.BaseType;
205+
206+
if (baseType != null)
207+
return AddDependenciesFromField(dependencies, factory, baseType, fieldName);
208+
209+
// Not found. This is bad metadata that will result in a runtime failure, but we shouldn't fail the compilation.
210+
return true;
211+
}
212+
189213
private static bool AddDependenciesFromPropertySetter(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, string propertyName)
190214
{
191215
EcmaType attributeTypeDefinition = (EcmaType)attributeType.GetTypeDefinition();

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FieldMetadataNode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
4141
}
4242
protected override string GetName(NodeFactory factory)
4343
{
44-
return "Reflectable field: " + _field.ToString();
44+
return "Field metadata: " + _field.ToString();
4545
}
4646

4747
protected override void OnMarked(NodeFactory factory)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticsNode.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class GCStaticsNode : ObjectNode, ISymbolDefinitionNode, ISortableSymbolN
1919
public GCStaticsNode(MetadataType type, PreinitializationManager preinitManager)
2020
{
2121
Debug.Assert(!type.IsCanonicalSubtype(CanonicalFormKind.Specific));
22+
Debug.Assert(!type.IsGenericDefinition);
2223
_type = type;
2324

2425
if (preinitManager.IsPreinitialized(type))

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
4949
}
5050
protected override string GetName(NodeFactory factory)
5151
{
52-
return "Reflectable method: " + _method.ToString();
52+
return "Method metadata: " + _method.ToString();
5353
}
5454

5555
protected override void OnMarked(NodeFactory factory)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ private void CreateNodeCaches()
294294
return new ReflectableMethodNode(method);
295295
});
296296

297+
_reflectableFields = new NodeCache<FieldDesc, ReflectableFieldNode>(field =>
298+
{
299+
return new ReflectableFieldNode(field);
300+
});
301+
297302
_objectGetTypeFlowDependencies = new NodeCache<MetadataType, ObjectGetTypeFlowDependenciesNode>(type =>
298303
{
299304
return new ObjectGetTypeFlowDependenciesNode(type);
@@ -852,6 +857,12 @@ public ReflectableMethodNode ReflectableMethod(MethodDesc method)
852857
return _reflectableMethods.GetOrAdd(method);
853858
}
854859

860+
private NodeCache<FieldDesc, ReflectableFieldNode> _reflectableFields;
861+
public ReflectableFieldNode ReflectableField(FieldDesc field)
862+
{
863+
return _reflectableFields.GetOrAdd(field);
864+
}
865+
855866
private NodeCache<MetadataType, ObjectGetTypeFlowDependenciesNode> _objectGetTypeFlowDependencies;
856867
internal ObjectGetTypeFlowDependenciesNode ObjectGetTypeFlowDependencies(MetadataType type)
857868
{

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NonGCStaticsNode.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class NonGCStaticsNode : ObjectNode, ISymbolDefinitionNode, ISortableSymb
2525
public NonGCStaticsNode(MetadataType type, PreinitializationManager preinitializationManager)
2626
{
2727
Debug.Assert(!type.IsCanonicalSubtype(CanonicalFormKind.Specific));
28+
Debug.Assert(!type.IsGenericDefinition);
2829
_type = type;
2930
_preinitializationManager = preinitializationManager;
3031
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
7+
using ILCompiler.DependencyAnalysisFramework;
8+
9+
using Internal.TypeSystem;
10+
11+
using Debug = System.Diagnostics.Debug;
12+
13+
namespace ILCompiler.DependencyAnalysis
14+
{
15+
/// <summary>
16+
/// Represents a field that is gettable/settable from reflection.
17+
/// </summary>
18+
public class ReflectableFieldNode : DependencyNodeCore<NodeFactory>
19+
{
20+
private readonly FieldDesc _field;
21+
22+
public ReflectableFieldNode(FieldDesc field)
23+
{
24+
Debug.Assert(!field.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any)
25+
|| field.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific) == field.OwningType);
26+
_field = field;
27+
}
28+
29+
public FieldDesc Field => _field;
30+
31+
public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
32+
{
33+
Debug.Assert(!factory.MetadataManager.IsReflectionBlocked(_field.GetTypicalFieldDefinition()));
34+
35+
DependencyList dependencies = new DependencyList();
36+
factory.MetadataManager.GetDependenciesDueToReflectability(ref dependencies, factory, _field);
37+
38+
// No runtime artifacts needed if this is a generic definition or literal field
39+
if (_field.OwningType.IsGenericDefinition || _field.IsLiteral)
40+
{
41+
return dependencies;
42+
}
43+
44+
FieldDesc typicalField = _field.GetTypicalFieldDefinition();
45+
if (typicalField != _field)
46+
{
47+
// Ensure we consistently apply reflectability to all fields sharing the same definition.
48+
// Bases for different instantiations of the field have a conditional dependency on the definition node that
49+
// brings a ReflectableField of the instantiated field if it's necessary for it to be reflectable.
50+
dependencies.Add(factory.ReflectableField(typicalField), "Definition of the reflectable field");
51+
}
52+
53+
// Runtime reflection stack needs to see the type handle of the owning type
54+
dependencies.Add(factory.MaximallyConstructableType(_field.OwningType), "Instance base of a reflectable field");
55+
56+
// Root the static base of the type
57+
if (_field.IsStatic && !_field.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any))
58+
{
59+
// Infrastructure around static constructors is stashed in the NonGC static base
60+
bool needsNonGcStaticBase = factory.PreinitializationManager.HasLazyStaticConstructor(Field.OwningType);
61+
62+
if (_field.HasRva)
63+
{
64+
// No reflection access right now
65+
}
66+
else if (_field.IsThreadStatic)
67+
{
68+
dependencies.Add(factory.TypeThreadStaticIndex((MetadataType)_field.OwningType), "Threadstatic base of a reflectable field");
69+
}
70+
else if (_field.HasGCStaticBase)
71+
{
72+
dependencies.Add(factory.TypeGCStaticsSymbol((MetadataType)_field.OwningType), "GC static base of a reflectable field");
73+
}
74+
else
75+
{
76+
dependencies.Add(factory.TypeNonGCStaticsSymbol((MetadataType)_field.OwningType), "NonGC static base of a reflectable field");
77+
needsNonGcStaticBase = false;
78+
}
79+
80+
if (needsNonGcStaticBase)
81+
{
82+
dependencies.Add(factory.TypeNonGCStaticsSymbol((MetadataType)_field.OwningType), "CCtor context");
83+
}
84+
}
85+
86+
// Runtime reflection stack needs to obtain the type handle of the field
87+
// (but there's no type handles for function pointers)
88+
if (!_field.FieldType.IsFunctionPointer)
89+
dependencies.Add(factory.MaximallyConstructableType(_field.FieldType.NormalizeInstantiation()), "Type of the field");
90+
91+
return dependencies;
92+
}
93+
protected override string GetName(NodeFactory factory)
94+
{
95+
return "Reflectable field: " + _field.ToString();
96+
}
97+
98+
public override bool InterestingForDynamicDependencyAnalysis => false;
99+
public override bool HasDynamicDependencies => false;
100+
public override bool HasConditionalStaticDependencies => false;
101+
public override bool StaticDependenciesAreComputed => true;
102+
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory) => null;
103+
public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory factory) => null;
104+
}
105+
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ThreadStaticsNode.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public class ThreadStaticsNode : EmbeddedObjectNode, ISymbolDefinitionNode
1919

2020
public ThreadStaticsNode(MetadataType type, NodeFactory factory)
2121
{
22-
Debug.Assert(factory.Target.Abi == TargetAbi.NativeAot || factory.Target.Abi == TargetAbi.CppCodegen);
22+
Debug.Assert(!type.IsCanonicalSubtype(CanonicalFormKind.Specific));
23+
Debug.Assert(!type.IsGenericDefinition);
2324
_type = type;
2425
}
2526

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeMetadataNode.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
5858
// A lot of the enum reflection actually happens on top of the respective MethodTable (e.g. getting the underlying type),
5959
// so for enums also include their MethodTable.
6060
dependencies.Add(factory.MaximallyConstructableType(_type), "Reflectable enum");
61+
62+
// Enums are not useful without their literal fields. The literal fields are not referenced
63+
// from anywhere (source code reference to enums compiles to the underlying numerical constants in IL).
64+
foreach (FieldDesc enumField in _type.GetFields())
65+
{
66+
if (enumField.IsLiteral)
67+
{
68+
dependencies.Add(factory.FieldMetadata(enumField), "Value of a reflectable enum");
69+
}
70+
}
6171
}
6272

6373
// If the user asked for complete metadata to be generated for all types that are getting metadata, ensure that.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/IRootingServiceProvider.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public interface IRootingServiceProvider
1313
void AddCompilationRoot(MethodDesc method, string reason, string exportName = null);
1414
void AddCompilationRoot(TypeDesc type, string reason);
1515
void AddReflectionRoot(MethodDesc method, string reason);
16+
void AddReflectionRoot(FieldDesc field, string reason);
1617
void RootThreadStaticBaseForType(TypeDesc type, string reason);
1718
void RootGCStaticBaseForType(TypeDesc type, string reason);
1819
void RootNonGCStaticBaseForType(TypeDesc type, string reason);

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,19 @@ public void GetDependenciesDueToReflectability(ref DependencyList dependencies,
345345
}
346346
}
347347

348+
/// <summary>
349+
/// This method is an extension point that can provide additional metadata-based dependencies to generated fields.
350+
/// </summary>
351+
public void GetDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, FieldDesc field)
352+
{
353+
MetadataCategory category = GetMetadataCategory(field);
354+
355+
if ((category & MetadataCategory.Description) != 0)
356+
{
357+
GetMetadataDependenciesDueToReflectability(ref dependencies, factory, field);
358+
}
359+
}
360+
348361
/// <summary>
349362
/// This method is an extension point that can provide additional metadata-based dependencies on a virtual method.
350363
/// </summary>
@@ -359,6 +372,13 @@ protected virtual void GetMetadataDependenciesDueToReflectability(ref Dependency
359372
// and property setters)
360373
}
361374

375+
protected virtual void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, FieldDesc field)
376+
{
377+
// MetadataManagers can override this to provide additional dependencies caused by the emission of metadata
378+
// (E.g. dependencies caused by the field having custom attributes applied to it: making sure we compile the attribute constructor
379+
// and property setters)
380+
}
381+
362382
/// <summary>
363383
/// This method is an extension point that can provide additional metadata-based dependencies to generated EETypes.
364384
/// </summary>
@@ -371,15 +391,6 @@ public void GetDependenciesDueToReflectability(ref DependencyList dependencies,
371391
GetMetadataDependenciesDueToReflectability(ref dependencies, factory, type);
372392
}
373393

374-
if ((category & MetadataCategory.RuntimeMapping) != 0)
375-
{
376-
// We're going to generate a mapping table entry for this. Collect dependencies.
377-
378-
// Nothing special is needed for the mapping table (we only emit the MethodTable and we already
379-
// have one, since we got this callback). But check if a child wants to do something extra.
380-
GetRuntimeMappingDependenciesDueToReflectability(ref dependencies, factory, type);
381-
}
382-
383394
GetDependenciesDueToEETypePresence(ref dependencies, factory, type);
384395
}
385396

@@ -390,12 +401,6 @@ protected virtual void GetMetadataDependenciesDueToReflectability(ref Dependency
390401
// and property setters)
391402
}
392403

393-
protected virtual void GetRuntimeMappingDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, TypeDesc type)
394-
{
395-
// MetadataManagers can override this to provide additional dependencies caused by the emission of a runtime
396-
// mapping for a type.
397-
}
398-
399404
protected virtual void GetDependenciesDueToEETypePresence(ref DependencyList dependencies, NodeFactory factory, TypeDesc type)
400405
{
401406
// MetadataManagers can override this to provide additional dependencies caused by the emission of an MethodTable.

0 commit comments

Comments
 (0)