Skip to content

Commit 30295f8

Browse files
authored
[CoreCLR] Initial support of R2R builds (#10007)
Context: https://learn.microsoft.com/en-us/dotnet/core/deploying/ready-to-run Context: https://github.com/dotnet/runtime/blob/c83f92ad7aa7afa152b833a8e8f5ffe36a3287d1/docs/design/coreclr/botr/readytorun-overview.md Context: https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/readytorun-format.md Context: xamarin/monodroid@f22cc20 ReadyToRun (R2R) is a form of ahead-of-time compilation supported by CoreCLR.It works by adding setting the `ManagedNativeHeader` CLR header entrypoint to refer to a `READYTORUN_HEADER` section. R2R is enabled by using `dotnet publish` alongside `$(PublishReadyToRun)`=true: dotnet new console dotnet publish -p:PublishReadyToRun=true You can see some of the changes that R2R produces by using [`dumpbin /CLRHEADER`][0]. Consider the original assembly: > dumpbin /clrheader .\obj\Release\net9.0\win-x64\*.dll … clr Header: … 6000001 entry point token … 0 [ 0] RVA [size] of ManagedNativeHeader Directory vs. the R2R assembly: > dumpbin /clrheader .\obj\Release\net9.0\win-x64\R2R\*.dll … clr Header: … 6000001 entry point token … 1548 [ 94] RVA [size] of ManagedNativeHeader Directory The `ManagedNativeHeader` entry point having a non-zero RVA indicates that the assembly contains native machine code, possibly R2R data. Enable R2R usage with .NET for Android and CoreCLR, when `$(UseMonoRuntime)`=false. Additionally, `$(RuntimeIdentifier)` must be set, via `-r RID`: dotnet new android dotnet publish -p:PublishReadyToRun=true -p:UseMonoRuntime=false -r android-arm64 The resulting packaged assemblies will contain R2R data. ## Problem Android app build runs two assembly post-processing build tasks which modify assemblies using Mono.Cecil: `<RewriteMarshalMethods/>` (86260ed) and `<RemoveRegisterAttribute/>` (xamarin/monodroid@f22cc208). Unfortunately, if this happens *after* R2R image generation, Cecil will throw a NotSupportedException, as Cecil does not support modifying assemblies which contain native data: System.NotSupportedException: Writing mixed-mode assemblies is not supported    at Mono.Cecil.ModuleWriter.Write(ModuleDefinition module, Disposable`1 stream, WriterParameters parameters)    at Mono.Cecil.ModuleWriter.WriteModule(ModuleDefinition module, Disposable`1 stream, WriterParameters parameters)    at Mono.Cecil.ModuleDefinition.Write(Stream stream, WriterParameters parameters)    at Mono.Cecil.ModuleDefinition.Write(WriterParameters parameters)    at Mono.Cecil.ModuleDefinition.Write()    at Mono.Cecil.AssemblyDefinition.Write()    at Xamarin.Android.Tasks.RemoveRegisterAttribute.RunTask() in /Users/runner/work/1/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Tasks/RemoveRegisterAttribute.cs:line 37    at Microsoft.Android.Build.Tasks.AndroidTask.Execute() in /Users/runner/work/1/s/xamarin-android/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs:line 25 "Fix" this by *disabling* use of the `<RewriteMarshalMethods/>` and `<RemoveRegisterAttribute/>` tasks when using R2R. ## TODO Investigate running R2R image generation build task after `<RewriteMarshalMethods/>` and `<RemoveRegisterAttribute/>` so that R2R builds could also benefit from the performance improvements these build tasks bring. Investigate running R2R image generation as part of the `$(RuntimeIdentifiers)` inner build, so that `-r RID` is not required. Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com> [0]: https://learn.microsoft.com/en-us/cpp/build/reference/clrheader?view=msvc-170
1 parent 33763ef commit 30295f8

File tree

2 files changed

+62
-10
lines changed

2 files changed

+62
-10
lines changed

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,8 @@ public void BuildBasicApplication ([Values (true, false)] bool isRelease, [Value
107107
}
108108

109109
[Test]
110-
public void BasicApplicationOtherRuntime ([Values (true, false)] bool isRelease)
110+
public void BasicApplicationBuildCoreCLR ([Values (true, false)] bool isRelease)
111111
{
112-
// This test would fail, as it requires **our** updated runtime pack, which isn't currently created
113-
// It is created in `src/native/native-clr.csproj` which isn't built atm.
114-
Assert.Ignore ("CoreCLR support isn't fully enabled yet. This test will be enabled in a follow-up PR.");
115112
var proj = new XamarinAndroidApplicationProject {
116113
IsRelease = isRelease,
117114
// Add locally downloaded CoreCLR packs
@@ -124,6 +121,64 @@ public void BasicApplicationOtherRuntime ([Values (true, false)] bool isRelease)
124121
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
125122
}
126123

124+
static object [] ReadyToRunConfigurationSource = new object [] {
125+
new object[] {
126+
/* isComposite */ true,
127+
/* rid */ "android-x64"
128+
},
129+
new object[] {
130+
/* isComposite */ false,
131+
/* rid */ "android-x64"
132+
},
133+
new object[] {
134+
/* isComposite */ true,
135+
/* rid */ "android-arm64"
136+
},
137+
new object[] {
138+
/* isComposite */ false,
139+
/* rid */ "android-arm64"
140+
}
141+
};
142+
143+
[Test]
144+
[TestCaseSource (nameof (ReadyToRunConfigurationSource))]
145+
public void BasicApplicationPublishReadyToRun (bool isComposite, string rid)
146+
{
147+
var proj = new XamarinAndroidApplicationProject {
148+
IsRelease = true,
149+
// Add locally downloaded CoreCLR packs
150+
ExtraNuGetConfigSources = {
151+
Path.Combine (XABuildPaths.BuildOutputDirectory, "nuget-unsigned"),
152+
}
153+
};
154+
155+
proj.SetProperty ("RuntimeIdentifier", rid);
156+
proj.SetProperty ("UseMonoRuntime", "false"); // Enables CoreCLR
157+
proj.SetProperty ("_IsPublishing", "true"); // Make "dotnet build" act as "dotnet publish"
158+
proj.SetProperty ("PublishReadyToRun", "true"); // Enable R2R
159+
proj.SetProperty ("AndroidEnableAssemblyCompression", "false");
160+
161+
if (isComposite)
162+
proj.SetProperty ("PublishReadyToRunComposite", "true"); // Enable R2R composite
163+
164+
var b = CreateApkBuilder ();
165+
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
166+
167+
var assemblyName = proj.ProjectName;
168+
var apk = Path.Combine (Root, b.ProjectDirectory, proj.OutputPath, rid, $"{proj.PackageName}-Signed.apk");
169+
FileAssert.Exists (apk);
170+
171+
var helper = new ArchiveAssemblyHelper (apk, true);
172+
var abi = MonoAndroidHelper.RidToAbi (rid);
173+
Assert.IsTrue (helper.Exists ($"assemblies/{abi}/{assemblyName}.dll"), $"{assemblyName}.dll should exist in apk!");
174+
175+
using var stream = helper.ReadEntry ($"assemblies/{assemblyName}.dll");
176+
stream.Position = 0;
177+
using var peReader = new System.Reflection.PortableExecutable.PEReader (stream);
178+
Assert.IsTrue (peReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory.Size > 0,
179+
$"ReadyToRun image not found in {assemblyName}.dll! ManagedNativeHeaderDirectory should not be empty!");
180+
}
181+
127182
[Test]
128183
public void NativeAOT ()
129184
{

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -342,13 +342,10 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
342342
<AndroidUseAssemblyStore Condition=" '$(AndroidUseAssemblyStore)' == '' ">true</AndroidUseAssemblyStore>
343343
<AndroidAotEnableLazyLoad Condition=" '$(AndroidAotEnableLazyLoad)' == '' And '$(AotAssemblies)' == 'true' And '$(AndroidIncludeDebugSymbols)' != 'true' ">True</AndroidAotEnableLazyLoad>
344344
<AndroidEnableMarshalMethods Condition=" '$(AndroidEnableMarshalMethods)' == '' and ('$(UsingMicrosoftNETSdkRazor)' == 'true') ">False</AndroidEnableMarshalMethods>
345-
<AndroidEnableMarshalMethods Condition=" '$(AndroidEnableMarshalMethods)' == '' and '$(_AndroidRuntime)' != 'NativeAOT' ">True</AndroidEnableMarshalMethods>
345+
<AndroidEnableMarshalMethods Condition=" '$(AndroidEnableMarshalMethods)' == '' and '$(_AndroidRuntime)' != 'NativeAOT' and '$(PublishReadyToRun)' != 'true' ">True</AndroidEnableMarshalMethods>
346346
<!-- NOTE: temporarily disable for NativeAOT for now, to get build passing -->
347-
<AndroidEnableMarshalMethods Condition=" '$(AndroidEnableMarshalMethods)' == '' and '$(_AndroidRuntime)' == 'NativeAOT' ">False</AndroidEnableMarshalMethods>
347+
<AndroidEnableMarshalMethods Condition=" '$(AndroidEnableMarshalMethods)' == '' and ('$(_AndroidRuntime)' == 'NativeAOT' or '$(PublishReadyToRun)' == 'true') ">False</AndroidEnableMarshalMethods>
348348

349-
<!-- NOTE: temporarily disable for CoreCLR for now, until we have an implementation that works
350-
with the new runtime -->
351-
<AndroidEnableMarshalMethods Condition=" '$(AndroidEnableMarshalMethods)' == '' and '$(_AndroidRuntime)' == 'CoreCLR' ">False</AndroidEnableMarshalMethods>
352349
<_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' ">False</_AndroidUseMarshalMethods>
353350
<_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">$(AndroidEnableMarshalMethods)</_AndroidUseMarshalMethods>
354351

@@ -2074,7 +2071,7 @@ because xbuild doesn't support framework reference assemblies.
20742071

20752072
<!-- Shrink Mono.Android.dll by removing attribute only needed for GenerateJavaStubs -->
20762073
<RemoveRegisterAttribute
2077-
Condition="'$(AndroidLinkMode)' != 'None' and '$(AndroidIncludeDebugSymbols)' != 'true' and '$(AndroidStripILAfterAOT)' != 'true' and '$(_AndroidRuntime)' != 'NativeAOT' "
2074+
Condition="'$(AndroidLinkMode)' != 'None' and '$(AndroidIncludeDebugSymbols)' != 'true' and '$(AndroidStripILAfterAOT)' != 'true' and '$(_AndroidRuntime)' != 'NativeAOT' and '$(PublishReadyToRun)' != 'true' "
20782075
ShrunkFrameworkAssemblies="@(_ShrunkAssemblies)" />
20792076

20802077
<MakeDir Directories="$(MonoAndroidIntermediateAssemblyDir)shrunk" />

0 commit comments

Comments
 (0)