diff --git a/src/BenchmarkDotNet.Annotations/Jobs/RuntimeMoniker.cs b/src/BenchmarkDotNet.Annotations/Jobs/RuntimeMoniker.cs index d74ac3cde9..7b6d6830db 100644 --- a/src/BenchmarkDotNet.Annotations/Jobs/RuntimeMoniker.cs +++ b/src/BenchmarkDotNet.Annotations/Jobs/RuntimeMoniker.cs @@ -143,6 +143,16 @@ public enum RuntimeMoniker /// /// Mono with the Ahead of Time LLVM Compiler backend and .net7.0 /// - MonoAOTLLVMNet70 + MonoAOTLLVMNet70, + + /// + /// .NET 6 using MonoVM (not CLR which is the default) + /// + Mono60, + + /// + /// .NET 7 using MonoVM (not CLR which is the default) + /// + Mono70, } } diff --git a/src/BenchmarkDotNet/Attributes/Jobs/MonoJobAttribute.cs b/src/BenchmarkDotNet/Attributes/Jobs/MonoJobAttribute.cs index b102033693..f47e5b77eb 100644 --- a/src/BenchmarkDotNet/Attributes/Jobs/MonoJobAttribute.cs +++ b/src/BenchmarkDotNet/Attributes/Jobs/MonoJobAttribute.cs @@ -1,5 +1,6 @@ using System; using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Jobs; namespace BenchmarkDotNet.Attributes @@ -11,6 +12,10 @@ public MonoJobAttribute(bool baseline = false) : base(Job.Default.WithRuntime(Mo { } + public MonoJobAttribute(RuntimeMoniker runtimeMoniker, bool baseline = false) : base(Job.Default.WithRuntime(runtimeMoniker.GetRuntime()).WithBaseline(baseline)) + { + } + public MonoJobAttribute(string name, string path, bool baseline = false) : base(new Job(name, new EnvironmentMode(new MonoRuntime(name, path)).Freeze()).WithBaseline(baseline).Freeze()) { diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj index 4e28175a44..4c401072ee 100644 --- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj +++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj @@ -33,7 +33,7 @@ - + false diff --git a/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs index 691d8d9e75..93d1cb3c53 100644 --- a/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs +++ b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs @@ -29,6 +29,7 @@ using Perfolizer.Mathematics.OutlierDetection; using Perfolizer.Mathematics.SignificanceTesting; using Perfolizer.Mathematics.Thresholds; +using BenchmarkDotNet.Toolchains.Mono; namespace BenchmarkDotNet.ConsoleArguments { @@ -425,6 +426,12 @@ private static Job CreateJobForGivenRuntime(Job baseJob, string runtimeId, Comma case RuntimeMoniker.MonoAOTLLVMNet70: return MakeMonoAOTLLVMJob(baseJob, options, "net7.0"); + case RuntimeMoniker.Mono60: + return MakeMonoJob(baseJob, options, MonoRuntime.Mono60); + + case RuntimeMoniker.Mono70: + return MakeMonoJob(baseJob, options, MonoRuntime.Mono70); + default: throw new NotSupportedException($"Runtime {runtimeId} is not supported"); } @@ -452,6 +459,19 @@ private static Job CreateAotJob(Job baseJob, CommandLineOptions options, Runtime return baseJob.WithRuntime(runtime).WithToolchain(builder.ToToolchain()); } + private static Job MakeMonoJob(Job baseJob, CommandLineOptions options, MonoRuntime runtime) + { + return baseJob + .WithRuntime(runtime) + .WithToolchain(MonoToolchain.From( + new NetCoreAppSettings( + targetFrameworkMoniker: runtime.MsBuildMoniker, + runtimeFrameworkVersion: null, + name: runtime.Name, + customDotNetCliPath: options.CliPath?.FullName, + packagesPath: options.RestorePath?.FullName))); + } + private static Job MakeMonoAOTLLVMJob(Job baseJob, CommandLineOptions options, string msBuildMoniker) { var monoAotLLVMRuntime = new MonoAotLLVMRuntime(aotCompilerPath: options.AOTCompilerPath, aotCompilerMode: options.AOTCompilerMode, msBuildMoniker: msBuildMoniker); diff --git a/src/BenchmarkDotNet/Environments/Runtimes/MonoRuntime.cs b/src/BenchmarkDotNet/Environments/Runtimes/MonoRuntime.cs index ad045cc7b7..36ef6e4976 100644 --- a/src/BenchmarkDotNet/Environments/Runtimes/MonoRuntime.cs +++ b/src/BenchmarkDotNet/Environments/Runtimes/MonoRuntime.cs @@ -6,6 +6,8 @@ namespace BenchmarkDotNet.Environments public class MonoRuntime : Runtime, IEquatable { public static readonly MonoRuntime Default = new MonoRuntime("Mono"); + public static readonly MonoRuntime Mono60 = new MonoRuntime("Mono with .NET 6.0", RuntimeMoniker.Mono60, "net6.0", isDotNetBuiltIn: true); + public static readonly MonoRuntime Mono70 = new MonoRuntime("Mono with .NET 7.0", RuntimeMoniker.Mono70, "net7.0", isDotNetBuiltIn: true); public string CustomPath { get; } @@ -15,8 +17,15 @@ public class MonoRuntime : Runtime, IEquatable public string MonoBclPath { get; } + internal bool IsDotNetBuiltIn { get; } + private MonoRuntime(string name) : base(RuntimeMoniker.Mono, "mono", name) { } + private MonoRuntime(string name, RuntimeMoniker runtimeMoniker, string msBuildMoniker, bool isDotNetBuiltIn) : base(runtimeMoniker, msBuildMoniker, name) + { + IsDotNetBuiltIn = isDotNetBuiltIn; + } + public MonoRuntime(string name, string customPath) : this(name) => CustomPath = customPath; public MonoRuntime(string name, string customPath, string aotArgs, string monoBclPath) : this(name) diff --git a/src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs b/src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs index e88ae890d1..a8ba6c5632 100644 --- a/src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs +++ b/src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs @@ -49,6 +49,10 @@ internal static Runtime GetRuntime(this RuntimeMoniker runtimeMoniker) return NativeAotRuntime.Net60; case RuntimeMoniker.NativeAot70: return NativeAotRuntime.Net70; + case RuntimeMoniker.Mono60: + return MonoRuntime.Mono60; + case RuntimeMoniker.Mono70: + return MonoRuntime.Mono70; default: throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, "Runtime Moniker not supported"); } diff --git a/src/BenchmarkDotNet/Portability/RuntimeInformation.cs b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs index 1e0d88c67d..a630e2daf9 100644 --- a/src/BenchmarkDotNet/Portability/RuntimeInformation.cs +++ b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs @@ -25,7 +25,7 @@ internal static class RuntimeInformation internal const string ReleaseConfigurationName = "RELEASE"; internal const string Unknown = "?"; - 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! + 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! public static bool IsFullFramework => #if NET6_0_OR_GREATER @@ -184,6 +184,10 @@ internal static string GetRuntimeVersion() return "Mono " + version; } + else + { + return $"{GetNetCoreVersion()} using MonoVM"; + } } else if (IsFullFramework) { @@ -208,22 +212,7 @@ internal static string GetRuntimeVersion() } else if (IsNetCore) { - var coreclrAssemblyInfo = FileVersionInfo.GetVersionInfo(typeof(object).GetTypeInfo().Assembly.Location); - var corefxAssemblyInfo = FileVersionInfo.GetVersionInfo(typeof(Regex).GetTypeInfo().Assembly.Location); - - if (CoreRuntime.TryGetVersion(out var version) && version >= new Version(5, 0)) - { - // after the merge of dotnet/corefx and dotnet/coreclr into dotnet/runtime the version should always be the same - Debug.Assert(coreclrAssemblyInfo.FileVersion == corefxAssemblyInfo.FileVersion); - - return $".NET {version} ({coreclrAssemblyInfo.FileVersion})"; - } - else - { - string runtimeVersion = version != default ? version.ToString() : "?"; - - return $".NET Core {runtimeVersion} (CoreCLR {coreclrAssemblyInfo.FileVersion}, CoreFX {corefxAssemblyInfo.FileVersion})"; - } + return GetNetCoreVersion(); } else if (IsNativeAOT) { @@ -233,6 +222,26 @@ internal static string GetRuntimeVersion() return Unknown; } + private static string GetNetCoreVersion() + { + var coreclrAssemblyInfo = FileVersionInfo.GetVersionInfo(typeof(object).GetTypeInfo().Assembly.Location); + var corefxAssemblyInfo = FileVersionInfo.GetVersionInfo(typeof(Regex).GetTypeInfo().Assembly.Location); + + if (CoreRuntime.TryGetVersion(out var version) && version >= new Version(5, 0)) + { + // after the merge of dotnet/corefx and dotnet/coreclr into dotnet/runtime the version should always be the same + Debug.Assert(coreclrAssemblyInfo.FileVersion == corefxAssemblyInfo.FileVersion); + + return $".NET {version} ({coreclrAssemblyInfo.FileVersion})"; + } + else + { + string runtimeVersion = version != default ? version.ToString() : "?"; + + return $".NET Core {runtimeVersion} (CoreCLR {coreclrAssemblyInfo.FileVersion}, CoreFX {corefxAssemblyInfo.FileVersion})"; + } + } + internal static Runtime GetCurrentRuntime() { //do not change the order of conditions because it may cause incorrect determination of runtime diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjCoreToolchain.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjCoreToolchain.cs index 4b7c43feab..9dbaacc68c 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjCoreToolchain.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjCoreToolchain.cs @@ -24,7 +24,7 @@ public class CsProjCoreToolchain : Toolchain, IEquatable [PublicAPI] public static readonly IToolchain NetCoreApp60 = From(NetCoreAppSettings.NetCoreApp60); [PublicAPI] public static readonly IToolchain NetCoreApp70 = From(NetCoreAppSettings.NetCoreApp70); - private CsProjCoreToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, string customDotNetCliPath) + internal CsProjCoreToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, string customDotNetCliPath) : base(name, generator, builder, executor) { CustomDotNetCliPath = customDotNetCliPath; diff --git a/src/BenchmarkDotNet/Toolchains/Mono/MonoGenerator.cs b/src/BenchmarkDotNet/Toolchains/Mono/MonoGenerator.cs new file mode 100644 index 0000000000..f71a0f0733 --- /dev/null +++ b/src/BenchmarkDotNet/Toolchains/Mono/MonoGenerator.cs @@ -0,0 +1,19 @@ +using BenchmarkDotNet.Characteristics; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Toolchains.CsProj; + +namespace BenchmarkDotNet.Toolchains.Mono +{ + public class MonoGenerator : CsProjGenerator + { + public MonoGenerator(string targetFrameworkMoniker, string cliPath, string packagesPath, string runtimeFrameworkVersion) : base(targetFrameworkMoniker, cliPath, packagesPath, runtimeFrameworkVersion, true) + { + } + + protected override string GetRuntimeSettings(GcMode gcMode, IResolver resolver) + { + // Workaround for 'Found multiple publish output files with the same relative path' error + return base.GetRuntimeSettings(gcMode, resolver) + "false"; + } + } +} diff --git a/src/BenchmarkDotNet/Toolchains/Mono/MonoPublisher.cs b/src/BenchmarkDotNet/Toolchains/Mono/MonoPublisher.cs new file mode 100644 index 0000000000..ff98540cbf --- /dev/null +++ b/src/BenchmarkDotNet/Toolchains/Mono/MonoPublisher.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Toolchains.DotNetCli; +using BenchmarkDotNet.Toolchains.Results; + +namespace BenchmarkDotNet.Toolchains.Mono +{ + public class MonoPublisher : IBuilder + { + public MonoPublisher(string customDotNetCliPath) + { + CustomDotNetCliPath = customDotNetCliPath; + var runtimeIdentifier = CustomDotNetCliToolchainBuilder.GetPortableRuntimeIdentifier(); + + // /p:RuntimeIdentifiers is set explicitly here because --self-contained requires it, see https://github.com/dotnet/sdk/issues/10566 + ExtraArguments = $"--self-contained -r {runtimeIdentifier} /p:UseMonoRuntime=true /p:RuntimeIdentifiers={runtimeIdentifier}"; + } + + private string CustomDotNetCliPath { get; } + + private string ExtraArguments { get; } + + private IReadOnlyList EnvironmentVariables { get; } + + public BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger) + => new DotNetCliCommand( + CustomDotNetCliPath, + ExtraArguments, + generateResult, + logger, + buildPartition, + EnvironmentVariables, + buildPartition.Timeout) + .Publish().ToBuildResult(generateResult); + } +} diff --git a/src/BenchmarkDotNet/Toolchains/Mono/MonoToolchain.cs b/src/BenchmarkDotNet/Toolchains/Mono/MonoToolchain.cs new file mode 100644 index 0000000000..28ea147c97 --- /dev/null +++ b/src/BenchmarkDotNet/Toolchains/Mono/MonoToolchain.cs @@ -0,0 +1,35 @@ +using BenchmarkDotNet.Toolchains.CsProj; +using BenchmarkDotNet.Toolchains.DotNetCli; +using JetBrains.Annotations; +using System; + +namespace BenchmarkDotNet.Toolchains.Mono +{ + [PublicAPI] + public class MonoToolchain : CsProjCoreToolchain, IEquatable + { + [PublicAPI] public static readonly IToolchain Mono60 = From(new NetCoreAppSettings("net6.0", null, "mono60")); + [PublicAPI] public static readonly IToolchain Mono70 = From(new NetCoreAppSettings("net7.0", null, "mono70")); + + private MonoToolchain(string name, IGenerator generator, IBuilder builder, IExecutor executor, string customDotNetCliPath) + : base(name, generator, builder, executor, customDotNetCliPath) + { + } + + [PublicAPI] + public static new IToolchain From(NetCoreAppSettings settings) + { + return new MonoToolchain(settings.Name, + new MonoGenerator(settings.TargetFrameworkMoniker, settings.CustomDotNetCliPath, settings.PackagesPath, settings.RuntimeFrameworkVersion), + new MonoPublisher(settings.CustomDotNetCliPath), + new DotNetCliExecutor(settings.CustomDotNetCliPath), + settings.CustomDotNetCliPath); + } + + public override bool Equals(object obj) => obj is MonoToolchain typed && Equals(typed); + + public bool Equals(MonoToolchain other) => Generator.Equals(other.Generator); + + public override int GetHashCode() => Generator.GetHashCode(); + } +} diff --git a/src/BenchmarkDotNet/Toolchains/Toolchain.cs b/src/BenchmarkDotNet/Toolchains/Toolchain.cs index 4424f20291..8c5d1596a7 100644 --- a/src/BenchmarkDotNet/Toolchains/Toolchain.cs +++ b/src/BenchmarkDotNet/Toolchains/Toolchain.cs @@ -39,7 +39,7 @@ public virtual IEnumerable Validate(BenchmarkCase benchmarkCase benchmarkCase); } - if (runtime is MonoRuntime mono && !benchmarkCase.GetToolchain().IsInProcess) + if (runtime is MonoRuntime mono && !mono.IsDotNetBuiltIn && !benchmarkCase.GetToolchain().IsInProcess) { if (string.IsNullOrEmpty(mono.CustomPath) && !HostEnvironmentInfo.GetCurrent().IsMonoInstalled.Value) { diff --git a/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs b/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs index 51bb7c88bd..6741f7b86e 100644 --- a/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs +++ b/src/BenchmarkDotNet/Toolchains/ToolchainExtensions.cs @@ -48,6 +48,8 @@ internal static IToolchain GetToolchain(this Runtime runtime, Descriptor descrip return InProcessNoEmitToolchain.Instance; if (!string.IsNullOrEmpty(mono.AotArgs)) return MonoAotToolchain.Instance; + if (mono.IsDotNetBuiltIn) + return MonoToolchain.From(new NetCoreAppSettings(targetFrameworkMoniker: mono.MsBuildMoniker, runtimeFrameworkVersion: null, name: mono.Name)); return RoslynToolchain.Instance; @@ -129,6 +131,12 @@ private static IToolchain GetToolchain(RuntimeMoniker runtimeMoniker) case RuntimeMoniker.NativeAot70: return NativeAotToolchain.Net70; + case RuntimeMoniker.Mono60: + return MonoToolchain.Mono60; + + case RuntimeMoniker.Mono70: + return MonoToolchain.Mono70; + default: throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, "RuntimeMoniker not supported"); } diff --git a/tests/BenchmarkDotNet.IntegrationTests/MonoTests.cs b/tests/BenchmarkDotNet.IntegrationTests/MonoTests.cs new file mode 100644 index 0000000000..2dacf85173 --- /dev/null +++ b/tests/BenchmarkDotNet.IntegrationTests/MonoTests.cs @@ -0,0 +1,31 @@ +using System; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Tests.XUnit; + +namespace BenchmarkDotNet.IntegrationTests +{ + public class MonoTests : BenchmarkTestExecutor + { + [FactDotNetCoreOnly("UseMonoRuntime option is available in .NET Core only starting from .NET 6")] + public void Mono60IsSupported() + { + var config = ManualConfig.CreateEmpty().AddJob(Job.Dry.WithRuntime(MonoRuntime.Mono60)); + CanExecute(config); + } + + public class MonoBenchmark + { + [Benchmark] + public void Check() + { + if (Type.GetType("Mono.RuntimeStructs") == null) + { + throw new Exception("This is not Mono runtime"); + } + } + } + } +}