Skip to content

[NativeAOT] Preserve GC bridge temporary peers through R8 - #12828

Open
jonathanpeppers wants to merge 1 commit into
dotnet:mainfrom
jonathanpeppers:jonathanpeppers-nativeaot-gc-bridge-abort
Open

jonathanpeppers wants to merge 1 commit into
dotnet:mainfrom
jonathanpeppers:jonathanpeppers-nativeaot-gc-bridge-abort

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

NativeAOT Jetchat aborted during GC bridge processing with Failed to create GC bridge temporary peer. The failed APK's DEX contains mono.android.GCUserPeer as an abstract class with no constructor or methods; the matching native source aborts when JNI NewObject() returns null. Keeping IGCUserPeer alone does not preserve this JNI-created implementation.

Preserve only GCUserPeer, its default constructor, and monodroidAddReference(Object) / monodroidClearReferences(). Add a Release NativeAOT/R8 build regression asserting that these methods survive in the final DEX. No GC algorithm changes, runtime fallback, or broad keep rules are introduced.

Validation

  • Full Release product/native build and packaging succeeded, including NativeAOT arm, arm64, and x64 artifacts.
  • Matched Jetchat builds using the same frozen Compose source and official RC1 toolchain reproduce the stripped abstract peer in the baseline and retain a concrete peer with all three methods in the candidate. Baseline DEX files match the original failing APK byte for byte.
  • An isolated R8/JNI host probe confirms that the candidate can be constructed and its reference callbacks invoked. Host JVM behavior is not presented as Android runtime validation.
  • The new build regression was attempted but blocked before compilation by local workload discovery (NETSDK1147). Device gesture validation remains pending; no device run was performed in this session. The original RC1 abort did not log the pending Java exception.

Pull Request
title and
description
should follow the
commit-messages.md workflow documentation, and in particular should include:

  • Useful description of why the change is necessary.
  • Links to issues fixed - N/A; no linked issue.
  • Unit tests - focused build/DEX regression added; execution limitation noted above.

R8 can reduce mono.android.GCUserPeer to an abstract class with no
constructor or reference callbacks.  NativeAOT constructs this helper
through JNI when processing empty strongly connected components, so
NewObject returns null and the GC bridge aborts.

Keep only the helper class, default constructor, and two JNI reference
callbacks.  Add a Release NativeAOT build regression that verifies all
three methods survive in the final DEX output.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 18, 2026 17:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The regression test must verify concreteness and reliably scope member checks to GCUserPeer.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Preserves the NativeAOT GC bridge peer through R8 and adds Release DEX regression coverage.

Changes:

  • Adds targeted keep rules for GCUserPeer.
  • Adds assertions for its constructor and reference-management methods.
File summaries
File Review
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs Adds regression coverage; must also verify the class is non-abstract and fix per-class helper state handling.
src/Xamarin.Android.Build.Tasks/Resources/proguard_trimmable_nativeaot.cfg Adds targeted GCUserPeer keep rules.
Review details

Suppressed comments (1)

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs:1671

  • Test validityDexUtils.ContainsClassWithMethod does not reset its hasType flag when Class descriptor changes (it only resets hasName), so a matching method/signature in an earlier DEX class can make this assertion succeed even when GCUserPeer has no such member. Since these are common methods and <init>()V is ubiquitous, please fix the helper to reset the per-class match state (or use a class-scoped DEX parser) before relying on this regression test.
				Assert.IsTrue (dexFiles.Any (dex => DexUtils.ContainsClassWithMethod (
					"Lmono/android/GCUserPeer;", method, signature, dex, AndroidSdkPath)),
					$"R8 must preserve GCUserPeer.{method}{signature} for the native GC bridge.");
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +1669 to +1671
Assert.IsTrue (dexFiles.Any (dex => DexUtils.ContainsClassWithMethod (
"Lmono/android/GCUserPeer;", method, signature, dex, AndroidSdkPath)),
$"R8 must preserve GCUserPeer.{method}{signature} for the native GC bridge.");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants