Skip to content

Commit 4a917dc

Browse files
Nathan Ricciadamsitnik
Nathan Ricci
andauthored
Add tool chain for Netcore Mono AOT. (#1662)
* Add tool chain for Netcore Mono AOT. Co-authored-by: Adam Sitnik <[email protected]>
1 parent 349f9d9 commit 4a917dc

File tree

15 files changed

+393
-6
lines changed

15 files changed

+393
-6
lines changed

NuGet.Config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@
1111
<add key="perfolizer-nightly" value="https://www.myget.org/F/perfolizer/api/v3/index.json" />
1212
<!-- reuquired to run Mono Wasm benchmarks -->
1313
<add key="dotnet5" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json" />
14+
<!-- reuquired to run Mono AOT benchmarks -->
15+
<add key="dotnet6" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json" />
1416
</packageSources>
1517
</configuration>

docs/articles/configs/toolchains.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,38 @@ Now you should be able to run the Wasm benchmarks!
353353

354354
[!include[IntroWasm](../samples/IntroWasm.md)]
355355

356+
## MonoAotLLVM
357+
358+
BenchmarkDotNet supports doing Mono AOT runs with both the Mono-Mini compiler and the Mono-LLVM compiler (which uses llvm on the back end).
359+
360+
Using this tool chain requires the following flags:
361+
362+
```
363+
--runtimes monoaotllvm
364+
--aotcompilerpath <path to mono aot compiler>
365+
--customruntimepack <path to runtime pack>
366+
```
367+
368+
and optionally (defaults to mini)
369+
370+
```
371+
--aotcompilermode <mini|llvm>
372+
```
373+
374+
As of this writing, the mono aot compiler is not available as a seperate download or nuget package. Therefore, it is required to build the compiler in the [dotnet/runtime repository].
375+
376+
The compiler binary (mono-sgen) is built as part of the `mono` subset, so it can be built (along with the runtime pack) like so (in the root of [dotnet/runtime]).
377+
378+
`./build.sh -subset mono+libs -c Release`
379+
380+
The compiler binary should be generated here (modify for your platform):
381+
382+
```
383+
<runtime root>/artifacts/obj/mono/OSX.x64.Release/mono/mini/mono-sgen
384+
```
385+
386+
And the runtime pack should be generated here:
387+
388+
```
389+
<runtimeroot>artifacts/bin/microsoft.netcore.app.runtime.osx-x64/Release/
390+
```

src/BenchmarkDotNet.Annotations/Jobs/RuntimeMoniker.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ public enum RuntimeMoniker
138138
/// <summary>
139139
/// WebAssembly with .net6.0
140140
/// </summary>
141-
WasmNet60
141+
WasmNet60,
142+
143+
/// <summary>
144+
/// Mono with the Ahead of Time LLVM Compiler backend
145+
/// </summary>
146+
MonoAOTLLVM
142147
}
143148
}

src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using BenchmarkDotNet.Engines;
88
using BenchmarkDotNet.Environments;
99
using BenchmarkDotNet.Helpers;
10+
using BenchmarkDotNet.Toolchains.MonoAotLLVM;
1011
using CommandLine;
1112
using CommandLine.Text;
1213
using JetBrains.Annotations;
@@ -180,9 +181,15 @@ public class CommandLineOptions
180181
[Option("wasmArgs", Required = false, Default = "--expose_wasm", HelpText = "Arguments for the javascript engine used by Wasm toolchain.")]
181182
public string WasmJavaScriptEngineArguments { get; set; }
182183

183-
[Option("customRuntimePack", Required = false, HelpText = "Specify the path to a custom runtime pack. Only used for wasm currently.")]
184+
[Option("customRuntimePack", Required = false, HelpText = "Path to a custom runtime pack. Only used for wasm/MonoAotLLVM currently.")]
184185
public string CustomRuntimePack { get; set; }
185186

187+
[Option("AOTCompilerPath", Required = false, HelpText = "Path to Mono AOT compiler, used for MonoAotLLVM.")]
188+
public FileInfo AOTCompilerPath { get; set; }
189+
190+
[Option("AOTCompilerMode", Required = false, Default = MonoAotCompilerMode.mini, HelpText = "Mono AOT compiler mode, either 'mini' or 'llvm'")]
191+
public MonoAotCompilerMode AOTCompilerMode { get; set; }
192+
186193
internal bool UserProvidedFilters => Filters.Any() || AttributeNames.Any() || AllCategories.Any() || AnyCategories.Any();
187194

188195
[Usage(ApplicationAlias = "")]

src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
using BenchmarkDotNet.Toolchains.DotNetCli;
2525
using BenchmarkDotNet.Toolchains.InProcess.Emit;
2626
using BenchmarkDotNet.Toolchains.MonoWasm;
27+
using BenchmarkDotNet.Toolchains.MonoAotLLVM;
2728
using CommandLine;
2829
using Perfolizer.Horology;
2930
using Perfolizer.Mathematics.OutlierDetection;
@@ -116,6 +117,10 @@ private static bool Validate(CommandLineOptions options, ILogger logger)
116117
logger.WriteLineError($"The provided {nameof(options.WasmMainJs)} \"{options.WasmMainJs}\" does NOT exist. It MUST be provided.");
117118
return false;
118119
}
120+
else if (runtimeMoniker == RuntimeMoniker.MonoAOTLLVM && (options.AOTCompilerPath == null || options.AOTCompilerPath.IsNotNullButDoesNotExist()))
121+
{
122+
logger.WriteLineError($"The provided {nameof(options.AOTCompilerPath)} \"{ options.AOTCompilerPath }\" does NOT exist. It MUST be provided.");
123+
}
119124
}
120125

121126
foreach (string exporter in options.Exporters)
@@ -386,7 +391,22 @@ private static Job CreateJobForGivenRuntime(Job baseJob, string runtimeId, Comma
386391
return MakeWasmJob(baseJob, options, timeOut, "net5.0");
387392
case RuntimeMoniker.WasmNet60:
388393
return MakeWasmJob(baseJob, options, timeOut, "net6.0");
394+
case RuntimeMoniker.MonoAOTLLVM:
395+
var monoAotLLVMRuntime = new MonoAotLLVMRuntime(aotCompilerPath: options.AOTCompilerPath);
396+
397+
var toolChain = MonoAotLLVMToolChain.From(
398+
new NetCoreAppSettings(
399+
targetFrameworkMoniker: monoAotLLVMRuntime.MsBuildMoniker,
400+
runtimeFrameworkVersion: null,
401+
name: monoAotLLVMRuntime.Name,
402+
customDotNetCliPath: options.CliPath?.FullName,
403+
packagesPath: options.RestorePath?.FullName,
404+
timeout: timeOut ?? NetCoreAppSettings.DefaultBuildTimeout,
405+
customRuntimePack: options.CustomRuntimePack,
406+
aotCompilerPath: options.AOTCompilerPath.ToString(),
407+
aotCompilerMode: options.AOTCompilerMode));
389408

409+
return baseJob.WithRuntime(monoAotLLVMRuntime).WithToolchain(toolChain);
390410
default:
391411
throw new NotSupportedException($"Runtime {runtimeId} is not supported");
392412
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.IO;
4+
using BenchmarkDotNet.Extensions;
5+
using BenchmarkDotNet.Jobs;
6+
7+
namespace BenchmarkDotNet.Environments
8+
{
9+
public class MonoAotLLVMRuntime : Runtime, IEquatable<MonoAotLLVMRuntime>
10+
{
11+
[EditorBrowsable(EditorBrowsableState.Never)]
12+
internal static readonly MonoAotLLVMRuntime Default = new MonoAotLLVMRuntime();
13+
14+
public FileInfo AOTCompilerPath { get; }
15+
16+
/// <summary>
17+
/// creates new instance of MonoAotLLVMRuntime
18+
/// </summary>
19+
public MonoAotLLVMRuntime(FileInfo aotCompilerPath, string msBuildMoniker = "net6.0", string displayName = "MonoAOTLLVM") : base(RuntimeMoniker.MonoAOTLLVM, msBuildMoniker, displayName)
20+
{
21+
if (aotCompilerPath == null)
22+
throw new ArgumentNullException(paramName: nameof(aotCompilerPath));
23+
if (aotCompilerPath.IsNotNullButDoesNotExist())
24+
throw new FileNotFoundException($"Provided {nameof(aotCompilerPath)} file: \"{aotCompilerPath.FullName}\" doest NOT exist");
25+
26+
AOTCompilerPath = aotCompilerPath;
27+
}
28+
29+
// this ctor exists only for the purpose of having .Default property that returns something consumable by RuntimeInformation.GetCurrentRuntime()
30+
private MonoAotLLVMRuntime(string msBuildMoniker = "net6.0", string displayName = "MonoAOTLLVM") : base(RuntimeMoniker.MonoAOTLLVM, msBuildMoniker, displayName)
31+
{
32+
AOTCompilerPath = new FileInfo("fake");
33+
}
34+
35+
public override bool Equals(object obj)
36+
=> obj is MonoAotLLVMRuntime other && Equals(other);
37+
38+
public bool Equals(MonoAotLLVMRuntime other)
39+
=> other != null && base.Equals(other) && other.AOTCompilerPath == AOTCompilerPath;
40+
41+
public override int GetHashCode()
42+
=> base.GetHashCode() ^ AOTCompilerPath.GetHashCode();
43+
}
44+
}

src/BenchmarkDotNet/Portability/RuntimeInformation.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,24 @@ public static bool IsCoreRT
4444

4545
public static bool IsWasm => IsOSPlatform(OSPlatform.Create("BROWSER"));
4646

47+
public static bool IsAot { get; } = IsAotMethod(); // This allocates, so we only want to call it once statically.
48+
49+
private static bool IsAotMethod()
50+
{
51+
Type runtimeFeature = Type.GetType("System.Runtime.CompilerServices.RuntimeFeature");
52+
if (runtimeFeature != null)
53+
{
54+
MethodInfo methodInfo = runtimeFeature.GetProperty("IsDynamicCodeCompiled", BindingFlags.Public | BindingFlags.Static)?.GetMethod;
55+
56+
if (methodInfo != null)
57+
{
58+
return !(bool)methodInfo.Invoke(null, null);
59+
}
60+
}
61+
62+
return false;
63+
}
64+
4765
public static bool IsRunningInContainer => string.Equals(Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER"), "true");
4866

4967
internal static string ExecutableExtension => IsWindows() ? ".exe" : string.Empty;
@@ -197,6 +215,8 @@ internal static string GetRuntimeVersion()
197215
internal static Runtime GetCurrentRuntime()
198216
{
199217
//do not change the order of conditions because it may cause incorrect determination of runtime
218+
if (IsAot && IsMono)
219+
return MonoAotLLVMRuntime.Default;
200220
if (IsMono)
201221
return MonoRuntime.Default;
202222
if (IsFullFramework)
@@ -252,7 +272,7 @@ internal static bool HasRyuJit()
252272

253273
internal static string GetJitInfo()
254274
{
255-
if (IsCoreRT || IsNetNative)
275+
if (IsCoreRT || IsNetNative || IsAot)
256276
return "AOT";
257277
if (IsMono || IsWasm)
258278
return ""; // There is no helpful information about JIT on Mono
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<Project Sdk="Microsoft.NET.Sdk" DefaultTarget="Publish">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<OutputPath>bin</OutputPath>
5+
<TargetFramework>$TFM$</TargetFramework>
6+
<MicrosoftNetCoreAppRuntimePackDir>$RUNTIMEPACK$</MicrosoftNetCoreAppRuntimePackDir>
7+
<RuntimeIdentifier>$RUNTIMEIDENTIFIER$</RuntimeIdentifier>
8+
<EnableTargetingPackDownload>false</EnableTargetingPackDownload>
9+
<PublishTrimmed>false</PublishTrimmed>
10+
<AssemblyName>$PROGRAMNAME$</AssemblyName>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Microsoft.NET.Runtime.MonoAOTCompiler.Task" version="6.0.0-*" GeneratePathProperty="true" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<Compile Include="$CODEFILENAME$" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<ProjectReference Include="$CSPROJPATH$" />
23+
</ItemGroup>
24+
25+
<!-- Redirect 'dotnet publish' to in-tree runtime pack -->
26+
<Target Name="TrickRuntimePackLocation" AfterTargets="ProcessFrameworkReferences">
27+
<ItemGroup>
28+
<RuntimePack>
29+
<PackageDirectory>$(MicrosoftNetCoreAppRuntimePackDir)</PackageDirectory>
30+
</RuntimePack>
31+
</ItemGroup>
32+
<Message Text="Packaged ID: %(RuntimePack.PackageDirectory)" Importance="high" />
33+
</Target>
34+
35+
<UsingTask TaskName="MonoAOTCompiler" AssemblyFile="$(PkgMicrosoft_NET_Runtime_MonoAOTCompiler_Task)/MonoAOTCompiler.dll" />
36+
37+
<Target Name="AotApp" AfterTargets="Build" DependsOnTargets="Publish">
38+
39+
<PropertyGroup>
40+
<PublishDirFullPath>$([System.IO.Path]::GetFullPath($(PublishDir)))</PublishDirFullPath>
41+
</PropertyGroup>
42+
43+
<ItemGroup>
44+
<AotInputAssemblies Include="$(PublishDirFullPath)\*.dll">
45+
<AotArguments>@(MonoAOTCompilerDefaultAotArguments, ';')</AotArguments>
46+
<ProcessArguments>@(MonoAOTCompilerDefaultProcessArguments, ';')</ProcessArguments>
47+
</AotInputAssemblies>
48+
</ItemGroup>
49+
50+
<MonoAOTCompiler
51+
CompilerBinaryPath="$COMPILERBINARYPATH$"
52+
Mode="Normal"
53+
OutputType="Normal"
54+
Assemblies="@(AotInputAssemblies)"
55+
UseLLVM="$USELLVM$"
56+
LLVMPath="$(MicrosoftNetCoreAppRuntimePackDir)\runtimes\$(RuntimeIdentifier)\native"
57+
OutputDir="$(PublishDir)"
58+
UseAotDataFile="false">
59+
<Output TaskParameter="CompiledAssemblies" ItemName="BundleAssemblies" />
60+
</MonoAOTCompiler>
61+
62+
<Message Text="CompiledAssemblies: $(BundleAssemblies)" Importance="high"/>
63+
64+
</Target>
65+
66+
</Project>

src/BenchmarkDotNet/Toolchains/DotNetCli/CustomDotNetCliToolchainBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public CustomDotNetCliToolchainBuilder Timeout(TimeSpan timeout)
133133
return this;
134134
}
135135

136-
protected static string GetPortableRuntimeIdentifier()
136+
internal static string GetPortableRuntimeIdentifier()
137137
{
138138
// Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier()
139139
// returns win10-x64, we want the simpler form win-x64

src/BenchmarkDotNet/Toolchains/DotNetCli/NetCoreAppSettings.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using BenchmarkDotNet.Toolchains.MonoAotLLVM;
23
using JetBrains.Annotations;
34

5+
46
namespace BenchmarkDotNet.Toolchains.DotNetCli
57
{
68
/// <summary>
@@ -36,6 +38,9 @@ public class NetCoreAppSettings
3638
/// </param>
3739
/// <param name="packagesPath">the directory to restore packages to</param>
3840
/// <param name="timeout">timeout to build the benchmark</param>
41+
/// <param name="customRuntimePack">path to a custom runtime pack</param>
42+
/// <param name="aotCompilerPath">path to Mono AOT compiler</param>
43+
/// <param name="aotCompilerMode">Mono AOT compiler moder</param>
3944
/// </summary>
4045
[PublicAPI]
4146
public NetCoreAppSettings(
@@ -45,7 +50,9 @@ public NetCoreAppSettings(
4550
string customDotNetCliPath = null,
4651
string packagesPath = null,
4752
TimeSpan? timeout = null,
48-
string customRuntimePack = null
53+
string customRuntimePack = null,
54+
string aotCompilerPath = null,
55+
MonoAotCompilerMode aotCompilerMode = MonoAotCompilerMode.mini
4956
)
5057
{
5158
TargetFrameworkMoniker = targetFrameworkMoniker;
@@ -56,6 +63,8 @@ public NetCoreAppSettings(
5663
PackagesPath = packagesPath;
5764
Timeout = timeout ?? DefaultBuildTimeout;
5865
CustomRuntimePack = customRuntimePack;
66+
AOTCompilerPath = aotCompilerPath;
67+
AOTCompilerMode = aotCompilerMode;
5968
}
6069

6170
/// <summary>
@@ -87,6 +96,16 @@ public NetCoreAppSettings(
8796
/// </summary>
8897
public string CustomRuntimePack { get; }
8998

99+
/// <summary>
100+
/// Path to the Mono AOT Compiler
101+
/// </summary>
102+
public string AOTCompilerPath { get; }
103+
104+
/// <summary>
105+
/// Mono AOT Compiler mode, either 'mini' or 'llvm'
106+
/// </summary>
107+
public MonoAotCompilerMode AOTCompilerMode { get; }
108+
90109
public NetCoreAppSettings WithCustomDotNetCliPath(string customDotNetCliPath, string displayName = null)
91110
=> new NetCoreAppSettings(TargetFrameworkMoniker, RuntimeFrameworkVersion, displayName ?? Name, customDotNetCliPath, PackagesPath, Timeout);
92111

@@ -95,6 +114,5 @@ public NetCoreAppSettings WithCustomPackagesRestorePath(string packagesPath, str
95114

96115
public NetCoreAppSettings WithTimeout(TimeSpan? timeOut)
97116
=> new NetCoreAppSettings(TargetFrameworkMoniker, RuntimeFrameworkVersion, Name, CustomDotNetCliPath, PackagesPath, timeOut ?? Timeout);
98-
99117
}
100118
}

src/BenchmarkDotNet/Toolchains/Executor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ private ProcessStartInfo CreateStartInfo(BenchmarkCase benchmarkCase, ArtifactsP
120120
start.Arguments = $"{wasm.JavaScriptEngineArguments} runtime.js -- --run {artifactsPaths.ProgramName}.dll {args} ";
121121
start.WorkingDirectory = artifactsPaths.BinariesDirectoryPath;
122122
break;
123+
case MonoAotLLVMRuntime _:
124+
start.FileName = exePath;
125+
start.Arguments = args;
126+
start.WorkingDirectory = artifactsPaths.BinariesDirectoryPath;
127+
break;
123128
default:
124129
throw new NotSupportedException("Runtime = " + runtime);
125130
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace BenchmarkDotNet.Toolchains.MonoAotLLVM
2+
{
3+
public enum MonoAotCompilerMode
4+
{
5+
mini = 0, // default
6+
llvm
7+
}
8+
}

0 commit comments

Comments
 (0)