Skip to content

[dotnet-linker] Mark the generated type map assemblies as trimmable. - #26351

Merged
rolfbjarne merged 5 commits into
mainfrom
dev/rolf/typemap-assembly-trimmable
Jul 30, 2026
Merged

[dotnet-linker] Mark the generated type map assemblies as trimmable.#26351
rolfbjarne merged 5 commits into
mainfrom
dev/rolf/typemap-assembly-trimmable

Conversation

@rolfbjarne

Copy link
Copy Markdown
Member

The trimmable-static registrar generates one type map assembly per assembly that
contains Objective-C types (_<AssemblyName>.TypeMap.dll). When
PrepareAssemblies=true these assemblies are generated before ILLink runs, are
written to disk, and are then handed to ILLink as ordinary input assemblies.

ILLink's trim mode for our apps defaults to partial, which means ILLink only
trims assemblies that are marked with:

[assembly: AssemblyMetadata ("IsTrimmable", "True")]

Every other assembly gets AssemblyAction.Copy, which also roots everything the
assembly references.

The generated type map assemblies weren't marked as trimmable, so ILLink copied
them verbatim - and since they reference every Objective-C type in the app,
nothing could be trimmed. The platform assembly in particular survived almost
untouched, which then cascaded into the ReadyToRun image, the static registrar's
native code, and a pile of extra BCL assemblies.

This is fixed by adding the IsTrimmable assembly metadata to the generated type
map assemblies.

Results

iOS, Release, ios-arm64, CoreCLR + ReadyToRun, default link mode,
PrepareAssemblies=true + PostProcessAssemblies=true (tests/dotnet/SizeTestApp):

Configuration App size
managed-static (baseline) 11,337,504 B
trimmable-static (before) 32,135,255 B
trimmable-static (after) 11,523,224 B

The biggest individual changes (before → after):

File Before After
_Microsoft.iOS.TypeMap.dll 5,849,600 B 27,136 B
Microsoft.iOS.dll (linked) 3,912,704 B 141,312 B

The remaining ~1.6% difference versus managed-static is inherent to the
trimmable-static design (the registrar runs before trimming, so it roots the
exported members of every type it keeps).

Testing

  • AppSizeTest passes on main with no expected-size changes at all: on
    .NET 10 the type map assemblies are either generated inside ILLink (where
    Annotations.SetAction already applies) or ILLink is skipped entirely
    (NativeAOT), so this change is a no-op there. It only takes effect on .NET 11+.
  • AppSizeTest passes on the net11.0 branch (23/23).
  • monotouch-test was run on macOS with
    Registrar=trimmable-static PrepareAssemblies=true PostProcessAssemblies=true UseMonoRuntime=false MtouchLink=SdkOnly
    both with and without this change: the failures are identical, so they're
    pre-existing (generic NSObject subclasses on CoreCLR + trimmable-static) and
    not caused by this change.

🤖 Pull request created by Copilot

The trimmable-static registrar generates one type map assembly per assembly
containing Objective-C types. When PrepareAssemblies=true these assemblies are
written to disk before ILLink runs, and are then passed to ILLink as ordinary
input assemblies.

ILLink's default trim mode for our apps is 'partial', which means that only
assemblies marked with [assembly: AssemblyMetadata ("IsTrimmable", "True")] are
trimmed - every other assembly is copied as-is, and everything it references is
rooted.

The generated type map assemblies weren't marked as trimmable, so they were
copied as-is, which rooted every Objective-C type in the app - making it
impossible to trim anything at all.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66
@rolfbjarne
rolfbjarne marked this pull request as ready for review July 29, 2026 16:57
Copilot AI review requested due to automatic review settings July 29, 2026 16:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves trimming behavior for the trimmable-static registrar’s generated “type map” assemblies so they don’t block ILLink trimming when PrepareAssemblies=true (where those assemblies are generated on disk before ILLink runs).

Changes:

  • Add AssemblyMetadata("IsTrimmable","True") to generated per-assembly type map assemblies.
  • Extend AppBundleRewriter with Cecil references for AssemblyMetadataAttribute and its (string,string) constructor.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs Adds logic to stamp generated _* .TypeMap.dll assemblies as trimmable so ILLink will trim them in TrimMode=partial.
tools/dotnet-linker/AppBundleRewriter.cs Adds type/method reference helpers for System.Reflection.AssemblyMetadataAttribute to support emitting the new metadata.

Comment thread tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

When we create a separate root type map assembly (.NET 11+), we already force
it to AssemblyAction.Link when we're running as a custom linker step. Do the
same when we generate it before ILLink runs (PrepareAssemblies=true) by marking
it as trimmable, so that the two code paths behave the same way.

Verified that ILLink keeps all the TypeMapAssemblyTarget attributes when linking
the root type map assembly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66
@rolfbjarne
rolfbjarne enabled auto-merge (squash) July 29, 2026 18:12
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

rolfbjarne and others added 3 commits July 30, 2026 11:32
…vive trimming.

When the app assembly's linker action is 'Copy' (which is the case for
MtouchLink=SdkOnly), ILLink's RootAssemblyInput never calls
Annotations.SetEntryPointAssembly. As a result ILLink's TypeMapHandler has no
starting point, records nothing, and every TypeMapAttribute /
TypeMapAssociationAttribute in the generated type map assemblies is swept away.

This didn't matter until the generated type map assemblies were marked as
IsTrimmable (a832d80), because before that they were copied as-is and their
attributes were preserved. Afterwards the app would crash at startup with:

    ObjCRuntime.RuntimeException: Unable to find the managed function with id -1

Fix this by adding a custom ILLink step that runs before MarkStep and sets the
entry-point assembly annotation if ILLink didn't already do it.

.NET 11+ is not affected: we pass --typemap-entry-assembly there, and .NET 11's
MarkStep prefers that over the entry-point assembly annotation.

Also fix an unrelated pre-existing bug in tests/common/shared-dotnet.mk: the
recursive 'make delete-saved-state' at the end of 'run-bare' inherited the
exported RUNTIMEIDENTIFIER/RUNTIMEIDENTIFIERS variables and thus tripped the
guard against setting them, making the whole make invocation fail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3f879a89-7c49-4886-93e9-e43d848c5846
The step is only needed on .NET 10, so:

* Condition the item group in Xamarin.Shared.Sdk.targets on the target
  framework version being less than 11.0.
* Surround the step itself with '#if !NET11_0_OR_GREATER', so that it isn't
  even compiled on .NET 11+.

Also revert the change to tests/common/shared-dotnet.mk, that's fixed in a
separate pull request.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3f879a89-7c49-4886-93e9-e43d848c5846
@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ API diff for current PR / commit

NET (empty diffs)

✅ API diff vs stable

NET (empty diffs)

ℹ️ Generator diff

Generator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes)

Pipeline on Agent
Hash: 768c1fdee022ff9d6b323b4d452a5cb1753d4deb [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🚀 [CI Build #768c1fd] 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
✅ cecil: All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (iOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (MacCatalyst): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (macOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (Multiple platforms): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (tvOS): All 1 tests passed. Html Report (VSDrops) Download
✅ framework: All 2 tests passed. Html Report (VSDrops) Download
✅ fsharp: All 4 tests passed. Html Report (VSDrops) Download
✅ generator: All 5 tests passed. Html Report (VSDrops) Download
✅ interdependent-binding-projects: All 4 tests passed. Html Report (VSDrops) Download
✅ introspection: All 4 tests passed. Html Report (VSDrops) Download
✅ linker (iOS): All 15 tests passed. Html Report (VSDrops) Download
✅ linker (MacCatalyst): All 15 tests passed. Html Report (VSDrops) Download
✅ linker (macOS): All 21 tests passed. Html Report (VSDrops) Download
✅ linker (tvOS): All 15 tests passed. Html Report (VSDrops) Download
✅ monotouch (iOS): All 19 tests passed. Html Report (VSDrops) Download
✅ monotouch (MacCatalyst): All 18 tests passed. Html Report (VSDrops) Download
✅ monotouch (macOS): All 19 tests passed. Html Report (VSDrops) Download
✅ monotouch (tvOS): All 19 tests passed. Html Report (VSDrops) Download
✅ msbuild: All 2 tests passed. Html Report (VSDrops) Download
✅ sharpie: All 1 tests passed. Html Report (VSDrops) Download
✅ windows: All 3 tests passed. Html Report (VSDrops) Download
✅ xcframework: All 4 tests passed. Html Report (VSDrops) Download
✅ xtro: All 1 tests passed. Html Report (VSDrops) Download

macOS tests

✅ Tests on macOS Monterey (12): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Ventura (13): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Sonoma (14): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Sequoia (15): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Tahoe (26): All 5 tests passed. Html Report (VSDrops) Download

Linux Build Verification

Linux build succeeded

Pipeline on Agent
Hash: 768c1fdee022ff9d6b323b4d452a5cb1753d4deb [PR build]

@rolfbjarne rolfbjarne added the ready-to-review This PR is ready to review/merge. label Jul 30, 2026
@rolfbjarne
rolfbjarne merged commit 7250377 into main Jul 30, 2026
57 checks passed
@rolfbjarne
rolfbjarne deleted the dev/rolf/typemap-assembly-trimmable branch July 30, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot ready-to-review This PR is ready to review/merge.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants