[dotnet] Default to the trimmable-static registrar for NativeAOT on .NET 11 - #26346
[dotnet] Default to the trimmable-static registrar for NativeAOT on .NET 11#26346rolfbjarne wants to merge 7 commits into
Conversation
…-static type maps
The types named by our type-map entries only show up in the custom attribute
blobs, as assembly-qualified names. Nothing else in the generated type map
assembly refers to them from IL, so Cecil doesn't emit a TypeRef row for them.
That's valid metadata: ECMA-335 II.23.3 only requires a System.Type custom
attribute argument to be stored as a SerString holding the type's canonical name,
and neither the CustomAttribute (II.22.10) nor the TypeRef (II.22.38) validity
rules require a matching TypeRef row. The runtime agrees - it resolves these
types by parsing the string, not by looking at the TypeRef table - and the type
map design explicitly says the assembly name in TypeMapAssemblyTargetAttribute
doesn't need a matching AssemblyRef row either.
crossgen2 nonetheless assumes the TypeRef row is there. It fails all four lookups
in ModuleTokenResolver.GetModuleTokenForType (the type doesn't version with the
compilation, there's no TypeRef in the input, and nothing has added it to the
manifest module), and falls through to a bare
`throw new NotImplementedException (type.ToString ())`, so ReadyToRun-compiling
an app with the trimmable-static registrar fails:
System.NotImplementedException: [Microsoft.macOS]UserNotificationsUI.IUNNotificationContentExtension
at ILCompiler.DependencyAnalysis.ReadyToRun.ModuleTokenResolver.GetModuleTokenForType(TypeDesc, Boolean, Boolean)
at ILCompiler.DependencyAnalysis.ReadyToRun.SignatureContext.GetTargetModule(TypeDesc)
at ILCompiler.DependencyAnalysis.ReadyToRun.TypeFixupSignature.GetData(NodeFactory, Boolean)
at ILCompiler.DependencyAnalysis.ReadyToRun.ImportSectionNode.MaterializeSignature(NodeFactory)
at ILCompiler.DependencyAnalysis.ReadyToRun.ManifestMetadataTableNode.ComputeLastSetOfModuleIndices()
at ILCompiler.DependencyAnalysis.ReadyToRun.ManifestAssemblyMvidHeaderNode.GetData(NodeFactory, Boolean)
Work around it by adding an unused method to each generated type map assembly
that does an `ldtoken` for every externally defined type the maps name. That's
enough for Cecil to emit the TypeRef rows, and crossgen2 then resolves the types
through `TryGetModuleTokenForExternalType`. `ldtoken` is used rather than a field
or parameter because it also works for open generic types. The method is never
called, so it's trimmed away again by ILLink.
Only CoreCLR needs this, so the other runtimes don't pay for it. In particular
NativeAOT app size is unaffected (bit-for-bit identical). For CoreCLR it adds
~64 KB per runtime identifier to the platform type map assembly.
Reported upstream as dotnet/runtime#131527, and tracked
for removal in #26343.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66
The workaround emits TypeRef rows for every type map entry into the generated type map assemblies when using CoreCLR, which makes those assemblies (and thus the app) slightly bigger. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66
The previous commit was generated on a local machine whose build produces a slightly different Info.plist / Microsoft.macOS.dll / executable than CI does (a pre-existing ~5.9 KB difference that also shows up in the unmodified CoreCLR_Interpreter test). Keep only the delta actually caused by this change: +64,000 bytes per RID in the generated type map assembly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66
…NET 11. On .NET 11+, default the registrar to 'trimmable-static' (instead of 'managed-static') for NativeAOT, and enable the PrepareAssemblies flow by default for NativeAOT. Both are set at evaluation time (in Xamarin.Shared.Sdk.props) so the static _SkipILLink / _PostprocessAssembliesAfterIlc gates see them; PostProcessAssemblies gets the same $(PrepareAssemblies) default here too because that gate reads it earlier than the default in Xamarin.Shared.targets runs. Scoped to NativeAOT on purpose: the trimmable-static registrar only produces a smaller app under NativeAOT (where the TypeMap feature and the post-ILC registrar reorder let us trim the registered binding surface). On Mono/CoreCLR it keeps the whole registered NSObject surface and makes apps 2-3x larger, so those runtimes keep managed-static. This is a no-op on .NET 10 (the condition requires TargetFrameworkVersion >= 11.0). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66
The same note is added by "[dotnet] Fix latent trimmable-static registrar bugs on non-NativeAOT runtimes", so adding it here too would just cause a merge conflict. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66
Two different assemblies can contain types with the same full name, in which case deduplicating on the full name alone would skip a TypeRef we need. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66
…immable-static-default
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
This PR updates the .NET SDK MSBuild defaults so that, on .NET 11+ NativeAOT publishes, the build uses the trimmable-static registrar and enables the PrepareAssemblies/PostProcessAssemblies pipeline early (at evaluation time) to satisfy existing static gating logic in the SDK targets.
Changes:
- Default
$(Registrar)totrimmable-staticfor NativeAOT publishes on .NET 11+. - Default
$(PrepareAssemblies)totruefor NativeAOT publishes on .NET 11+. - Default
$(PostProcessAssemblies)to$(PrepareAssemblies)at evaluation time for NativeAOT publishes on .NET 11+ (so evaluation-time gates observe the intended value).
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ API diff for current PR / commitNET (empty diffs)✅ API diff vs stableNET (empty diffs)ℹ️ Generator diffGenerator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes) Pipeline on Agent |
🚀 [CI Build #5b41cc2] Test results 🚀Test results✅ All tests passed on VSTS: test results. 🎉 All 203 tests passed 🎉 Tests counts✅ assembly-processing: All 1 tests passed. Html Report (VSDrops) Download macOS tests✅ Tests on macOS Monterey (12): All 5 tests passed. Html Report (VSDrops) Download Linux Build VerificationPipeline on Agent |
On .NET 11+, default
Registrartotrimmable-static(instead ofmanaged-static) for NativeAOT, and enable thePrepareAssemblies/PostProcessAssembliesflow by default for NativeAOT.All three are set at evaluation time (in
Xamarin.Shared.Sdk.props) rather than in theSelectRegistrartarget, because the static_SkipILLink/_PostprocessAssembliesAfterIlcgates read them at evaluation time.PostProcessAssembliesnormally defaults to$(PrepareAssemblies)inXamarin.Shared.targets, but that default runs later than the gate reads it, so the same default is applied here too.This is a no-op on .NET 10 — every condition requires
TargetFrameworkVersion >= 11.0. Verified: allAppSizeTestNativeAOT and NativeAOT_TrimmableStatic expected sizes are unchanged onmain(within ±5 bytes), so no expected files needed updating.Why NativeAOT only?
ios-arm64, measured on thenet11.0branch:managed-static(current default)trimmable-staticmanaged-static,MtouchLink=Fulltrimmable-static+PrepareAssemblies+MtouchLink=FullWith the default trim mode (SDK-only)
trimmable-staticis ~3x larger, and even in the best case (full linking +PrepareAssemblies, which is not the default) it's still 41 KB larger thanmanaged-staticin the same trim mode. macOS CoreCLR-Interpreter shows the same pattern (246.9 MB → 255.9 MB).The reason is that the size win of
trimmable-staticcomes from the post-ILC registrar reorder, which lets us drop the native code for theUnmanagedCallersOnlytrampolines that ILC trimmed away. There is no equivalent on CoreCLR/Mono, so the whole registeredNSObjectsurface is kept.Changing the CoreCLR default should be revisited once the registered surface can actually be trimmed there.
🤖 Pull request created by Copilot