Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions .github/workflows/run-tests-selected.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
- windows-latest
- ubuntu-latest
- macos-latest
- windows-11-arm
- windows-11-arm
- ubuntu-24.04-arm
- macos-15-intel
project:
Expand Down Expand Up @@ -50,12 +50,34 @@ jobs:
timeout-minutes: 60 # Explicitly set timeout. When wrong input parameter is passed. It may continue to run until it times out (Default:360 minutes))
steps:
- uses: actions/checkout@v4

# Setup
- name: Setup
run: |
mkdir artifacts

- name: Install workloads
run: |
dotnet workload install wasm-tools
dotnet workload install wasm-tools-net8

- name: Set up node
uses: actions/setup-node@v6
with:
node-version: "24"
- name: Set up v8
shell: pwsh
run: |
npm install jsvu -g
jsvu --os=default --engines=v8

$homeDir = $env:HOME
if (-not $homeDir) {
$homeDir = $env:USERPROFILE
}

Add-Content -Path $env:GITHUB_PATH -Value (Join-Path $homeDir ".jsvu/bin")

# Build
- name: Run build
working-directory: ${{ github.event.inputs.project }}
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/run-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ jobs:
Add-MpPreference -ExclusionPath $env:GITHUB_WORKSPACE
Add-MpPreference -ExclusionPath $env:TEMP
- uses: actions/checkout@v6
# Setup wasm
- name: Set up node
uses: actions/setup-node@v6
with:
node-version: "24"
- name: Set up v8
run: |
npm install jsvu -g
jsvu --os=win64 --engines=v8
Add-Content -Path $env:GITHUB_PATH -Value "$env:USERPROFILE\.jsvu\bin"
- name: Install wasm-tools workload
run: ./build.cmd install-wasm-tools
# Build and Test
- name: Run task 'build'
shell: cmd
Expand Down Expand Up @@ -65,6 +77,18 @@ jobs:
Add-MpPreference -ExclusionPath $env:GITHUB_WORKSPACE
Add-MpPreference -ExclusionPath $env:TEMP
- uses: actions/checkout@v6
# Setup wasm
- name: Set up node
uses: actions/setup-node@v6
with:
node-version: "24"
- name: Set up v8
run: |
npm install jsvu -g
jsvu --os=win64 --engines=v8
Add-Content -Path $env:GITHUB_PATH -Value "$env:USERPROFILE\.jsvu\bin"
- name: Install wasm-tools workload
run: ./build.cmd install-wasm-tools
# Build and Test
- name: Run task 'build'
shell: cmd
Expand Down
6 changes: 3 additions & 3 deletions samples/BenchmarkDotNet.Samples/IntroWasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ public static void Run()
// the Wasm Toolchain requires two mandatory arguments:
const string cliPath = @"/home/adam/projects/runtime/dotnet.sh";

WasmRuntime runtime = new WasmRuntime(msBuildMoniker: "net5.0");
WasmRuntime runtime = new WasmRuntime(msBuildMoniker: "net8.0", RuntimeMoniker.WasmNet80, "Wasm .net8.0", false, "v8");
NetCoreAppSettings netCoreAppSettings = new NetCoreAppSettings(
targetFrameworkMoniker: "net5.0", runtimeFrameworkVersion: "", name: "Wasm",
targetFrameworkMoniker: "net8.0", runtimeFrameworkVersion: "", name: "Wasm",
customDotNetCliPath: cliPath);
IToolchain toolChain = WasmToolchain.From(netCoreAppSettings);

Expand All @@ -50,4 +50,4 @@ public void Foo()
// Benchmark body
}
}
}
}
12 changes: 6 additions & 6 deletions src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,15 @@ public bool UseDisassemblyDiagnoser
[Option("memoryRandomization", Required = false, HelpText = "Specifies whether Engine should allocate some random-sized memory between iterations. It makes [GlobalCleanup] and [GlobalSetup] methods to be executed after every iteration.")]
public bool MemoryRandomization { get; set; }

[Option("wasmEngine", Required = false, HelpText = "Full path to a java script engine used to run the benchmarks, used by Wasm toolchain.")]
public FileInfo? WasmJavascriptEngine { get; set; }
[Option("wasmEngine", Required = false, HelpText = "Specifies the executable (in PATH) or full path to a java script engine used to run the benchmarks, used by Wasm toolchain.", Default = "v8")]
public string? WasmJavaScriptEngine { get; set; } = "v8";

[Option("wasmArgs", Required = false, Default = "--expose_wasm", HelpText = "Arguments for the javascript engine used by Wasm toolchain.")]
[Option("wasmArgs", Required = false, HelpText = "Arguments for the javascript engine used by Wasm toolchain.")]
public string? WasmJavaScriptEngineArguments { get; set; }

[Option("wasmMainJsTemplate", Required = false, HelpText = "Path to main.js template.")]
public FileInfo? WasmMainJsTemplate { get; set; }

[Option("customRuntimePack", Required = false, HelpText = "Path to a custom runtime pack. Only used for wasm/MonoAotLLVM currently.")]
public string? CustomRuntimePack { get; set; }

Expand All @@ -216,9 +219,6 @@ public bool UseDisassemblyDiagnoser
[Option("AOTCompilerMode", Required = false, Default = MonoAotCompilerMode.mini, HelpText = "Mono AOT compiler mode, either 'mini' or 'llvm'")]
public MonoAotCompilerMode AOTCompilerMode { get; set; }

[Option("wasmDataDir", Required = false, HelpText = "Wasm data directory")]
public DirectoryInfo? WasmDataDirectory { get; set; }

[Option("wasmRuntimeFlavor", Required = false, Default = Environments.RuntimeFlavor.Mono, HelpText = "Runtime flavor for WASM benchmarks: 'Mono' (default) uses the Mono runtime pack, 'CoreCLR' uses the CoreCLR runtime pack.")]
public Environments.RuntimeFlavor WasmRuntimeFlavor { get; set; }

Expand Down
28 changes: 16 additions & 12 deletions src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,22 @@
using BenchmarkDotNet.Exporters.Xml;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Filters;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Toolchains.R2R;
using BenchmarkDotNet.Toolchains.CoreRun;
using BenchmarkDotNet.Toolchains.CsProj;
using BenchmarkDotNet.Toolchains.DotNetCli;
using BenchmarkDotNet.Toolchains.Mono;
using BenchmarkDotNet.Toolchains.MonoAotLLVM;
using BenchmarkDotNet.Toolchains.MonoWasm;
using BenchmarkDotNet.Toolchains.NativeAot;
using BenchmarkDotNet.Toolchains.R2R;
using CommandLine;
using Perfolizer.Horology;
using Perfolizer.Mathematics.OutlierDetection;
using BenchmarkDotNet.Toolchains.Mono;
using Perfolizer.Metrology;

namespace BenchmarkDotNet.ConsoleArguments
Expand Down Expand Up @@ -249,6 +250,14 @@ private static bool Validate(CommandLineOptions options, ILogger logger)
{
logger.WriteLineError($"The provided {nameof(options.AOTCompilerPath)} \"{options.AOTCompilerPath}\" does NOT exist. It MUST be provided.");
}
else if (runtimeMoniker >= RuntimeMoniker.WasmNet80 && runtimeMoniker < RuntimeMoniker.MonoAOTLLVM)
{
if (!ProcessHelper.TryResolveExecutableInPath(options.WasmJavaScriptEngine, out _))
{
logger.WriteLineError($"The provided {nameof(options.WasmJavaScriptEngine)} \"{options.WasmJavaScriptEngine}\" does NOT exist.");
return false;
}
}
}

foreach (string exporter in options.Exporters)
Expand Down Expand Up @@ -285,12 +294,6 @@ private static bool Validate(CommandLineOptions options, ILogger logger)
return false;
}

if (options.WasmJavascriptEngine.IsNotNullButDoesNotExist())
{
logger.WriteLineError($"The provided {nameof(options.WasmJavascriptEngine)} \"{options.WasmJavascriptEngine}\" does NOT exist.");
return false;
}

if (options.IlcPackages.IsNotNullButDoesNotExist())
{
logger.WriteLineError($"The provided {nameof(options.IlcPackages)} \"{options.IlcPackages}\" does NOT exist.");
Expand Down Expand Up @@ -701,12 +704,13 @@ private static Job MakeWasmJob(Job baseJob, CommandLineOptions options, string m

var wasmRuntime = new WasmRuntime(
msBuildMoniker: msBuildMoniker,
javaScriptEngine: options.WasmJavascriptEngine?.FullName ?? "v8",
javaScriptEngineArguments: options.WasmJavaScriptEngineArguments ?? "",
aot: wasmAot,
wasmDataDir: options.WasmDataDirectory?.FullName ?? "",
moniker: moniker,
displayName: "Wasm",
javaScriptEngine: options.WasmJavaScriptEngine ?? "",
javaScriptEngineArguments: options.WasmJavaScriptEngineArguments,
aot: wasmAot,
runtimeFlavor: options.WasmRuntimeFlavor,
mainJsTemplate: options.WasmMainJsTemplate,
processTimeoutMinutes: options.WasmProcessTimeoutMinutes);

var toolChain = WasmToolchain.From(new NetCoreAppSettings(
Expand Down
86 changes: 61 additions & 25 deletions src/BenchmarkDotNet/Environments/Runtimes/WasmRuntime.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
using System;
using System.ComponentModel;
using System.IO;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Toolchains;

namespace BenchmarkDotNet.Environments
{
public class WasmRuntime : Runtime, IEquatable<WasmRuntime>
{
public delegate string ArgumentFormatter(WasmRuntime runtime, ArtifactsPaths artifactsPaths, string args);

[EditorBrowsable(EditorBrowsableState.Never)]
internal static readonly WasmRuntime Default = new WasmRuntime();

public string JavaScriptEngine { get; }

public string JavaScriptEngineArguments { get; }

public bool Aot { get; }
public ArgumentFormatter JavaScriptEngineArgumentFormatter { get; }

public string WasmDataDir { get; }
public override bool IsAOT { get; }

/// <summary>
/// Specifies the runtime flavor used for WASM benchmarks. <see cref="Environments.RuntimeFlavor.Mono"/> (default) resolves the
Expand All @@ -31,48 +35,80 @@ public class WasmRuntime : Runtime, IEquatable<WasmRuntime>
/// </summary>
public int ProcessTimeoutMinutes { get; }

public FileInfo? MainJsTemplate { get; set; }

/// <summary>
/// creates new instance of WasmRuntime
/// </summary>
/// <param name="javaScriptEngine">Full path to a java script engine used to run the benchmarks. "v8" by default</param>
/// <param name="javaScriptEngineArguments">Arguments for the javascript engine. "--expose_wasm" by default</param>
/// <param name="msBuildMoniker">moniker, default: "net5.0"</param>
/// <param name="displayName">default: "Wasm"</param>
/// <param name="aot">Specifies whether AOT or Interpreter (default) project should be generated.</param>
/// <param name="wasmDataDir">Specifies a wasm data directory surfaced as $(WasmDataDir) for the project</param>
/// <param name="msBuildMoniker">moniker</param>
/// <param name="moniker">Runtime moniker</param>
/// <param name="displayName">display name</param>
/// <param name="aot">Specifies whether AOT or Interpreter project should be generated.</param>
/// <param name="javaScriptEngine">Full path to a java script engine used to run the benchmarks.</param>
/// <param name="javaScriptEngineArguments">Arguments for the javascript engine.</param>
/// <param name="runtimeFlavor">Runtime flavor to use: Mono (default) or CoreCLR.</param>
/// <param name="processTimeoutMinutes">Maximum time in minutes to wait for a single benchmark process to finish. Default is 10.</param>
/// <param name="mainJsTemplate">Optional custom template for the generated main.js file. If not provided, a default template will be used.</param>
/// <param name="javaScriptEngineArgumentFormatter">Allows to format or customize the arguments passed to the javascript engine.</param>
public WasmRuntime(
string msBuildMoniker = "net8.0",
string displayName = "Wasm",
string javaScriptEngine = "v8",
string javaScriptEngineArguments = "--expose_wasm",
bool aot = false,
string wasmDataDir = "",
RuntimeMoniker moniker = RuntimeMoniker.WasmNet80,
string msBuildMoniker,
RuntimeMoniker moniker,
string displayName,
bool aot,
string javaScriptEngine,
string? javaScriptEngineArguments = "",
RuntimeFlavor runtimeFlavor = RuntimeFlavor.Mono,
int processTimeoutMinutes = 10)
: base(moniker, msBuildMoniker, displayName)
int processTimeoutMinutes = 10,
FileInfo? mainJsTemplate = null,
ArgumentFormatter? javaScriptEngineArgumentFormatter = null) : base(moniker, msBuildMoniker, displayName)
{
if (javaScriptEngine.IsNotBlank() && javaScriptEngine != "v8" && !File.Exists(javaScriptEngine))
throw new FileNotFoundException($"Provided {nameof(javaScriptEngine)} file: \"{javaScriptEngine}\" doest NOT exist");
// Resolve path for windows because we can't use ProcessStartInfo.UseShellExecute while redirecting std out in the executor.
if (!ProcessHelper.TryResolveExecutableInPath(javaScriptEngine, out javaScriptEngine!))
throw new FileNotFoundException($"Provided {nameof(javaScriptEngine)} file: \"{javaScriptEngine}\" does NOT exist");

JavaScriptEngine = javaScriptEngine;
JavaScriptEngineArguments = javaScriptEngineArguments;
Aot = aot;
WasmDataDir = wasmDataDir;
JavaScriptEngineArguments = javaScriptEngineArguments ?? "";
JavaScriptEngineArgumentFormatter = javaScriptEngineArgumentFormatter ?? DefaultArgumentFormatter;
RuntimeFlavor = runtimeFlavor;
IsAOT = aot;
ProcessTimeoutMinutes = processTimeoutMinutes;
MainJsTemplate = mainJsTemplate;
}

private WasmRuntime() : base(RuntimeMoniker.WasmNet80, "Wasm", "Wasm")
{
IsAOT = RuntimeInformation.IsAot;
JavaScriptEngine = "";
JavaScriptEngineArguments = "";
ProcessTimeoutMinutes = 10;
JavaScriptEngineArgumentFormatter = DefaultArgumentFormatter;
}

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.RuntimeFlavor == RuntimeFlavor;
{
return other != null
&& base.Equals(other)
&& other.JavaScriptEngine == JavaScriptEngine
&& other.JavaScriptEngineArguments == JavaScriptEngineArguments
&& other.JavaScriptEngineArgumentFormatter == JavaScriptEngineArgumentFormatter
&& other.IsAOT == IsAOT
&& other.ProcessTimeoutMinutes == ProcessTimeoutMinutes
&& other.RuntimeFlavor == RuntimeFlavor;
}

public override int GetHashCode()
=> HashCode.Combine(base.GetHashCode(), JavaScriptEngine, JavaScriptEngineArguments, Aot, RuntimeFlavor);
=> HashCode.Combine(base.GetHashCode(), JavaScriptEngine, JavaScriptEngineArguments, JavaScriptEngineArgumentFormatter, IsAOT, RuntimeFlavor, ProcessTimeoutMinutes);

private static string DefaultArgumentFormatter(WasmRuntime runtime, ArtifactsPaths artifactsPaths, string args)
{
return Path.GetFileNameWithoutExtension(runtime.JavaScriptEngine).ToLower() switch
{
"node" or "bun" => $"{runtime.JavaScriptEngineArguments} {artifactsPaths.ExecutablePath} -- --run {artifactsPaths.ProgramName}.dll {args}",
_ => $"{runtime.JavaScriptEngineArguments} --module {artifactsPaths.ExecutablePath} -- --run {artifactsPaths.ProgramName}.dll {args}",
};
}
}
}
Loading