Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emit less metadata for not-reflection-visible types #91660

Merged
merged 2 commits into from
Sep 6, 2023

Conversation

MichalStrehovsky
Copy link
Member

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:

[TypeForwardedFrom("PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
[TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
[ValueSerializer("System.Windows.Input.CommandValueSerializer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
public interface ICommand
.

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 MethodTables get no reflection metadata, and that's the end of the story.

After #85810 however, the reflection stack can no longer reason about MethodTables 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.

Cc @dotnet/ilc-contrib

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 dotnet#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.
@ghost
Copy link

ghost commented Sep 6, 2023

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

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:

[TypeForwardedFrom("PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
[TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
[ValueSerializer("System.Windows.Input.CommandValueSerializer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
public interface ICommand
.

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 MethodTables get no reflection metadata, and that's the end of the story.

After #85810 however, the reflection stack can no longer reason about MethodTables 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.

Cc @dotnet/ilc-contrib

Author: MichalStrehovsky
Assignees: MichalStrehovsky
Labels:

area-NativeAOT-coreclr

Milestone: -

@@ -24,11 +24,13 @@ namespace ILCompiler.DependencyAnalysis
internal sealed class TypeMetadataNode : DependencyNodeCore<NodeFactory>
{
private readonly MetadataType _type;
private readonly bool _isMinimal;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a comment describing what it means for type metadata to be "minimal"?
And why is it OK to not include custom attributes in this case for example.

@@ -100,7 +110,7 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
/// Decomposes a constructed type into individual <see cref="TypeMetadataNode"/> units that will be needed to
/// express the constructed type in metadata.
/// </summary>
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I find it confusing the "minimal"/"full" usage. If they're complementary (type is either minimal or full, but nothing else), then I would prefer to use only one of the two terms and negation.
If they're not complementary then I think there should be a comment somewhere describing the differences and where/how it's used.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried to address both.

Copy link
Member

@vitek-karas vitek-karas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@MichalStrehovsky
Copy link
Member Author

/azp run runtime-extra-platforms

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@MichalStrehovsky
Copy link
Member Author

/backport to release/8.0

@github-actions
Copy link
Contributor

github-actions bot commented Sep 6, 2023

Started backporting to release/8.0: https://github.com/dotnet/runtime/actions/runs/6103552055

@MichalStrehovsky MichalStrehovsky merged commit 0bfc0c9 into dotnet:main Sep 6, 2023
151 of 183 checks passed
@MichalStrehovsky MichalStrehovsky deleted the limitedmd branch September 6, 2023 23:44
MichalStrehovsky added a commit to MichalStrehovsky/runtime that referenced this pull request Sep 13, 2023
github-actions bot pushed a commit that referenced this pull request Sep 13, 2023
carlossanlop added a commit that referenced this pull request Sep 13, 2023
…ypes (#91660)" (#91989)

* Revert "Emit less metadata for not-reflection-visible types (#91660)"

This reverts commit 0bfc0c9.

* Regression test

---------

Co-authored-by: Michal Strehovský <[email protected]>
Co-authored-by: Carlos Sánchez López <[email protected]>
@radical radical mentioned this pull request Sep 26, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Oct 7, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants