From 6367255a56b81ffe61934d0d935c47b99fc6942d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 6 Sep 2023 10:05:08 +0200 Subject: [PATCH 1/2] Emit less metadata for not-reflection-visible types 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. --- .../Compiler/DependencyAnalysis/EETypeNode.cs | 3 +- .../GenericDefinitionEETypeNode.cs | 2 +- .../DependencyAnalysis/NodeFactory.cs | 17 ++++++- .../DependencyAnalysis/TypeMetadataNode.cs | 49 ++++++++++--------- .../Compiler/MetadataManager.cs | 6 +-- .../Compiler/UsageBasedMetadataManager.cs | 10 ++-- .../TrimmingBehaviors/DeadCodeElimination.cs | 46 +++++++++++++++++ 7 files changed, 100 insertions(+), 33 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs index 61520b4bfadaf..cd1ef49a22147 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs @@ -625,7 +625,8 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact // Ask the metadata manager // if we have any dependencies due to presence of the EEType. - factory.MetadataManager.GetDependenciesDueToEETypePresence(ref dependencies, factory, _type); + bool isFullType = factory.MaximallyConstructableType(_type) == this; + factory.MetadataManager.GetDependenciesDueToEETypePresence(ref dependencies, factory, _type, isFullType); if (_type is MetadataType mdType) ModuleUseBasedDependencyAlgorithm.AddDependenciesDueToModuleUse(ref dependencies, factory, mdType.Module); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs index b42d93273e468..2bf7672884de0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs @@ -25,7 +25,7 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact DependencyList dependencyList = null; // Ask the metadata manager if we have any dependencies due to the presence of the EEType. - factory.MetadataManager.GetDependenciesDueToEETypePresence(ref dependencyList, factory, _type); + factory.MetadataManager.GetDependenciesDueToEETypePresence(ref dependencyList, factory, _type, isFullType: true); return dependencyList; } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index fbb6c08e04006..0134c066ba3a5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -458,7 +458,12 @@ private void CreateNodeCaches() _typesWithMetadata = new NodeCache(type => { - return new TypeMetadataNode(type); + return new TypeMetadataNode(type, isMinimal: false); + }); + + _typesWithMinimalMetadata = new NodeCache(type => + { + return new TypeMetadataNode(type, isMinimal: true); }); _methodsWithMetadata = new NodeCache(method => @@ -1156,6 +1161,16 @@ internal TypeMetadataNode TypeMetadata(MetadataType type) return _typesWithMetadata.GetOrAdd(type); } + private NodeCache _typesWithMinimalMetadata; + + internal TypeMetadataNode TypeMinimalMetadata(MetadataType type) + { + // These are only meaningful for UsageBasedMetadataManager. We should not have them + // in the dependency graph otherwise. + Debug.Assert(MetadataManager is UsageBasedMetadataManager); + return _typesWithMinimalMetadata.GetOrAdd(type); + } + private NodeCache _methodsWithMetadata; internal MethodMetadataNode MethodMetadata(MethodDesc method) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeMetadataNode.cs index de162987d508b..5d002e78983ed 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeMetadataNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeMetadataNode.cs @@ -24,11 +24,13 @@ namespace ILCompiler.DependencyAnalysis internal sealed class TypeMetadataNode : DependencyNodeCore { private readonly MetadataType _type; + private readonly bool _isMinimal; - public TypeMetadataNode(MetadataType type) + public TypeMetadataNode(MetadataType type, bool isMinimal) { Debug.Assert(type.IsTypeDefinition); _type = type; + _isMinimal = isMinimal; } public MetadataType Type => _type; @@ -37,13 +39,21 @@ public override IEnumerable GetStaticDependencies(NodeFacto { DependencyList dependencies = new DependencyList(); - CustomAttributeBasedDependencyAlgorithm.AddDependenciesDueToCustomAttributes(ref dependencies, factory, ((EcmaType)_type)); + if (!_isMinimal) + CustomAttributeBasedDependencyAlgorithm.AddDependenciesDueToCustomAttributes(ref dependencies, factory, ((EcmaType)_type)); DefType containingType = _type.ContainingType; if (containingType != null) - dependencies.Add(factory.TypeMetadata((MetadataType)containingType), "Containing type of a reflectable type"); + { + TypeMetadataNode metadataNode = _isMinimal + ? factory.TypeMinimalMetadata((MetadataType)containingType) + : factory.TypeMetadata((MetadataType)containingType); + dependencies.Add(metadataNode, "Containing type of a reflectable type"); + } else + { dependencies.Add(factory.ModuleMetadata(_type.Module), "Containing module of a reflectable type"); + } var mdManager = (UsageBasedMetadataManager)factory.MetadataManager; @@ -100,7 +110,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto /// Decomposes a constructed type into individual units that will be needed to /// express the constructed type in metadata. /// - public static void GetMetadataDependencies(ref DependencyList dependencies, NodeFactory nodeFactory, TypeDesc type, string reason) + public static void GetMetadataDependencies(ref DependencyList dependencies, NodeFactory nodeFactory, TypeDesc type, string reason, bool isFullType = true) { MetadataManager mdManager = nodeFactory.MetadataManager; @@ -110,13 +120,13 @@ public static void GetMetadataDependencies(ref DependencyList dependencies, Node case TypeFlags.SzArray: case TypeFlags.ByRef: case TypeFlags.Pointer: - GetMetadataDependencies(ref dependencies, nodeFactory, ((ParameterizedType)type).ParameterType, reason); + GetMetadataDependencies(ref dependencies, nodeFactory, ((ParameterizedType)type).ParameterType, reason, isFullType); break; case TypeFlags.FunctionPointer: var pointerType = (FunctionPointerType)type; - GetMetadataDependencies(ref dependencies, nodeFactory, pointerType.Signature.ReturnType, reason); + GetMetadataDependencies(ref dependencies, nodeFactory, pointerType.Signature.ReturnType, reason, isFullType); foreach (TypeDesc paramType in pointerType.Signature) - GetMetadataDependencies(ref dependencies, nodeFactory, paramType, reason); + GetMetadataDependencies(ref dependencies, nodeFactory, paramType, reason, isFullType); break; case TypeFlags.SignatureMethodVariable: @@ -126,27 +136,22 @@ public static void GetMetadataDependencies(ref DependencyList dependencies, Node default: Debug.Assert(type.IsDefType); - TypeDesc typeDefinition = type.GetTypeDefinition(); + var typeDefinition = (MetadataType)type.GetTypeDefinition(); if (typeDefinition != type) { - if (mdManager.CanGenerateMetadata((MetadataType)typeDefinition)) - { - dependencies ??= new DependencyList(); - dependencies.Add(nodeFactory.TypeMetadata((MetadataType)typeDefinition), reason); - } - foreach (TypeDesc typeArg in type.Instantiation) { - GetMetadataDependencies(ref dependencies, nodeFactory, typeArg, reason); + GetMetadataDependencies(ref dependencies, nodeFactory, typeArg, reason, isFullType); } } - else + + if (mdManager.CanGenerateMetadata(typeDefinition)) { - if (mdManager.CanGenerateMetadata((MetadataType)type)) - { - dependencies ??= new DependencyList(); - dependencies.Add(nodeFactory.TypeMetadata((MetadataType)type), reason); - } + dependencies ??= new DependencyList(); + TypeMetadataNode node = isFullType + ? nodeFactory.TypeMetadata(typeDefinition) + : nodeFactory.TypeMinimalMetadata(typeDefinition); + dependencies.Add(node, reason); } break; } @@ -154,7 +159,7 @@ public static void GetMetadataDependencies(ref DependencyList dependencies, Node protected override string GetName(NodeFactory factory) { - return "Reflectable type: " + _type.ToString(); + return $"Reflectable type: {_type}{(_isMinimal ? " (Minimal)" : "")}"; } protected override void OnMarked(NodeFactory factory) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs index 8658068c1ebe4..b62304f362c16 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs @@ -478,13 +478,13 @@ protected virtual void GetMetadataDependenciesDueToReflectability(ref Dependency /// /// This method is an extension point that can provide additional metadata-based dependencies to generated EETypes. /// - public virtual void GetDependenciesDueToEETypePresence(ref DependencyList dependencies, NodeFactory factory, TypeDesc type) + public virtual void GetDependenciesDueToEETypePresence(ref DependencyList dependencies, NodeFactory factory, TypeDesc type, bool isFullType) { MetadataCategory category = GetMetadataCategory(type); if ((category & MetadataCategory.Description) != 0) { - GetMetadataDependenciesDueToReflectability(ref dependencies, factory, type); + GetMetadataDependenciesDueToReflectability(ref dependencies, factory, type, isFullType); } } @@ -493,7 +493,7 @@ internal virtual void GetDependenciesDueToModuleUse(ref DependencyList dependenc // MetadataManagers can override this to provide additional dependencies caused by using a module } - protected virtual void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, TypeDesc type) + protected virtual void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, TypeDesc type, bool isFullType) { // MetadataManagers can override this to provide additional dependencies caused by the emission of metadata // (E.g. dependencies caused by the type having custom attributes applied to it: making sure we compile the attribute constructor diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index e680bf80f2dfa..ce13c46b29586 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -271,9 +271,9 @@ internal override void GetDependenciesDueToModuleUse(ref DependencyList dependen } } - protected override void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, TypeDesc type) + protected override void GetMetadataDependenciesDueToReflectability(ref DependencyList dependencies, NodeFactory factory, TypeDesc type, bool isFullType) { - TypeMetadataNode.GetMetadataDependencies(ref dependencies, factory, type, "Reflectable type"); + TypeMetadataNode.GetMetadataDependencies(ref dependencies, factory, type, "Reflectable type", isFullType); if (type.IsDelegate) { @@ -385,9 +385,9 @@ private static bool IsTrimmableAssembly(ModuleDesc assembly) return false; } - public override void GetDependenciesDueToEETypePresence(ref DependencyList dependencies, NodeFactory factory, TypeDesc type) + public override void GetDependenciesDueToEETypePresence(ref DependencyList dependencies, NodeFactory factory, TypeDesc type, bool isFullType) { - base.GetDependenciesDueToEETypePresence(ref dependencies, factory, type); + base.GetDependenciesDueToEETypePresence(ref dependencies, factory, type, isFullType); DataflowAnalyzedTypeDefinitionNode.GetDependencies(ref dependencies, factory, FlowAnnotations, type); } @@ -970,7 +970,7 @@ public bool GeneratesMetadata(MethodDesc methodDef) public bool GeneratesMetadata(MetadataType typeDef) { - return _factory.TypeMetadata(typeDef).Marked; + return _factory.TypeMetadata(typeDef).Marked || _factory.TypeMinimalMetadata(typeDef).Marked; } public bool GeneratesMetadata(EcmaModule module, CustomAttributeHandle caHandle) diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs index 152c851455666..e030b3a1de99c 100644 --- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs @@ -21,6 +21,7 @@ public static int Run() TestStaticVirtualMethodOptimizations.Run(); TestTypeEquals.Run(); TestBranchesInGenericCodeRemoval.Run(); + TestLimitedMetadataBlobs.Run(); return 100; } @@ -378,6 +379,51 @@ public static void Run() } } + class TestLimitedMetadataBlobs + { + class MyAttribute : Attribute + { + public MyAttribute([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type t) { } + } + + class ShouldNotBeNeeded + { + } + + [My(typeof(ShouldNotBeNeeded))] + interface INeverImplemented + { + void Never(); + } + + static INeverImplemented s_instance; +#if !DEBUG + static Type s_type; +#endif + + internal static void Run() + { + Console.WriteLine("Testing generation of limited metadata blobs"); + + // Force a reference to the interface from a dispatch cell + if (s_instance != null) + s_instance.Never(); + + // Following is for release only since it relies on optimizing the typeof into an unconstructed + // MethodTable. +#if !DEBUG + // Force another reference from an LDTOKEN + if (s_type == typeof(INeverImplemented)) + s_type = typeof(object); +#endif + + ThrowIfPresent(typeof(TestLimitedMetadataBlobs), nameof(ShouldNotBeNeeded)); + ThrowIfPresent(typeof(TestLimitedMetadataBlobs), nameof(MyAttribute)); + ThrowIfNotPresent(typeof(TestLimitedMetadataBlobs), nameof(INeverImplemented)); + } + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", Justification = "That's the point")] private static Type GetTypeSecretly(Type testType, string typeName) => testType.GetNestedType(typeName, BindingFlags.NonPublic | BindingFlags.Public); From 48beb9a6be8c45b658f008ac0f2d7026b7b6f12c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 6 Sep 2023 12:11:27 +0200 Subject: [PATCH 2/2] CR feedback --- .../Compiler/DependencyAnalysis/NodeFactory.cs | 12 ++++++------ .../DependencyAnalysis/TypeMetadataNode.cs | 18 +++++++++--------- .../Compiler/UsageBasedMetadataManager.cs | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 0134c066ba3a5..5bc64c02d2886 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -458,12 +458,12 @@ private void CreateNodeCaches() _typesWithMetadata = new NodeCache(type => { - return new TypeMetadataNode(type, isMinimal: false); + return new TypeMetadataNode(type, includeCustomAttributes: true); }); - _typesWithMinimalMetadata = new NodeCache(type => + _typesWithMetadataWithoutCustomAttributes = new NodeCache(type => { - return new TypeMetadataNode(type, isMinimal: true); + return new TypeMetadataNode(type, includeCustomAttributes: false); }); _methodsWithMetadata = new NodeCache(method => @@ -1161,14 +1161,14 @@ internal TypeMetadataNode TypeMetadata(MetadataType type) return _typesWithMetadata.GetOrAdd(type); } - private NodeCache _typesWithMinimalMetadata; + private NodeCache _typesWithMetadataWithoutCustomAttributes; - internal TypeMetadataNode TypeMinimalMetadata(MetadataType type) + internal TypeMetadataNode TypeMetadataWithoutCustomAttributes(MetadataType type) { // These are only meaningful for UsageBasedMetadataManager. We should not have them // in the dependency graph otherwise. Debug.Assert(MetadataManager is UsageBasedMetadataManager); - return _typesWithMinimalMetadata.GetOrAdd(type); + return _typesWithMetadataWithoutCustomAttributes.GetOrAdd(type); } private NodeCache _methodsWithMetadata; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeMetadataNode.cs index 5d002e78983ed..8979feb1061ca 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeMetadataNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeMetadataNode.cs @@ -24,13 +24,13 @@ namespace ILCompiler.DependencyAnalysis internal sealed class TypeMetadataNode : DependencyNodeCore { private readonly MetadataType _type; - private readonly bool _isMinimal; + private readonly bool _includeCustomAttributes; - public TypeMetadataNode(MetadataType type, bool isMinimal) + public TypeMetadataNode(MetadataType type, bool includeCustomAttributes) { Debug.Assert(type.IsTypeDefinition); _type = type; - _isMinimal = isMinimal; + _includeCustomAttributes = includeCustomAttributes; } public MetadataType Type => _type; @@ -39,15 +39,15 @@ public override IEnumerable GetStaticDependencies(NodeFacto { DependencyList dependencies = new DependencyList(); - if (!_isMinimal) + if (_includeCustomAttributes) CustomAttributeBasedDependencyAlgorithm.AddDependenciesDueToCustomAttributes(ref dependencies, factory, ((EcmaType)_type)); DefType containingType = _type.ContainingType; if (containingType != null) { - TypeMetadataNode metadataNode = _isMinimal - ? factory.TypeMinimalMetadata((MetadataType)containingType) - : factory.TypeMetadata((MetadataType)containingType); + TypeMetadataNode metadataNode = _includeCustomAttributes + ? factory.TypeMetadata((MetadataType)containingType) + : factory.TypeMetadataWithoutCustomAttributes((MetadataType)containingType); dependencies.Add(metadataNode, "Containing type of a reflectable type"); } else @@ -150,7 +150,7 @@ public static void GetMetadataDependencies(ref DependencyList dependencies, Node dependencies ??= new DependencyList(); TypeMetadataNode node = isFullType ? nodeFactory.TypeMetadata(typeDefinition) - : nodeFactory.TypeMinimalMetadata(typeDefinition); + : nodeFactory.TypeMetadataWithoutCustomAttributes(typeDefinition); dependencies.Add(node, reason); } break; @@ -159,7 +159,7 @@ public static void GetMetadataDependencies(ref DependencyList dependencies, Node protected override string GetName(NodeFactory factory) { - return $"Reflectable type: {_type}{(_isMinimal ? " (Minimal)" : "")}"; + return $"Reflectable type: {_type}{(!_includeCustomAttributes ? " (No custom attributes)" : "")}"; } protected override void OnMarked(NodeFactory factory) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index ce13c46b29586..fc0249f76847e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -970,7 +970,7 @@ public bool GeneratesMetadata(MethodDesc methodDef) public bool GeneratesMetadata(MetadataType typeDef) { - return _factory.TypeMetadata(typeDef).Marked || _factory.TypeMinimalMetadata(typeDef).Marked; + return _factory.TypeMetadata(typeDef).Marked || _factory.TypeMetadataWithoutCustomAttributes(typeDef).Marked; } public bool GeneratesMetadata(EcmaModule module, CustomAttributeHandle caHandle)