Skip to content

Commit 64c3a3c

Browse files
Serg046adamsitnik
andauthored
Implement MonoVM toolchain for net6.0 and net7.0 monikers (#2142) fixes #2064
Co-authored-by: Adam Sitnik <[email protected]>
1 parent 7d83758 commit 64c3a3c

File tree

14 files changed

+209
-21
lines changed

14 files changed

+209
-21
lines changed

src/BenchmarkDotNet.Annotations/Jobs/RuntimeMoniker.cs

+11-1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ public enum RuntimeMoniker
143143
/// <summary>
144144
/// Mono with the Ahead of Time LLVM Compiler backend and .net7.0
145145
/// </summary>
146-
MonoAOTLLVMNet70
146+
MonoAOTLLVMNet70,
147+
148+
/// <summary>
149+
/// .NET 6 using MonoVM (not CLR which is the default)
150+
/// </summary>
151+
Mono60,
152+
153+
/// <summary>
154+
/// .NET 7 using MonoVM (not CLR which is the default)
155+
/// </summary>
156+
Mono70,
147157
}
148158
}

src/BenchmarkDotNet/Attributes/Jobs/MonoJobAttribute.cs

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using BenchmarkDotNet.Environments;
3+
using BenchmarkDotNet.Extensions;
34
using BenchmarkDotNet.Jobs;
45

56
namespace BenchmarkDotNet.Attributes
@@ -11,6 +12,10 @@ public MonoJobAttribute(bool baseline = false) : base(Job.Default.WithRuntime(Mo
1112
{
1213
}
1314

15+
public MonoJobAttribute(RuntimeMoniker runtimeMoniker, bool baseline = false) : base(Job.Default.WithRuntime(runtimeMoniker.GetRuntime()).WithBaseline(baseline))
16+
{
17+
}
18+
1419
public MonoJobAttribute(string name, string path, bool baseline = false)
1520
: base(new Job(name, new EnvironmentMode(new MonoRuntime(name, path)).Freeze()).WithBaseline(baseline).Freeze())
1621
{

src/BenchmarkDotNet/BenchmarkDotNet.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
3434
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
3535
</ItemGroup>
36-
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
36+
<ItemGroup Condition="'$(OS)' == 'Windows_NT' AND '$(UseMonoRuntime)' != 'true' ">
3737
<ProjectReference Include="..\BenchmarkDotNet.Disassembler.x64\BenchmarkDotNet.Disassembler.x64.csproj">
3838
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
3939
</ProjectReference>

src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs

+20
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
using Perfolizer.Mathematics.OutlierDetection;
3030
using Perfolizer.Mathematics.SignificanceTesting;
3131
using Perfolizer.Mathematics.Thresholds;
32+
using BenchmarkDotNet.Toolchains.Mono;
3233

3334
namespace BenchmarkDotNet.ConsoleArguments
3435
{
@@ -425,6 +426,12 @@ private static Job CreateJobForGivenRuntime(Job baseJob, string runtimeId, Comma
425426
case RuntimeMoniker.MonoAOTLLVMNet70:
426427
return MakeMonoAOTLLVMJob(baseJob, options, "net7.0");
427428

429+
case RuntimeMoniker.Mono60:
430+
return MakeMonoJob(baseJob, options, MonoRuntime.Mono60);
431+
432+
case RuntimeMoniker.Mono70:
433+
return MakeMonoJob(baseJob, options, MonoRuntime.Mono70);
434+
428435
default:
429436
throw new NotSupportedException($"Runtime {runtimeId} is not supported");
430437
}
@@ -452,6 +459,19 @@ private static Job CreateAotJob(Job baseJob, CommandLineOptions options, Runtime
452459
return baseJob.WithRuntime(runtime).WithToolchain(builder.ToToolchain());
453460
}
454461

462+
private static Job MakeMonoJob(Job baseJob, CommandLineOptions options, MonoRuntime runtime)
463+
{
464+
return baseJob
465+
.WithRuntime(runtime)
466+
.WithToolchain(MonoToolchain.From(
467+
new NetCoreAppSettings(
468+
targetFrameworkMoniker: runtime.MsBuildMoniker,
469+
runtimeFrameworkVersion: null,
470+
name: runtime.Name,
471+
customDotNetCliPath: options.CliPath?.FullName,
472+
packagesPath: options.RestorePath?.FullName)));
473+
}
474+
455475
private static Job MakeMonoAOTLLVMJob(Job baseJob, CommandLineOptions options, string msBuildMoniker)
456476
{
457477
var monoAotLLVMRuntime = new MonoAotLLVMRuntime(aotCompilerPath: options.AOTCompilerPath, aotCompilerMode: options.AOTCompilerMode, msBuildMoniker: msBuildMoniker);

src/BenchmarkDotNet/Environments/Runtimes/MonoRuntime.cs

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace BenchmarkDotNet.Environments
66
public class MonoRuntime : Runtime, IEquatable<MonoRuntime>
77
{
88
public static readonly MonoRuntime Default = new MonoRuntime("Mono");
9+
public static readonly MonoRuntime Mono60 = new MonoRuntime("Mono with .NET 6.0", RuntimeMoniker.Mono60, "net6.0", isDotNetBuiltIn: true);
10+
public static readonly MonoRuntime Mono70 = new MonoRuntime("Mono with .NET 7.0", RuntimeMoniker.Mono70, "net7.0", isDotNetBuiltIn: true);
911

1012
public string CustomPath { get; }
1113

@@ -15,8 +17,15 @@ public class MonoRuntime : Runtime, IEquatable<MonoRuntime>
1517

1618
public string MonoBclPath { get; }
1719

20+
internal bool IsDotNetBuiltIn { get; }
21+
1822
private MonoRuntime(string name) : base(RuntimeMoniker.Mono, "mono", name) { }
1923

24+
private MonoRuntime(string name, RuntimeMoniker runtimeMoniker, string msBuildMoniker, bool isDotNetBuiltIn) : base(runtimeMoniker, msBuildMoniker, name)
25+
{
26+
IsDotNetBuiltIn = isDotNetBuiltIn;
27+
}
28+
2029
public MonoRuntime(string name, string customPath) : this(name) => CustomPath = customPath;
2130

2231
public MonoRuntime(string name, string customPath, string aotArgs, string monoBclPath) : this(name)

src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ internal static Runtime GetRuntime(this RuntimeMoniker runtimeMoniker)
4949
return NativeAotRuntime.Net60;
5050
case RuntimeMoniker.NativeAot70:
5151
return NativeAotRuntime.Net70;
52+
case RuntimeMoniker.Mono60:
53+
return MonoRuntime.Mono60;
54+
case RuntimeMoniker.Mono70:
55+
return MonoRuntime.Mono70;
5256
default:
5357
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, "Runtime Moniker not supported");
5458
}

src/BenchmarkDotNet/Portability/RuntimeInformation.cs

+26-17
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ internal static class RuntimeInformation
2525
internal const string ReleaseConfigurationName = "RELEASE";
2626
internal const string Unknown = "?";
2727

28-
public static bool IsMono { get; } = Type.GetType("Mono.Runtime") != null; // it allocates a lot of memory, we need to check it once in order to keep Engine non-allocating!
28+
public static bool IsMono { get; } = Type.GetType("Mono.RuntimeStructs") != null; // it allocates a lot of memory, we need to check it once in order to keep Engine non-allocating!
2929

3030
public static bool IsFullFramework =>
3131
#if NET6_0_OR_GREATER
@@ -184,6 +184,10 @@ internal static string GetRuntimeVersion()
184184

185185
return "Mono " + version;
186186
}
187+
else
188+
{
189+
return $"{GetNetCoreVersion()} using MonoVM";
190+
}
187191
}
188192
else if (IsFullFramework)
189193
{
@@ -208,22 +212,7 @@ internal static string GetRuntimeVersion()
208212
}
209213
else if (IsNetCore)
210214
{
211-
var coreclrAssemblyInfo = FileVersionInfo.GetVersionInfo(typeof(object).GetTypeInfo().Assembly.Location);
212-
var corefxAssemblyInfo = FileVersionInfo.GetVersionInfo(typeof(Regex).GetTypeInfo().Assembly.Location);
213-
214-
if (CoreRuntime.TryGetVersion(out var version) && version >= new Version(5, 0))
215-
{
216-
// after the merge of dotnet/corefx and dotnet/coreclr into dotnet/runtime the version should always be the same
217-
Debug.Assert(coreclrAssemblyInfo.FileVersion == corefxAssemblyInfo.FileVersion);
218-
219-
return $".NET {version} ({coreclrAssemblyInfo.FileVersion})";
220-
}
221-
else
222-
{
223-
string runtimeVersion = version != default ? version.ToString() : "?";
224-
225-
return $".NET Core {runtimeVersion} (CoreCLR {coreclrAssemblyInfo.FileVersion}, CoreFX {corefxAssemblyInfo.FileVersion})";
226-
}
215+
return GetNetCoreVersion();
227216
}
228217
else if (IsNativeAOT)
229218
{
@@ -233,6 +222,26 @@ internal static string GetRuntimeVersion()
233222
return Unknown;
234223
}
235224

225+
private static string GetNetCoreVersion()
226+
{
227+
var coreclrAssemblyInfo = FileVersionInfo.GetVersionInfo(typeof(object).GetTypeInfo().Assembly.Location);
228+
var corefxAssemblyInfo = FileVersionInfo.GetVersionInfo(typeof(Regex).GetTypeInfo().Assembly.Location);
229+
230+
if (CoreRuntime.TryGetVersion(out var version) && version >= new Version(5, 0))
231+
{
232+
// after the merge of dotnet/corefx and dotnet/coreclr into dotnet/runtime the version should always be the same
233+
Debug.Assert(coreclrAssemblyInfo.FileVersion == corefxAssemblyInfo.FileVersion);
234+
235+
return $".NET {version} ({coreclrAssemblyInfo.FileVersion})";
236+
}
237+
else
238+
{
239+
string runtimeVersion = version != default ? version.ToString() : "?";
240+
241+
return $".NET Core {runtimeVersion} (CoreCLR {coreclrAssemblyInfo.FileVersion}, CoreFX {corefxAssemblyInfo.FileVersion})";
242+
}
243+
}
244+
236245
internal static Runtime GetCurrentRuntime()
237246
{
238247
//do not change the order of conditions because it may cause incorrect determination of runtime

src/BenchmarkDotNet/Toolchains/CsProj/CsProjCoreToolchain.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class CsProjCoreToolchain : Toolchain, IEquatable<CsProjCoreToolchain>
2424
[PublicAPI] public static readonly IToolchain NetCoreApp60 = From(NetCoreAppSettings.NetCoreApp60);
2525
[PublicAPI] public static readonly IToolchain NetCoreApp70 = From(NetCoreAppSettings.NetCoreApp70);
2626

27-
private CsProjCoreToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, string customDotNetCliPath)
27+
internal CsProjCoreToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, string customDotNetCliPath)
2828
: base(name, generator, builder, executor)
2929
{
3030
CustomDotNetCliPath = customDotNetCliPath;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using BenchmarkDotNet.Characteristics;
2+
using BenchmarkDotNet.Jobs;
3+
using BenchmarkDotNet.Toolchains.CsProj;
4+
5+
namespace BenchmarkDotNet.Toolchains.Mono
6+
{
7+
public class MonoGenerator : CsProjGenerator
8+
{
9+
public MonoGenerator(string targetFrameworkMoniker, string cliPath, string packagesPath, string runtimeFrameworkVersion) : base(targetFrameworkMoniker, cliPath, packagesPath, runtimeFrameworkVersion, true)
10+
{
11+
}
12+
13+
protected override string GetRuntimeSettings(GcMode gcMode, IResolver resolver)
14+
{
15+
// Workaround for 'Found multiple publish output files with the same relative path' error
16+
return base.GetRuntimeSettings(gcMode, resolver) + "<PropertyGroup><ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles></PropertyGroup>";
17+
}
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System.Collections.Generic;
2+
using BenchmarkDotNet.Jobs;
3+
using BenchmarkDotNet.Loggers;
4+
using BenchmarkDotNet.Running;
5+
using BenchmarkDotNet.Toolchains.DotNetCli;
6+
using BenchmarkDotNet.Toolchains.Results;
7+
8+
namespace BenchmarkDotNet.Toolchains.Mono
9+
{
10+
public class MonoPublisher : IBuilder
11+
{
12+
public MonoPublisher(string customDotNetCliPath)
13+
{
14+
CustomDotNetCliPath = customDotNetCliPath;
15+
var runtimeIdentifier = CustomDotNetCliToolchainBuilder.GetPortableRuntimeIdentifier();
16+
17+
// /p:RuntimeIdentifiers is set explicitly here because --self-contained requires it, see https://github.com/dotnet/sdk/issues/10566
18+
ExtraArguments = $"--self-contained -r {runtimeIdentifier} /p:UseMonoRuntime=true /p:RuntimeIdentifiers={runtimeIdentifier}";
19+
}
20+
21+
private string CustomDotNetCliPath { get; }
22+
23+
private string ExtraArguments { get; }
24+
25+
private IReadOnlyList<EnvironmentVariable> EnvironmentVariables { get; }
26+
27+
public BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger)
28+
=> new DotNetCliCommand(
29+
CustomDotNetCliPath,
30+
ExtraArguments,
31+
generateResult,
32+
logger,
33+
buildPartition,
34+
EnvironmentVariables,
35+
buildPartition.Timeout)
36+
.Publish().ToBuildResult(generateResult);
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using BenchmarkDotNet.Toolchains.CsProj;
2+
using BenchmarkDotNet.Toolchains.DotNetCli;
3+
using JetBrains.Annotations;
4+
using System;
5+
6+
namespace BenchmarkDotNet.Toolchains.Mono
7+
{
8+
[PublicAPI]
9+
public class MonoToolchain : CsProjCoreToolchain, IEquatable<MonoToolchain>
10+
{
11+
[PublicAPI] public static readonly IToolchain Mono60 = From(new NetCoreAppSettings("net6.0", null, "mono60"));
12+
[PublicAPI] public static readonly IToolchain Mono70 = From(new NetCoreAppSettings("net7.0", null, "mono70"));
13+
14+
private MonoToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, string customDotNetCliPath)
15+
: base(name, generator, builder, executor, customDotNetCliPath)
16+
{
17+
}
18+
19+
[PublicAPI]
20+
public static new IToolchain From(NetCoreAppSettings settings)
21+
{
22+
return new MonoToolchain(settings.Name,
23+
new MonoGenerator(settings.TargetFrameworkMoniker, settings.CustomDotNetCliPath, settings.PackagesPath, settings.RuntimeFrameworkVersion),
24+
new MonoPublisher(settings.CustomDotNetCliPath),
25+
new DotNetCliExecutor(settings.CustomDotNetCliPath),
26+
settings.CustomDotNetCliPath);
27+
}
28+
29+
public override bool Equals(object obj) => obj is MonoToolchain typed && Equals(typed);
30+
31+
public bool Equals(MonoToolchain other) => Generator.Equals(other.Generator);
32+
33+
public override int GetHashCode() => Generator.GetHashCode();
34+
}
35+
}

src/BenchmarkDotNet/Toolchains/Toolchain.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public virtual IEnumerable<ValidationError> Validate(BenchmarkCase benchmarkCase
3939
benchmarkCase);
4040
}
4141

42-
if (runtime is MonoRuntime mono && !benchmarkCase.GetToolchain().IsInProcess)
42+
if (runtime is MonoRuntime mono && !mono.IsDotNetBuiltIn && !benchmarkCase.GetToolchain().IsInProcess)
4343
{
4444
if (string.IsNullOrEmpty(mono.CustomPath) && !HostEnvironmentInfo.GetCurrent().IsMonoInstalled.Value)
4545
{

src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs

+8
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ internal static IToolchain GetToolchain(this Runtime runtime, Descriptor descrip
4848
return InProcessNoEmitToolchain.Instance;
4949
if (!string.IsNullOrEmpty(mono.AotArgs))
5050
return MonoAotToolchain.Instance;
51+
if (mono.IsDotNetBuiltIn)
52+
return MonoToolchain.From(new NetCoreAppSettings(targetFrameworkMoniker: mono.MsBuildMoniker, runtimeFrameworkVersion: null, name: mono.Name));
5153

5254
return RoslynToolchain.Instance;
5355

@@ -129,6 +131,12 @@ private static IToolchain GetToolchain(RuntimeMoniker runtimeMoniker)
129131
case RuntimeMoniker.NativeAot70:
130132
return NativeAotToolchain.Net70;
131133

134+
case RuntimeMoniker.Mono60:
135+
return MonoToolchain.Mono60;
136+
137+
case RuntimeMoniker.Mono70:
138+
return MonoToolchain.Mono70;
139+
132140
default:
133141
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, "RuntimeMoniker not supported");
134142
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using BenchmarkDotNet.Attributes;
3+
using BenchmarkDotNet.Configs;
4+
using BenchmarkDotNet.Environments;
5+
using BenchmarkDotNet.Jobs;
6+
using BenchmarkDotNet.Tests.XUnit;
7+
8+
namespace BenchmarkDotNet.IntegrationTests
9+
{
10+
public class MonoTests : BenchmarkTestExecutor
11+
{
12+
[FactDotNetCoreOnly("UseMonoRuntime option is available in .NET Core only starting from .NET 6")]
13+
public void Mono60IsSupported()
14+
{
15+
var config = ManualConfig.CreateEmpty().AddJob(Job.Dry.WithRuntime(MonoRuntime.Mono60));
16+
CanExecute<MonoBenchmark>(config);
17+
}
18+
19+
public class MonoBenchmark
20+
{
21+
[Benchmark]
22+
public void Check()
23+
{
24+
if (Type.GetType("Mono.RuntimeStructs") == null)
25+
{
26+
throw new Exception("This is not Mono runtime");
27+
}
28+
}
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)