Skip to content

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

Closed
@jonpryor

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions