Skip to content

Commit 0bfc0c9

Browse files
Emit less metadata for not-reflection-visible types (#91660)
In .NET 8 we massively regressed the size of an empty WinForms app. A WinForms app now brings in a big chunk of WPF with it. I traced it down to the `ICommand` interface having a WPF `TypeConverter` and `ValueSerializer` attribute on it: https://github.com/dotnet/runtime/blob/04bd438844482c907062583153a43a9e3b37dbb8/src/libraries/System.ObjectModel/src/System/Windows/Input/ICommand.cs#L13-L16. An empty app will have a call to a method on `ICommand`, but nothing actually implements `ICommand`. Previously this would mean we generate an unconstructed `MethodTable` for `ICommand`, the unconstructed `MethodTable`s get no reflection metadata, and that's the end of the story. After #85810 however, the reflection stack can no longer reason about `MethodTable`s that don't have reflection metadata, so we need to generate it. This means we end up with the custom attribute and all the reflection dataflow that comes out of it. But this metadata is not actually visible in trim safe apps (the only place where reflection could see these method tables in trim safe code is if they're used in a type comparison `x == typeof(Foo)` and we were able to optimize the method table to the unconstructed version because of that). So we can generate less of it and still get away with it. In this PR I'm adding support for skipping generation of custom attribute metadata for such types. The size of an empty WinForms app goes from 50-something MB to 20-something MB. I think we'll be able to further reduce this number to ~7 MB or less because 12 MB of this are embedded resources that look designer related.
1 parent d902b2a commit 0bfc0c9

File tree

7 files changed

+100
-33
lines changed

7 files changed

+100
-33
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,8 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
625625

626626
// Ask the metadata manager
627627
// if we have any dependencies due to presence of the EEType.
628-
factory.MetadataManager.GetDependenciesDueToEETypePresence(ref dependencies, factory, _type);
628+
bool isFullType = factory.MaximallyConstructableType(_type) == this;
629+
factory.MetadataManager.GetDependenciesDueToEETypePresence(ref dependencies, factory, _type, isFullType);
629630

630631
if (_type is MetadataType mdType)
631632
ModuleUseBasedDependencyAlgorithm.AddDependenciesDueToModuleUse(ref dependencies, factory, mdType.Module);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
2525
DependencyList dependencyList = null;
2626

2727
// Ask the metadata manager if we have any dependencies due to the presence of the EEType.
28-
factory.MetadataManager.GetDependenciesDueToEETypePresence(ref dependencyList, factory, _type);
28+
factory.MetadataManager.GetDependenciesDueToEETypePresence(ref dependencyList, factory, _type, isFullType: true);
2929

3030
return dependencyList;
3131
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,12 @@ private void CreateNodeCaches()
458458

459459
_typesWithMetadata = new NodeCache<MetadataType, TypeMetadataNode>(type =>
460460
{
461-
return new TypeMetadataNode(type);
461+
return new TypeMetadataNode(type, includeCustomAttributes: true);
462+
});
463+
464+
_typesWithMetadataWithoutCustomAttributes = new NodeCache<MetadataType, TypeMetadataNode>(type =>
465+
{
466+
return new TypeMetadataNode(type, includeCustomAttributes: false);
462467
});
463468

464469
_methodsWithMetadata = new NodeCache<MethodDesc, MethodMetadataNode>(method =>
@@ -1156,6 +1161,16 @@ internal TypeMetadataNode TypeMetadata(MetadataType type)
11561161
return _typesWithMetadata.GetOrAdd(type);
11571162
}
11581163

1164+
private NodeCache<MetadataType, TypeMetadataNode> _typesWithMetadataWithoutCustomAttributes;
1165+
1166+
internal TypeMetadataNode TypeMetadataWithoutCustomAttributes(MetadataType type)
1167+
{
1168+
// These are only meaningful for UsageBasedMetadataManager. We should not have them
1169+
// in the dependency graph otherwise.
1170+
Debug.Assert(MetadataManager is UsageBasedMetadataManager);
1171+
return _typesWithMetadataWithoutCustomAttributes.GetOrAdd(type);
1172+
}
1173+
11591174
private NodeCache<MethodDesc, MethodMetadataNode> _methodsWithMetadata;
11601175

11611176
internal MethodMetadataNode MethodMetadata(MethodDesc method)

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

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ namespace ILCompiler.DependencyAnalysis
2424
internal sealed class TypeMetadataNode : DependencyNodeCore<NodeFactory>
2525
{
2626
private readonly MetadataType _type;
27+
private readonly bool _includeCustomAttributes;
2728

28-
public TypeMetadataNode(MetadataType type)
29+
public TypeMetadataNode(MetadataType type, bool includeCustomAttributes)
2930
{
3031
Debug.Assert(type.IsTypeDefinition);
3132
_type = type;
33+
_includeCustomAttributes = includeCustomAttributes;
3234
}
3335

3436
public MetadataType Type => _type;
@@ -37,13 +39,21 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
3739
{
3840
DependencyList dependencies = new DependencyList();
3941

40-
CustomAttributeBasedDependencyAlgorithm.AddDependenciesDueToCustomAttributes(ref dependencies, factory, ((EcmaType)_type));
42+
if (_includeCustomAttributes)
43+
CustomAttributeBasedDependencyAlgorithm.AddDependenciesDueToCustomAttributes(ref dependencies, factory, ((EcmaType)_type));
4144

4245
DefType containingType = _type.ContainingType;
4346
if (containingType != null)
44-
dependencies.Add(factory.TypeMetadata((MetadataType)containingType), "Containing type of a reflectable type");
47+
{
48+
TypeMetadataNode metadataNode = _includeCustomAttributes
49+
? factory.TypeMetadata((MetadataType)containingType)
50+
: factory.TypeMetadataWithoutCustomAttributes((MetadataType)containingType);
51+
dependencies.Add(metadataNode, "Containing type of a reflectable type");
52+
}
4553
else
54+
{
4655
dependencies.Add(factory.ModuleMetadata(_type.Module), "Containing module of a reflectable type");
56+
}
4757

4858
var mdManager = (UsageBasedMetadataManager)factory.MetadataManager;
4959

@@ -100,7 +110,7 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
100110
/// Decomposes a constructed type into individual <see cref="TypeMetadataNode"/> units that will be needed to
101111
/// express the constructed type in metadata.
102112
/// </summary>
103-
public static void GetMetadataDependencies(ref DependencyList dependencies, NodeFactory nodeFactory, TypeDesc type, string reason)
113+
public static void GetMetadataDependencies(ref DependencyList dependencies, NodeFactory nodeFactory, TypeDesc type, string reason, bool isFullType = true)
104114
{
105115
MetadataManager mdManager = nodeFactory.MetadataManager;
106116

@@ -110,13 +120,13 @@ public static void GetMetadataDependencies(ref DependencyList dependencies, Node
110120
case TypeFlags.SzArray:
111121
case TypeFlags.ByRef:
112122
case TypeFlags.Pointer:
113-
GetMetadataDependencies(ref dependencies, nodeFactory, ((ParameterizedType)type).ParameterType, reason);
123+
GetMetadataDependencies(ref dependencies, nodeFactory, ((ParameterizedType)type).ParameterType, reason, isFullType);
114124
break;
115125
case TypeFlags.FunctionPointer:
116126
var pointerType = (FunctionPointerType)type;
117-
GetMetadataDependencies(ref dependencies, nodeFactory, pointerType.Signature.ReturnType, reason);
127+
GetMetadataDependencies(ref dependencies, nodeFactory, pointerType.Signature.ReturnType, reason, isFullType);
118128
foreach (TypeDesc paramType in pointerType.Signature)
119-
GetMetadataDependencies(ref dependencies, nodeFactory, paramType, reason);
129+
GetMetadataDependencies(ref dependencies, nodeFactory, paramType, reason, isFullType);
120130
break;
121131

122132
case TypeFlags.SignatureMethodVariable:
@@ -126,35 +136,30 @@ public static void GetMetadataDependencies(ref DependencyList dependencies, Node
126136
default:
127137
Debug.Assert(type.IsDefType);
128138

129-
TypeDesc typeDefinition = type.GetTypeDefinition();
139+
var typeDefinition = (MetadataType)type.GetTypeDefinition();
130140
if (typeDefinition != type)
131141
{
132-
if (mdManager.CanGenerateMetadata((MetadataType)typeDefinition))
133-
{
134-
dependencies ??= new DependencyList();
135-
dependencies.Add(nodeFactory.TypeMetadata((MetadataType)typeDefinition), reason);
136-
}
137-
138142
foreach (TypeDesc typeArg in type.Instantiation)
139143
{
140-
GetMetadataDependencies(ref dependencies, nodeFactory, typeArg, reason);
144+
GetMetadataDependencies(ref dependencies, nodeFactory, typeArg, reason, isFullType);
141145
}
142146
}
143-
else
147+
148+
if (mdManager.CanGenerateMetadata(typeDefinition))
144149
{
145-
if (mdManager.CanGenerateMetadata((MetadataType)type))
146-
{
147-
dependencies ??= new DependencyList();
148-
dependencies.Add(nodeFactory.TypeMetadata((MetadataType)type), reason);
149-
}
150+
dependencies ??= new DependencyList();
151+
TypeMetadataNode node = isFullType
152+
? nodeFactory.TypeMetadata(typeDefinition)
153+
: nodeFactory.TypeMetadataWithoutCustomAttributes(typeDefinition);
154+
dependencies.Add(node, reason);
150155
}
151156
break;
152157
}
153158
}
154159

155160
protected override string GetName(NodeFactory factory)
156161
{
157-
return "Reflectable type: " + _type.ToString();
162+
return $"Reflectable type: {_type}{(!_includeCustomAttributes ? " (No custom attributes)" : "")}";
158163
}
159164

160165
protected override void OnMarked(NodeFactory factory)

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -478,13 +478,13 @@ protected virtual void GetMetadataDependenciesDueToReflectability(ref Dependency
478478
/// <summary>
479479
/// This method is an extension point that can provide additional metadata-based dependencies to generated EETypes.
480480
/// </summary>
481-
public virtual void GetDependenciesDueToEETypePresence(ref DependencyList dependencies, NodeFactory factory, TypeDesc type)
481+
public virtual void GetDependenciesDueToEETypePresence(ref DependencyList dependencies, NodeFactory factory, TypeDesc type, bool isFullType)
482482
{
483483
MetadataCategory category = GetMetadataCategory(type);
484484

485485
if ((category & MetadataCategory.Description) != 0)
486486
{
487-
GetMetadataDependenciesDueToReflectability(ref dependencies, factory, type);
487+
GetMetadataDependenciesDueToReflectability(ref dependencies, factory, type, isFullType);
488488
}
489489
}
490490

@@ -493,7 +493,7 @@ internal virtual void GetDependenciesDueToModuleUse(ref DependencyList dependenc
493493
// MetadataManagers can override this to provide additional dependencies caused by using a module
494494
}
495495

496-
protected virtual void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, TypeDesc type)
496+
protected virtual void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, TypeDesc type, bool isFullType)
497497
{
498498
// MetadataManagers can override this to provide additional dependencies caused by the emission of metadata
499499
// (E.g. dependencies caused by the type having custom attributes applied to it: making sure we compile the attribute constructor

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,9 @@ internal override void GetDependenciesDueToModuleUse(ref DependencyList dependen
271271
}
272272
}
273273

274-
protected override void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, TypeDesc type)
274+
protected override void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, TypeDesc type, bool isFullType)
275275
{
276-
TypeMetadataNode.GetMetadataDependencies(ref dependencies, factory, type, "Reflectable type");
276+
TypeMetadataNode.GetMetadataDependencies(ref dependencies, factory, type, "Reflectable type", isFullType);
277277

278278
if (type.IsDelegate)
279279
{
@@ -385,9 +385,9 @@ private static bool IsTrimmableAssembly(ModuleDesc assembly)
385385
return false;
386386
}
387387

388-
public override void GetDependenciesDueToEETypePresence(ref DependencyList dependencies, NodeFactory factory, TypeDesc type)
388+
public override void GetDependenciesDueToEETypePresence(ref DependencyList dependencies, NodeFactory factory, TypeDesc type, bool isFullType)
389389
{
390-
base.GetDependenciesDueToEETypePresence(ref dependencies, factory, type);
390+
base.GetDependenciesDueToEETypePresence(ref dependencies, factory, type, isFullType);
391391

392392
DataflowAnalyzedTypeDefinitionNode.GetDependencies(ref dependencies, factory, FlowAnnotations, type);
393393
}
@@ -970,7 +970,7 @@ public bool GeneratesMetadata(MethodDesc methodDef)
970970

971971
public bool GeneratesMetadata(MetadataType typeDef)
972972
{
973-
return _factory.TypeMetadata(typeDef).Marked;
973+
return _factory.TypeMetadata(typeDef).Marked || _factory.TypeMetadataWithoutCustomAttributes(typeDef).Marked;
974974
}
975975

976976
public bool GeneratesMetadata(EcmaModule module, CustomAttributeHandle caHandle)

src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public static int Run()
2121
TestStaticVirtualMethodOptimizations.Run();
2222
TestTypeEquals.Run();
2323
TestBranchesInGenericCodeRemoval.Run();
24+
TestLimitedMetadataBlobs.Run();
2425

2526
return 100;
2627
}
@@ -378,6 +379,51 @@ public static void Run()
378379
}
379380
}
380381

382+
class TestLimitedMetadataBlobs
383+
{
384+
class MyAttribute : Attribute
385+
{
386+
public MyAttribute([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type t) { }
387+
}
388+
389+
class ShouldNotBeNeeded
390+
{
391+
}
392+
393+
[My(typeof(ShouldNotBeNeeded))]
394+
interface INeverImplemented
395+
{
396+
void Never();
397+
}
398+
399+
static INeverImplemented s_instance;
400+
#if !DEBUG
401+
static Type s_type;
402+
#endif
403+
404+
internal static void Run()
405+
{
406+
Console.WriteLine("Testing generation of limited metadata blobs");
407+
408+
// Force a reference to the interface from a dispatch cell
409+
if (s_instance != null)
410+
s_instance.Never();
411+
412+
// Following is for release only since it relies on optimizing the typeof into an unconstructed
413+
// MethodTable.
414+
#if !DEBUG
415+
// Force another reference from an LDTOKEN
416+
if (s_type == typeof(INeverImplemented))
417+
s_type = typeof(object);
418+
#endif
419+
420+
ThrowIfPresent(typeof(TestLimitedMetadataBlobs), nameof(ShouldNotBeNeeded));
421+
ThrowIfPresent(typeof(TestLimitedMetadataBlobs), nameof(MyAttribute));
422+
ThrowIfNotPresent(typeof(TestLimitedMetadataBlobs), nameof(INeverImplemented));
423+
}
424+
}
425+
426+
381427
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",
382428
Justification = "That's the point")]
383429
private static Type GetTypeSecretly(Type testType, string typeName) => testType.GetNestedType(typeName, BindingFlags.NonPublic | BindingFlags.Public);

0 commit comments

Comments
 (0)