Skip to content

[TrimmableTypeMap] Improve incremental build performance#12229

Open
simonrozsival wants to merge 3 commits into
mainfrom
trimmable-typemap-incremental-builds
Open

[TrimmableTypeMap] Improve incremental build performance#12229
simonrozsival wants to merge 3 commits into
mainfrom
trimmable-typemap-incremental-builds

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

  • update post-trim JCWs in place so unchanged Java sources keep stable timestamps
  • give linked acw-map.txt and ApplicationRegistration.java a single post-trim owner
  • make _GenerateJavaStubs react independently to pre-trim manifest changes and post-trim Java changes
  • persist and validate the expected linked-Java output set, recovering missing or stale files
  • preserve last known-good linked JCWs when generation fails
  • make the legacy llvm-ir post-trimming assembly pipeline incremental
  • reuse persisted TypeMap and Java output lists instead of wildcard/glob discovery
  • skip unchanged CoreCLR shrunk-assembly copies while preserving NativeAOT's always-run remap

Motivation

On a Release CoreCLR dotnet new maui --sample-content app, an ordinary managed source touch caused the trimmable path to delete and recreate all 566 linked JCWs. Their new timestamps forced javac and D8 to rebuild identical output:

Scenario llvm-ir Trimmable before This PR
C# source touch 50.73s 68.32s 52.31s
Android manifest touch 5.63s 39.74s 10.97s

With the generic universal-APK no-op fix from #12230 applied, the additional
trimmable-specific no-op changes in this PR reduce repeated dotnet build
from 4.58 seconds to approximately 3.0-3.5 seconds:

Change Real time
#12230 only 4.58s
Use typemap-assemblies.txt for ILLink inputs 4.02s
Reuse post-trim Java list for FileWrites 3.93s
Persist/reuse pre-trim Java list 3.85s
Incremental CoreCLR shrunk-assembly copy ~3.0-3.5s

The manifest case also exposed an output-ownership bug. The pre-trim pass overwrote the final post-trim files while the post-trim target remained up to date:

Output Correct post-trim Overwriting pre-trim
acw-map.txt 660,756 B / 6,186 lines 4,927,847 B / 49,665 lines
ApplicationRegistration.java 341 B / 12 lines 413 B / 13 lines

The overwritten registration source additionally registered android.app.Application, and its changed timestamp triggered javac and D8.

Implementation

  • CoreCLR trimmed pre-generation no longer writes the final ACW map or application-registration source.
  • The post-trim pass uses content-based in-place updates instead of wiping linked-java.
  • _GenerateJavaStubs depends on both producer stamps: pre-trim for the manifest/shared outputs and post-trim for linked JCWs.
  • linked-java-files.txt records the expected JCW set. Missing or unexpected Java files, a missing ACW map, or a missing registration source invalidate the post-trim stamp.
  • Stale pruning is suppressed after generation errors so a partial result cannot delete the previous valid output set.
  • _PostTrimmingPipeline now has explicit inputs/outputs while a separate collector target continues to repopulate its dynamic assembly items.
  • _AddTrimmableTypeMapToLinker consumes typemap-assemblies.txt instead of wildcarding the output directory.
  • java-files.txt and linked-java-files.txt drive no-op FileWrites; migration fallbacks preserve outputs from older obj trees.
  • CoreCLR uses an incremental shrunk-assembly copy target. NativeAOT retains the original always-run project-local remap/copy path.

Validation

  • Debug solution build
  • Build_WithTrimmableTypeMap_PublishTrimmed_IncrementalChangesAvoidUnnecessaryJavaWork
  • Build_WithTrimmableTypeMap_PublishTrimmed_PostTrimJavaGenerationIsIncremental
  • Build_WithTrimmableTypeMap_PublishTrimmed_DeletesStaleLinkedJavaWhenLinkedJavaShrinks
  • Execute_MissingJavaSource_DoesNotPruneExistingOutput
  • Build_WithTrimmableTypeMap_MissingJavaListPreservesGeneratedJava
  • Build_WithTrimmableTypeMap_PublishTrimmed_MissingLinkedJavaListRegenerates
  • parameterized Build_WithTrimmableTypeMap_IncrementalBuild
  • AfterILLinkAdditionalStepsIsSkippedOnSecondBuild
  • API 36 arm64 emulator: both trimmable and llvm-ir apps launched and survived 100 Monkey events

Follow-up

Clean trimmable builds still carry 85 managed TypeMap DLLs through ILLink, ProcessAssemblies, compression, and packaging; 49 are empty stubs. Reducing that file fan-out is a separate, larger optimization.

Related to #12184

Preserve unchanged post-trim Java outputs, prevent pre-trim generation from clobbering linked metadata, and make the legacy post-trimming pipeline incremental.

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

Copilot-Session: a301fa68-0e6c-4280-b2f2-bc3d4c9ae3de
Copilot AI review requested due to automatic review settings July 24, 2026 14:54

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 incremental build performance for the Trimmable TypeMap pipeline (CoreCLR trimmed builds) by avoiding unnecessary Java churn, correcting output ownership for post-trim generated files, and making the legacy llvm-ir post-trimming pipeline incremental.

Changes:

  • Updates post-trim linked-Java generation to be incremental and recover missing outputs, with explicit tracking of the expected linked-Java output set.
  • Fixes output ownership so pre-trim generation doesn’t overwrite post-trim acw-map.txt / ApplicationRegistration.java, and makes _GenerateJavaStubs react to both pre- and post-trim producer stamps.
  • Makes llvm-ir _PostTrimmingPipeline incremental via explicit inputs/outputs and a separate collector target; adds/extends test coverage around these behaviors.
Show a summary per file
File Description
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs Adds incremental/recovery tests for post-trim linked-Java outputs and verifies Java toolchain targets remain skipped when appropriate.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs Adds a regression test ensuring failing in-place updates don’t prune last-known-good outputs; extends test harness to collect errors.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs Extends incremental test expectations to include _PostTrimmingPipeline behavior for CoreCLR.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs Adjusts stale-Java pruning behavior to avoid deleting outputs when generation has logged errors.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets Updates stamp/input wiring and prevents pre-trim generator from overwriting post-trim-owned outputs; records linked-Java file list as a tracked write.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets Adds stamp invalidation when linked-Java outputs are missing/unexpected; switches post-trim generation to in-place updates and persists expected output list.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets Makes _PostTrimmingPipeline incremental with explicit inputs/outputs, plus a collector target; ensures Deterministic participates in the property cache.
src/Microsoft.Android.Sdk.TrimmableTypeMap/README.md Updates documentation to reflect the new incremental stamp ownership model and in-place linked-Java behavior.

Copilot's findings

Comments suppressed due to low confidence (1)

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets:129

  • linked-java-files.txt currently records the raw @(_PostTrimGeneratedJavaFiles) item values. To make the subsequent validation resilient across OS path separators and relative/absolute differences, it should persist a normalized form (e.g., %(FullPath)) that matches what MSBuild produces from globbing.
    <WriteLinesToFile
        File="$(_PostTrimTypeMapJavaFilesList)"
        Lines="@(_PostTrimGeneratedJavaFiles)"
        Overwrite="true"
        WriteOnlyWhenDifferent="true" />
  • Files reviewed: 8/8 changed files
  • Comments generated: 2

Reuse persisted TypeMap and Java output lists, and skip unchanged CoreCLR shrunk-assembly copies while retaining NativeAOT remapping behavior.

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

Copilot-Session: a301fa68-0e6c-4280-b2f2-bc3d4c9ae3de
Persist and compare fully qualified Java paths and normalize the post-trim output base directory for reliable Windows incrementality.

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

Copilot-Session: a301fa68-0e6c-4280-b2f2-bc3d4c9ae3de
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants