Skip to content

Commit

Permalink
[One .NET] AOT support
Browse files Browse the repository at this point in the history
Helpful reading:

* https://github.com/dotnet/runtime/blob/15dec9a2aa5a4236d6ba70de2e9c146867b9d2e0/src/tasks/AotCompilerTask/MonoAOTCompiler.cs
* https://github.com/dotnet/runtime/blob/15dec9a2aa5a4236d6ba70de2e9c146867b9d2e0/src/mono/netcore/nuget/Microsoft.NET.Runtime.MonoAOTCompiler.Task/README.md

TODO / limitations:

* We only have the osx-x64 host packages
* I have not updated the installers yet. The AOT packs are placed in
  `~/android-toolchain/dotnet` for local testing.
* Many other `TODO` comments

To make this work, I moved the existing `<Aot/>` MSBuild task calls to
a new `_AndroidAot` MSBuild target in `Xamarin.Android.Legacy.targets`.

In the .NET 6 targets, there is a *different* `_AndroidAot` MSBuild
target that runs the new `<MonoAOTCompiler/>` MSBuild task.

In order to acquire the AOT packages, I created a new
`mono-aot-compiler.proj` that is invoked such as:

    <Exec
        Command="$(DotNetPreviewTool) build @(_GlobalProperties, ' ') &quot;$(MSBuildThisFileDirectory)..\mono-aot-compiler\mono-aot-compiler.proj&quot;"
        EnvironmentVariables="NUGET_PACKAGES=$(DotNetPreviewPath)packs"
    />

Setting `$NUGET_PACKAGES` allows us to extract the AOT NuGet packages
directly to `dotnet/packs` when this project is restored.

Then we conditionally import the Sdk:

    <Import Project="..\build\Microsoft.NET.Runtime.MonoAOTCompiler.Task.props" Sdk="Microsoft.NET.Runtime.MonoAOTCompiler.Task"
        Condition=" '$(AotAssemblies)' == 'true' " />

Which allows us to use `$(MonoAOTCompilerTaskAssemblyPath)`:

    <UsingTask
        Condition=" '$(AotAssemblies)' == 'true' "
        TaskName="MonoAOTCompiler"
        AssemblyFile="$(MonoAOTCompilerTaskAssemblyPath)"
    />

I still need to add the new packs to our installers.
  • Loading branch information
jonathanpeppers committed Jun 2, 2021
1 parent c677a16 commit 482210f
Show file tree
Hide file tree
Showing 7 changed files with 403 additions and 271 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This file is imported *after* the Microsoft.NET.Sdk/Sdk.targets.
<Import Project="..\tools\Xamarin.Android.Bindings.Core.targets" />
<Import Project="..\tools\Xamarin.Android.Bindings.ClassParse.targets" />
<Import Project="Microsoft.Android.Sdk.AndroidLibraries.targets" />
<Import Project="Microsoft.Android.Sdk.Aot.targets" Condition=" '$(AndroidApplication)' == 'true' " />
<Import Project="Microsoft.Android.Sdk.Application.targets" Condition=" '$(AndroidApplication)' == 'true' " />
<Import Project="Microsoft.Android.Sdk.AssemblyResolution.targets" />
<Import Project="Microsoft.Android.Sdk.ILLink.targets" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!--
***********************************************************************************************
Microsoft.Android.Sdk.Aot.targets
.NET 6 AOT support. You can find "legacy" Xamarin.Android AOT support
in Xamarin.Android.Legacy.targets.
For <MonoAOTCompiler/> usage, see:
* https://github.com/dotnet/runtime/blob/15dec9a2aa5a4236d6ba70de2e9c146867b9d2e0/src/tasks/AotCompilerTask/MonoAOTCompiler.cs
* https://github.com/dotnet/runtime/blob/15dec9a2aa5a4236d6ba70de2e9c146867b9d2e0/src/mono/netcore/nuget/Microsoft.NET.Runtime.MonoAOTCompiler.Task/README.md
***********************************************************************************************
-->
<Project>

<UsingTask TaskName="Xamarin.Android.Tasks.GetAotArguments" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />

<Target Name="_AndroidAot"
Condition=" '$(RunAOTCompilation)' == 'true' "
Inputs="$(_BuildApkEmbedInputs)"
Outputs="$(_BuildApkEmbedOutputs)">

<!--TODO: $(MonoAotCrossCompilerPath) is only set to the arm64 path -->
<Error
Condition=" '$(RuntimeIdentifier)' != 'android-arm64' "
Text=" AOT is currently only supported when %24(RuntimeIdentifier) is set to 'android-arm64'."
/>

<GetAotArguments
AndroidAotMode="$(AndroidAotMode)"
AndroidNdkDirectory="$(_AndroidNdkDirectory)"
AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)"
AndroidApiLevel="$(_AndroidApiLevel)"
ManifestFile="$(IntermediateOutputPath)android\AndroidManifest.xml"
AndroidSequencePointsMode="$(_SequencePointsMode)"
AotAdditionalArguments="$(AndroidAotAdditionalArguments)"
AotOutputDirectory="$(_AndroidAotBinDirectory)"
RuntimeIdentifier="$(RuntimeIdentifier)"
EnableLLVM="$(EnableLLVM)"
Profiles="@(_AotProfiles)">
<Output PropertyName="_AotArguments" TaskParameter="Arguments" />
</GetAotArguments>
<ItemGroup>
<_MonoAOTAssemblies Include="@(_ShrunkAssemblies->'%(FullPath)')" AotArguments="$(_AotArguments)" />
</ItemGroup>
<MakeDir Directories="$(IntermediateOutputPath)aot\$(RuntimeIdentifier)\" />
<!--
TODO:
* I set every [Input] with a non-existent property.
* AotProfilePath is a single string, not sure how to pass @(_AotProfiles)?
-->
<MonoAOTCompiler
AotProfilePath="$(_AotProfilePath)"
Assemblies="@(_MonoAOTAssemblies)"
CompilerBinaryPath="$(MonoAotCrossCompilerPath)"
DisableParallelAot="$(_DisableParallelAot)"
LLVMPath="$(_LLVMPath)"
Mode="$(AndroidAotMode)"
OutputDir="$(IntermediateOutputPath)aot\$(RuntimeIdentifier)\"
Profilers="@(_AotProfiles)"
UseAotDataFile="$(_UseAotDataFile)"
UseLLVM="$(EnableLLVM)">
<Output TaskParameter="CompiledAssemblies" ItemName="_AotCompiledAssemblies" />
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
</MonoAOTCompiler>
</Target>
</Project>
212 changes: 4 additions & 208 deletions src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using Microsoft.Build.Framework;
Expand Down Expand Up @@ -33,111 +31,29 @@ public enum SequencePointsMode {
}

// can't be a single ToolTask, because it has to run mkbundle many times for each arch.
public class Aot : AndroidAsyncTask
public class Aot : GetAotArguments
{
public override string TaskPrefix => "AOT";

[Required]
public string AndroidAotMode { get; set; }

public string AndroidNdkDirectory { get; set; }

[Required]
public string AndroidApiLevel { get; set; }

[Required]
public ITaskItem ManifestFile { get; set; }

[Required]
public ITaskItem[] ResolvedAssemblies { get; set; }

// Which ABIs to include native libs for
[Required]
public string [] SupportedAbis { get; set; }

[Required]
public string AotOutputDirectory { get; set; }

[Required]
public string IntermediateAssemblyDir { get; set; }

public string LinkMode { get; set; }

public bool EnableLLVM { get; set; }

public string AndroidSequencePointsMode { get; set; }

public string AotAdditionalArguments { get; set; }

public ITaskItem[] AdditionalNativeLibraryReferences { get; set; }

public string ExtraAotOptions { get; set; }

public ITaskItem [] Profiles { get; set; }

[Required]
public string AndroidBinUtilsDirectory { get; set; }

[Output]
public string[] NativeLibrariesReferences { get; set; }

AotMode AotMode;
SequencePointsMode sequencePointsMode;

public override bool RunTask ()
{
// NdkUtil must always be initialized - once per thread
if (!NdkUtil.Init (LogCodedError, AndroidNdkDirectory))
return false;

return base.RunTask ();
}

public static bool GetAndroidAotMode(string androidAotMode, out AotMode aotMode)
{
aotMode = AotMode.Normal;

switch ((androidAotMode ?? string.Empty).ToLowerInvariant().Trim())
{
case "":
case "none":
aotMode = AotMode.None;
return true;
case "normal":
aotMode = AotMode.Normal;
return true;
case "hybrid":
aotMode = AotMode.Hybrid;
return true;
case "full":
aotMode = AotMode.Full;
return true;
case "interpreter":
aotMode = AotMode.Interp;
return true; // We don't do anything here for this mode, this is just to set the flag for the XA
// runtime to initialize Mono in the inrepreter "AOT" mode.
}

return false;
}

public static bool TryGetSequencePointsMode (string value, out SequencePointsMode mode)
{
mode = SequencePointsMode.None;
switch ((value ?? string.Empty).ToLowerInvariant().Trim ()) {
case "none":
mode = SequencePointsMode.None;
return true;
case "normal":
mode = SequencePointsMode.Normal;
return true;
case "offline":
mode = SequencePointsMode.Offline;
return true;
}
return false;
}

static string GetNdkToolchainLibraryDir(string binDir, string archDir = null)
{
var baseDir = Path.GetFullPath(Path.Combine(binDir, ".."));
Expand Down Expand Up @@ -183,51 +99,6 @@ static string QuoteFileName(string fileName)
return builder.ToString();
}

int GetNdkApiLevel(string androidNdkPath, string androidApiLevel, AndroidTargetArch arch)
{
var manifest = AndroidAppManifest.Load (ManifestFile.ItemSpec, MonoAndroidHelper.SupportedVersions);

int level;
if (manifest.MinSdkVersion.HasValue) {
level = manifest.MinSdkVersion.Value;
}
else if (int.TryParse (androidApiLevel, out level)) {
// level already set
}
else {
// Probably not ideal!
level = MonoAndroidHelper.SupportedVersions.MaxStableVersion.ApiLevel;
}

// Some Android API levels do not exist on the NDK level. Workaround this my mapping them to the
// most appropriate API level that does exist.
if (level == 6 || level == 7) level = 5;
else if (level == 10) level = 9;
else if (level == 11) level = 12;
else if (level == 20) level = 19;
else if (level == 22) level = 21;
else if (level == 23) level = 21;

// API levels below level 21 do not provide support for 64-bit architectures.
if (NdkUtil.IsNdk64BitArch(arch) && level < 21) {
level = 21;
}

// We perform a downwards API level lookup search since we might not have hardcoded the correct API
// mapping above and we do not want to crash needlessly.
for (; level >= 5; level--) {
try {
NdkUtil.GetNdkPlatformLibPath (androidNdkPath, arch, level);
break;
} catch (InvalidOperationException ex) {
// Path not found, continue searching...
continue;
}
}

return level;
}

public async override System.Threading.Tasks.Task RunTaskAsync ()
{
// NdkUtil must always be initialized - once per thread
Expand All @@ -236,19 +107,6 @@ public async override System.Threading.Tasks.Task RunTaskAsync ()
return;
}

bool hasValidAotMode = GetAndroidAotMode (AndroidAotMode, out AotMode);
if (!hasValidAotMode) {
LogCodedError ("XA3002", Properties.Resources.XA3002, AndroidAotMode);
return;
}

if (AotMode == AotMode.Interp) {
LogDebugMessage ("Interpreter AOT mode enabled");
return;
}

TryGetSequencePointsMode (AndroidSequencePointsMode, out sequencePointsMode);

var nativeLibs = new List<string> ();

await this.WhenAllWithLock (GetAotConfigs (),
Expand Down Expand Up @@ -282,48 +140,8 @@ IEnumerable<Config> GetAotConfigs ()
if (!Directory.Exists (AotOutputDirectory))
Directory.CreateDirectory (AotOutputDirectory);

var sdkBinDirectory = MonoAndroidHelper.GetOSBinPath ();
foreach (var abi in SupportedAbis) {
string aotCompiler = "";
string outdir = "";
string mtriple = "";
AndroidTargetArch arch;

switch (abi) {
case "armeabi-v7a":
aotCompiler = Path.Combine (sdkBinDirectory, "cross-arm");
outdir = Path.Combine (AotOutputDirectory, "armeabi-v7a");
mtriple = "armv7-linux-gnueabi";
arch = AndroidTargetArch.Arm;
break;

case "arm64":
case "arm64-v8a":
case "aarch64":
aotCompiler = Path.Combine (sdkBinDirectory, "cross-arm64");
outdir = Path.Combine (AotOutputDirectory, "arm64-v8a");
mtriple = "aarch64-linux-android";
arch = AndroidTargetArch.Arm64;
break;

case "x86":
aotCompiler = Path.Combine (sdkBinDirectory, "cross-x86");
outdir = Path.Combine (AotOutputDirectory, "x86");
mtriple = "i686-linux-android";
arch = AndroidTargetArch.X86;
break;

case "x86_64":
aotCompiler = Path.Combine (sdkBinDirectory, "cross-x86_64");
outdir = Path.Combine (AotOutputDirectory, "x86_64");
mtriple = "x86_64-linux-android";
arch = AndroidTargetArch.X86_64;
break;

// case "mips":
default:
throw new Exception ("Unsupported Android target architecture ABI: " + abi);
}
(string aotCompiler, string outdir, string mtriple, AndroidTargetArch arch) = GetAbiSettings (abi);

if (EnableLLVM && !NdkUtil.ValidateNdkPlatform (LogMessage, LogCodedError, AndroidNdkDirectory, arch, enableLLVM:EnableLLVM)) {
yield return Config.Invalid;
Expand All @@ -339,10 +157,7 @@ IEnumerable<Config> GetAotConfigs ()
outdir = outdir.Replace (WorkingDirectory + Path.DirectorySeparatorChar, string.Empty);
}

int level = 0;
string toolPrefix = EnableLLVM
? NdkUtil.GetNdkToolPrefix (AndroidNdkDirectory, arch, level = GetNdkApiLevel (AndroidNdkDirectory, AndroidApiLevel, arch))
: Path.Combine (AndroidBinUtilsDirectory, $"{NdkUtil.GetArchDirName (arch)}-");
string toolPrefix = GetToolPrefix (arch, out int level);
var toolchainPath = toolPrefix.Substring(0, toolPrefix.LastIndexOf(Path.DirectorySeparatorChar));
var ldFlags = string.Empty;
if (EnableLLVM) {
Expand Down Expand Up @@ -407,27 +222,8 @@ IEnumerable<Config> GetAotConfigs ()
if (!Directory.Exists (tempDir))
Directory.CreateDirectory (tempDir);

List<string> aotOptions = new List<string> ();

if (Profiles != null && Profiles.Length > 0) {
aotOptions.Add ("profile-only");
foreach (var p in Profiles) {
var fp = Path.GetFullPath (p.ItemSpec);
aotOptions.Add ($"profile={fp}");
}
}
if (!string.IsNullOrEmpty (AotAdditionalArguments))
aotOptions.Add (AotAdditionalArguments);
if (sequencePointsMode == SequencePointsMode.Offline)
aotOptions.Add ($"msym-dir={outdir}");
if (AotMode != AotMode.Normal)
aotOptions.Add (AotMode.ToString ().ToLowerInvariant ());

var aotOptions = GetAotOptions (outdir, mtriple, toolPrefix);
aotOptions.Add ($"outfile={outputFile}");
aotOptions.Add ("asmwriter");
aotOptions.Add ($"mtriple={mtriple}");
aotOptions.Add ($"tool-prefix={toolPrefix}");
aotOptions.Add ($"llvm-path={sdkBinDirectory}");
aotOptions.Add ($"temp-path={tempDir}");

if (!String.IsNullOrEmpty (ldName)) {
Expand Down

0 comments on commit 482210f

Please sign in to comment.