Skip to content

[DRAFT] [net11.0] App size investigations. - #26291

Draft
rolfbjarne wants to merge 4 commits into
net11.0from
dev/rolf/appsize-net11.0
Draft

[DRAFT] [net11.0] App size investigations.#26291
rolfbjarne wants to merge 4 commits into
net11.0from
dev/rolf/appsize-net11.0

Conversation

@rolfbjarne

Copy link
Copy Markdown
Member

Do not review; this PR will not be merged.

rolfbjarne and others added 4 commits July 23, 2026 08:01
…ar after ILC

Set the AssociatedSourceType field on the UnmanagedCallersOnly attributes the
managed registrar emits, so the NativeAOT compiler (ILC) can drop a trampoline's
native export when the associated type (the NSObject subclass declaring the
exported method) can't be constructed. This is an app-size optimization for
NativeAOT (issue #25631).

The field is only emitted for NativeAOT + the trimmable-static registrar +
PrepareAssemblies:

* Only ILC understands the field; CoreCLR's attribute parser rejects any
  UnmanagedCallersOnly named argument it doesn't recognize ("Known custom
  attribute named argument not recognized.") at startup, so it must not be
  emitted for CoreCLR/Mono.
* The trimmable-static registrar emits native glue (registrar.o) that references
  the trampoline symbols directly for the whole set of registered SDK types.
  ILLink keeps those types (so the glue is emitted), but ILC independently drops
  some trampoline exports, which would leave undefined symbols at native link
  time. The fix requires a post-ILC step that can reconcile the generated glue
  with the exports that survived ILC, which only PrepareAssemblies has.

To make this work with the trimmable-static registrar, the native
registrar-generating postprocessing pipeline is reordered to run *after* ILC
instead of before (gated on trimmable-static + NativeAOT + PrepareAssemblies).
This is possible because ILLink is skipped in this scenario (ILC consumes the
prepared assemblies directly), so the postprocessing output no longer feeds ILC.

After ILC, the registrar inspects ILC's output object file to determine which
trampolines survived:

* Trampolines ILC trimmed away are routed through the dlsym fallback (a runtime
  string reference) instead of a direct native symbol reference. Since ILC only
  trims a trampoline when the associated type can't be constructed, the trampoline
  is never actually invoked, so the fallback is never reached.
* Classes whose trampolines were all trimmed away are skipped entirely (no
  @interface/@implementation is emitted), except base classes still referenced as
  a superclass of an emitted class (the whole base-class chain is kept).

Additionally, a warning is emitted if any post-ILC postprocessing step modifies a
managed assembly, since ILC has already consumed them and the change would be
silently lost. Two spurious "modifications" are fixed to avoid false warnings:
ManagedRegistrarStep.ProcessType now reports the actual modification state (during
post-processing it only collects information, a read-only operation), and the
NSObject.RegisterToggleRef stub (already applied before ILC) is no longer
re-applied post-ILC.

Fixes #25631

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b6450f9-2ad9-4e89-823a-3cc6e2f33f53
…SSet<T> alive

The test verifies that a generic type referenced only from an optional protocol
member (Foundation.NSSet<T>) is trimmed away when using the trimmable static
registrar. It did so with:

    typeof (NSObject).Assembly.GetType (NamespacePrefix + "Foundation.NSSet`1")

Since NamespacePrefix is a `const string = ""`, the argument is a compile-time
constant, so ILLink's intrinsic handling of Assembly.GetType(string) resolves it
and marks Foundation.NSSet`1 as kept - the test's own assertion was the sole
reason the type survived (confirmed via --dump-dependencies). The result was the
assertion failing with "NSSet<T> must be linked away, otherwise this test is
useless".

Fix it the same way the sibling RemovedAttributes test already does: copy the
name pieces into non-const locals and go through Helper.GetType, so ILLink can't
statically resolve the type name and keep it. With this change NSSet<T> is
trimmed and the test passes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b6450f9-2ad9-4e89-823a-3cc6e2f33f53
…e-static

When building for NativeAOT with the trimmable-static registrar on .NET 11+,
enable the PrepareAssemblies/PostProcessAssemblies flow by default, so the native
static registrar code is generated *after* ILC (_PostprocessAssembliesAfterIlc) and
can drop the native code for UnmanagedCallersOnly trampolines ILC trimmed away (an
app-size optimization built on the AssociatedSourceType support).

Previously this flow only activated if the user explicitly set both PrepareAssemblies
and PostProcessAssemblies. Setting only PrepareAssemblies wasn't enough: the static
_PostprocessAssembliesAfterIlc gate is evaluated at import time and requires
PostProcessAssemblies to already be 'true' there, but PostProcessAssemblies only
defaults to $(PrepareAssemblies) later (at build time). So the gate saw an empty value
and stayed off, the native registrar was generated before ILC, and it emitted direct
references to trampolines ILC later trimmed away - undefined symbols at native link
time. Setting both properties at evaluation time makes the gate (and _SkipILLink)
see them and generates the registrar after ILC, where the existing survival-aware
logic (skip classes whose trampolines were all trimmed; route the rest through the
dlsym fallback) prevents the undefined symbols.

This reduces the iOS trimmable-static NativeAOT SizeTestApp from 14.40 MB to 11.28 MB.

Gated on .NET 11+ (the post-ILC reorder requires the .NET 11 ILC; see _SkipILLink), so
there's no effect on .NET 10.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66
@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: 1143cf4047ca15acff16537f39bb688d9fa6ec07 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🔥 [CI Build #1143cf4] Test results 🔥

Test results

❌ Tests failed on VSTS: test results

3 tests crashed, 11 tests failed, 160 tests passed.

Failures

❌ dotnettests tests (iOS)

1 tests failed, 0 tests passed.

Failed tests

  • DotNet tests: Failed (Execution failed with exit code 1)
    • Xamarin.Tests.AppSizeTest.NativeAOT_TrimmableStatic(iOS,"ios-arm...: App size changed significantly (-3,277,916 bytes (-3,201.1 KB = -3.1 MB) different > tolerance of +-10,240 bytes (10.0 KB = 0.0 ...

Html Report (VSDrops) Download

❌ dotnettests tests (MacCatalyst)

1 tests failed, 0 tests passed.

Failed tests

  • DotNet tests: Failed (Execution failed with exit code 1)
    • Xamarin.Tests.AppSizeTest.NativeAOT_TrimmableStatic(MacCatalyst,...: App size changed significantly (-3,130,500 bytes (-3,057.1 KB = -3.0 MB) different > tolerance of +-10,240 bytes (10.0 KB = 0.0 ...

Html Report (VSDrops) Download

❌ dotnettests tests (macOS)

1 tests failed, 0 tests passed.

Failed tests

  • DotNet tests: Failed (Execution failed with exit code 1)
    • Xamarin.Tests.AppSizeTest.NativeAOT_TrimmableStatic(MacOSX,"osx-...: App size changed significantly (-4,606,052 bytes (-4,498.1 KB = -4.4 MB) different > tolerance of +-10,240 bytes (10.0 KB = 0.0 ...

Html Report (VSDrops) Download

❌ dotnettests tests (tvOS)

1 tests failed, 0 tests passed.

Failed tests

  • DotNet tests: Failed (Execution failed with exit code 1)
    • Xamarin.Tests.AppSizeTest.NativeAOT_TrimmableStatic(TVOS,"tvos-a...: App size changed significantly (-2,711,412 bytes (-2,647.9 KB = -2.6 MB) different > tolerance of +-10,240 bytes (10.0 KB = 0.0 ...

Html Report (VSDrops) Download

❌ linker tests (iOS)

🔥 Failed catastrophically on VSTS: test results - linker_ios (no summary found).

Html Report (VSDrops) Download

❌ linker tests (MacCatalyst)

🔥 Failed catastrophically on VSTS: test results - linker_maccatalyst (no summary found).

Html Report (VSDrops) Download

❌ linker tests (tvOS)

4 tests failed, 27 tests passed.

Failed tests

  • link all/tvOS - simulator/Debug (PrepareAssemblies, CoreCLR, Trimmable Static Registrar): Failed
  • link all/tvOS - simulator/Debug (PrepareAssemblies, MonoVM, Trimmable Static Registrar): Failed
  • link all/tvOS - simulator/Release (PrepareAssemblies, CoreCLR, Trimmable Static Registrar): Failed
  • link all/tvOS - simulator/Release (PrepareAssemblies, MonoVM, Trimmable Static Registrar): Failed

Html Report (VSDrops) Download

❌ monotouch tests (iOS)

1 tests failed, 22 tests passed.

Failed tests

  • monotouch-test/iOS - simulator/Release (trimmable static registrar, NativeAOT): BuildFailure

Html Report (VSDrops) Download

❌ monotouch tests (MacCatalyst)

🔥 Failed catastrophically on VSTS: test results - monotouch_maccatalyst (no summary found).

Html Report (VSDrops) Download

❌ monotouch tests (macOS)

1 tests failed, 17 tests passed.

Failed tests

  • monotouch-test/macOS/Release (trimmable static registrar, NativeAOT): BuildFailure

Html Report (VSDrops) Download

❌ monotouch tests (tvOS)

1 tests failed, 22 tests passed.

Failed tests

  • monotouch-test/tvOS - simulator/Release (trimmable static registrar, NativeAOT): BuildFailure

Html Report (VSDrops) Download

Successes

✅ assembly-processing: All 1 tests passed. Html Report (VSDrops) Download
✅ cecil: All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (Multiple platforms): 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 7 tests passed. Html Report (VSDrops) Download
✅ linker (macOS): All 21 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 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: 1143cf4047ca15acff16537f39bb688d9fa6ec07 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

⚠️ AppSizeTest expected files changed ⚠️

The AppSizeTest detected changes in the expected app size files.

To update the expected files, add a comment with the following command:

/apply-gist https://gist.github.com/vs-mobiletools-engineering-service2/6b34d79bd355e4a3b9de2ceb3d1056c1
Updated files
  • iOS-NativeAOT-TrimmableStatic-size.txt
  • MacCatalyst-NativeAOT-TrimmableStatic-size.txt
  • MacOSX-NativeAOT-TrimmableStatic-size.txt
  • TVOS-NativeAOT-TrimmableStatic-size.txt

Pipeline on Agent
Hash: 1143cf4047ca15acff16537f39bb688d9fa6ec07 [PR build]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants