[TrimmableTypeMap] R8 shrinking removes unreferenced AndroidJavaSource (user Java) classes under NativeAOT
- Dominant language
- C#
- Stars
- 2.1k
- Forks
- 579
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 257
Description
### Summary
Under the trimmable typemap (NativeAOT), **R8 shrinking removes user-authored `AndroidJavaSource` Java classes** that aren't referenced from the managed dependency graph. This breaks multidex behavior and, more importantly, can break real apps that call user Java via JNI/reflection (which ILC's managed graph can't see).
Reproduced via `BuildAfterMultiDexIsNotRequired(NativeAOT)`:
```
Expected: file exists
But was: ".../obj/Release/android/bin/classes2.dex"
```
### Diagnosis
`CreateMultiDexRequiredApplication` adds two `AndroidJavaSource` files (`ManyMethods.java`, `ManyMethods2.java`), each with 32768 methods (`Bind=False`, unreferenced) → 65536 methods > the 64K dex limit → must produce `classes2.dex`.
Local repro (NativeAOT, arm64):
- The Java is compiled: `obj/Release/.../android/bin/classes/ManyMethods.class` is 1.7 MB each.
- The classes **zip** that D8/R8 consumes (`classes.zip`, 3.9 MB) **contains** `ManyMethods.class` / `ManyMethods2.class`.
- But the produced `classes.dex` is only ~275 KB and contains **none** of those methods, and no `classes2.dex` is produced.
Because `AndroidEnableMultiDex=True` selects **R8** (`Xamarin.Android.D8.targets`, `_UseR8`), and `_R8EnableShrinking=True` for `AndroidLinkTool=r8`, R8 shrinks the unreferenced `ManyMethods` classes away. The trimmable NativeAOT proguard config (`GenerateNativeAotProguardConfiguration`, built from the ILC DGML + acw-map) keeps ACW/JCW types and `android.R`/`android.Manifest`, but **not** user `AndroidJavaSource` classes — so R8 drops them.
The legacy/CoreCLR path keeps them (the `CoreCLR` variant of the test passes).
### Why this matters beyond the test
`AndroidJavaSource` (`Bind=False`) classes are user-authored Java, often invoked from native/JNI or reflection. R8 shrinking them based on the *managed* reachability graph is unsound and can silently remove Java the app needs at runtime.
### Fix direction (needs design)
- Keep user `AndroidJavaSource`-derived classes in the trimmable NativeAOT proguard config (emit `-keep` for them), **or**
- Don't apply R8 *shrinking* to the user Java side under NativeAOT (the managed side is already trimmed by ILC; the Java side's reachability isn't captured by the managed graph), **or**
- Treat the app's own compiled Java classes as kept program classes.
### Acceptance criteria
- [ ] `BuildAfterMultiDexIsNotRequired(NativeAOT)` passes (`classes2.dex` produced when required, absent when not).
- [ ] Unreferenced user `AndroidJavaSource` classes survive into the dex on NativeAOT.
- [ ] No unnecessary dex bloat for normal apps.
### References
- Repro test: `BuildAfterMultiDexIsNotRequired` (`src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs`), `CreateMultiDexRequiredApplication`.
- Code: `src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targets` (`_UseR8`, `_R8EnableShrinking`), `src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets` (`GenerateNativeAotProguardConfiguration`).
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with BuildAfterMultiDexIsNotRequired and CreateMultiDexRequiredApplication in src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs. Read Xamarin.Android.D8.targets alongside Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets, especially GenerateNativeAotProguardConfiguration, then run the NativeAOT test. Done means user AndroidJavaSource classes survive R8, the expected classes2.dex is produced, and the non-multidex case still passes without unnecessary bloat.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, csharp, java
- Domain
- build-system, mobile
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100