Description
Android application type
.NET Android (net7.0-android, etc.)
Affected platform version
.NET 8 in main (ff6eb66)
Description
The .NET Linker will apply optimizations that can result in per-ABI assemblies, see e.g. https://github.com/dotnet/runtime/blob/318c0e6708ced35180fd5218170f82246e2f2bac/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.64bit.xml
LLVM Marshal Methods (8bc7a3e) updates assemblies.
What happens if we mix these together?
Steps to Reproduce
See intrinsics+mm.zip
, which is:
-
Create an Android Class Library project:
dotnet new androidlib -n androidlib
-
To (1), add
MyRunner.java
, which contains avirtual
/abstract
/etc. method. (There needs to bevirtual
methods so that the assembly contains marshal methods.)package com.example.androidlib; public abstract class MyRunner { public static void run(MyRunner r) { r.run(); } public abstract void run(); }
-
To (1), update
Class1.cs
to inherit from theMyRunner
type and override the method. Additionally,Class1.cs
(or some other type in the same project/assembly) should useIntPtr.Size
:namespace androidlib; public class Class1 : Com.Example.Androidlib.MyRunner { public static readonly bool Is64Bit = IntPtr.Size >= 8; public override void Run () { Console.WriteLine("androidlib.Class1.Run! IntPtr.Size={0}", IntPtr.Size); } }
-
Enable trimming for
androidlib.csproj
:<IsTrimmable>True</IsTrimmable>
-
Create an Android App project:
dotnet new androidlib -n android
-
Update
android.csproj
to referenceandrodilib.csproj
:<ProjectReference Include="..\androidlib\androidlib.csproj" />
-
Update
MainActivity.cs
to use the types from (2), (3):Com.Example.Androidlib.MyRunner.Run(new androidlib.Class1())
Build & run the resulting app in Release configuration.
The app builds successfully:
% ../../dotnet-local.sh build -c Release -p:AndroidSdkDirectory=$HOME/android-toolchain/sdk
# no errors
The app runs successfully:
% ../../dotnet-local.sh build -c Release -p:AndroidSdkDirectory=$HOME/android-toolchain/sdk -t:Install
# side-note: why does `Install` *re-optimize* assemblies?
MSBuild version 17.7.0-preview-23316-03+0fdab8fb8 for .NET
Determining projects to restore...
All projects are up-to-date for restore.
androidlib -> …/intrinsics+mm/androidlib/bin/Release/net8.0-android/androidlib.dll
android -> …/intrinsics+mm/android/bin/Release/net8.0-android/android.dll
androidlib -> …/intrinsics+mm/androidlib/bin/Release/net8.0-android/androidlib.dll
Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
…
[1/11] _Microsoft.Android.Resource.Designer.dll -> _Microsoft.Android.Resource.Designer.dll.so
[1/11] android.dll -> android.dll.so
…
% ../../dotnet-local.sh build -c Release -p:AndroidSdkDirectory=$HOME/android-toolchain/sdk -t:StartAndroidActivity
However, the app is "wrong". Because we set $(IsTrimmable)
=True on androidlib.dll
, the linker will "inline" IntPtr.Size
to a constant value based on the architecture, 8
for 64-bit platforms, 4
for 32-bit platforms. But…
% ikdasm obj/Release/net8.0-android/android-arm64/linked/shrunk/androidlib.dll
…
.method public hidebysig virtual instance void
Run() cil managed
{
// Code size 21 (0x15)
.maxstack 8
IL_0000: ldstr "androidlib.Class1.Run! IntPtr.Size={0}"
IL_0005: ldc.i4 0x4
IL_000a: box [System.Private.CoreLib]System.Int32
IL_000f: call void [System.Console]System.Console::WriteLine(string,
object)
IL_0014: ret
} // end of method Class1::Run
We can see that IntPtr.Size
was inlined to 4 for arm64, where it should be 8!
Additionally, all of the androidlib.dll
assemblies are identical!
% find obj -iname androidlib.dll | xargs shasum
0dc7a87d79f4507abf22e258f4c7c5a151ec8321 obj/Release/net8.0-android/android-arm/linked/shrunk/androidlib.dll
0dc7a87d79f4507abf22e258f4c7c5a151ec8321 obj/Release/net8.0-android/android-arm/linked/androidlib.dll
0dc7a87d79f4507abf22e258f4c7c5a151ec8321 obj/Release/net8.0-android/android-x86/linked/shrunk/androidlib.dll
0dc7a87d79f4507abf22e258f4c7c5a151ec8321 obj/Release/net8.0-android/android-x86/linked/androidlib.dll
0dc7a87d79f4507abf22e258f4c7c5a151ec8321 obj/Release/net8.0-android/android-arm64/linked/shrunk/androidlib.dll
0dc7a87d79f4507abf22e258f4c7c5a151ec8321 obj/Release/net8.0-android/android-arm64/linked/androidlib.dll
0dc7a87d79f4507abf22e258f4c7c5a151ec8321 obj/Release/net8.0-android/android-x64/linked/shrunk/androidlib.dll
0dc7a87d79f4507abf22e258f4c7c5a151ec8321 obj/Release/net8.0-android/android-x64/linked/androidlib.dll
When running the app on a Pixel 6 (64-bit device!), we see the same erroneous behavior:
I DOTNET : androidlib.Class1.Run! IntPtr.Size=4
😱
Did you find any workaround?
Don't enable trimming; either don't set $(IsTrimmable)
at all, or set $(IsTrimmable)
=False.
I DOTNET : androidlib.Class1.Run! IntPtr.Size=8
Relevant log output
No response