Skip to content

[assembly-preparer] Skip re-serializing assemblies that weren't modified.#25971

Open
rolfbjarne wants to merge 1 commit into
mainfrom
dev/rolf/assembly-preparer-no-unnecessary-reserialization
Open

[assembly-preparer] Skip re-serializing assemblies that weren't modified.#25971
rolfbjarne wants to merge 1 commit into
mainfrom
dev/rolf/assembly-preparer-no-unnecessary-reserialization

Conversation

@rolfbjarne

Copy link
Copy Markdown
Member

SaveAssembliesStep used to Cecil-Write every assembly whose action is Link or Save,
which in a trimming build re-serializes every assembly in the trim set even though
the assembly-preparer didn't modify them (the trimming is done later by ILLink). Now
assemblies that weren't modified are output as-is - the original file is referenced
during preparation, or copied during post-processing so all assemblies end up in the
same directory for AOT compilation - instead of being re-serialized.

The modified assemblies are collected in a set on the LinkerConfiguration from the
AppBundleRewriter.AssemblySaved callback. Note we can't just encode "modified" in the
assembly action (i.e. switch Link to Save when an assembly is modified and then only
re-serialize Save assemblies), because AssemblyAction.Link is used as a gating
condition by many steps (their IsActiveFor checks 'GetAction (assembly) == Link');
changing a modified assembly's action from Link to Save mid-pipeline would make later
Link-gated steps skip it.

For monotouch-test (ios-arm64, SdkOnly) this cut the number of Cecil writes from ~170
to 14 and roughly halved the time spent in SaveAssembliesStep. Verified with the
monotouch-test suite (both 'release|linksdk' and link None, with PrepareAssemblies=true):
all tests pass.

Before build times:

Project Runtime identifier Link mode PrepareAssemblies=false PrepareAssemblies=true Difference
monotouch-test ios-arm64 None 00:02:04.03 00:02:05.67 00:00:01.64
monotouch-test ios-arm64 SdkOnly 00:01:17.47 00:01:43.61 00:00:26.14
monotouch-test iossimulator-arm64 None 00:01:37.04 00:01:38.26 00:00:01.21
monotouch-test iossimulator-arm64 SdkOnly 00:01:09.96 00:01:19.42 00:00:09.46
MySimpleApp ios-arm64 None 00:01:12.77 00:01:15.47 00:00:02.70
MySimpleApp ios-arm64 SdkOnly 00:00:20.12 00:00:47.29 00:00:27.17
MySimpleApp iossimulator-arm64 None 00:00:56.20 00:00:55.54 00:00:00.66
MySimpleApp iossimulator-arm64 SdkOnly 00:00:19.68 00:00:31.96 00:00:12.27

After build times:

Project Runtime identifier Link mode PrepareAssemblies=false PrepareAssemblies=true Difference
monotouch-test ios-arm64 None 00:02:05.92 00:02:07.87 00:00:01.94
monotouch-test ios-arm64 SdkOnly 00:01:22.09 00:01:45.01 00:00:22.92
monotouch-test iossimulator-arm64 None 00:01:36.61 00:01:37.47 00:00:00.85
monotouch-test iossimulator-arm64 SdkOnly 00:01:10.02 00:01:14.54 00:00:04.51
MySimpleApp ios-arm64 None 00:01:14.37 00:01:16.49 00:00:02.11
MySimpleApp ios-arm64 SdkOnly 00:00:20.63 00:00:42.59 00:00:21.96
MySimpleApp iossimulator-arm64 None 00:00:53.89 00:00:54.73 00:00:00.83
MySimpleApp iossimulator-arm64 SdkOnly 00:00:19.67 00:00:27.25 00:00:07.58

…ied.

SaveAssembliesStep used to Cecil-Write every assembly whose action is Link or Save,
which in a trimming build re-serializes every assembly in the trim set even though
the assembly-preparer didn't modify them (the trimming is done later by ILLink). Now
assemblies that weren't modified are output as-is - the original file is referenced
during preparation, or copied during post-processing so all assemblies end up in the
same directory for AOT compilation - instead of being re-serialized.

The modified assemblies are collected in a set on the LinkerConfiguration from the
AppBundleRewriter.AssemblySaved callback. Note we can't just encode "modified" in the
assembly action (i.e. switch Link to Save when an assembly is modified and then only
re-serialize Save assemblies), because AssemblyAction.Link is used as a gating
condition by many steps (their IsActiveFor checks 'GetAction (assembly) == Link');
changing a modified assembly's action from Link to Save mid-pipeline would make later
Link-gated steps skip it.

For monotouch-test (ios-arm64, SdkOnly) this cut the number of Cecil writes from ~170
to 14 and roughly halved the time spent in SaveAssembliesStep. Verified with the
monotouch-test suite (both 'release|linksdk' and link None, with PrepareAssemblies=true):
all tests pass.

Before build times:

| Project        | Runtime identifier | Link mode | PrepareAssemblies=false | PrepareAssemblies=true | Difference  |
| -------------- | ------------------ | --------- | ----------------------- | ---------------------- | ----------- |
| monotouch-test | ios-arm64          | None      | 00:02:04.03             | 00:02:05.67            | 00:00:01.64 |
| monotouch-test | ios-arm64          | SdkOnly   | 00:01:17.47             | 00:01:43.61            | 00:00:26.14 |
| monotouch-test | iossimulator-arm64 | None      | 00:01:37.04             | 00:01:38.26            | 00:00:01.21 |
| monotouch-test | iossimulator-arm64 | SdkOnly   | 00:01:09.96             | 00:01:19.42            | 00:00:09.46 |
| MySimpleApp    | ios-arm64          | None      | 00:01:12.77             | 00:01:15.47            | 00:00:02.70 |
| MySimpleApp    | ios-arm64          | SdkOnly   | 00:00:20.12             | 00:00:47.29            | 00:00:27.17 |
| MySimpleApp    | iossimulator-arm64 | None      | 00:00:56.20             | 00:00:55.54            | 00:00:00.66 |
| MySimpleApp    | iossimulator-arm64 | SdkOnly   | 00:00:19.68             | 00:00:31.96            | 00:00:12.27 |

After build times:

| Project        | Runtime identifier | Link mode | PrepareAssemblies=false | PrepareAssemblies=true | Difference  |
| -------------- | ------------------ | --------- | ----------------------- | ---------------------- | ----------- |
| monotouch-test | ios-arm64          | None      | 00:02:05.92             | 00:02:07.87            | 00:00:01.94 |
| monotouch-test | ios-arm64          | SdkOnly   | 00:01:22.09             | 00:01:45.01            | 00:00:22.92 |
| monotouch-test | iossimulator-arm64 | None      | 00:01:36.61             | 00:01:37.47            | 00:00:00.85 |
| monotouch-test | iossimulator-arm64 | SdkOnly   | 00:01:10.02             | 00:01:14.54            | 00:00:04.51 |
| MySimpleApp    | ios-arm64          | None      | 00:01:14.37             | 00:01:16.49            | 00:00:02.11 |
| MySimpleApp    | ios-arm64          | SdkOnly   | 00:00:20.63             | 00:00:42.59            | 00:00:21.96 |
| MySimpleApp    | iossimulator-arm64 | None      | 00:00:53.89             | 00:00:54.73            | 00:00:00.83 |
| MySimpleApp    | iossimulator-arm64 | SdkOnly   | 00:00:19.67             | 00:00:27.25            | 00:00:07.58 |

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@rolfbjarne rolfbjarne marked this pull request as ready for review July 8, 2026 18:38
Copilot AI review requested due to automatic review settings July 8, 2026 18:38

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

This PR optimizes the assembly-preparer pipeline by avoiding unnecessary Mono.Cecil re-serialization for assemblies that were not actually modified by assembly-preparer steps (even if they’re in the trim set). Instead, unmodified assemblies are forwarded as-is during preparation (by referencing the original path) and copied during post-processing when needed to co-locate assemblies for AOT.

Changes:

  • Track which AssemblyDefinitions were actually modified via AppBundleRewriter.AssemblySaved and store them on LinkerConfiguration.
  • Update SaveAssembliesStep to skip Cecil Write for Link/Save assemblies not present in the modified set, outputting them without rewriting.
  • Extend the assembly-preparer step runner to populate the modified-assembly set while still recording per-step “modified any assemblies” timing info.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
tools/dotnet-linker/LinkerConfiguration.cs Adds a ModifiedAssemblies set to track which assemblies were actually changed and therefore require serialization.
tools/assembly-preparer/SaveAssembliesStep.cs Skips Cecil write for unmodified Link/Save assemblies; factors “output without rewriting” into a helper for both Copy* and unchanged Link/Save cases.
tools/assembly-preparer/AssemblyPreparer.cs Hooks AppBundleRewriter.AssemblySaved to both mark the current step as modifying assemblies and record the specific modified assembly into configuration state.

@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: eda01c0edde1e80ba2f016250c89b77cfeeafbe1 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🔥 [CI Build #eda01c0] Test results 🔥

Test results

❌ Tests failed on VSTS: test results

0 tests crashed, 1 tests failed, 198 tests passed.

Failures

❌ monotouch tests (iOS)

1 tests failed, 17 tests passed.

Failed tests

  • monotouch-test/iOS - simulator/Debug: Failed

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 (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 (MacCatalyst): All 17 tests passed. Html Report (VSDrops) Download
✅ monotouch (macOS): All 18 tests passed. Html Report (VSDrops) Download
✅ monotouch (tvOS): All 18 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: eda01c0edde1e80ba2f016250c89b77cfeeafbe1 [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.

4 participants