Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Mono.Android] fix trimming warnings, part 2 #8758

Merged
merged 6 commits into from
Mar 1, 2024

Conversation

jonathanpeppers
Copy link
Member

This fixes the next set of trimmer warnings found via:

<IsTrimmable>true</IsTrimmable>
<EnableAotAnalyzer>true</EnableAotAnalyzer>

JavaObjectExtensions

JavaCast<T>() now requires PublicConstructors and NonPublicConstructors because of Activator.CreateInstance<T>(). This change bubbles up to various other types that have a Find*ById<T>() method:

  • Activity
  • Dialog
  • FragmentManager
  • View
  • Window

GetInvokerType() also has suppressions around Assembly.GetType() and Type.MakeGenericType(). We track this for the future at:

#8724

AndroidRuntime

Update [DynamicallyAccessedMembers] based on changes to RegisterNativeMembers in:

xamarin/java.interop@b8f6f88

JNINativeWrapper

$(EnableAotAnalyzer) found usage of DynamicMethod. Suppress for now, as we track this for the future at:

#8724

ResourceIdManager

Usage of Type.GetMethod ("UpdateIdValues") leads to decoration of [ResourceDesignerAttribute] with:

[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
public string FullName { get; set; }

I also had to suppress warnings around Assembly.GetType(). This should be OK, as Resource.designer.cs is always in the "root assembly" of Android application projects.

JavaProxyThrowable

Suppress warning around StackFrame.GetMethod(), we already handle null return values and exceptions. The existing code appears to be "best effort" to provide additional stack trace information.

TypeManager

Suppress warning around a call to Type.GetType() with a string passed in from Java. Not really much we can do yet, except rely on MarkJavaObjects trimmer step.

Likely also a problem for the future:

#8724

This fixes the next set of trimmer warnings found via:

    <IsTrimmable>true</IsTrimmable>
    <EnableAotAnalyzer>true</EnableAotAnalyzer>

~~ JavaObjectExtensions ~~

`JavaCast<T>()` now requires `PublicConstructors` and
`NonPublicConstructors` because of `Activator.CreateInstance<T>()`.
This change bubbles up to various other types that have a
`Find*ById<T>()` method:

* `Activity`
* `Dialog`
* `FragmentManager`
* `View`
* `Window`

`GetInvokerType()` also has suppressions around `Assembly.GetType()`
and `Type.MakeGenericType()`. We track this for the future at:

xamarin#8724

~~ AndroidRuntime ~~

Update `[DynamicallyAccessedMembers]` based on changes to
`RegisterNativeMembers` in:

xamarin/java.interop@b8f6f88

~~ JNINativeWrapper ~~

`$(EnableAotAnalyzer)` found usage of `DynamicMethod`. Suppress for now,
as we track this for the future at:

xamarin#8724

~~ ResourceIdManager ~~

Usage of `Type.GetMethod ("UpdateIdValues")` leads to decoration of
`[ResourceDesignerAttribute]` with:

    [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
    public string FullName { get; set; }

I also had to suppress warnings around `Assembly.GetType()`. This
*should* be OK, as `Resource.designer.cs` is always in the "root
assembly" of Android application projects.

~~ JavaProxyThrowable ~~

Suppress warning around `StackFrame.GetMethod()`, we already handle
null return values and exceptions. The existing code appears to be
"best effort" to provide additional stack trace information.

~~ TypeManager ~~

Suppress warning around a call to `Type.GetType()` with a string
passed in from Java. Not really much we can do yet, except rely on
`MarkJavaObjects` trimmer step.

Likely also a problem for the future:

xamarin#8724
@jonathanpeppers
Copy link
Member Author

/cc @simonrozsival @vitek-karas

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = rootAssembly)]
[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = rootAssembly)]
[return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
static Type AssemblyGetType (Assembly a, string name) => a.GetType (name);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this really work? I can't see how the trimmer would be able to determine what the returned type is and so it won't be able to preserve the public methods on it. I think at the very least the name would need to be attributed as well, but I'm not sure if that's enough.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, I don't think the trimmer preserves anything here -- this is just ignoring the warning.

This code is looking for a Resource.designer.cs file that is generated in every app, which is the root assembly. There will likely be some problem here for NativeAOT one day.

Copy link
Member Author

Choose a reason for hiding this comment

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

I did preserve the attribute that is kind of the "entry point":

[AttributeUsage (AttributeTargets.Assembly)]
public class ResourceDesignerAttribute : Attribute
{
	public ResourceDesignerAttribute (string fullName)
	public ResourceDesignerAttribute (
			[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
			string fullName)
	{
		FullName = fullName;
	}

	[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
	public string FullName { get; set; }

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we add a Type property to the attribute?

[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] public Type Type { get; set; }

That would be perfectly trimmable and AOT friendly.

Copy link
Member Author

Choose a reason for hiding this comment

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

We can add a new API in .NET 9, but currently Xamarin.Android class libraries built against Mono.Android.dll from ~2012 still work. This is different from iOS, as they have breaking changes.

So there would be some number of libraries out there that won't use the new API.

Copy link
Member

Choose a reason for hiding this comment

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

This PR is for .NET 9,so we can certainly add the suggested Type property, and also have appropriate fallback code for when Type is null. (This in turn means that the new property would need to allow null, e.g. Type? Type {get;set;}.) We'd also want a "semantically meaningful" name; what is ResourceDesignerAttribute.Type supposed to mean? A better name is warranted. ResourceDesignerType, perhaps?

[assembly: Android.Runtime.ResourceDesignerAttribute(
    "Example.Android.Resource",
    ResourceDesignerType=typeof(Example.Android.Resource),
    IsApplication=true
)]

That said… the "new" Resource design of dc3ccf2 was thought/hoped to (somewhat?) address trimming support; by using properties/methods for everything the trimmer should be able to "see" what methods are used and preserve them, while allowing everything else to be trimmed away.

Setting [DynamicallyAccessedMembers-(DynamicallyAccessedMemberTypes.PublicMethods)] feels contrary to that intention? Why would all methods on the Resource type need to be preserved?

Rando aside: why'd we use a string to name the Resource type instead of typeof() in the first place? A constructor overload feels reasonable here!

partial class ResourceDesignerAttribute {
    public ResourceDesignerAttribute (Type resourceDesignerType);
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Setting [DynamicallyAccessedMembers-(DynamicallyAccessedMemberTypes.PublicMethods)] feels contrary to that intention? Why would all methods on the Resource type need to be preserved?

We used System.Reflection to find an UpdateIdValues() method and call it, so I think the current trimmer design is to add DynamicallyAccessedMemberTypes.PublicMethods?

partial class ResourceDesignerAttribute {
public ResourceDesignerAttribute (Type resourceDesignerType);
}

For this part, let me see what it would take to:

  1. Generate usage of this overload, as we create Resource.designer.cs at build time
  2. Maybe the old overload could emit a trimmer warning.

Copy link
Member

@jonpryor jonpryor Feb 29, 2024

Choose a reason for hiding this comment

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

@jonathanpeppers wrote:

We used System.Reflection to find an UpdateIdValues() method and call it,

Yes, we used to. Commit dc3ccf2 should have removed that call for most (all?) cases. Yes, UpdateIdValues() still exists, but "nothing" should call it (unless some customer decided to manually call it For Reasons™?). We certainly shouldn't call it, and we should be actively removing it, e.g. in ClearDesignerClass():

@dellis1972: do we even need [assembly: ResourceDesigner(…)] anymore?

Copy link
Member

Choose a reason for hiding this comment

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

@dellis1972 replies on Discord:

Its not called if they are using the new Designer Assembly. If they turn it off then it will be used.

I overlooked/forgot that the new stuff can be disabled. It's controlled via $(AndroidUseDesignerAssembly).

When can we drop support for this? It was added in .NET 8; removing support for $(AndroidUseDesignerAssembly)=false from .NET 9 feels too early, maybe? Maybe .NET 10?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm on with .NET 10 , people should have found all the bugs in the new stuff by then :P

"PackageSize": 10864931
"PackageSize": 10897699
Copy link
Member Author

Choose a reason for hiding this comment

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

There is a small app size change here.

It looks like some are:

  • Past .NET bumps were under the threshold
  • libmonodroid.so changed, which must be other recent native code changes
  • We are actually preserving more stuff now. Perhaps we had some subtle trimmer bugs?

Copy link
Member

Choose a reason for hiding this comment

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

@jonathanpeppers wrote:

We are actually preserving more stuff now. Perhaps we had some subtle trimmer bugs?

Are we preserving more stuff? What stuff and why? I think this might warrant more investigation. Consider my earlier comment: we should not be preserving all public methods on the Resource type! That also is apparently the case: we're not preserving all public methods, as indicated by the fact that UnnamedProject.dll is smaller, as is _Microsoft.Android.Resource.Designer.dll (by two bytes!).

Java.Interop.dll increased in size by ~3KB, while Mono.Android.dll shrank by ~2KB.

Of interest is that classes*.dex also increased by ~7KB:

    "classes.dex": {
-     "Size": 9458972
+     "Size": 9418292
    },
    "classes2.dex": {
-     "Size": 103648
+     "Size": 150904
    },

This implies that we're emitting Java Callable Wrappers for more types. What types are we now emitting JCWs for, and why? That could be the "subtle trimmer bug" you elude to, and I would like more details here.

Comment on lines +42 to +43
[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "StackFrame.GetMethod() is \"best attempt\", we handle null & exceptions")]
static MethodBase? StackFrameGetMethod (StackFrame frame) =>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Will this reason hold for actual usage?
For example - in OTel, we had a similar issue, and the outcome of the discussion was that it's very important to get something for each frame. So there we rely on .ToString() on the StackFrame which works even in NativeAOT/trimming (unless one explicitly disable stack frame info in NativeAOT as a size optimization).

I'm not saying this is wrong, just asking where is this used, and if we would like to get at least some info all the time in those scenarios.

Copy link
Member Author

Choose a reason for hiding this comment

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

Let me investigate if we could just use StackFrame.ToString() for this. It's possible we would get the same text, and the tests would pass: 1aa0ea7

Copy link
Member Author

Choose a reason for hiding this comment

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

I tested in a console app, and it appears that StackFrame.GetMethod() returns null under NativeAOT. But calling StackFrame.ToString() returns useful information that contains the method name, example:

MainActivity.OnCreate() + 0x37 at offset 55 in file:line:column <filename unknown>:0:0

To support that one day, we could parse the string, but I think delaying that work for now is best.

I left a code comment for now, linking to:

Code comments + FIXME
Doing it this way, keeps the warning present if someone was attempting to *use* NativeAOT, they still get the warning.
@jonathanpeppers jonathanpeppers marked this pull request as ready for review February 27, 2024 21:51
jonathanpeppers added a commit to jonathanpeppers/java.interop that referenced this pull request Feb 28, 2024
Context: xamarin/xamarin-android#8758 (comment)
Context: xamarin#1192

In 7d1e705 and b8f6f88, we suppressed IL3050, an AOT-related
warning with:

    // FIXME: xamarin#1192
    [UnconditionalSuppressMessage ("AOT", "IL3050")]
    // Problematic code here

We don't immediately *plan* to support NativeAOT on Android in .NET 9,
so it would be nice if we could suppress the warning *for now*. But if
anyone tried to use this code in a NativeAOT context, they could get a
warning.

So instead we should use:

    // FIXME: xamarin#1192
    #pragma warning disable IL3050
    // Problematic code here
    #pragma warning restore IL3050

This will prevent us from introducing new `IL3050` and related
warnings, but if anyone consumes `Java.Interop.dll` from NativeAOT --
they will see the warning.
jonathanpeppers added a commit to jonathanpeppers/xamarin-android that referenced this pull request Feb 29, 2024
Fixes: xamarin#5652

This is a WIP.

This depends on xamarin#8758, will rebase after this one is merged.
Code formatting
@jonathanpeppers
Copy link
Member Author

@jonpryor on the app size changes, this is main vs this PR:

    Size difference in bytes ([*1] apk1 only, [*2] apk2 only):
      +      47,432 classes2.dex
      +         361 assemblies/Mono.Android.dll
      +          81 assemblies/Xamarin.Google.Android.Material.dll
      +          64 assemblies/Xamarin.AndroidX.CoordinatorLayout.dll
      +          54 assemblies/Xamarin.AndroidX.AppCompat.dll
      -           1 assemblies/UnnamedProject.dll
      -      40,604 classes.dex
    Summary:
      +           0 Other entries 0.00% (of 1,566,414)
      +         559 Assemblies 0.01% (of 4,140,795)
      +       6,828 Dalvik executables 0.07% (of 9,562,368)
      +           0 Shared libraries 0.00% (of 4,731,120)
      +           0 Package size difference 0.00% (of 10,897,699)

So we crossed the multi-dex threshold, and that's why classes2.dex appeared.

I have a diff of the two apps, and it looks like constructors like these were preserved now, and so we have JCWs for JLO subclasses like:

image

image

So I bet decorating JavaObjectExtensions.JavaCast<T> with:

[DynamicallyAccessedMembers (Constructors)]

Preserved these constructors.

@jonpryor jonpryor merged commit 5205a5f into xamarin:main Mar 1, 2024
45 of 47 checks passed
@jonathanpeppers jonathanpeppers deleted the Mono.Android-Trimming-Part-2 branch March 1, 2024 21:06
grendello added a commit that referenced this pull request Mar 1, 2024
* main:
  [Mono.Android] fix trimming warnings, part 2 (#8758)
jonathanpeppers added a commit to jonathanpeppers/xamarin-android that referenced this pull request Mar 1, 2024
Fixes: xamarin#5652

This is a WIP.

This depends on xamarin#8758, will rebase after this one is merged.
grendello added a commit that referenced this pull request Mar 6, 2024
* main:
  [Mono.Android] Fix race condition in AndroidMessageHandler (#8753)
  [ci] Fix SDL Sources Analysis for PRs from forks (#8785)
  [ci] Add 1ESPT override to MSBuild test stages (#8784)
  [ci] Do not use @self annotation for templates (#8783)
  [ci] Migrate to the 1ES template (#8747)
  [Mono.Android] fix trimming warnings, part 2 (#8758)
  [Xamarin.Android.Build.Tasks] set `%(DefineConstantsOnly)` for older API levels (#8777)
  [tests] fix duplicate sources in `NuGet.config` (#8772)
  Bump to xamarin/monodroid@e13723e701 (#8771)
grendello added a commit that referenced this pull request Mar 7, 2024
* main:
  [Mono.Android] Fix race condition in AndroidMessageHandler (#8753)
  [ci] Fix SDL Sources Analysis for PRs from forks (#8785)
  [ci] Add 1ESPT override to MSBuild test stages (#8784)
  [ci] Do not use @self annotation for templates (#8783)
  [ci] Migrate to the 1ES template (#8747)
  [Mono.Android] fix trimming warnings, part 2 (#8758)
grendello added a commit to grendello/xamarin-android that referenced this pull request Mar 8, 2024
* main: (306 commits)
  [templates] Remove redundant "template" from display name. (xamarin#8773)
  Bump to xamarin/java.interop@a7e09b7 (xamarin#8793)
  [build] Include MIT license in most NuGet packages (xamarin#8787)
  Bump to dotnet/installer@893b762b6e 9.0.100-preview.3.24153.2 (xamarin#8782)
  [docs] update notes about `dotnet-trace` and `dotnet-gcdump` (xamarin#8713)
  [Mono.Android] Fix race condition in AndroidMessageHandler (xamarin#8753)
  [ci] Fix SDL Sources Analysis for PRs from forks (xamarin#8785)
  [ci] Add 1ESPT override to MSBuild test stages (xamarin#8784)
  [ci] Do not use @self annotation for templates (xamarin#8783)
  [ci] Migrate to the 1ES template (xamarin#8747)
  [Mono.Android] fix trimming warnings, part 2 (xamarin#8758)
  [Xamarin.Android.Build.Tasks] set `%(DefineConstantsOnly)` for older API levels (xamarin#8777)
  [tests] fix duplicate sources in `NuGet.config` (xamarin#8772)
  Bump to xamarin/monodroid@e13723e701 (xamarin#8771)
  Bump to xamarin/xamarin-android-tools@37d79c9 (xamarin#8752)
  Bump to dotnet/installer@d070660282 9.0.100-preview.3.24126.2 (xamarin#8763)
  Bump to xamarin/java.interop@14a9470 (xamarin#8766)
  $(AndroidPackVersionSuffix)=preview.3; net9 is 34.99.0.preview.3 (xamarin#8765)
  [Mono.Android] Do not dispose request content stream in AndroidMessageHandler (xamarin#8764)
  Bump com.android.tools:r8 from 8.2.42 to 8.2.47 (xamarin#8761)
  ...
jonpryor pushed a commit to xamarin/java.interop that referenced this pull request Mar 11, 2024
Context: xamarin/xamarin-android#8758 (comment)
Context: #1192
Context: 2197579

In 7d1e705 and b8f6f88, we suppressed IL3050, an AOT-related
warning with:

	// FIXME: #1192
	[UnconditionalSuppressMessage ("AOT", "IL3050")]
	// Problematic code here

We don't immediately *plan* to support NativeAOT on Android in .NET 9,
so it would be nice if we could suppress the warning *for now*.
However, if/when anyone tried to use this code in a NativeAOT context
such as 2197579, they *should* get a warning (but didn't).

We should instead use:

	// FIXME: #1192
	#pragma warning disable IL3050
	// Problematic code here
	#pragma warning restore IL3050

This will prevent us from introducing new `IL3050` and related
warnings, but if anyone consumes `Java.Interop.dll` from NativeAOT
they will see the warning.

This (re-)introduces IL3050 warnings in the build for
`samples/Hello-NativeAOTFromJNI` (2197579):

	% dotnet publish -c Release -r osx-x64
	…
	…/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs(665): AOT analysis warning IL3050: Java.Interop.JniRuntime.JniValueManager.<GetObjectArrayMarshaler>g__MakeGenericMethod|41_0(MethodInfo,Type): Using member 'System.Reflection.MethodInfo.MakeGenericMethod(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime.
	…/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs(381): AOT analysis warning IL3050: Java.Interop.JniRuntime.JniValueManager.<GetInvokerType>g__MakeGenericType|31_1(Type,Type[]): Using member 'System.Type.MakeGenericType(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime.
	…/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs(789): AOT analysis warning IL3050: Java.Interop.JavaPeerableValueMarshaler.CreateParameterToManagedExpression(JniValueMarshalerContext,ParameterExpression,ParameterAttributes,Type): Using member 'System.Linq.Expressions.Expression.Call(Expression,String,Type[],Expression[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. Calling a generic method requires dynamic code generation. This can be suppressed if the method is not generic.
	…/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs(284): AOT analysis warning IL3050: Java.Interop.JniRuntime.JniTypeManager.MakeGenericType(Type,Type): Using member 'System.Type.MakeGenericType(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime.
	…/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs(277): AOT analysis warning IL3050: Java.Interop.JniRuntime.JniTypeManager.MakeArrayType(Type): Using member 'System.Type.MakeArrayType()' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The code for an array of the specified type might not be available.
@github-actions github-actions bot locked and limited conversation to collaborators Apr 1, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants