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

.NET 9: Make .NET MAUI trimmer-friendly #18658

Closed
Tracked by #80905
jonathanpeppers opened this issue Nov 9, 2023 · 12 comments
Closed
Tracked by #80905

.NET 9: Make .NET MAUI trimmer-friendly #18658

jonathanpeppers opened this issue Nov 9, 2023 · 12 comments
Labels
area-publishing Issues with the app packaging/publishing process (ipk/apk/msix/trimming) proposal/open t/app-size Application Size / Trimming (sub: perf)

Comments

@jonathanpeppers
Copy link
Member

jonathanpeppers commented Nov 9, 2023

Description

In .NET 6/7 timeframe, we were able to make a few .NET MAUI assemblies trimmable:

  • Essentials
  • Compatibility

The remaining assemblies (see #1962) were not able to easily become trimmable due to some of the dynamic features of .NET MAUI.

As we've shipped the NativeAOT experiment for iOS in .NET 8, this makes trimming even more important:

  • NativeAOT has to trim all assemblies, it has to do static analysis on all managed code.
  • NativeAOT basically ignores if an assembly declares itself IsTrimmable=true, and trims everything.
  • Linker warnings become extremely important: NativeAOT apps aren't guaranteed to work unless there are 0 trimmer warnings

Current Trimming Behavior

For .NET MAUI apps running on Mono (iOS, Android, Catalyst), we default to TrimMode=partial. This is the same as the "SDK-only" trimming mode that we got from the Xamarin days.

Current behavior is:

  • Assemblies marked with IsTrimmable=true are trimmed
    • BCL types are trimmed
    • Android APIs are trimmed (Mono.Android.dll)
    • iOS APIs are trimmed (Microsoft.iOS.dll)

We also suppress most trimmer warnings in this mode, as the warnings shown for .NET MAUI, Android assemblies, etc. are not currently actionable.

Thus, most user code and third-party libraries are not trimmed at all. We get a reasonable small application, with a lot of IL removed, but not all that is possible.

.NET 6/7/8 apps can opt into "full trimming" with the likelihood of running into runtime crashes. Few customers (if any) are using this today.

.NET Ecosystem Concerns

There are many third-party .NET libraries on NuGet, etc. that are not implemented with "trimmability" in mind. Existing .NET MAUI applications are likely using these libraries.

How would we get large adoption of NativeAOT, if we don't get existing customers to opt into this setting? We need to find out what libraries are problematic so library authors can react.

Trimmability for Everyone

I propose as part of the "trimmable" effort for .NET MAUI, that we also adopt full trimming for all platforms running on Mono: this includes Android, iOS, MacCatalyst. Existing customers will also get the benefit of smaller app sizes.

If WindowsAppSDK (by luck?) also works without issue, we can include Windows platforms as well. This issue appears to have been solved, so it might work: microsoft/CsWinRT#373

Goal for .NET 9

Project templates (dotnet new maui, android, ios, etc.) will opt into:

<TrimMode>full</TrimMode>

When in this mode, we should make sure all trimmer warnings are visible for users to react to. Project templates should show 0 warnings.

Greenfield apps will have an easier time adopting full trimming, as they can react individually as NuGet packages are added, etc. The setting can also be disabled by simply removing a line from the .csproj.

Existing projects can also "opt into" full trimming, and find out if it works for them.

(Public) API Changes

Problematic MAUI APIs include:

Dynamic XAML

grid.LoadFromXaml("<Grid></Grid>");

Bindings

new Binding("Some.Complex.PropertyPath.WithIndexersToo[0]", source);

The idea is we'd leave these APIs intact. The behavior in each case would be something like:

  • Mono, TrimMode=Full: trimmer warning at build time. API "might work" if appropriate types are preserved.
  • NativeAOT: trimmer warning at build time (we could consider an error), throws at runtime

Usage Scenarios

Up to a 50% reduction in .NET MAUI app size?

@ivanpovazan has some examples here.

Most of the size improvements were merely from opting into full-trimming. NativeAOT may also make apps smaller, but trimming is the major win for app size.

Backward Compatibility

Existing .NET MAUI apps should not see a change in behavior.

Difficulty

High

@ivanpovazan
Copy link
Member

/cc: @simonrozsival

@Eilon Eilon added the area-publishing Issues with the app packaging/publishing process (ipk/apk/msix/trimming) label Nov 9, 2023
@eerhardt
Copy link
Member

eerhardt commented Nov 9, 2023

The idea is we'd leave these APIs intact. The behavior in each case would be something like:

Mono, TrimMode=Full: trimmer warning at build time. API "might work" if appropriate types are preserved.
NativeAOT: throws at runtime

Note: on NativeAOT these APIs should also produce a build/publish-time trim/AOT warning

@rolfbjarne
Copy link
Member

Few customers (if any) are using this today.

A few have tried: #16861

That's the 9th issue when sorting issues by number of comments: https://github.com/dotnet/maui/issues?q=is%3Aissue+is%3Aopen+sort%3Acomments-desc

@AmrAlSayed0
Copy link
Contributor

Would it be possible to write a source generator that fully replaces the bindings with code that the linker can reason about especially in x:DataType compiled bindings mode?
I think the linker can pick up things like

if (source is ViewModel vm)
{
    if (vm.Some is InnerViewModel i1vm)
    {
        if (i1vm.PropertyPath is SecondInnerViewModel i2vm)
        {
            //Obviously this goes on ...
        }
    }
}

And I know I'm skipping over a lot of stuff here like hooking into intermediate INPC and converters and formatting and fallback values, etc... but if the source generated code is written in such a way that it's generically parameterized all the way down the linker can follow it.

@jonathanpeppers
Copy link
Member Author

jonathanpeppers commented Nov 29, 2023

x:Bind is a feature of other XAML dialects that supports compiled bindings, supposedly exists for UWP and WinUI3:

Either way, I think getting dotnet new maui to 0 trimmer warnings is probably where we should start -- and then investigate the state of bindings. I think the project template has 0 bindings.

@dotMorten
Copy link
Contributor

dotMorten commented Nov 29, 2023

I think getting dotnet new maui to 0 trimmer warnings is probably where we should start

One issue that is a problem already today is, if you reference a 3rd party library that already did the work to support trimming, binding to any properties in that library is extremely error prone, because the binding expressions won't ensure the properties are preserved.
I think we do need a way to detect these problems soon, preferably at compile time, but alternatively with some tool at runtime that can help us find all the bindings that tried to resolve to properties no longer available. Today the experience is that those bindings just resolve to null, with no error or warning in the output window, so they are very time consuming to resolve.

@simonrozsival
Copy link
Member

@jonathanpeppers we hit some bindings which produce warnings even in the template app, but they're all defined in C# and not in XAML which should make it easier to deal with (for example in BaseShellItem).

The x:Bind looks like something we might need to make bindings trimming-friendly. The existing Binding is so flexible that none of the tools we have (ILLink, XamlC, ILCompiler) can figure out what's going on at compile time. Moreover, the target property is sometimes unknown at compile time so any static analysis is impossible (for example in ShellSearchResultsRenderer).

@jonathanpeppers
Copy link
Member Author

Bindings in code like BaseShellItem, could use TypedBinding:

https://github.com/dotnet/maui/blob/main/src/Controls/src/Core/TypedBinding.cs#L72

This is a form of compiled bindings that can be used in some cases:

https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/compiled-bindings?view=net-maui-8.0

That might be better in general, it replaces System.Reflection calls with just an anonymous method/lambda. But I bet it doesn't cover all cases like x:Bind would.

jonathanpeppers pushed a commit that referenced this issue Dec 13, 2023
…19194)

This introduces two new integration tests which are testing MAUI compatibility with NativeAOT on iOS.
The added tests will enable us to catch early any regression in compatibility between MAUI iOS and NativeAOT, but also to guide us forward as we progress with: #18658

### Changes

- Introduced new test cases:
    1. `PublishNativeAOT` - tests publishing a template MAUI iOS app with NativeAOT 
    2. `PublishNativeAOTCheckWarnings` - tests the generated warnings according to #19194 (comment)
- Created a new utility method `DotnetInternal.ConstructBuildArgs` with a common logic for setting up build arguments which is shared between tests calling `Build` and `Publish` targets
- Added `BuildWarningsUtilities.cs` which encapsulates all the logic for parsing, storing and comparing build warnings

NOTE: This should be an initial set of tests verifying our progress in the effort of making MAUI trim-compatible (with NativeAOT). The set can and will be expanded to testing on device, covering support for other iOS-platforms with NativeAOT etc, as we progress.
@samhouts samhouts added t/app-size Application Size / Trimming (sub: perf) and removed area-publishing Issues with the app packaging/publishing process (ipk/apk/msix/trimming) labels Jan 15, 2024
jonathanpeppers pushed a commit that referenced this issue Apr 17, 2024
…lyzers in Controls.Core.csproj (#21621)

Contributes to #18658

We can use `[FeatureSwitchDefinition("...")]` attributes instead of `ILLink.Substitutions.xml` since dotnet/runtime#99338 has been merged and has flown into MAUI.

* Use new FeatureSwitchDefinition attributes instead of ILLink substitutions
* Update comment
* Update Controls
* Update Compatibility
* Disable analyzers in Controls.Core on Tizen
* Skip warning on Windows
* Add RUC attributes to NativeBindingExtensions
* Add RUC attributes to NativeBindingService
* Add comment explaining why DoNothing moved from Binding to MultiBinding
@Eilon Eilon added the area-publishing Issues with the app packaging/publishing process (ipk/apk/msix/trimming) label May 10, 2024
@jonathanpeppers
Copy link
Member Author

We believe this is now "complete" in .NET 9 Preview 7, as of reaching:

  • 0 trimmer warnings in dotnet new android
  • 0 trimmer warnings in dotnet new ios (and other Apple platforms)
  • 0 trimmer warnings in dotnet new maui

We also opted into TrimMode=full for dotnet new android and dotnet new ios project templates:

In the future, we may consider doing this in the dotnet new maui template as more NuGet packages in the ecosystem opt into IsTrimmable=true and IsAotCompatible=true.

We have new alternatives for C# data-binding for NativeAOT-based apps, such as:

And lastly, the following feature-switches are automatically toggled for TrimMode=full and NativeAOT-based apps:

@eerhardt
Copy link
Member

Congrats team!! It's been a long road.

@IeuanWalker
Copy link

@jonathanpeppers is there any guidance for library owners to make their libraries
Trimmable and AotCompatible?

I have quite a few libraries and not sure what I need to do or how to test it.

@eerhardt
Copy link
Member

is there any guidance for library owners to make their libraries Trimmable and AotCompatible?

Check out:

@github-actions github-actions bot locked and limited conversation to collaborators Sep 28, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-publishing Issues with the app packaging/publishing process (ipk/apk/msix/trimming) proposal/open t/app-size Application Size / Trimming (sub: perf)
Projects
None yet
Development

No branches or pull requests

10 participants