diff --git a/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs b/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs index 397443767a..5a965aefbf 100644 --- a/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs +++ b/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs @@ -221,6 +221,9 @@ public bool UseDisassemblyDiagnoser [Option("wasmDataDir", Required = false, HelpText = "Wasm data directory")] public DirectoryInfo? WasmDataDirectory { get; set; } + [Option("wasmCoreCLR", Required = false, Default = false, HelpText = "Use CoreCLR runtime pack (Microsoft.NETCore.App.Runtime.browser-wasm) instead of the Mono runtime pack for WASM benchmarks.")] + public bool WasmCoreCLR { get; set; } + [Option("noForcedGCs", Required = false, HelpText = "Specifying would not forcefully induce any GCs.")] public bool NoForcedGCs { get; set; } diff --git a/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs index 292b27cca8..20fa357c59 100644 --- a/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs +++ b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs @@ -735,7 +735,8 @@ private static Job MakeWasmJob(Job baseJob, CommandLineOptions options, string m javaScriptEngineArguments: options.WasmJavaScriptEngineArguments ?? "", aot: wasmAot, wasmDataDir: options.WasmDataDirectory?.FullName ?? "", - moniker: moniker); + moniker: moniker, + isMonoRuntime: !options.WasmCoreCLR); var toolChain = WasmToolchain.From(new NetCoreAppSettings( targetFrameworkMoniker: wasmRuntime.MsBuildMoniker, diff --git a/src/BenchmarkDotNet/Environments/Runtimes/WasmRuntime.cs b/src/BenchmarkDotNet/Environments/Runtimes/WasmRuntime.cs index 66247c49d9..be51bebff6 100644 --- a/src/BenchmarkDotNet/Environments/Runtimes/WasmRuntime.cs +++ b/src/BenchmarkDotNet/Environments/Runtimes/WasmRuntime.cs @@ -21,6 +21,13 @@ public class WasmRuntime : Runtime, IEquatable public string WasmDataDir { get; } + /// + /// When true (default), the generated project uses Microsoft.NET.Sdk.WebAssembly which sets UseMonoRuntime=true + /// and resolves the Mono runtime pack (Microsoft.NETCore.App.Runtime.Mono.browser-wasm). When false, the generated + /// project uses Microsoft.NET.Sdk which resolves the CoreCLR runtime pack (Microsoft.NETCore.App.Runtime.browser-wasm). + /// + public bool IsMonoRuntime { get; } + /// /// creates new instance of WasmRuntime /// @@ -31,7 +38,8 @@ public class WasmRuntime : Runtime, IEquatable /// Specifies whether AOT or Interpreter (default) project should be generated. /// Specifies a wasm data directory surfaced as $(WasmDataDir) for the project /// Runtime moniker - public WasmRuntime(string msBuildMoniker = "net5.0", string displayName = "Wasm", string javaScriptEngine = "v8", string javaScriptEngineArguments = "--expose_wasm", bool aot = false, string wasmDataDir = "", RuntimeMoniker moniker = RuntimeMoniker.Wasm) : base(moniker, msBuildMoniker, displayName) + /// When true (default), use Mono runtime pack; when false, use CoreCLR runtime pack. + public WasmRuntime(string msBuildMoniker = "net5.0", string displayName = "Wasm", string javaScriptEngine = "v8", string javaScriptEngineArguments = "--expose_wasm", bool aot = false, string wasmDataDir = "", RuntimeMoniker moniker = RuntimeMoniker.Wasm, bool isMonoRuntime = true) : base(moniker, msBuildMoniker, displayName) { if (javaScriptEngine.IsNotBlank() && javaScriptEngine != "v8" && !File.Exists(javaScriptEngine)) throw new FileNotFoundException($"Provided {nameof(javaScriptEngine)} file: \"{javaScriptEngine}\" doest NOT exist"); @@ -40,15 +48,16 @@ public WasmRuntime(string msBuildMoniker = "net5.0", string displayName = "Wasm" JavaScriptEngineArguments = javaScriptEngineArguments; Aot = aot; WasmDataDir = wasmDataDir; + IsMonoRuntime = isMonoRuntime; } public override bool Equals(object? obj) => obj is WasmRuntime other && Equals(other); public bool Equals(WasmRuntime? other) - => other != null && base.Equals(other) && other.JavaScriptEngine == JavaScriptEngine && other.JavaScriptEngineArguments == JavaScriptEngineArguments && other.Aot == Aot; + => other != null && base.Equals(other) && other.JavaScriptEngine == JavaScriptEngine && other.JavaScriptEngineArguments == JavaScriptEngineArguments && other.Aot == Aot && other.IsMonoRuntime == IsMonoRuntime; public override int GetHashCode() - => HashCode.Combine(base.GetHashCode(), JavaScriptEngine, JavaScriptEngineArguments, Aot); + => HashCode.Combine(base.GetHashCode(), JavaScriptEngine, JavaScriptEngineArguments, Aot, IsMonoRuntime); } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Templates/WasmCsProj.txt b/src/BenchmarkDotNet/Templates/WasmCsProj.txt index 3af58fc514..bf35251428 100644 --- a/src/BenchmarkDotNet/Templates/WasmCsProj.txt +++ b/src/BenchmarkDotNet/Templates/WasmCsProj.txt @@ -21,7 +21,6 @@ true $RUN_AOT$ $(RunAOTCompilation) - true false false false @@ -32,8 +31,8 @@ - + diff --git a/tests/BenchmarkDotNet.IntegrationTests/AppBundle/test-main.js b/src/BenchmarkDotNet/Templates/benchmark-main.mjs similarity index 100% rename from tests/BenchmarkDotNet.IntegrationTests/AppBundle/test-main.js rename to src/BenchmarkDotNet/Templates/benchmark-main.mjs diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs index 9cb1a7b56e..bda01f9487 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs @@ -140,7 +140,6 @@ internal static string GetRestoreCommand(ArtifactsPaths artifactsPaths, BuildPar .AppendArgument($"\"{filePath}\"") // restore doesn't support -f argument. .AppendArgument(artifactsPaths.PackagesDirectoryName.IsBlank() ? string.Empty : $"--packages \"{artifactsPaths.PackagesDirectoryName}\"") - .AppendArgument(buildPartition?.Runtime is WasmRuntime ? "-r browser-wasm" : string.Empty) .AppendArgument(GetCustomMsBuildArguments(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver)) .AppendArgument(extraArguments) .AppendArgument(GetMandatoryMsBuildSettings(buildPartition.BuildConfiguration)) @@ -221,7 +220,9 @@ internal static StringBuilder MaybeAppendOutputPaths(this StringBuilder stringBu ? stringBuilder : stringBuilder // Use AltDirectorySeparatorChar so it's not interpreted as an escaped quote `\"`. - .AppendArgument($"/p:ArtifactsPath=\"{artifactsPaths.BuildArtifactsDirectoryPath}{Path.AltDirectorySeparatorChar}\"") + // Use a subdirectory for ArtifactsPath so that DefaultItemExcludes (which the SDK + // sets to $(ArtifactsPath)/**) doesn't cover project-level files like wwwroot/. + .AppendArgument($"/p:ArtifactsPath=\"{artifactsPaths.BuildArtifactsDirectoryPath}{Path.AltDirectorySeparatorChar}.artifacts{Path.AltDirectorySeparatorChar}\"") .AppendArgument($"/p:OutDir=\"{artifactsPaths.BinariesDirectoryPath}{Path.AltDirectorySeparatorChar}\"") // OutputPath is legacy, per-project version of OutDir. We set both just in case. https://github.com/dotnet/msbuild/issues/87 .AppendArgument($"/p:OutputPath=\"{artifactsPaths.BinariesDirectoryPath}{Path.AltDirectorySeparatorChar}\"") diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliPublisher.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliPublisher.cs index e46864b98a..35f375cbd1 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliPublisher.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliPublisher.cs @@ -6,7 +6,7 @@ namespace BenchmarkDotNet.Toolchains.DotNetCli; -public class DotNetCliPublisher(string tfm, string? customDotNetCliPath = null, string? extraArguments = null, IReadOnlyList? environmentVariables = null) : IBuilder +public class DotNetCliPublisher(string tfm, string? customDotNetCliPath = null, string? extraArguments = null, IReadOnlyList? environmentVariables = null, bool logOutput = false) : IBuilder { public string TargetFrameworkMoniker { get; } = tfm; public string CustomDotNetCliPath { get; } = customDotNetCliPath; @@ -21,6 +21,7 @@ public virtual BuildResult Build(GenerateResult generateResult, BuildPartition b logger, buildPartition, environmentVariables, - buildPartition.Timeout + buildPartition.Timeout, + logOutput: logOutput ).RestoreThenBuildThenPublish(); } diff --git a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmExecutor.cs b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmExecutor.cs index f58eb786a5..4534670f50 100644 --- a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmExecutor.cs +++ b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmExecutor.cs @@ -56,13 +56,13 @@ private static ExecuteResult Execute(BenchmarkCase benchmarkCase, BenchmarkId be private static Process CreateProcess(BenchmarkCase benchmarkCase, ArtifactsPaths artifactsPaths, string args, IResolver resolver) { WasmRuntime runtime = (WasmRuntime)benchmarkCase.GetRuntime(); - string mainJs = runtime.RuntimeMoniker < RuntimeMoniker.WasmNet70 ? "main.js" : "test-main.js"; + const string mainJs = "benchmark-main.mjs"; var start = new ProcessStartInfo { FileName = runtime.JavaScriptEngine, Arguments = $"{runtime.JavaScriptEngineArguments} {mainJs} -- --run {artifactsPaths.ProgramName}.dll {args} ", - WorkingDirectory = Path.Combine(artifactsPaths.BinariesDirectoryPath, "AppBundle"), + WorkingDirectory = Path.Combine(artifactsPaths.BinariesDirectoryPath, "wwwroot"), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardInput = false, // not supported by WASM! diff --git a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs index d17ad8c304..5619e9ce76 100644 --- a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs @@ -13,13 +13,12 @@ namespace BenchmarkDotNet.Toolchains.MonoWasm public class WasmGenerator : CsProjGenerator { private readonly string CustomRuntimePack; - private readonly string MainJS; + private const string MainJS = "benchmark-main.mjs"; public WasmGenerator(string targetFrameworkMoniker, string cliPath, string packagesPath, string customRuntimePack, bool aot) : base(targetFrameworkMoniker, cliPath, packagesPath, runtimeFrameworkVersion: null) { CustomRuntimePack = customRuntimePack; - MainJS = (targetFrameworkMoniker == "net5.0" || targetFrameworkMoniker == "net6.0") ? "main.js" : "test-main.js"; BenchmarkRunCallType = aot ? Code.CodeGenBenchmarkRunCallType.Direct : Code.CodeGenBenchmarkRunCallType.Reflection; } @@ -46,7 +45,9 @@ protected void GenerateProjectFile(BuildPartition buildPartition, ArtifactsPaths var xmlDoc = new XmlDocument(); xmlDoc.Load(projectFile.FullName); - var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile); + var (customProperties, _) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile); + // Microsoft.NET.Sdk.WebAssembly auto-defaults UseMonoRuntime=true. + string sdkName = runtime.IsMonoRuntime ? "Microsoft.NET.Sdk.WebAssembly" : "Microsoft.NET.Sdk"; string content = new StringBuilder(ResourceHelper.LoadTemplate("WasmCsProj.txt")) .Replace("$PLATFORM$", buildPartition.Platform.ToConfig()) @@ -63,10 +64,15 @@ protected void GenerateProjectFile(BuildPartition buildPartition, ArtifactsPaths File.WriteAllText(artifactsPaths.ProjectFilePath, content); + // Place benchmark-main.mjs in wwwroot/ next to the generated csproj. + string projectWwwroot = Path.Combine(Path.GetDirectoryName(artifactsPaths.ProjectFilePath)!, "wwwroot"); + Directory.CreateDirectory(projectWwwroot); + File.WriteAllText(Path.Combine(projectWwwroot, MainJS), ResourceHelper.LoadTemplate(MainJS)); + GatherReferences(buildPartition, artifactsPaths, logger); } - protected override string GetExecutablePath(string binariesDirectoryPath, string programName) => Path.Combine(binariesDirectoryPath, "AppBundle", MainJS); + protected override string GetExecutablePath(string binariesDirectoryPath, string programName) => Path.Combine(binariesDirectoryPath, "wwwroot", MainJS); protected override string GetBinariesDirectoryPath(string buildArtifactsDirectoryPath, string configuration) => Path.Combine(buildArtifactsDirectoryPath, "bin", configuration, TargetFrameworkMoniker, "browser-wasm"); diff --git a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmToolchain.cs b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmToolchain.cs index 71ad2ade2b..6a546714ce 100644 --- a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmToolchain.cs +++ b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmToolchain.cs @@ -48,7 +48,7 @@ public static IToolchain From(NetCoreAppSettings netCoreAppSettings) netCoreAppSettings.PackagesPath, netCoreAppSettings.CustomRuntimePack, netCoreAppSettings.AOTCompilerMode == MonoAotLLVM.MonoAotCompilerMode.wasm), - new DotNetCliBuilder(netCoreAppSettings.TargetFrameworkMoniker, + new DotNetCliPublisher(netCoreAppSettings.TargetFrameworkMoniker, netCoreAppSettings.CustomDotNetCliPath, // aot builds can be very slow logOutput: netCoreAppSettings.AOTCompilerMode == MonoAotLLVM.MonoAotCompilerMode.wasm), diff --git a/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj b/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj index 219bd88d02..09c750d424 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj +++ b/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj @@ -59,7 +59,6 @@ - diff --git a/tests/BenchmarkDotNet.IntegrationTests/WasmTests.cs b/tests/BenchmarkDotNet.IntegrationTests/WasmTests.cs index 2f4493c99d..690f5a5173 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/WasmTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/WasmTests.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Linq; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; @@ -33,12 +32,10 @@ private ManualConfig GetConfig(MonoAotCompilerMode aotCompilerMode) var dotnetVersion = "net8.0"; var logger = new OutputLogger(Output); var netCoreAppSettings = new NetCoreAppSettings(dotnetVersion, null, "Wasm", aotCompilerMode: aotCompilerMode); - var mainJsPath = Path.Combine(AppContext.BaseDirectory, "AppBundle", "test-main.js"); return ManualConfig.CreateEmpty() .AddLogger(logger) .AddJob(Job.Dry - .WithArguments([new MsBuildArgument($"/p:WasmMainJSPath={mainJsPath}")]) .WithRuntime(new WasmRuntime(dotnetVersion, moniker: RuntimeMoniker.WasmNet80, javaScriptEngineArguments: "--expose_wasm --module")) .WithToolchain(WasmToolchain.From(netCoreAppSettings))) .WithBuildTimeout(TimeSpan.FromSeconds(240))