Skip to content

IL Linker Intrinsics + Marshal Methods, oh my! #8155

Description

@jonpryor

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:

  1. Create an Android Class Library project: dotnet new androidlib -n androidlib

  2. To (1), add MyRunner.java, which contains a virtual/abstract/etc. method. (There needs to be virtual 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();
    }
  3. To (1), update Class1.cs to inherit from the MyRunner type and override the method. Additionally, Class1.cs (or some other type in the same project/assembly) should use IntPtr.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);
        }
    }
  4. Enable trimming for androidlib.csproj:

     <IsTrimmable>True</IsTrimmable>
  5. Create an Android App project: dotnet new androidlib -n android

  6. Update android.csproj to reference androdilib.csproj:

    <ProjectReference Include="..\androidlib\androidlib.csproj" />
  7. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions