[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML - #11449

Merged
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata
May 29, 2026
Merged

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML#11449
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata

Conversation

@simonrozsival

@simonrozsivalsimonrozsival commented May 22, 2026

Copy link
Copy Markdown
Member

Summary

  • enable R8 by default for NativeAOT trimmable typemap builds
  • generate trimmable NativeAOT ProGuard keep rules after IlcCompile by intersecting retained managed type metadata from ILC scan DGML with acw-map.txt
  • keep the DGML-based logic in a dedicated GenerateNativeAotProguardConfiguration task, leaving the existing linked-assembly GenerateProguardConfiguration path unchanged
  • use a trimmable NativeAOT ProGuard resource for the common Xamarin rules so R8 can remove unused generated Java wrappers

Stacked on #11292
Related to dotnet/runtime#120204
Part of #10790
Part of #11052

Results

HelloWorld NativeAOT android-arm64 Release with trimmable typemap. Before is the existing default without R8 Java shrinking; after is the new default R8 behavior from this PR.

MetricBeforeAfter
APK size3,638.4 kB3,572.8 kB
classes.dex size259.4 kB15.2 kB
compressed classes.dex in APK72.2 kB7.0 kB
Java class count36421
Java method count3,035155

The HelloWorld ILC scan DGML input is 24,044,488 bytes and acw-map.txt has 27,234 lines. The generated ProGuard configuration has 49 rules.

20-run measurement on Apple M1 for the isolated GenerateNativeAotProguardConfiguration task:

MetricTask timeIsolated MSBuild wall time
Average377 ms918 ms
Median244 ms837 ms
Min152 ms518 ms
Max856 ms1,565 ms
P90803 ms1,327 ms
Std dev261 ms287 ms

Validation

  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj -t:SignAndroidPackage -c Release -p:PublishAot=true -p:_AndroidTypeMapImplementation=trimmable -p:RuntimeIdentifier=android-arm64 -p:AndroidPackageFormat=apk -nr:false --nologo -v minimal

CopilotAI review requested due to automatic review settings May 22, 2026 08:14
@simonrozsivalsimonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels May 22, 2026

CopilotAI 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 adds a trimmable NativeAOT + R8 workflow where ProGuard/R8 keep rules are generated from NativeAOT ILC DGML scan output intersected with acw-map.txt, allowing R8 to remove unused generated Java wrappers and significantly reduce classes.dex size.

Changes:

  • Generate NativeAOT-specific ProGuard rules after IlcCompile from *.scan.dgml.xml + acw-map.txt, and adjust R8 configuration generation to avoid broad keep rules in this mode.
  • Extend the trimmable typemap pipeline to classify framework peers, selectively emit array typemap entries only when referenced from non-framework assemblies, and persist the list of generated typemap assemblies to stabilize incremental builds.
  • Add runtime feature plumbing for IsNativeAotRuntime and adjust NativeAOT JNI initialization ordering/inputs to provide required Java peer marker classes.

Reviewed changes

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

Show a summary per file
FileDescription
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.csAdds framework-like SSL and network peer types to exercise framework JCW/peer scanning scenarios.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.csAdds test coverage for framework peer marking and array-entry emission behavior.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.csTests framework ACW conditionality and array-entry emission rules; updates anchor visibility assertions.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.csVerifies generator can emit expected framework JCW Java sources.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targetsPlumbs UseTrimmableNativeAotProguardConfiguration into the R8 task invocation.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targetsAdds _AndroidTrimmableTypeMapMaxArrayRank to the property cache for incremental invalidation.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.csAdds incremental/build validation for array-rank changes and NativeAOT/CoreCLR typemap behaviors.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.csAdds task-level tests for generated typemap assembly list file and framework JCW emission.
src/Xamarin.Android.Build.Tasks/Tasks/R8.csAdds a trimmable NativeAOT mode that alters generated ProGuard config inputs and common rules.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.csAdds framework assembly classification and writes a generated-assemblies list file.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.csAdds DGML+ACW-map based ProGuard rule generation for NativeAOT trimmable builds.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targetsPersists typemap-generated assembly list and uses it for downstream item population/incremental correctness.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targetsHooks DGML-based ProGuard rule generation and adjusts ILC inputs for trimmable typemap NativeAOT.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targetsAdds opt-out switch to skip linked-assembly ProGuard configuration generation when replaced by DGML flow.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targetsEnables DGML generation for R8 builds, configures skip/alternate ProGuard generation, and sets runtime feature.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targetsSets IsNativeAotRuntime=false runtime feature for MonoVM.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targetsSets IsNativeAotRuntime=false runtime feature for CoreCLR.
src/native/nativeaot/host/host.ccEnsures NativeAOT host provides global refs for required Java peer marker classes during init.
src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.csAdds RuntimeFeature.IsNativeAotRuntime AppContext switch.
src/Mono.Android/Android.Runtime/JNIEnvInit.csRefactors JNI initialization to share common state init and add NativeAOT-specific runtime initialization entrypoint.
src/Mono.Android/Android.Runtime/JNIEnv.csRoutes unhandled exception propagation consistently for NativeAOT.
src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.csTreats NativeAOT unhandled exceptions like the CoreCLR path.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.csPasses framework assembly names into scanning and tightens JCW generation filtering logic.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.csTracks framework peers and controls array-entry emission based on cross-assembly references.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.csAdds IsFrameworkAssembly and GenerateArrayEntries to peer model.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.csIndexes referenced types by referenced assembly to support framework peer reference detection.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.csUpdates documentation around __ArrayMapRank{N} anchors.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.csAdds no-array-map initialization paths when max array rank is 0.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.csAdds additional unconditional types and prevents unconditional rooting for framework ACWs; gates array emission.
src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.csUses the refactored JNIEnvInit initialization path and reuses common type/value manager creation helpers.

Comment threadsrc/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs Outdated
@simonrozsival
simonrozsival changed the base branch from main to dev/simonrozsival/11052-nativeaot-typemap-initMay 22, 2026 08:20
Base automatically changed from dev/simonrozsival/11052-nativeaot-typemap-init to mainMay 22, 2026 14:26
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 7b41d33 to 158bce0CompareMay 22, 2026 15:24
simonrozsivaland others added 6 commits May 23, 2026 00:27
Use NativeAOT scan DGML to identify retained managed types, intersect them with acw-map.txt, and emit concrete R8 keep rules for trimmable typemap NativeAOT builds. This avoids keeping the broad generated Java wrapper set when R8 is enabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move DGML-based NativeAOT ProGuard generation into a separate MSBuild task so GenerateProguardConfiguration remains focused on linked assembly scanning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep NativeAOT.targets free of ProGuard coordination properties. Let LlvmIr targets hardcode linked-assembly ProGuard timing for ILLink and NativeAOT, and keep the trimmable NativeAOT R8 mode flag with the trimmable NativeAOT typemap targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the trimmable NativeAOT common R8 rules to an embedded ProGuard resource, keep GenerateProguardConfiguration unchanged, add a shared trimmable typemap ProGuard target name with a CoreCLR stub, and restore the existing linked-assembly ProGuard target wiring.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Default NativeAOT trimmable typemap builds to AndroidLinkTool=r8 so the generated DGML-based keep rules are active without requiring users to opt in manually.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tolerate the no-input typemap generation case by creating an empty assembly list, avoid nested framework reference scans, and disable XML resolver use when reading ILC DGML.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 158bce0 to 3d4e1cdCompareMay 22, 2026 22:28
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot there is a failure in CI, fix it:
image

Agent-Logs-Url: https://github.com/dotnet/android/sessions/91b096b3-9ab7-4980-8234-097a0c3caca5
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

CopilotAI commented May 23, 2026

Copy link
Copy Markdown
Contributor

@copilot there is a failure in CI, fix it:
image

Fixed in a5277f4. The NativeAOT trimmable typemap target now filters unmanaged-entrypoint roots by assembly name instead of full generated paths, so framework typemap assemblies stay as ILC references without being exported.

Collect NativeAOT DGML files from per-RID intermediate output paths and merge their retained type metadata into the single ProGuard configuration consumed by R8/D8.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsivalsimonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label May 25, 2026
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

⚠️ Needs Changes

Issue counts: 0 ❌ errors · 3 ⚠️ warnings · 2 💡 suggestions

Summary

The approach is solid — intersecting ILC DGML metadata with acw-map.txt to generate focused ProGuard rules is well-designed, and the size reduction results are impressive (classes.dex from 259 kB → 15 kB).

Key items to address:

  1. O(n2) dedup in LoadJavaTypesFromAcwMapList.Contains() for dedup is quadratic; use a HashSet alongside the list.
  2. Uncoded error messagesLog.LogError calls in GenerateNativeAotProguardConfiguration should use Log.LogCodedError with XA#### codes and Properties.Resources.
  3. Null-unsafe GetManifestResourceStream — the new code path in R8.cs calls .CopyTo() on a potentially null stream.

Positive callouts:

  • Clean separation: DGML-based logic stays in a dedicated task, leaving the existing GenerateProguardConfiguration path untouched.
  • Good use of XmlReader (streaming) over LINQ-to-XML for parsing the large DGML files.
  • Proper DtdProcessing.Prohibit and XmlResolver = null for XML security.
  • The MSBuild targets have correct Inputs/Outputs for incremental build support.
  • The _ReadGeneratedTrimmableTypeMapAssemblies fallback with MakeDir+Touch is a nice resilience improvement.

Generated by Android PR Reviewer for issue #11449 · ● 18.8M

Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/R8.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Code Review Summary

Verdict: ⚠️ Needs Changes (minor — missing error code documentation)

Overview

Well-structured PR that introduces DGML-based ProGuard rule generation for NativeAOT trimmable typemap builds. The architecture is clean: a dedicated GenerateNativeAotProguardConfiguration task handles the DGML↔ACW intersection, keeping it separate from the existing GenerateProguardConfiguration path. The results (classes.dex from 259 kB → 15 kB) are impressive.

Issues

SeverityCountCategory
⚠️ warning1Missing error code documentation
💡 suggestion3Performance, documentation

What's good

  • JavaPeerScanner fix: Iterating over ReferencedTypeNamesByAssembly and checking frameworkAssemblyNames.Contains() is more efficient than the reverse — good algorithmic improvement.
  • Proper XML security: DtdProcessing.Prohibit and XmlResolver = null on the DGML reader.
  • Clean task structure: Extends AndroidTask, proper TaskPrefix, [Required] properties with defaults, coded errors from Properties.Resources, returns !Log.HasLoggedErrors.
  • Incremental build support: The _GenerateTrimmableTypeMapProguardConfiguration target has proper Inputs/Outputs and FileWrites.
  • Good separation: CoreCLR gets an empty stub target with a TODO linking to #11052, NativeAOT gets the real implementation.
  • GetEmbeddedResourceStream helper: Nice extraction that adds null-checking the original code lacked.

Generated by Android PR Reviewer for issue #11449 · ● 30.7M

Document the new XA43xx diagnostics, clarify the NativeAOT DGML/acw-map matching logic, and avoid unnecessary allocations in the ProGuard configuration task.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

✅ LGTM — Clean, well-structured PR

Solid work generating NativeAOT ProGuard rules from DGML metadata. The results are impressive — 95% reduction in classes.dex size and 94% fewer Java classes.

Highlights:

  • Good use of streaming XmlReader for the large DGML files (24MB+)
  • Proper DtdProcessing.Prohibit + XmlResolver = null for XML security
  • Clean separation: new GenerateNativeAotProguardConfiguration task handles DGML-based logic without touching the existing GenerateProguardConfiguration path
  • Error codes (XA4319–4321) properly documented with markdown files and index entries
  • Incremental build support via Inputs/Outputs and FileWrites
  • The GetEmbeddedResourceStream helper in R8.cs is a nice refactor to avoid null-returning GetManifestResourceStream

Summary of suggestions (all 💡):

#SeverityCategoryFile
1⚠️PerformanceGenerateNativeAotProguardConfiguration.cs — use CopyIfStringChanged to avoid unnecessary timestamp updates
2💡PatternsGenerateNativeAotProguardConfiguration.cs — prefer !Log.HasLoggedErrors over return false
3💡FormattingGenerateNativeAotProguardConfiguration.cs — file-scoped namespace
4💡PerformanceGenerateNativeAotProguardConfiguration.cs — ordinal IndexOf
5💡MSBuild targetsNativeAOT.targetsBeforeTargets vs DependsOn

Not flagged (no tests found): There are no unit tests for the new GenerateNativeAotProguardConfiguration task. Given the parsing logic for DGML XML and acw-map files, isolated tests would be valuable — but the PR description shows successful end-to-end validation, so this is not blocking.

CI: Unable to check CI status (tooling not authenticated in this environment).

Generated by Android PR Reviewer for issue #11449 · ● 21.8M

@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot address all review comments

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
simonrozsivaland others added 3 commits May 27, 2026 22:59
…Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets
Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Avoid rerunning IlcCompile from the outer multi-RID build when generating NativeAOT trimmable typemap ProGuard rules. The RID-specific inner builds already produce the DGML files consumed by the outer build.
Also match DGML type metadata to assembly-qualified acw-map keys so same-named managed types from unrelated assemblies are not retained accidentally.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lc-metadata
Resolve conflict in Trimmable.CoreCLR.targets: main now implements the
CoreCLR proguard generation that the PR previously stubbed out as a TODO,
so take main's _GenerateProguardAfterTargets PropertyGroup and drop the
placeholder _GenerateTrimmableTypeMapProguardConfiguration target. The
NativeAOT path retains its own _GenerateTrimmableTypeMapProguardConfiguration
target in Trimmable.NativeAOT.targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival merged commit 9984cf3 into mainMay 29, 2026
3 checks passed
@simonrozsival
simonrozsival deleted the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch May 29, 2026 12:40
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

copilot`copilot-cli` or other AIs were used to author thisready-to-reviewThis PR is ready to review/merge, I think any CI failures are just flaky (ignorable).trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@simonrozsival@jonathanpeppers
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML - #11449

Merged
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata
May 29, 2026
Merged

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML#11449
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata

Conversation

@simonrozsival

@simonrozsivalsimonrozsival commented May 22, 2026

Copy link
Copy Markdown
Member

Summary

  • enable R8 by default for NativeAOT trimmable typemap builds
  • generate trimmable NativeAOT ProGuard keep rules after IlcCompile by intersecting retained managed type metadata from ILC scan DGML with acw-map.txt
  • keep the DGML-based logic in a dedicated GenerateNativeAotProguardConfiguration task, leaving the existing linked-assembly GenerateProguardConfiguration path unchanged
  • use a trimmable NativeAOT ProGuard resource for the common Xamarin rules so R8 can remove unused generated Java wrappers

Stacked on #11292
Related to dotnet/runtime#120204
Part of #10790
Part of #11052

Results

HelloWorld NativeAOT android-arm64 Release with trimmable typemap. Before is the existing default without R8 Java shrinking; after is the new default R8 behavior from this PR.

MetricBeforeAfter
APK size3,638.4 kB3,572.8 kB
classes.dex size259.4 kB15.2 kB
compressed classes.dex in APK72.2 kB7.0 kB
Java class count36421
Java method count3,035155

The HelloWorld ILC scan DGML input is 24,044,488 bytes and acw-map.txt has 27,234 lines. The generated ProGuard configuration has 49 rules.

20-run measurement on Apple M1 for the isolated GenerateNativeAotProguardConfiguration task:

MetricTask timeIsolated MSBuild wall time
Average377 ms918 ms
Median244 ms837 ms
Min152 ms518 ms
Max856 ms1,565 ms
P90803 ms1,327 ms
Std dev261 ms287 ms

Validation

  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj -t:SignAndroidPackage -c Release -p:PublishAot=true -p:_AndroidTypeMapImplementation=trimmable -p:RuntimeIdentifier=android-arm64 -p:AndroidPackageFormat=apk -nr:false --nologo -v minimal

CopilotAI review requested due to automatic review settings May 22, 2026 08:14
@simonrozsivalsimonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels May 22, 2026

CopilotAI 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 adds a trimmable NativeAOT + R8 workflow where ProGuard/R8 keep rules are generated from NativeAOT ILC DGML scan output intersected with acw-map.txt, allowing R8 to remove unused generated Java wrappers and significantly reduce classes.dex size.

Changes:

  • Generate NativeAOT-specific ProGuard rules after IlcCompile from *.scan.dgml.xml + acw-map.txt, and adjust R8 configuration generation to avoid broad keep rules in this mode.
  • Extend the trimmable typemap pipeline to classify framework peers, selectively emit array typemap entries only when referenced from non-framework assemblies, and persist the list of generated typemap assemblies to stabilize incremental builds.
  • Add runtime feature plumbing for IsNativeAotRuntime and adjust NativeAOT JNI initialization ordering/inputs to provide required Java peer marker classes.

Reviewed changes

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

Show a summary per file
FileDescription
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.csAdds framework-like SSL and network peer types to exercise framework JCW/peer scanning scenarios.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.csAdds test coverage for framework peer marking and array-entry emission behavior.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.csTests framework ACW conditionality and array-entry emission rules; updates anchor visibility assertions.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.csVerifies generator can emit expected framework JCW Java sources.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targetsPlumbs UseTrimmableNativeAotProguardConfiguration into the R8 task invocation.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targetsAdds _AndroidTrimmableTypeMapMaxArrayRank to the property cache for incremental invalidation.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.csAdds incremental/build validation for array-rank changes and NativeAOT/CoreCLR typemap behaviors.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.csAdds task-level tests for generated typemap assembly list file and framework JCW emission.
src/Xamarin.Android.Build.Tasks/Tasks/R8.csAdds a trimmable NativeAOT mode that alters generated ProGuard config inputs and common rules.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.csAdds framework assembly classification and writes a generated-assemblies list file.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.csAdds DGML+ACW-map based ProGuard rule generation for NativeAOT trimmable builds.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targetsPersists typemap-generated assembly list and uses it for downstream item population/incremental correctness.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targetsHooks DGML-based ProGuard rule generation and adjusts ILC inputs for trimmable typemap NativeAOT.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targetsAdds opt-out switch to skip linked-assembly ProGuard configuration generation when replaced by DGML flow.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targetsEnables DGML generation for R8 builds, configures skip/alternate ProGuard generation, and sets runtime feature.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targetsSets IsNativeAotRuntime=false runtime feature for MonoVM.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targetsSets IsNativeAotRuntime=false runtime feature for CoreCLR.
src/native/nativeaot/host/host.ccEnsures NativeAOT host provides global refs for required Java peer marker classes during init.
src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.csAdds RuntimeFeature.IsNativeAotRuntime AppContext switch.
src/Mono.Android/Android.Runtime/JNIEnvInit.csRefactors JNI initialization to share common state init and add NativeAOT-specific runtime initialization entrypoint.
src/Mono.Android/Android.Runtime/JNIEnv.csRoutes unhandled exception propagation consistently for NativeAOT.
src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.csTreats NativeAOT unhandled exceptions like the CoreCLR path.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.csPasses framework assembly names into scanning and tightens JCW generation filtering logic.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.csTracks framework peers and controls array-entry emission based on cross-assembly references.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.csAdds IsFrameworkAssembly and GenerateArrayEntries to peer model.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.csIndexes referenced types by referenced assembly to support framework peer reference detection.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.csUpdates documentation around __ArrayMapRank{N} anchors.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.csAdds no-array-map initialization paths when max array rank is 0.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.csAdds additional unconditional types and prevents unconditional rooting for framework ACWs; gates array emission.
src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.csUses the refactored JNIEnvInit initialization path and reuses common type/value manager creation helpers.

Comment threadsrc/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs Outdated
@simonrozsival
simonrozsival changed the base branch from main to dev/simonrozsival/11052-nativeaot-typemap-initMay 22, 2026 08:20
Base automatically changed from dev/simonrozsival/11052-nativeaot-typemap-init to mainMay 22, 2026 14:26
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 7b41d33 to 158bce0CompareMay 22, 2026 15:24
simonrozsivaland others added 6 commits May 23, 2026 00:27
Use NativeAOT scan DGML to identify retained managed types, intersect them with acw-map.txt, and emit concrete R8 keep rules for trimmable typemap NativeAOT builds. This avoids keeping the broad generated Java wrapper set when R8 is enabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move DGML-based NativeAOT ProGuard generation into a separate MSBuild task so GenerateProguardConfiguration remains focused on linked assembly scanning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep NativeAOT.targets free of ProGuard coordination properties. Let LlvmIr targets hardcode linked-assembly ProGuard timing for ILLink and NativeAOT, and keep the trimmable NativeAOT R8 mode flag with the trimmable NativeAOT typemap targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the trimmable NativeAOT common R8 rules to an embedded ProGuard resource, keep GenerateProguardConfiguration unchanged, add a shared trimmable typemap ProGuard target name with a CoreCLR stub, and restore the existing linked-assembly ProGuard target wiring.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Default NativeAOT trimmable typemap builds to AndroidLinkTool=r8 so the generated DGML-based keep rules are active without requiring users to opt in manually.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tolerate the no-input typemap generation case by creating an empty assembly list, avoid nested framework reference scans, and disable XML resolver use when reading ILC DGML.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 158bce0 to 3d4e1cdCompareMay 22, 2026 22:28
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot there is a failure in CI, fix it:
image

Agent-Logs-Url: https://github.com/dotnet/android/sessions/91b096b3-9ab7-4980-8234-097a0c3caca5
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

CopilotAI commented May 23, 2026

Copy link
Copy Markdown
Contributor

@copilot there is a failure in CI, fix it:
image

Fixed in a5277f4. The NativeAOT trimmable typemap target now filters unmanaged-entrypoint roots by assembly name instead of full generated paths, so framework typemap assemblies stay as ILC references without being exported.

Collect NativeAOT DGML files from per-RID intermediate output paths and merge their retained type metadata into the single ProGuard configuration consumed by R8/D8.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsivalsimonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label May 25, 2026
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

⚠️ Needs Changes

Issue counts: 0 ❌ errors · 3 ⚠️ warnings · 2 💡 suggestions

Summary

The approach is solid — intersecting ILC DGML metadata with acw-map.txt to generate focused ProGuard rules is well-designed, and the size reduction results are impressive (classes.dex from 259 kB → 15 kB).

Key items to address:

  1. O(n2) dedup in LoadJavaTypesFromAcwMapList.Contains() for dedup is quadratic; use a HashSet alongside the list.
  2. Uncoded error messagesLog.LogError calls in GenerateNativeAotProguardConfiguration should use Log.LogCodedError with XA#### codes and Properties.Resources.
  3. Null-unsafe GetManifestResourceStream — the new code path in R8.cs calls .CopyTo() on a potentially null stream.

Positive callouts:

  • Clean separation: DGML-based logic stays in a dedicated task, leaving the existing GenerateProguardConfiguration path untouched.
  • Good use of XmlReader (streaming) over LINQ-to-XML for parsing the large DGML files.
  • Proper DtdProcessing.Prohibit and XmlResolver = null for XML security.
  • The MSBuild targets have correct Inputs/Outputs for incremental build support.
  • The _ReadGeneratedTrimmableTypeMapAssemblies fallback with MakeDir+Touch is a nice resilience improvement.

Generated by Android PR Reviewer for issue #11449 · ● 18.8M

Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/R8.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Code Review Summary

Verdict: ⚠️ Needs Changes (minor — missing error code documentation)

Overview

Well-structured PR that introduces DGML-based ProGuard rule generation for NativeAOT trimmable typemap builds. The architecture is clean: a dedicated GenerateNativeAotProguardConfiguration task handles the DGML↔ACW intersection, keeping it separate from the existing GenerateProguardConfiguration path. The results (classes.dex from 259 kB → 15 kB) are impressive.

Issues

SeverityCountCategory
⚠️ warning1Missing error code documentation
💡 suggestion3Performance, documentation

What's good

  • JavaPeerScanner fix: Iterating over ReferencedTypeNamesByAssembly and checking frameworkAssemblyNames.Contains() is more efficient than the reverse — good algorithmic improvement.
  • Proper XML security: DtdProcessing.Prohibit and XmlResolver = null on the DGML reader.
  • Clean task structure: Extends AndroidTask, proper TaskPrefix, [Required] properties with defaults, coded errors from Properties.Resources, returns !Log.HasLoggedErrors.
  • Incremental build support: The _GenerateTrimmableTypeMapProguardConfiguration target has proper Inputs/Outputs and FileWrites.
  • Good separation: CoreCLR gets an empty stub target with a TODO linking to #11052, NativeAOT gets the real implementation.
  • GetEmbeddedResourceStream helper: Nice extraction that adds null-checking the original code lacked.

Generated by Android PR Reviewer for issue #11449 · ● 30.7M

Document the new XA43xx diagnostics, clarify the NativeAOT DGML/acw-map matching logic, and avoid unnecessary allocations in the ProGuard configuration task.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

✅ LGTM — Clean, well-structured PR

Solid work generating NativeAOT ProGuard rules from DGML metadata. The results are impressive — 95% reduction in classes.dex size and 94% fewer Java classes.

Highlights:

  • Good use of streaming XmlReader for the large DGML files (24MB+)
  • Proper DtdProcessing.Prohibit + XmlResolver = null for XML security
  • Clean separation: new GenerateNativeAotProguardConfiguration task handles DGML-based logic without touching the existing GenerateProguardConfiguration path
  • Error codes (XA4319–4321) properly documented with markdown files and index entries
  • Incremental build support via Inputs/Outputs and FileWrites
  • The GetEmbeddedResourceStream helper in R8.cs is a nice refactor to avoid null-returning GetManifestResourceStream

Summary of suggestions (all 💡):

#SeverityCategoryFile
1⚠️PerformanceGenerateNativeAotProguardConfiguration.cs — use CopyIfStringChanged to avoid unnecessary timestamp updates
2💡PatternsGenerateNativeAotProguardConfiguration.cs — prefer !Log.HasLoggedErrors over return false
3💡FormattingGenerateNativeAotProguardConfiguration.cs — file-scoped namespace
4💡PerformanceGenerateNativeAotProguardConfiguration.cs — ordinal IndexOf
5💡MSBuild targetsNativeAOT.targetsBeforeTargets vs DependsOn

Not flagged (no tests found): There are no unit tests for the new GenerateNativeAotProguardConfiguration task. Given the parsing logic for DGML XML and acw-map files, isolated tests would be valuable — but the PR description shows successful end-to-end validation, so this is not blocking.

CI: Unable to check CI status (tooling not authenticated in this environment).

Generated by Android PR Reviewer for issue #11449 · ● 21.8M

@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot address all review comments

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
simonrozsivaland others added 3 commits May 27, 2026 22:59
…Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets
Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Avoid rerunning IlcCompile from the outer multi-RID build when generating NativeAOT trimmable typemap ProGuard rules. The RID-specific inner builds already produce the DGML files consumed by the outer build.
Also match DGML type metadata to assembly-qualified acw-map keys so same-named managed types from unrelated assemblies are not retained accidentally.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lc-metadata
Resolve conflict in Trimmable.CoreCLR.targets: main now implements the
CoreCLR proguard generation that the PR previously stubbed out as a TODO,
so take main's _GenerateProguardAfterTargets PropertyGroup and drop the
placeholder _GenerateTrimmableTypeMapProguardConfiguration target. The
NativeAOT path retains its own _GenerateTrimmableTypeMapProguardConfiguration
target in Trimmable.NativeAOT.targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival merged commit 9984cf3 into mainMay 29, 2026
3 checks passed
@simonrozsival
simonrozsival deleted the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch May 29, 2026 12:40
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

copilot`copilot-cli` or other AIs were used to author thisready-to-reviewThis PR is ready to review/merge, I think any CI failures are just flaky (ignorable).trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@simonrozsival@jonathanpeppers
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML - #11449

Merged
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata
May 29, 2026
Merged

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML#11449
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata

Conversation

@simonrozsival

@simonrozsivalsimonrozsival commented May 22, 2026

Copy link
Copy Markdown
Member

Summary

  • enable R8 by default for NativeAOT trimmable typemap builds
  • generate trimmable NativeAOT ProGuard keep rules after IlcCompile by intersecting retained managed type metadata from ILC scan DGML with acw-map.txt
  • keep the DGML-based logic in a dedicated GenerateNativeAotProguardConfiguration task, leaving the existing linked-assembly GenerateProguardConfiguration path unchanged
  • use a trimmable NativeAOT ProGuard resource for the common Xamarin rules so R8 can remove unused generated Java wrappers

Stacked on #11292
Related to dotnet/runtime#120204
Part of #10790
Part of #11052

Results

HelloWorld NativeAOT android-arm64 Release with trimmable typemap. Before is the existing default without R8 Java shrinking; after is the new default R8 behavior from this PR.

MetricBeforeAfter
APK size3,638.4 kB3,572.8 kB
classes.dex size259.4 kB15.2 kB
compressed classes.dex in APK72.2 kB7.0 kB
Java class count36421
Java method count3,035155

The HelloWorld ILC scan DGML input is 24,044,488 bytes and acw-map.txt has 27,234 lines. The generated ProGuard configuration has 49 rules.

20-run measurement on Apple M1 for the isolated GenerateNativeAotProguardConfiguration task:

MetricTask timeIsolated MSBuild wall time
Average377 ms918 ms
Median244 ms837 ms
Min152 ms518 ms
Max856 ms1,565 ms
P90803 ms1,327 ms
Std dev261 ms287 ms

Validation

  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj -t:SignAndroidPackage -c Release -p:PublishAot=true -p:_AndroidTypeMapImplementation=trimmable -p:RuntimeIdentifier=android-arm64 -p:AndroidPackageFormat=apk -nr:false --nologo -v minimal

CopilotAI review requested due to automatic review settings May 22, 2026 08:14
@simonrozsivalsimonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels May 22, 2026

CopilotAI 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 adds a trimmable NativeAOT + R8 workflow where ProGuard/R8 keep rules are generated from NativeAOT ILC DGML scan output intersected with acw-map.txt, allowing R8 to remove unused generated Java wrappers and significantly reduce classes.dex size.

Changes:

  • Generate NativeAOT-specific ProGuard rules after IlcCompile from *.scan.dgml.xml + acw-map.txt, and adjust R8 configuration generation to avoid broad keep rules in this mode.
  • Extend the trimmable typemap pipeline to classify framework peers, selectively emit array typemap entries only when referenced from non-framework assemblies, and persist the list of generated typemap assemblies to stabilize incremental builds.
  • Add runtime feature plumbing for IsNativeAotRuntime and adjust NativeAOT JNI initialization ordering/inputs to provide required Java peer marker classes.

Reviewed changes

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

Show a summary per file
FileDescription
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.csAdds framework-like SSL and network peer types to exercise framework JCW/peer scanning scenarios.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.csAdds test coverage for framework peer marking and array-entry emission behavior.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.csTests framework ACW conditionality and array-entry emission rules; updates anchor visibility assertions.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.csVerifies generator can emit expected framework JCW Java sources.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targetsPlumbs UseTrimmableNativeAotProguardConfiguration into the R8 task invocation.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targetsAdds _AndroidTrimmableTypeMapMaxArrayRank to the property cache for incremental invalidation.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.csAdds incremental/build validation for array-rank changes and NativeAOT/CoreCLR typemap behaviors.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.csAdds task-level tests for generated typemap assembly list file and framework JCW emission.
src/Xamarin.Android.Build.Tasks/Tasks/R8.csAdds a trimmable NativeAOT mode that alters generated ProGuard config inputs and common rules.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.csAdds framework assembly classification and writes a generated-assemblies list file.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.csAdds DGML+ACW-map based ProGuard rule generation for NativeAOT trimmable builds.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targetsPersists typemap-generated assembly list and uses it for downstream item population/incremental correctness.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targetsHooks DGML-based ProGuard rule generation and adjusts ILC inputs for trimmable typemap NativeAOT.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targetsAdds opt-out switch to skip linked-assembly ProGuard configuration generation when replaced by DGML flow.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targetsEnables DGML generation for R8 builds, configures skip/alternate ProGuard generation, and sets runtime feature.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targetsSets IsNativeAotRuntime=false runtime feature for MonoVM.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targetsSets IsNativeAotRuntime=false runtime feature for CoreCLR.
src/native/nativeaot/host/host.ccEnsures NativeAOT host provides global refs for required Java peer marker classes during init.
src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.csAdds RuntimeFeature.IsNativeAotRuntime AppContext switch.
src/Mono.Android/Android.Runtime/JNIEnvInit.csRefactors JNI initialization to share common state init and add NativeAOT-specific runtime initialization entrypoint.
src/Mono.Android/Android.Runtime/JNIEnv.csRoutes unhandled exception propagation consistently for NativeAOT.
src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.csTreats NativeAOT unhandled exceptions like the CoreCLR path.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.csPasses framework assembly names into scanning and tightens JCW generation filtering logic.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.csTracks framework peers and controls array-entry emission based on cross-assembly references.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.csAdds IsFrameworkAssembly and GenerateArrayEntries to peer model.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.csIndexes referenced types by referenced assembly to support framework peer reference detection.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.csUpdates documentation around __ArrayMapRank{N} anchors.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.csAdds no-array-map initialization paths when max array rank is 0.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.csAdds additional unconditional types and prevents unconditional rooting for framework ACWs; gates array emission.
src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.csUses the refactored JNIEnvInit initialization path and reuses common type/value manager creation helpers.

Comment threadsrc/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs Outdated
@simonrozsival
simonrozsival changed the base branch from main to dev/simonrozsival/11052-nativeaot-typemap-initMay 22, 2026 08:20
Base automatically changed from dev/simonrozsival/11052-nativeaot-typemap-init to mainMay 22, 2026 14:26
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 7b41d33 to 158bce0CompareMay 22, 2026 15:24
simonrozsivaland others added 6 commits May 23, 2026 00:27
Use NativeAOT scan DGML to identify retained managed types, intersect them with acw-map.txt, and emit concrete R8 keep rules for trimmable typemap NativeAOT builds. This avoids keeping the broad generated Java wrapper set when R8 is enabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move DGML-based NativeAOT ProGuard generation into a separate MSBuild task so GenerateProguardConfiguration remains focused on linked assembly scanning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep NativeAOT.targets free of ProGuard coordination properties. Let LlvmIr targets hardcode linked-assembly ProGuard timing for ILLink and NativeAOT, and keep the trimmable NativeAOT R8 mode flag with the trimmable NativeAOT typemap targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the trimmable NativeAOT common R8 rules to an embedded ProGuard resource, keep GenerateProguardConfiguration unchanged, add a shared trimmable typemap ProGuard target name with a CoreCLR stub, and restore the existing linked-assembly ProGuard target wiring.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Default NativeAOT trimmable typemap builds to AndroidLinkTool=r8 so the generated DGML-based keep rules are active without requiring users to opt in manually.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tolerate the no-input typemap generation case by creating an empty assembly list, avoid nested framework reference scans, and disable XML resolver use when reading ILC DGML.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 158bce0 to 3d4e1cdCompareMay 22, 2026 22:28
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot there is a failure in CI, fix it:
image

Agent-Logs-Url: https://github.com/dotnet/android/sessions/91b096b3-9ab7-4980-8234-097a0c3caca5
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

CopilotAI commented May 23, 2026

Copy link
Copy Markdown
Contributor

@copilot there is a failure in CI, fix it:
image

Fixed in a5277f4. The NativeAOT trimmable typemap target now filters unmanaged-entrypoint roots by assembly name instead of full generated paths, so framework typemap assemblies stay as ILC references without being exported.

Collect NativeAOT DGML files from per-RID intermediate output paths and merge their retained type metadata into the single ProGuard configuration consumed by R8/D8.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsivalsimonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label May 25, 2026
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

⚠️ Needs Changes

Issue counts: 0 ❌ errors · 3 ⚠️ warnings · 2 💡 suggestions

Summary

The approach is solid — intersecting ILC DGML metadata with acw-map.txt to generate focused ProGuard rules is well-designed, and the size reduction results are impressive (classes.dex from 259 kB → 15 kB).

Key items to address:

  1. O(n2) dedup in LoadJavaTypesFromAcwMapList.Contains() for dedup is quadratic; use a HashSet alongside the list.
  2. Uncoded error messagesLog.LogError calls in GenerateNativeAotProguardConfiguration should use Log.LogCodedError with XA#### codes and Properties.Resources.
  3. Null-unsafe GetManifestResourceStream — the new code path in R8.cs calls .CopyTo() on a potentially null stream.

Positive callouts:

  • Clean separation: DGML-based logic stays in a dedicated task, leaving the existing GenerateProguardConfiguration path untouched.
  • Good use of XmlReader (streaming) over LINQ-to-XML for parsing the large DGML files.
  • Proper DtdProcessing.Prohibit and XmlResolver = null for XML security.
  • The MSBuild targets have correct Inputs/Outputs for incremental build support.
  • The _ReadGeneratedTrimmableTypeMapAssemblies fallback with MakeDir+Touch is a nice resilience improvement.

Generated by Android PR Reviewer for issue #11449 · ● 18.8M

Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/R8.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Code Review Summary

Verdict: ⚠️ Needs Changes (minor — missing error code documentation)

Overview

Well-structured PR that introduces DGML-based ProGuard rule generation for NativeAOT trimmable typemap builds. The architecture is clean: a dedicated GenerateNativeAotProguardConfiguration task handles the DGML↔ACW intersection, keeping it separate from the existing GenerateProguardConfiguration path. The results (classes.dex from 259 kB → 15 kB) are impressive.

Issues

SeverityCountCategory
⚠️ warning1Missing error code documentation
💡 suggestion3Performance, documentation

What's good

  • JavaPeerScanner fix: Iterating over ReferencedTypeNamesByAssembly and checking frameworkAssemblyNames.Contains() is more efficient than the reverse — good algorithmic improvement.
  • Proper XML security: DtdProcessing.Prohibit and XmlResolver = null on the DGML reader.
  • Clean task structure: Extends AndroidTask, proper TaskPrefix, [Required] properties with defaults, coded errors from Properties.Resources, returns !Log.HasLoggedErrors.
  • Incremental build support: The _GenerateTrimmableTypeMapProguardConfiguration target has proper Inputs/Outputs and FileWrites.
  • Good separation: CoreCLR gets an empty stub target with a TODO linking to #11052, NativeAOT gets the real implementation.
  • GetEmbeddedResourceStream helper: Nice extraction that adds null-checking the original code lacked.

Generated by Android PR Reviewer for issue #11449 · ● 30.7M

Document the new XA43xx diagnostics, clarify the NativeAOT DGML/acw-map matching logic, and avoid unnecessary allocations in the ProGuard configuration task.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

✅ LGTM — Clean, well-structured PR

Solid work generating NativeAOT ProGuard rules from DGML metadata. The results are impressive — 95% reduction in classes.dex size and 94% fewer Java classes.

Highlights:

  • Good use of streaming XmlReader for the large DGML files (24MB+)
  • Proper DtdProcessing.Prohibit + XmlResolver = null for XML security
  • Clean separation: new GenerateNativeAotProguardConfiguration task handles DGML-based logic without touching the existing GenerateProguardConfiguration path
  • Error codes (XA4319–4321) properly documented with markdown files and index entries
  • Incremental build support via Inputs/Outputs and FileWrites
  • The GetEmbeddedResourceStream helper in R8.cs is a nice refactor to avoid null-returning GetManifestResourceStream

Summary of suggestions (all 💡):

#SeverityCategoryFile
1⚠️PerformanceGenerateNativeAotProguardConfiguration.cs — use CopyIfStringChanged to avoid unnecessary timestamp updates
2💡PatternsGenerateNativeAotProguardConfiguration.cs — prefer !Log.HasLoggedErrors over return false
3💡FormattingGenerateNativeAotProguardConfiguration.cs — file-scoped namespace
4💡PerformanceGenerateNativeAotProguardConfiguration.cs — ordinal IndexOf
5💡MSBuild targetsNativeAOT.targetsBeforeTargets vs DependsOn

Not flagged (no tests found): There are no unit tests for the new GenerateNativeAotProguardConfiguration task. Given the parsing logic for DGML XML and acw-map files, isolated tests would be valuable — but the PR description shows successful end-to-end validation, so this is not blocking.

CI: Unable to check CI status (tooling not authenticated in this environment).

Generated by Android PR Reviewer for issue #11449 · ● 21.8M

@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot address all review comments

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
simonrozsivaland others added 3 commits May 27, 2026 22:59
…Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets
Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Avoid rerunning IlcCompile from the outer multi-RID build when generating NativeAOT trimmable typemap ProGuard rules. The RID-specific inner builds already produce the DGML files consumed by the outer build.
Also match DGML type metadata to assembly-qualified acw-map keys so same-named managed types from unrelated assemblies are not retained accidentally.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lc-metadata
Resolve conflict in Trimmable.CoreCLR.targets: main now implements the
CoreCLR proguard generation that the PR previously stubbed out as a TODO,
so take main's _GenerateProguardAfterTargets PropertyGroup and drop the
placeholder _GenerateTrimmableTypeMapProguardConfiguration target. The
NativeAOT path retains its own _GenerateTrimmableTypeMapProguardConfiguration
target in Trimmable.NativeAOT.targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival merged commit 9984cf3 into mainMay 29, 2026
3 checks passed
@simonrozsival
simonrozsival deleted the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch May 29, 2026 12:40
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

copilot`copilot-cli` or other AIs were used to author thisready-to-reviewThis PR is ready to review/merge, I think any CI failures are just flaky (ignorable).trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@simonrozsival@jonathanpeppers
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML - #11449

Merged
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata
May 29, 2026
Merged

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML#11449
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata

Conversation

@simonrozsival

@simonrozsivalsimonrozsival commented May 22, 2026

Copy link
Copy Markdown
Member

Summary

  • enable R8 by default for NativeAOT trimmable typemap builds
  • generate trimmable NativeAOT ProGuard keep rules after IlcCompile by intersecting retained managed type metadata from ILC scan DGML with acw-map.txt
  • keep the DGML-based logic in a dedicated GenerateNativeAotProguardConfiguration task, leaving the existing linked-assembly GenerateProguardConfiguration path unchanged
  • use a trimmable NativeAOT ProGuard resource for the common Xamarin rules so R8 can remove unused generated Java wrappers

Stacked on #11292
Related to dotnet/runtime#120204
Part of #10790
Part of #11052

Results

HelloWorld NativeAOT android-arm64 Release with trimmable typemap. Before is the existing default without R8 Java shrinking; after is the new default R8 behavior from this PR.

MetricBeforeAfter
APK size3,638.4 kB3,572.8 kB
classes.dex size259.4 kB15.2 kB
compressed classes.dex in APK72.2 kB7.0 kB
Java class count36421
Java method count3,035155

The HelloWorld ILC scan DGML input is 24,044,488 bytes and acw-map.txt has 27,234 lines. The generated ProGuard configuration has 49 rules.

20-run measurement on Apple M1 for the isolated GenerateNativeAotProguardConfiguration task:

MetricTask timeIsolated MSBuild wall time
Average377 ms918 ms
Median244 ms837 ms
Min152 ms518 ms
Max856 ms1,565 ms
P90803 ms1,327 ms
Std dev261 ms287 ms

Validation

  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj -t:SignAndroidPackage -c Release -p:PublishAot=true -p:_AndroidTypeMapImplementation=trimmable -p:RuntimeIdentifier=android-arm64 -p:AndroidPackageFormat=apk -nr:false --nologo -v minimal

CopilotAI review requested due to automatic review settings May 22, 2026 08:14
@simonrozsivalsimonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels May 22, 2026

CopilotAI 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 adds a trimmable NativeAOT + R8 workflow where ProGuard/R8 keep rules are generated from NativeAOT ILC DGML scan output intersected with acw-map.txt, allowing R8 to remove unused generated Java wrappers and significantly reduce classes.dex size.

Changes:

  • Generate NativeAOT-specific ProGuard rules after IlcCompile from *.scan.dgml.xml + acw-map.txt, and adjust R8 configuration generation to avoid broad keep rules in this mode.
  • Extend the trimmable typemap pipeline to classify framework peers, selectively emit array typemap entries only when referenced from non-framework assemblies, and persist the list of generated typemap assemblies to stabilize incremental builds.
  • Add runtime feature plumbing for IsNativeAotRuntime and adjust NativeAOT JNI initialization ordering/inputs to provide required Java peer marker classes.

Reviewed changes

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

Show a summary per file
FileDescription
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.csAdds framework-like SSL and network peer types to exercise framework JCW/peer scanning scenarios.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.csAdds test coverage for framework peer marking and array-entry emission behavior.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.csTests framework ACW conditionality and array-entry emission rules; updates anchor visibility assertions.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.csVerifies generator can emit expected framework JCW Java sources.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targetsPlumbs UseTrimmableNativeAotProguardConfiguration into the R8 task invocation.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targetsAdds _AndroidTrimmableTypeMapMaxArrayRank to the property cache for incremental invalidation.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.csAdds incremental/build validation for array-rank changes and NativeAOT/CoreCLR typemap behaviors.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.csAdds task-level tests for generated typemap assembly list file and framework JCW emission.
src/Xamarin.Android.Build.Tasks/Tasks/R8.csAdds a trimmable NativeAOT mode that alters generated ProGuard config inputs and common rules.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.csAdds framework assembly classification and writes a generated-assemblies list file.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.csAdds DGML+ACW-map based ProGuard rule generation for NativeAOT trimmable builds.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targetsPersists typemap-generated assembly list and uses it for downstream item population/incremental correctness.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targetsHooks DGML-based ProGuard rule generation and adjusts ILC inputs for trimmable typemap NativeAOT.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targetsAdds opt-out switch to skip linked-assembly ProGuard configuration generation when replaced by DGML flow.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targetsEnables DGML generation for R8 builds, configures skip/alternate ProGuard generation, and sets runtime feature.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targetsSets IsNativeAotRuntime=false runtime feature for MonoVM.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targetsSets IsNativeAotRuntime=false runtime feature for CoreCLR.
src/native/nativeaot/host/host.ccEnsures NativeAOT host provides global refs for required Java peer marker classes during init.
src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.csAdds RuntimeFeature.IsNativeAotRuntime AppContext switch.
src/Mono.Android/Android.Runtime/JNIEnvInit.csRefactors JNI initialization to share common state init and add NativeAOT-specific runtime initialization entrypoint.
src/Mono.Android/Android.Runtime/JNIEnv.csRoutes unhandled exception propagation consistently for NativeAOT.
src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.csTreats NativeAOT unhandled exceptions like the CoreCLR path.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.csPasses framework assembly names into scanning and tightens JCW generation filtering logic.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.csTracks framework peers and controls array-entry emission based on cross-assembly references.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.csAdds IsFrameworkAssembly and GenerateArrayEntries to peer model.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.csIndexes referenced types by referenced assembly to support framework peer reference detection.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.csUpdates documentation around __ArrayMapRank{N} anchors.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.csAdds no-array-map initialization paths when max array rank is 0.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.csAdds additional unconditional types and prevents unconditional rooting for framework ACWs; gates array emission.
src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.csUses the refactored JNIEnvInit initialization path and reuses common type/value manager creation helpers.

Comment threadsrc/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs Outdated
@simonrozsival
simonrozsival changed the base branch from main to dev/simonrozsival/11052-nativeaot-typemap-initMay 22, 2026 08:20
Base automatically changed from dev/simonrozsival/11052-nativeaot-typemap-init to mainMay 22, 2026 14:26
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 7b41d33 to 158bce0CompareMay 22, 2026 15:24
simonrozsivaland others added 6 commits May 23, 2026 00:27
Use NativeAOT scan DGML to identify retained managed types, intersect them with acw-map.txt, and emit concrete R8 keep rules for trimmable typemap NativeAOT builds. This avoids keeping the broad generated Java wrapper set when R8 is enabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move DGML-based NativeAOT ProGuard generation into a separate MSBuild task so GenerateProguardConfiguration remains focused on linked assembly scanning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep NativeAOT.targets free of ProGuard coordination properties. Let LlvmIr targets hardcode linked-assembly ProGuard timing for ILLink and NativeAOT, and keep the trimmable NativeAOT R8 mode flag with the trimmable NativeAOT typemap targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the trimmable NativeAOT common R8 rules to an embedded ProGuard resource, keep GenerateProguardConfiguration unchanged, add a shared trimmable typemap ProGuard target name with a CoreCLR stub, and restore the existing linked-assembly ProGuard target wiring.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Default NativeAOT trimmable typemap builds to AndroidLinkTool=r8 so the generated DGML-based keep rules are active without requiring users to opt in manually.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tolerate the no-input typemap generation case by creating an empty assembly list, avoid nested framework reference scans, and disable XML resolver use when reading ILC DGML.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 158bce0 to 3d4e1cdCompareMay 22, 2026 22:28
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot there is a failure in CI, fix it:
image

Agent-Logs-Url: https://github.com/dotnet/android/sessions/91b096b3-9ab7-4980-8234-097a0c3caca5
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

CopilotAI commented May 23, 2026

Copy link
Copy Markdown
Contributor

@copilot there is a failure in CI, fix it:
image

Fixed in a5277f4. The NativeAOT trimmable typemap target now filters unmanaged-entrypoint roots by assembly name instead of full generated paths, so framework typemap assemblies stay as ILC references without being exported.

Collect NativeAOT DGML files from per-RID intermediate output paths and merge their retained type metadata into the single ProGuard configuration consumed by R8/D8.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsivalsimonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label May 25, 2026
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

⚠️ Needs Changes

Issue counts: 0 ❌ errors · 3 ⚠️ warnings · 2 💡 suggestions

Summary

The approach is solid — intersecting ILC DGML metadata with acw-map.txt to generate focused ProGuard rules is well-designed, and the size reduction results are impressive (classes.dex from 259 kB → 15 kB).

Key items to address:

  1. O(n2) dedup in LoadJavaTypesFromAcwMapList.Contains() for dedup is quadratic; use a HashSet alongside the list.
  2. Uncoded error messagesLog.LogError calls in GenerateNativeAotProguardConfiguration should use Log.LogCodedError with XA#### codes and Properties.Resources.
  3. Null-unsafe GetManifestResourceStream — the new code path in R8.cs calls .CopyTo() on a potentially null stream.

Positive callouts:

  • Clean separation: DGML-based logic stays in a dedicated task, leaving the existing GenerateProguardConfiguration path untouched.
  • Good use of XmlReader (streaming) over LINQ-to-XML for parsing the large DGML files.
  • Proper DtdProcessing.Prohibit and XmlResolver = null for XML security.
  • The MSBuild targets have correct Inputs/Outputs for incremental build support.
  • The _ReadGeneratedTrimmableTypeMapAssemblies fallback with MakeDir+Touch is a nice resilience improvement.

Generated by Android PR Reviewer for issue #11449 · ● 18.8M

Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/R8.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Code Review Summary

Verdict: ⚠️ Needs Changes (minor — missing error code documentation)

Overview

Well-structured PR that introduces DGML-based ProGuard rule generation for NativeAOT trimmable typemap builds. The architecture is clean: a dedicated GenerateNativeAotProguardConfiguration task handles the DGML↔ACW intersection, keeping it separate from the existing GenerateProguardConfiguration path. The results (classes.dex from 259 kB → 15 kB) are impressive.

Issues

SeverityCountCategory
⚠️ warning1Missing error code documentation
💡 suggestion3Performance, documentation

What's good

  • JavaPeerScanner fix: Iterating over ReferencedTypeNamesByAssembly and checking frameworkAssemblyNames.Contains() is more efficient than the reverse — good algorithmic improvement.
  • Proper XML security: DtdProcessing.Prohibit and XmlResolver = null on the DGML reader.
  • Clean task structure: Extends AndroidTask, proper TaskPrefix, [Required] properties with defaults, coded errors from Properties.Resources, returns !Log.HasLoggedErrors.
  • Incremental build support: The _GenerateTrimmableTypeMapProguardConfiguration target has proper Inputs/Outputs and FileWrites.
  • Good separation: CoreCLR gets an empty stub target with a TODO linking to #11052, NativeAOT gets the real implementation.
  • GetEmbeddedResourceStream helper: Nice extraction that adds null-checking the original code lacked.

Generated by Android PR Reviewer for issue #11449 · ● 30.7M

Document the new XA43xx diagnostics, clarify the NativeAOT DGML/acw-map matching logic, and avoid unnecessary allocations in the ProGuard configuration task.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

✅ LGTM — Clean, well-structured PR

Solid work generating NativeAOT ProGuard rules from DGML metadata. The results are impressive — 95% reduction in classes.dex size and 94% fewer Java classes.

Highlights:

  • Good use of streaming XmlReader for the large DGML files (24MB+)
  • Proper DtdProcessing.Prohibit + XmlResolver = null for XML security
  • Clean separation: new GenerateNativeAotProguardConfiguration task handles DGML-based logic without touching the existing GenerateProguardConfiguration path
  • Error codes (XA4319–4321) properly documented with markdown files and index entries
  • Incremental build support via Inputs/Outputs and FileWrites
  • The GetEmbeddedResourceStream helper in R8.cs is a nice refactor to avoid null-returning GetManifestResourceStream

Summary of suggestions (all 💡):

#SeverityCategoryFile
1⚠️PerformanceGenerateNativeAotProguardConfiguration.cs — use CopyIfStringChanged to avoid unnecessary timestamp updates
2💡PatternsGenerateNativeAotProguardConfiguration.cs — prefer !Log.HasLoggedErrors over return false
3💡FormattingGenerateNativeAotProguardConfiguration.cs — file-scoped namespace
4💡PerformanceGenerateNativeAotProguardConfiguration.cs — ordinal IndexOf
5💡MSBuild targetsNativeAOT.targetsBeforeTargets vs DependsOn

Not flagged (no tests found): There are no unit tests for the new GenerateNativeAotProguardConfiguration task. Given the parsing logic for DGML XML and acw-map files, isolated tests would be valuable — but the PR description shows successful end-to-end validation, so this is not blocking.

CI: Unable to check CI status (tooling not authenticated in this environment).

Generated by Android PR Reviewer for issue #11449 · ● 21.8M

@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot address all review comments

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
simonrozsivaland others added 3 commits May 27, 2026 22:59
…Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets
Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Avoid rerunning IlcCompile from the outer multi-RID build when generating NativeAOT trimmable typemap ProGuard rules. The RID-specific inner builds already produce the DGML files consumed by the outer build.
Also match DGML type metadata to assembly-qualified acw-map keys so same-named managed types from unrelated assemblies are not retained accidentally.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lc-metadata
Resolve conflict in Trimmable.CoreCLR.targets: main now implements the
CoreCLR proguard generation that the PR previously stubbed out as a TODO,
so take main's _GenerateProguardAfterTargets PropertyGroup and drop the
placeholder _GenerateTrimmableTypeMapProguardConfiguration target. The
NativeAOT path retains its own _GenerateTrimmableTypeMapProguardConfiguration
target in Trimmable.NativeAOT.targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival merged commit 9984cf3 into mainMay 29, 2026
3 checks passed
@simonrozsival
simonrozsival deleted the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch May 29, 2026 12:40
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

copilot`copilot-cli` or other AIs were used to author thisready-to-reviewThis PR is ready to review/merge, I think any CI failures are just flaky (ignorable).trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@simonrozsival@jonathanpeppers
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML - #11449

Merged
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata
May 29, 2026
Merged

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML#11449
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata

Conversation

@simonrozsival

@simonrozsivalsimonrozsival commented May 22, 2026

Copy link
Copy Markdown
Member

Summary

  • enable R8 by default for NativeAOT trimmable typemap builds
  • generate trimmable NativeAOT ProGuard keep rules after IlcCompile by intersecting retained managed type metadata from ILC scan DGML with acw-map.txt
  • keep the DGML-based logic in a dedicated GenerateNativeAotProguardConfiguration task, leaving the existing linked-assembly GenerateProguardConfiguration path unchanged
  • use a trimmable NativeAOT ProGuard resource for the common Xamarin rules so R8 can remove unused generated Java wrappers

Stacked on #11292
Related to dotnet/runtime#120204
Part of #10790
Part of #11052

Results

HelloWorld NativeAOT android-arm64 Release with trimmable typemap. Before is the existing default without R8 Java shrinking; after is the new default R8 behavior from this PR.

MetricBeforeAfter
APK size3,638.4 kB3,572.8 kB
classes.dex size259.4 kB15.2 kB
compressed classes.dex in APK72.2 kB7.0 kB
Java class count36421
Java method count3,035155

The HelloWorld ILC scan DGML input is 24,044,488 bytes and acw-map.txt has 27,234 lines. The generated ProGuard configuration has 49 rules.

20-run measurement on Apple M1 for the isolated GenerateNativeAotProguardConfiguration task:

MetricTask timeIsolated MSBuild wall time
Average377 ms918 ms
Median244 ms837 ms
Min152 ms518 ms
Max856 ms1,565 ms
P90803 ms1,327 ms
Std dev261 ms287 ms

Validation

  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj -t:SignAndroidPackage -c Release -p:PublishAot=true -p:_AndroidTypeMapImplementation=trimmable -p:RuntimeIdentifier=android-arm64 -p:AndroidPackageFormat=apk -nr:false --nologo -v minimal

CopilotAI review requested due to automatic review settings May 22, 2026 08:14
@simonrozsivalsimonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels May 22, 2026

CopilotAI 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 adds a trimmable NativeAOT + R8 workflow where ProGuard/R8 keep rules are generated from NativeAOT ILC DGML scan output intersected with acw-map.txt, allowing R8 to remove unused generated Java wrappers and significantly reduce classes.dex size.

Changes:

  • Generate NativeAOT-specific ProGuard rules after IlcCompile from *.scan.dgml.xml + acw-map.txt, and adjust R8 configuration generation to avoid broad keep rules in this mode.
  • Extend the trimmable typemap pipeline to classify framework peers, selectively emit array typemap entries only when referenced from non-framework assemblies, and persist the list of generated typemap assemblies to stabilize incremental builds.
  • Add runtime feature plumbing for IsNativeAotRuntime and adjust NativeAOT JNI initialization ordering/inputs to provide required Java peer marker classes.

Reviewed changes

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

Show a summary per file
FileDescription
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.csAdds framework-like SSL and network peer types to exercise framework JCW/peer scanning scenarios.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.csAdds test coverage for framework peer marking and array-entry emission behavior.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.csTests framework ACW conditionality and array-entry emission rules; updates anchor visibility assertions.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.csVerifies generator can emit expected framework JCW Java sources.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targetsPlumbs UseTrimmableNativeAotProguardConfiguration into the R8 task invocation.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targetsAdds _AndroidTrimmableTypeMapMaxArrayRank to the property cache for incremental invalidation.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.csAdds incremental/build validation for array-rank changes and NativeAOT/CoreCLR typemap behaviors.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.csAdds task-level tests for generated typemap assembly list file and framework JCW emission.
src/Xamarin.Android.Build.Tasks/Tasks/R8.csAdds a trimmable NativeAOT mode that alters generated ProGuard config inputs and common rules.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.csAdds framework assembly classification and writes a generated-assemblies list file.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.csAdds DGML+ACW-map based ProGuard rule generation for NativeAOT trimmable builds.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targetsPersists typemap-generated assembly list and uses it for downstream item population/incremental correctness.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targetsHooks DGML-based ProGuard rule generation and adjusts ILC inputs for trimmable typemap NativeAOT.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targetsAdds opt-out switch to skip linked-assembly ProGuard configuration generation when replaced by DGML flow.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targetsEnables DGML generation for R8 builds, configures skip/alternate ProGuard generation, and sets runtime feature.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targetsSets IsNativeAotRuntime=false runtime feature for MonoVM.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targetsSets IsNativeAotRuntime=false runtime feature for CoreCLR.
src/native/nativeaot/host/host.ccEnsures NativeAOT host provides global refs for required Java peer marker classes during init.
src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.csAdds RuntimeFeature.IsNativeAotRuntime AppContext switch.
src/Mono.Android/Android.Runtime/JNIEnvInit.csRefactors JNI initialization to share common state init and add NativeAOT-specific runtime initialization entrypoint.
src/Mono.Android/Android.Runtime/JNIEnv.csRoutes unhandled exception propagation consistently for NativeAOT.
src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.csTreats NativeAOT unhandled exceptions like the CoreCLR path.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.csPasses framework assembly names into scanning and tightens JCW generation filtering logic.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.csTracks framework peers and controls array-entry emission based on cross-assembly references.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.csAdds IsFrameworkAssembly and GenerateArrayEntries to peer model.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.csIndexes referenced types by referenced assembly to support framework peer reference detection.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.csUpdates documentation around __ArrayMapRank{N} anchors.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.csAdds no-array-map initialization paths when max array rank is 0.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.csAdds additional unconditional types and prevents unconditional rooting for framework ACWs; gates array emission.
src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.csUses the refactored JNIEnvInit initialization path and reuses common type/value manager creation helpers.

Comment threadsrc/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs Outdated
@simonrozsival
simonrozsival changed the base branch from main to dev/simonrozsival/11052-nativeaot-typemap-initMay 22, 2026 08:20
Base automatically changed from dev/simonrozsival/11052-nativeaot-typemap-init to mainMay 22, 2026 14:26
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 7b41d33 to 158bce0CompareMay 22, 2026 15:24
simonrozsivaland others added 6 commits May 23, 2026 00:27
Use NativeAOT scan DGML to identify retained managed types, intersect them with acw-map.txt, and emit concrete R8 keep rules for trimmable typemap NativeAOT builds. This avoids keeping the broad generated Java wrapper set when R8 is enabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move DGML-based NativeAOT ProGuard generation into a separate MSBuild task so GenerateProguardConfiguration remains focused on linked assembly scanning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep NativeAOT.targets free of ProGuard coordination properties. Let LlvmIr targets hardcode linked-assembly ProGuard timing for ILLink and NativeAOT, and keep the trimmable NativeAOT R8 mode flag with the trimmable NativeAOT typemap targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the trimmable NativeAOT common R8 rules to an embedded ProGuard resource, keep GenerateProguardConfiguration unchanged, add a shared trimmable typemap ProGuard target name with a CoreCLR stub, and restore the existing linked-assembly ProGuard target wiring.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Default NativeAOT trimmable typemap builds to AndroidLinkTool=r8 so the generated DGML-based keep rules are active without requiring users to opt in manually.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tolerate the no-input typemap generation case by creating an empty assembly list, avoid nested framework reference scans, and disable XML resolver use when reading ILC DGML.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 158bce0 to 3d4e1cdCompareMay 22, 2026 22:28
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot there is a failure in CI, fix it:
image

Agent-Logs-Url: https://github.com/dotnet/android/sessions/91b096b3-9ab7-4980-8234-097a0c3caca5
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

CopilotAI commented May 23, 2026

Copy link
Copy Markdown
Contributor

@copilot there is a failure in CI, fix it:
image

Fixed in a5277f4. The NativeAOT trimmable typemap target now filters unmanaged-entrypoint roots by assembly name instead of full generated paths, so framework typemap assemblies stay as ILC references without being exported.

Collect NativeAOT DGML files from per-RID intermediate output paths and merge their retained type metadata into the single ProGuard configuration consumed by R8/D8.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsivalsimonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label May 25, 2026
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

⚠️ Needs Changes

Issue counts: 0 ❌ errors · 3 ⚠️ warnings · 2 💡 suggestions

Summary

The approach is solid — intersecting ILC DGML metadata with acw-map.txt to generate focused ProGuard rules is well-designed, and the size reduction results are impressive (classes.dex from 259 kB → 15 kB).

Key items to address:

  1. O(n2) dedup in LoadJavaTypesFromAcwMapList.Contains() for dedup is quadratic; use a HashSet alongside the list.
  2. Uncoded error messagesLog.LogError calls in GenerateNativeAotProguardConfiguration should use Log.LogCodedError with XA#### codes and Properties.Resources.
  3. Null-unsafe GetManifestResourceStream — the new code path in R8.cs calls .CopyTo() on a potentially null stream.

Positive callouts:

  • Clean separation: DGML-based logic stays in a dedicated task, leaving the existing GenerateProguardConfiguration path untouched.
  • Good use of XmlReader (streaming) over LINQ-to-XML for parsing the large DGML files.
  • Proper DtdProcessing.Prohibit and XmlResolver = null for XML security.
  • The MSBuild targets have correct Inputs/Outputs for incremental build support.
  • The _ReadGeneratedTrimmableTypeMapAssemblies fallback with MakeDir+Touch is a nice resilience improvement.

Generated by Android PR Reviewer for issue #11449 · ● 18.8M

Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/R8.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Code Review Summary

Verdict: ⚠️ Needs Changes (minor — missing error code documentation)

Overview

Well-structured PR that introduces DGML-based ProGuard rule generation for NativeAOT trimmable typemap builds. The architecture is clean: a dedicated GenerateNativeAotProguardConfiguration task handles the DGML↔ACW intersection, keeping it separate from the existing GenerateProguardConfiguration path. The results (classes.dex from 259 kB → 15 kB) are impressive.

Issues

SeverityCountCategory
⚠️ warning1Missing error code documentation
💡 suggestion3Performance, documentation

What's good

  • JavaPeerScanner fix: Iterating over ReferencedTypeNamesByAssembly and checking frameworkAssemblyNames.Contains() is more efficient than the reverse — good algorithmic improvement.
  • Proper XML security: DtdProcessing.Prohibit and XmlResolver = null on the DGML reader.
  • Clean task structure: Extends AndroidTask, proper TaskPrefix, [Required] properties with defaults, coded errors from Properties.Resources, returns !Log.HasLoggedErrors.
  • Incremental build support: The _GenerateTrimmableTypeMapProguardConfiguration target has proper Inputs/Outputs and FileWrites.
  • Good separation: CoreCLR gets an empty stub target with a TODO linking to #11052, NativeAOT gets the real implementation.
  • GetEmbeddedResourceStream helper: Nice extraction that adds null-checking the original code lacked.

Generated by Android PR Reviewer for issue #11449 · ● 30.7M

Document the new XA43xx diagnostics, clarify the NativeAOT DGML/acw-map matching logic, and avoid unnecessary allocations in the ProGuard configuration task.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

✅ LGTM — Clean, well-structured PR

Solid work generating NativeAOT ProGuard rules from DGML metadata. The results are impressive — 95% reduction in classes.dex size and 94% fewer Java classes.

Highlights:

  • Good use of streaming XmlReader for the large DGML files (24MB+)
  • Proper DtdProcessing.Prohibit + XmlResolver = null for XML security
  • Clean separation: new GenerateNativeAotProguardConfiguration task handles DGML-based logic without touching the existing GenerateProguardConfiguration path
  • Error codes (XA4319–4321) properly documented with markdown files and index entries
  • Incremental build support via Inputs/Outputs and FileWrites
  • The GetEmbeddedResourceStream helper in R8.cs is a nice refactor to avoid null-returning GetManifestResourceStream

Summary of suggestions (all 💡):

#SeverityCategoryFile
1⚠️PerformanceGenerateNativeAotProguardConfiguration.cs — use CopyIfStringChanged to avoid unnecessary timestamp updates
2💡PatternsGenerateNativeAotProguardConfiguration.cs — prefer !Log.HasLoggedErrors over return false
3💡FormattingGenerateNativeAotProguardConfiguration.cs — file-scoped namespace
4💡PerformanceGenerateNativeAotProguardConfiguration.cs — ordinal IndexOf
5💡MSBuild targetsNativeAOT.targetsBeforeTargets vs DependsOn

Not flagged (no tests found): There are no unit tests for the new GenerateNativeAotProguardConfiguration task. Given the parsing logic for DGML XML and acw-map files, isolated tests would be valuable — but the PR description shows successful end-to-end validation, so this is not blocking.

CI: Unable to check CI status (tooling not authenticated in this environment).

Generated by Android PR Reviewer for issue #11449 · ● 21.8M

@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot address all review comments

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
simonrozsivaland others added 3 commits May 27, 2026 22:59
…Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets
Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Avoid rerunning IlcCompile from the outer multi-RID build when generating NativeAOT trimmable typemap ProGuard rules. The RID-specific inner builds already produce the DGML files consumed by the outer build.
Also match DGML type metadata to assembly-qualified acw-map keys so same-named managed types from unrelated assemblies are not retained accidentally.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lc-metadata
Resolve conflict in Trimmable.CoreCLR.targets: main now implements the
CoreCLR proguard generation that the PR previously stubbed out as a TODO,
so take main's _GenerateProguardAfterTargets PropertyGroup and drop the
placeholder _GenerateTrimmableTypeMapProguardConfiguration target. The
NativeAOT path retains its own _GenerateTrimmableTypeMapProguardConfiguration
target in Trimmable.NativeAOT.targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival merged commit 9984cf3 into mainMay 29, 2026
3 checks passed
@simonrozsival
simonrozsival deleted the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch May 29, 2026 12:40
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

copilot`copilot-cli` or other AIs were used to author thisready-to-reviewThis PR is ready to review/merge, I think any CI failures are just flaky (ignorable).trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@simonrozsival@jonathanpeppers
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML - #11449

Merged
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata
May 29, 2026
Merged

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML#11449
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata

Conversation

@simonrozsival

@simonrozsivalsimonrozsival commented May 22, 2026

Copy link
Copy Markdown
Member

Summary

  • enable R8 by default for NativeAOT trimmable typemap builds
  • generate trimmable NativeAOT ProGuard keep rules after IlcCompile by intersecting retained managed type metadata from ILC scan DGML with acw-map.txt
  • keep the DGML-based logic in a dedicated GenerateNativeAotProguardConfiguration task, leaving the existing linked-assembly GenerateProguardConfiguration path unchanged
  • use a trimmable NativeAOT ProGuard resource for the common Xamarin rules so R8 can remove unused generated Java wrappers

Stacked on #11292
Related to dotnet/runtime#120204
Part of #10790
Part of #11052

Results

HelloWorld NativeAOT android-arm64 Release with trimmable typemap. Before is the existing default without R8 Java shrinking; after is the new default R8 behavior from this PR.

MetricBeforeAfter
APK size3,638.4 kB3,572.8 kB
classes.dex size259.4 kB15.2 kB
compressed classes.dex in APK72.2 kB7.0 kB
Java class count36421
Java method count3,035155

The HelloWorld ILC scan DGML input is 24,044,488 bytes and acw-map.txt has 27,234 lines. The generated ProGuard configuration has 49 rules.

20-run measurement on Apple M1 for the isolated GenerateNativeAotProguardConfiguration task:

MetricTask timeIsolated MSBuild wall time
Average377 ms918 ms
Median244 ms837 ms
Min152 ms518 ms
Max856 ms1,565 ms
P90803 ms1,327 ms
Std dev261 ms287 ms

Validation

  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj -t:SignAndroidPackage -c Release -p:PublishAot=true -p:_AndroidTypeMapImplementation=trimmable -p:RuntimeIdentifier=android-arm64 -p:AndroidPackageFormat=apk -nr:false --nologo -v minimal

CopilotAI review requested due to automatic review settings May 22, 2026 08:14
@simonrozsivalsimonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels May 22, 2026

CopilotAI 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 adds a trimmable NativeAOT + R8 workflow where ProGuard/R8 keep rules are generated from NativeAOT ILC DGML scan output intersected with acw-map.txt, allowing R8 to remove unused generated Java wrappers and significantly reduce classes.dex size.

Changes:

  • Generate NativeAOT-specific ProGuard rules after IlcCompile from *.scan.dgml.xml + acw-map.txt, and adjust R8 configuration generation to avoid broad keep rules in this mode.
  • Extend the trimmable typemap pipeline to classify framework peers, selectively emit array typemap entries only when referenced from non-framework assemblies, and persist the list of generated typemap assemblies to stabilize incremental builds.
  • Add runtime feature plumbing for IsNativeAotRuntime and adjust NativeAOT JNI initialization ordering/inputs to provide required Java peer marker classes.

Reviewed changes

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

Show a summary per file
FileDescription
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.csAdds framework-like SSL and network peer types to exercise framework JCW/peer scanning scenarios.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.csAdds test coverage for framework peer marking and array-entry emission behavior.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.csTests framework ACW conditionality and array-entry emission rules; updates anchor visibility assertions.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.csVerifies generator can emit expected framework JCW Java sources.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targetsPlumbs UseTrimmableNativeAotProguardConfiguration into the R8 task invocation.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targetsAdds _AndroidTrimmableTypeMapMaxArrayRank to the property cache for incremental invalidation.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.csAdds incremental/build validation for array-rank changes and NativeAOT/CoreCLR typemap behaviors.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.csAdds task-level tests for generated typemap assembly list file and framework JCW emission.
src/Xamarin.Android.Build.Tasks/Tasks/R8.csAdds a trimmable NativeAOT mode that alters generated ProGuard config inputs and common rules.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.csAdds framework assembly classification and writes a generated-assemblies list file.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.csAdds DGML+ACW-map based ProGuard rule generation for NativeAOT trimmable builds.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targetsPersists typemap-generated assembly list and uses it for downstream item population/incremental correctness.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targetsHooks DGML-based ProGuard rule generation and adjusts ILC inputs for trimmable typemap NativeAOT.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targetsAdds opt-out switch to skip linked-assembly ProGuard configuration generation when replaced by DGML flow.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targetsEnables DGML generation for R8 builds, configures skip/alternate ProGuard generation, and sets runtime feature.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targetsSets IsNativeAotRuntime=false runtime feature for MonoVM.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targetsSets IsNativeAotRuntime=false runtime feature for CoreCLR.
src/native/nativeaot/host/host.ccEnsures NativeAOT host provides global refs for required Java peer marker classes during init.
src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.csAdds RuntimeFeature.IsNativeAotRuntime AppContext switch.
src/Mono.Android/Android.Runtime/JNIEnvInit.csRefactors JNI initialization to share common state init and add NativeAOT-specific runtime initialization entrypoint.
src/Mono.Android/Android.Runtime/JNIEnv.csRoutes unhandled exception propagation consistently for NativeAOT.
src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.csTreats NativeAOT unhandled exceptions like the CoreCLR path.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.csPasses framework assembly names into scanning and tightens JCW generation filtering logic.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.csTracks framework peers and controls array-entry emission based on cross-assembly references.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.csAdds IsFrameworkAssembly and GenerateArrayEntries to peer model.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.csIndexes referenced types by referenced assembly to support framework peer reference detection.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.csUpdates documentation around __ArrayMapRank{N} anchors.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.csAdds no-array-map initialization paths when max array rank is 0.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.csAdds additional unconditional types and prevents unconditional rooting for framework ACWs; gates array emission.
src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.csUses the refactored JNIEnvInit initialization path and reuses common type/value manager creation helpers.

Comment threadsrc/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs Outdated
@simonrozsival
simonrozsival changed the base branch from main to dev/simonrozsival/11052-nativeaot-typemap-initMay 22, 2026 08:20
Base automatically changed from dev/simonrozsival/11052-nativeaot-typemap-init to mainMay 22, 2026 14:26
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 7b41d33 to 158bce0CompareMay 22, 2026 15:24
simonrozsivaland others added 6 commits May 23, 2026 00:27
Use NativeAOT scan DGML to identify retained managed types, intersect them with acw-map.txt, and emit concrete R8 keep rules for trimmable typemap NativeAOT builds. This avoids keeping the broad generated Java wrapper set when R8 is enabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move DGML-based NativeAOT ProGuard generation into a separate MSBuild task so GenerateProguardConfiguration remains focused on linked assembly scanning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep NativeAOT.targets free of ProGuard coordination properties. Let LlvmIr targets hardcode linked-assembly ProGuard timing for ILLink and NativeAOT, and keep the trimmable NativeAOT R8 mode flag with the trimmable NativeAOT typemap targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the trimmable NativeAOT common R8 rules to an embedded ProGuard resource, keep GenerateProguardConfiguration unchanged, add a shared trimmable typemap ProGuard target name with a CoreCLR stub, and restore the existing linked-assembly ProGuard target wiring.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Default NativeAOT trimmable typemap builds to AndroidLinkTool=r8 so the generated DGML-based keep rules are active without requiring users to opt in manually.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tolerate the no-input typemap generation case by creating an empty assembly list, avoid nested framework reference scans, and disable XML resolver use when reading ILC DGML.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 158bce0 to 3d4e1cdCompareMay 22, 2026 22:28
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot there is a failure in CI, fix it:
image

Agent-Logs-Url: https://github.com/dotnet/android/sessions/91b096b3-9ab7-4980-8234-097a0c3caca5
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

CopilotAI commented May 23, 2026

Copy link
Copy Markdown
Contributor

@copilot there is a failure in CI, fix it:
image

Fixed in a5277f4. The NativeAOT trimmable typemap target now filters unmanaged-entrypoint roots by assembly name instead of full generated paths, so framework typemap assemblies stay as ILC references without being exported.

Collect NativeAOT DGML files from per-RID intermediate output paths and merge their retained type metadata into the single ProGuard configuration consumed by R8/D8.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsivalsimonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label May 25, 2026
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

⚠️ Needs Changes

Issue counts: 0 ❌ errors · 3 ⚠️ warnings · 2 💡 suggestions

Summary

The approach is solid — intersecting ILC DGML metadata with acw-map.txt to generate focused ProGuard rules is well-designed, and the size reduction results are impressive (classes.dex from 259 kB → 15 kB).

Key items to address:

  1. O(n2) dedup in LoadJavaTypesFromAcwMapList.Contains() for dedup is quadratic; use a HashSet alongside the list.
  2. Uncoded error messagesLog.LogError calls in GenerateNativeAotProguardConfiguration should use Log.LogCodedError with XA#### codes and Properties.Resources.
  3. Null-unsafe GetManifestResourceStream — the new code path in R8.cs calls .CopyTo() on a potentially null stream.

Positive callouts:

  • Clean separation: DGML-based logic stays in a dedicated task, leaving the existing GenerateProguardConfiguration path untouched.
  • Good use of XmlReader (streaming) over LINQ-to-XML for parsing the large DGML files.
  • Proper DtdProcessing.Prohibit and XmlResolver = null for XML security.
  • The MSBuild targets have correct Inputs/Outputs for incremental build support.
  • The _ReadGeneratedTrimmableTypeMapAssemblies fallback with MakeDir+Touch is a nice resilience improvement.

Generated by Android PR Reviewer for issue #11449 · ● 18.8M

Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/R8.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Code Review Summary

Verdict: ⚠️ Needs Changes (minor — missing error code documentation)

Overview

Well-structured PR that introduces DGML-based ProGuard rule generation for NativeAOT trimmable typemap builds. The architecture is clean: a dedicated GenerateNativeAotProguardConfiguration task handles the DGML↔ACW intersection, keeping it separate from the existing GenerateProguardConfiguration path. The results (classes.dex from 259 kB → 15 kB) are impressive.

Issues

SeverityCountCategory
⚠️ warning1Missing error code documentation
💡 suggestion3Performance, documentation

What's good

  • JavaPeerScanner fix: Iterating over ReferencedTypeNamesByAssembly and checking frameworkAssemblyNames.Contains() is more efficient than the reverse — good algorithmic improvement.
  • Proper XML security: DtdProcessing.Prohibit and XmlResolver = null on the DGML reader.
  • Clean task structure: Extends AndroidTask, proper TaskPrefix, [Required] properties with defaults, coded errors from Properties.Resources, returns !Log.HasLoggedErrors.
  • Incremental build support: The _GenerateTrimmableTypeMapProguardConfiguration target has proper Inputs/Outputs and FileWrites.
  • Good separation: CoreCLR gets an empty stub target with a TODO linking to #11052, NativeAOT gets the real implementation.
  • GetEmbeddedResourceStream helper: Nice extraction that adds null-checking the original code lacked.

Generated by Android PR Reviewer for issue #11449 · ● 30.7M

Document the new XA43xx diagnostics, clarify the NativeAOT DGML/acw-map matching logic, and avoid unnecessary allocations in the ProGuard configuration task.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

✅ LGTM — Clean, well-structured PR

Solid work generating NativeAOT ProGuard rules from DGML metadata. The results are impressive — 95% reduction in classes.dex size and 94% fewer Java classes.

Highlights:

  • Good use of streaming XmlReader for the large DGML files (24MB+)
  • Proper DtdProcessing.Prohibit + XmlResolver = null for XML security
  • Clean separation: new GenerateNativeAotProguardConfiguration task handles DGML-based logic without touching the existing GenerateProguardConfiguration path
  • Error codes (XA4319–4321) properly documented with markdown files and index entries
  • Incremental build support via Inputs/Outputs and FileWrites
  • The GetEmbeddedResourceStream helper in R8.cs is a nice refactor to avoid null-returning GetManifestResourceStream

Summary of suggestions (all 💡):

#SeverityCategoryFile
1⚠️PerformanceGenerateNativeAotProguardConfiguration.cs — use CopyIfStringChanged to avoid unnecessary timestamp updates
2💡PatternsGenerateNativeAotProguardConfiguration.cs — prefer !Log.HasLoggedErrors over return false
3💡FormattingGenerateNativeAotProguardConfiguration.cs — file-scoped namespace
4💡PerformanceGenerateNativeAotProguardConfiguration.cs — ordinal IndexOf
5💡MSBuild targetsNativeAOT.targetsBeforeTargets vs DependsOn

Not flagged (no tests found): There are no unit tests for the new GenerateNativeAotProguardConfiguration task. Given the parsing logic for DGML XML and acw-map files, isolated tests would be valuable — but the PR description shows successful end-to-end validation, so this is not blocking.

CI: Unable to check CI status (tooling not authenticated in this environment).

Generated by Android PR Reviewer for issue #11449 · ● 21.8M

@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot address all review comments

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
simonrozsivaland others added 3 commits May 27, 2026 22:59
…Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets
Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Avoid rerunning IlcCompile from the outer multi-RID build when generating NativeAOT trimmable typemap ProGuard rules. The RID-specific inner builds already produce the DGML files consumed by the outer build.
Also match DGML type metadata to assembly-qualified acw-map keys so same-named managed types from unrelated assemblies are not retained accidentally.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lc-metadata
Resolve conflict in Trimmable.CoreCLR.targets: main now implements the
CoreCLR proguard generation that the PR previously stubbed out as a TODO,
so take main's _GenerateProguardAfterTargets PropertyGroup and drop the
placeholder _GenerateTrimmableTypeMapProguardConfiguration target. The
NativeAOT path retains its own _GenerateTrimmableTypeMapProguardConfiguration
target in Trimmable.NativeAOT.targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival merged commit 9984cf3 into mainMay 29, 2026
3 checks passed
@simonrozsival
simonrozsival deleted the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch May 29, 2026 12:40
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

copilot`copilot-cli` or other AIs were used to author thisready-to-reviewThis PR is ready to review/merge, I think any CI failures are just flaky (ignorable).trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@simonrozsival@jonathanpeppers
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML - #11449

Merged
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata
May 29, 2026
Merged

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML#11449
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata

Conversation

@simonrozsival

@simonrozsivalsimonrozsival commented May 22, 2026

Copy link
Copy Markdown
Member

Summary

  • enable R8 by default for NativeAOT trimmable typemap builds
  • generate trimmable NativeAOT ProGuard keep rules after IlcCompile by intersecting retained managed type metadata from ILC scan DGML with acw-map.txt
  • keep the DGML-based logic in a dedicated GenerateNativeAotProguardConfiguration task, leaving the existing linked-assembly GenerateProguardConfiguration path unchanged
  • use a trimmable NativeAOT ProGuard resource for the common Xamarin rules so R8 can remove unused generated Java wrappers

Stacked on #11292
Related to dotnet/runtime#120204
Part of #10790
Part of #11052

Results

HelloWorld NativeAOT android-arm64 Release with trimmable typemap. Before is the existing default without R8 Java shrinking; after is the new default R8 behavior from this PR.

MetricBeforeAfter
APK size3,638.4 kB3,572.8 kB
classes.dex size259.4 kB15.2 kB
compressed classes.dex in APK72.2 kB7.0 kB
Java class count36421
Java method count3,035155

The HelloWorld ILC scan DGML input is 24,044,488 bytes and acw-map.txt has 27,234 lines. The generated ProGuard configuration has 49 rules.

20-run measurement on Apple M1 for the isolated GenerateNativeAotProguardConfiguration task:

MetricTask timeIsolated MSBuild wall time
Average377 ms918 ms
Median244 ms837 ms
Min152 ms518 ms
Max856 ms1,565 ms
P90803 ms1,327 ms
Std dev261 ms287 ms

Validation

  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj -t:SignAndroidPackage -c Release -p:PublishAot=true -p:_AndroidTypeMapImplementation=trimmable -p:RuntimeIdentifier=android-arm64 -p:AndroidPackageFormat=apk -nr:false --nologo -v minimal

CopilotAI review requested due to automatic review settings May 22, 2026 08:14
@simonrozsivalsimonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels May 22, 2026

CopilotAI 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 adds a trimmable NativeAOT + R8 workflow where ProGuard/R8 keep rules are generated from NativeAOT ILC DGML scan output intersected with acw-map.txt, allowing R8 to remove unused generated Java wrappers and significantly reduce classes.dex size.

Changes:

  • Generate NativeAOT-specific ProGuard rules after IlcCompile from *.scan.dgml.xml + acw-map.txt, and adjust R8 configuration generation to avoid broad keep rules in this mode.
  • Extend the trimmable typemap pipeline to classify framework peers, selectively emit array typemap entries only when referenced from non-framework assemblies, and persist the list of generated typemap assemblies to stabilize incremental builds.
  • Add runtime feature plumbing for IsNativeAotRuntime and adjust NativeAOT JNI initialization ordering/inputs to provide required Java peer marker classes.

Reviewed changes

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

Show a summary per file
FileDescription
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.csAdds framework-like SSL and network peer types to exercise framework JCW/peer scanning scenarios.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.csAdds test coverage for framework peer marking and array-entry emission behavior.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.csTests framework ACW conditionality and array-entry emission rules; updates anchor visibility assertions.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.csVerifies generator can emit expected framework JCW Java sources.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targetsPlumbs UseTrimmableNativeAotProguardConfiguration into the R8 task invocation.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targetsAdds _AndroidTrimmableTypeMapMaxArrayRank to the property cache for incremental invalidation.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.csAdds incremental/build validation for array-rank changes and NativeAOT/CoreCLR typemap behaviors.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.csAdds task-level tests for generated typemap assembly list file and framework JCW emission.
src/Xamarin.Android.Build.Tasks/Tasks/R8.csAdds a trimmable NativeAOT mode that alters generated ProGuard config inputs and common rules.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.csAdds framework assembly classification and writes a generated-assemblies list file.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.csAdds DGML+ACW-map based ProGuard rule generation for NativeAOT trimmable builds.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targetsPersists typemap-generated assembly list and uses it for downstream item population/incremental correctness.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targetsHooks DGML-based ProGuard rule generation and adjusts ILC inputs for trimmable typemap NativeAOT.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targetsAdds opt-out switch to skip linked-assembly ProGuard configuration generation when replaced by DGML flow.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targetsEnables DGML generation for R8 builds, configures skip/alternate ProGuard generation, and sets runtime feature.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targetsSets IsNativeAotRuntime=false runtime feature for MonoVM.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targetsSets IsNativeAotRuntime=false runtime feature for CoreCLR.
src/native/nativeaot/host/host.ccEnsures NativeAOT host provides global refs for required Java peer marker classes during init.
src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.csAdds RuntimeFeature.IsNativeAotRuntime AppContext switch.
src/Mono.Android/Android.Runtime/JNIEnvInit.csRefactors JNI initialization to share common state init and add NativeAOT-specific runtime initialization entrypoint.
src/Mono.Android/Android.Runtime/JNIEnv.csRoutes unhandled exception propagation consistently for NativeAOT.
src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.csTreats NativeAOT unhandled exceptions like the CoreCLR path.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.csPasses framework assembly names into scanning and tightens JCW generation filtering logic.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.csTracks framework peers and controls array-entry emission based on cross-assembly references.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.csAdds IsFrameworkAssembly and GenerateArrayEntries to peer model.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.csIndexes referenced types by referenced assembly to support framework peer reference detection.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.csUpdates documentation around __ArrayMapRank{N} anchors.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.csAdds no-array-map initialization paths when max array rank is 0.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.csAdds additional unconditional types and prevents unconditional rooting for framework ACWs; gates array emission.
src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.csUses the refactored JNIEnvInit initialization path and reuses common type/value manager creation helpers.

Comment threadsrc/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs Outdated
@simonrozsival
simonrozsival changed the base branch from main to dev/simonrozsival/11052-nativeaot-typemap-initMay 22, 2026 08:20
Base automatically changed from dev/simonrozsival/11052-nativeaot-typemap-init to mainMay 22, 2026 14:26
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 7b41d33 to 158bce0CompareMay 22, 2026 15:24
simonrozsivaland others added 6 commits May 23, 2026 00:27
Use NativeAOT scan DGML to identify retained managed types, intersect them with acw-map.txt, and emit concrete R8 keep rules for trimmable typemap NativeAOT builds. This avoids keeping the broad generated Java wrapper set when R8 is enabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move DGML-based NativeAOT ProGuard generation into a separate MSBuild task so GenerateProguardConfiguration remains focused on linked assembly scanning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep NativeAOT.targets free of ProGuard coordination properties. Let LlvmIr targets hardcode linked-assembly ProGuard timing for ILLink and NativeAOT, and keep the trimmable NativeAOT R8 mode flag with the trimmable NativeAOT typemap targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the trimmable NativeAOT common R8 rules to an embedded ProGuard resource, keep GenerateProguardConfiguration unchanged, add a shared trimmable typemap ProGuard target name with a CoreCLR stub, and restore the existing linked-assembly ProGuard target wiring.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Default NativeAOT trimmable typemap builds to AndroidLinkTool=r8 so the generated DGML-based keep rules are active without requiring users to opt in manually.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tolerate the no-input typemap generation case by creating an empty assembly list, avoid nested framework reference scans, and disable XML resolver use when reading ILC DGML.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 158bce0 to 3d4e1cdCompareMay 22, 2026 22:28
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot there is a failure in CI, fix it:
image

Agent-Logs-Url: https://github.com/dotnet/android/sessions/91b096b3-9ab7-4980-8234-097a0c3caca5
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

CopilotAI commented May 23, 2026

Copy link
Copy Markdown
Contributor

@copilot there is a failure in CI, fix it:
image

Fixed in a5277f4. The NativeAOT trimmable typemap target now filters unmanaged-entrypoint roots by assembly name instead of full generated paths, so framework typemap assemblies stay as ILC references without being exported.

Collect NativeAOT DGML files from per-RID intermediate output paths and merge their retained type metadata into the single ProGuard configuration consumed by R8/D8.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsivalsimonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label May 25, 2026
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

⚠️ Needs Changes

Issue counts: 0 ❌ errors · 3 ⚠️ warnings · 2 💡 suggestions

Summary

The approach is solid — intersecting ILC DGML metadata with acw-map.txt to generate focused ProGuard rules is well-designed, and the size reduction results are impressive (classes.dex from 259 kB → 15 kB).

Key items to address:

  1. O(n2) dedup in LoadJavaTypesFromAcwMapList.Contains() for dedup is quadratic; use a HashSet alongside the list.
  2. Uncoded error messagesLog.LogError calls in GenerateNativeAotProguardConfiguration should use Log.LogCodedError with XA#### codes and Properties.Resources.
  3. Null-unsafe GetManifestResourceStream — the new code path in R8.cs calls .CopyTo() on a potentially null stream.

Positive callouts:

  • Clean separation: DGML-based logic stays in a dedicated task, leaving the existing GenerateProguardConfiguration path untouched.
  • Good use of XmlReader (streaming) over LINQ-to-XML for parsing the large DGML files.
  • Proper DtdProcessing.Prohibit and XmlResolver = null for XML security.
  • The MSBuild targets have correct Inputs/Outputs for incremental build support.
  • The _ReadGeneratedTrimmableTypeMapAssemblies fallback with MakeDir+Touch is a nice resilience improvement.

Generated by Android PR Reviewer for issue #11449 · ● 18.8M

Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/R8.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Code Review Summary

Verdict: ⚠️ Needs Changes (minor — missing error code documentation)

Overview

Well-structured PR that introduces DGML-based ProGuard rule generation for NativeAOT trimmable typemap builds. The architecture is clean: a dedicated GenerateNativeAotProguardConfiguration task handles the DGML↔ACW intersection, keeping it separate from the existing GenerateProguardConfiguration path. The results (classes.dex from 259 kB → 15 kB) are impressive.

Issues

SeverityCountCategory
⚠️ warning1Missing error code documentation
💡 suggestion3Performance, documentation

What's good

  • JavaPeerScanner fix: Iterating over ReferencedTypeNamesByAssembly and checking frameworkAssemblyNames.Contains() is more efficient than the reverse — good algorithmic improvement.
  • Proper XML security: DtdProcessing.Prohibit and XmlResolver = null on the DGML reader.
  • Clean task structure: Extends AndroidTask, proper TaskPrefix, [Required] properties with defaults, coded errors from Properties.Resources, returns !Log.HasLoggedErrors.
  • Incremental build support: The _GenerateTrimmableTypeMapProguardConfiguration target has proper Inputs/Outputs and FileWrites.
  • Good separation: CoreCLR gets an empty stub target with a TODO linking to #11052, NativeAOT gets the real implementation.
  • GetEmbeddedResourceStream helper: Nice extraction that adds null-checking the original code lacked.

Generated by Android PR Reviewer for issue #11449 · ● 30.7M

Document the new XA43xx diagnostics, clarify the NativeAOT DGML/acw-map matching logic, and avoid unnecessary allocations in the ProGuard configuration task.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

✅ LGTM — Clean, well-structured PR

Solid work generating NativeAOT ProGuard rules from DGML metadata. The results are impressive — 95% reduction in classes.dex size and 94% fewer Java classes.

Highlights:

  • Good use of streaming XmlReader for the large DGML files (24MB+)
  • Proper DtdProcessing.Prohibit + XmlResolver = null for XML security
  • Clean separation: new GenerateNativeAotProguardConfiguration task handles DGML-based logic without touching the existing GenerateProguardConfiguration path
  • Error codes (XA4319–4321) properly documented with markdown files and index entries
  • Incremental build support via Inputs/Outputs and FileWrites
  • The GetEmbeddedResourceStream helper in R8.cs is a nice refactor to avoid null-returning GetManifestResourceStream

Summary of suggestions (all 💡):

#SeverityCategoryFile
1⚠️PerformanceGenerateNativeAotProguardConfiguration.cs — use CopyIfStringChanged to avoid unnecessary timestamp updates
2💡PatternsGenerateNativeAotProguardConfiguration.cs — prefer !Log.HasLoggedErrors over return false
3💡FormattingGenerateNativeAotProguardConfiguration.cs — file-scoped namespace
4💡PerformanceGenerateNativeAotProguardConfiguration.cs — ordinal IndexOf
5💡MSBuild targetsNativeAOT.targetsBeforeTargets vs DependsOn

Not flagged (no tests found): There are no unit tests for the new GenerateNativeAotProguardConfiguration task. Given the parsing logic for DGML XML and acw-map files, isolated tests would be valuable — but the PR description shows successful end-to-end validation, so this is not blocking.

CI: Unable to check CI status (tooling not authenticated in this environment).

Generated by Android PR Reviewer for issue #11449 · ● 21.8M

@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot address all review comments

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
simonrozsivaland others added 3 commits May 27, 2026 22:59
…Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets
Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Avoid rerunning IlcCompile from the outer multi-RID build when generating NativeAOT trimmable typemap ProGuard rules. The RID-specific inner builds already produce the DGML files consumed by the outer build.
Also match DGML type metadata to assembly-qualified acw-map keys so same-named managed types from unrelated assemblies are not retained accidentally.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lc-metadata
Resolve conflict in Trimmable.CoreCLR.targets: main now implements the
CoreCLR proguard generation that the PR previously stubbed out as a TODO,
so take main's _GenerateProguardAfterTargets PropertyGroup and drop the
placeholder _GenerateTrimmableTypeMapProguardConfiguration target. The
NativeAOT path retains its own _GenerateTrimmableTypeMapProguardConfiguration
target in Trimmable.NativeAOT.targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival merged commit 9984cf3 into mainMay 29, 2026
3 checks passed
@simonrozsival
simonrozsival deleted the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch May 29, 2026 12:40
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

copilot`copilot-cli` or other AIs were used to author thisready-to-reviewThis PR is ready to review/merge, I think any CI failures are just flaky (ignorable).trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@simonrozsival@jonathanpeppers
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML - #11449

Merged
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata
May 29, 2026
Merged

[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML#11449
simonrozsival merged 14 commits into
mainfrom
dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata

Conversation

@simonrozsival

@simonrozsivalsimonrozsival commented May 22, 2026

Copy link
Copy Markdown
Member

Summary

  • enable R8 by default for NativeAOT trimmable typemap builds
  • generate trimmable NativeAOT ProGuard keep rules after IlcCompile by intersecting retained managed type metadata from ILC scan DGML with acw-map.txt
  • keep the DGML-based logic in a dedicated GenerateNativeAotProguardConfiguration task, leaving the existing linked-assembly GenerateProguardConfiguration path unchanged
  • use a trimmable NativeAOT ProGuard resource for the common Xamarin rules so R8 can remove unused generated Java wrappers

Stacked on #11292
Related to dotnet/runtime#120204
Part of #10790
Part of #11052

Results

HelloWorld NativeAOT android-arm64 Release with trimmable typemap. Before is the existing default without R8 Java shrinking; after is the new default R8 behavior from this PR.

MetricBeforeAfter
APK size3,638.4 kB3,572.8 kB
classes.dex size259.4 kB15.2 kB
compressed classes.dex in APK72.2 kB7.0 kB
Java class count36421
Java method count3,035155

The HelloWorld ILC scan DGML input is 24,044,488 bytes and acw-map.txt has 27,234 lines. The generated ProGuard configuration has 49 rules.

20-run measurement on Apple M1 for the isolated GenerateNativeAotProguardConfiguration task:

MetricTask timeIsolated MSBuild wall time
Average377 ms918 ms
Median244 ms837 ms
Min152 ms518 ms
Max856 ms1,565 ms
P90803 ms1,327 ms
Std dev261 ms287 ms

Validation

  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj -c Debug -nr:false --nologo -v minimal
  • MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj -t:SignAndroidPackage -c Release -p:PublishAot=true -p:_AndroidTypeMapImplementation=trimmable -p:RuntimeIdentifier=android-arm64 -p:AndroidPackageFormat=apk -nr:false --nologo -v minimal

CopilotAI review requested due to automatic review settings May 22, 2026 08:14
@simonrozsivalsimonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels May 22, 2026

CopilotAI 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 adds a trimmable NativeAOT + R8 workflow where ProGuard/R8 keep rules are generated from NativeAOT ILC DGML scan output intersected with acw-map.txt, allowing R8 to remove unused generated Java wrappers and significantly reduce classes.dex size.

Changes:

  • Generate NativeAOT-specific ProGuard rules after IlcCompile from *.scan.dgml.xml + acw-map.txt, and adjust R8 configuration generation to avoid broad keep rules in this mode.
  • Extend the trimmable typemap pipeline to classify framework peers, selectively emit array typemap entries only when referenced from non-framework assemblies, and persist the list of generated typemap assemblies to stabilize incremental builds.
  • Add runtime feature plumbing for IsNativeAotRuntime and adjust NativeAOT JNI initialization ordering/inputs to provide required Java peer marker classes.

Reviewed changes

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

Show a summary per file
FileDescription
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.csAdds framework-like SSL and network peer types to exercise framework JCW/peer scanning scenarios.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.csAdds test coverage for framework peer marking and array-entry emission behavior.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.csTests framework ACW conditionality and array-entry emission rules; updates anchor visibility assertions.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.csVerifies generator can emit expected framework JCW Java sources.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targetsPlumbs UseTrimmableNativeAotProguardConfiguration into the R8 task invocation.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targetsAdds _AndroidTrimmableTypeMapMaxArrayRank to the property cache for incremental invalidation.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdescUpdates expected APK contents/sizes after build output changes.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.csAdds incremental/build validation for array-rank changes and NativeAOT/CoreCLR typemap behaviors.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.csAdds task-level tests for generated typemap assembly list file and framework JCW emission.
src/Xamarin.Android.Build.Tasks/Tasks/R8.csAdds a trimmable NativeAOT mode that alters generated ProGuard config inputs and common rules.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.csAdds framework assembly classification and writes a generated-assemblies list file.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.csAdds DGML+ACW-map based ProGuard rule generation for NativeAOT trimmable builds.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targetsPersists typemap-generated assembly list and uses it for downstream item population/incremental correctness.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targetsHooks DGML-based ProGuard rule generation and adjusts ILC inputs for trimmable typemap NativeAOT.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targetsAdds opt-out switch to skip linked-assembly ProGuard configuration generation when replaced by DGML flow.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targetsEnables DGML generation for R8 builds, configures skip/alternate ProGuard generation, and sets runtime feature.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targetsSets IsNativeAotRuntime=false runtime feature for MonoVM.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targetsSets IsNativeAotRuntime=false runtime feature for CoreCLR.
src/native/nativeaot/host/host.ccEnsures NativeAOT host provides global refs for required Java peer marker classes during init.
src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.csAdds RuntimeFeature.IsNativeAotRuntime AppContext switch.
src/Mono.Android/Android.Runtime/JNIEnvInit.csRefactors JNI initialization to share common state init and add NativeAOT-specific runtime initialization entrypoint.
src/Mono.Android/Android.Runtime/JNIEnv.csRoutes unhandled exception propagation consistently for NativeAOT.
src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.csTreats NativeAOT unhandled exceptions like the CoreCLR path.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.csPasses framework assembly names into scanning and tightens JCW generation filtering logic.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.csTracks framework peers and controls array-entry emission based on cross-assembly references.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.csAdds IsFrameworkAssembly and GenerateArrayEntries to peer model.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.csIndexes referenced types by referenced assembly to support framework peer reference detection.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.csUpdates documentation around __ArrayMapRank{N} anchors.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.csAdds no-array-map initialization paths when max array rank is 0.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.csAdds additional unconditional types and prevents unconditional rooting for framework ACWs; gates array emission.
src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.csUses the refactored JNIEnvInit initialization path and reuses common type/value manager creation helpers.

Comment threadsrc/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs Outdated
@simonrozsival
simonrozsival changed the base branch from main to dev/simonrozsival/11052-nativeaot-typemap-initMay 22, 2026 08:20
Base automatically changed from dev/simonrozsival/11052-nativeaot-typemap-init to mainMay 22, 2026 14:26
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 7b41d33 to 158bce0CompareMay 22, 2026 15:24
simonrozsivaland others added 6 commits May 23, 2026 00:27
Use NativeAOT scan DGML to identify retained managed types, intersect them with acw-map.txt, and emit concrete R8 keep rules for trimmable typemap NativeAOT builds. This avoids keeping the broad generated Java wrapper set when R8 is enabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move DGML-based NativeAOT ProGuard generation into a separate MSBuild task so GenerateProguardConfiguration remains focused on linked assembly scanning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep NativeAOT.targets free of ProGuard coordination properties. Let LlvmIr targets hardcode linked-assembly ProGuard timing for ILLink and NativeAOT, and keep the trimmable NativeAOT R8 mode flag with the trimmable NativeAOT typemap targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the trimmable NativeAOT common R8 rules to an embedded ProGuard resource, keep GenerateProguardConfiguration unchanged, add a shared trimmable typemap ProGuard target name with a CoreCLR stub, and restore the existing linked-assembly ProGuard target wiring.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Default NativeAOT trimmable typemap builds to AndroidLinkTool=r8 so the generated DGML-based keep rules are active without requiring users to opt in manually.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tolerate the no-input typemap generation case by creating an empty assembly list, avoid nested framework reference scans, and disable XML resolver use when reading ILC DGML.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch from 158bce0 to 3d4e1cdCompareMay 22, 2026 22:28
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot there is a failure in CI, fix it:
image

Agent-Logs-Url: https://github.com/dotnet/android/sessions/91b096b3-9ab7-4980-8234-097a0c3caca5
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

CopilotAI commented May 23, 2026

Copy link
Copy Markdown
Contributor

@copilot there is a failure in CI, fix it:
image

Fixed in a5277f4. The NativeAOT trimmable typemap target now filters unmanaged-entrypoint roots by assembly name instead of full generated paths, so framework typemap assemblies stay as ILC references without being exported.

Collect NativeAOT DGML files from per-RID intermediate output paths and merge their retained type metadata into the single ProGuard configuration consumed by R8/D8.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsivalsimonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label May 25, 2026
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

⚠️ Needs Changes

Issue counts: 0 ❌ errors · 3 ⚠️ warnings · 2 💡 suggestions

Summary

The approach is solid — intersecting ILC DGML metadata with acw-map.txt to generate focused ProGuard rules is well-designed, and the size reduction results are impressive (classes.dex from 259 kB → 15 kB).

Key items to address:

  1. O(n2) dedup in LoadJavaTypesFromAcwMapList.Contains() for dedup is quadratic; use a HashSet alongside the list.
  2. Uncoded error messagesLog.LogError calls in GenerateNativeAotProguardConfiguration should use Log.LogCodedError with XA#### codes and Properties.Resources.
  3. Null-unsafe GetManifestResourceStream — the new code path in R8.cs calls .CopyTo() on a potentially null stream.

Positive callouts:

  • Clean separation: DGML-based logic stays in a dedicated task, leaving the existing GenerateProguardConfiguration path untouched.
  • Good use of XmlReader (streaming) over LINQ-to-XML for parsing the large DGML files.
  • Proper DtdProcessing.Prohibit and XmlResolver = null for XML security.
  • The MSBuild targets have correct Inputs/Outputs for incremental build support.
  • The _ReadGeneratedTrimmableTypeMapAssemblies fallback with MakeDir+Touch is a nice resilience improvement.

Generated by Android PR Reviewer for issue #11449 · ● 18.8M

Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/R8.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Code Review Summary

Verdict: ⚠️ Needs Changes (minor — missing error code documentation)

Overview

Well-structured PR that introduces DGML-based ProGuard rule generation for NativeAOT trimmable typemap builds. The architecture is clean: a dedicated GenerateNativeAotProguardConfiguration task handles the DGML↔ACW intersection, keeping it separate from the existing GenerateProguardConfiguration path. The results (classes.dex from 259 kB → 15 kB) are impressive.

Issues

SeverityCountCategory
⚠️ warning1Missing error code documentation
💡 suggestion3Performance, documentation

What's good

  • JavaPeerScanner fix: Iterating over ReferencedTypeNamesByAssembly and checking frameworkAssemblyNames.Contains() is more efficient than the reverse — good algorithmic improvement.
  • Proper XML security: DtdProcessing.Prohibit and XmlResolver = null on the DGML reader.
  • Clean task structure: Extends AndroidTask, proper TaskPrefix, [Required] properties with defaults, coded errors from Properties.Resources, returns !Log.HasLoggedErrors.
  • Incremental build support: The _GenerateTrimmableTypeMapProguardConfiguration target has proper Inputs/Outputs and FileWrites.
  • Good separation: CoreCLR gets an empty stub target with a TODO linking to #11052, NativeAOT gets the real implementation.
  • GetEmbeddedResourceStream helper: Nice extraction that adds null-checking the original code lacked.

Generated by Android PR Reviewer for issue #11449 · ● 30.7M

Document the new XA43xx diagnostics, clarify the NativeAOT DGML/acw-map matching logic, and avoid unnecessary allocations in the ProGuard configuration task.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

✅ LGTM — Clean, well-structured PR

Solid work generating NativeAOT ProGuard rules from DGML metadata. The results are impressive — 95% reduction in classes.dex size and 94% fewer Java classes.

Highlights:

  • Good use of streaming XmlReader for the large DGML files (24MB+)
  • Proper DtdProcessing.Prohibit + XmlResolver = null for XML security
  • Clean separation: new GenerateNativeAotProguardConfiguration task handles DGML-based logic without touching the existing GenerateProguardConfiguration path
  • Error codes (XA4319–4321) properly documented with markdown files and index entries
  • Incremental build support via Inputs/Outputs and FileWrites
  • The GetEmbeddedResourceStream helper in R8.cs is a nice refactor to avoid null-returning GetManifestResourceStream

Summary of suggestions (all 💡):

#SeverityCategoryFile
1⚠️PerformanceGenerateNativeAotProguardConfiguration.cs — use CopyIfStringChanged to avoid unnecessary timestamp updates
2💡PatternsGenerateNativeAotProguardConfiguration.cs — prefer !Log.HasLoggedErrors over return false
3💡FormattingGenerateNativeAotProguardConfiguration.cs — file-scoped namespace
4💡PerformanceGenerateNativeAotProguardConfiguration.cs — ordinal IndexOf
5💡MSBuild targetsNativeAOT.targetsBeforeTargets vs DependsOn

Not flagged (no tests found): There are no unit tests for the new GenerateNativeAotProguardConfiguration task. Given the parsing logic for DGML XML and acw-map files, isolated tests would be valuable — but the PR description shows successful end-to-end validation, so this is not blocking.

CI: Unable to check CI status (tooling not authenticated in this environment).

Generated by Android PR Reviewer for issue #11449 · ● 21.8M

@simonrozsival

Copy link
Copy Markdown
MemberAuthor

@copilot address all review comments

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
simonrozsivaland others added 3 commits May 27, 2026 22:59
…Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets
Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Avoid rerunning IlcCompile from the outer multi-RID build when generating NativeAOT trimmable typemap ProGuard rules. The RID-specific inner builds already produce the DGML files consumed by the outer build.
Also match DGML type metadata to assembly-qualified acw-map keys so same-named managed types from unrelated assemblies are not retained accidentally.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lc-metadata
Resolve conflict in Trimmable.CoreCLR.targets: main now implements the
CoreCLR proguard generation that the PR previously stubbed out as a TODO,
so take main's _GenerateProguardAfterTargets PropertyGroup and drop the
placeholder _GenerateTrimmableTypeMapProguardConfiguration target. The
NativeAOT path retains its own _GenerateTrimmableTypeMapProguardConfiguration
target in Trimmable.NativeAOT.targets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival merged commit 9984cf3 into mainMay 29, 2026
3 checks passed
@simonrozsival
simonrozsival deleted the dev/simonrozsival/11052-nativeaot-proguard-ilc-metadata branch May 29, 2026 12:40
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

copilot`copilot-cli` or other AIs were used to author thisready-to-reviewThis PR is ready to review/merge, I think any CI failures are just flaky (ignorable).trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@simonrozsival@jonathanpeppers