Skip to content

Commit 0928895

Browse files
author
Nathan Ricci
authored
Add Wasm Tool Chain (#1483)
1 parent a4dd37c commit 0928895

25 files changed

+564
-8
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<configuration>
2+
<packageSources>
3+
<add key="dotnet5" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json" />
4+
</packageSources>
5+
</configuration>

src/BenchmarkDotNet.Annotations/Jobs/RuntimeMoniker.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ public enum RuntimeMoniker
105105
/// <summary>
106106
/// CoreRT compiled as netcoreapp5.0
107107
/// </summary>
108-
CoreRt50
108+
CoreRt50,
109+
110+
/// <summary>
111+
/// WebAssembly
112+
/// </summary>
113+
Wasm
109114
}
110-
}
115+
}

src/BenchmarkDotNet/BenchmarkDotNet.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@
4949
<Compile Include="..\BenchmarkDotNet.Disassembler.x64\SourceCodeProvider.cs" Link="Disassemblers\SourceCodeProvider.cs" />
5050
<Compile Include="..\BenchmarkDotNet.Disassembler.x64\ClrMdDisassembler.cs" Link="Disassemblers\ClrMdDisassembler.cs" />
5151
</ItemGroup>
52-
</Project>
52+
</Project>

src/BenchmarkDotNet/Code/CodeGenerator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ internal static string Generate(BuildPartition buildPartition)
7575
extraDefines.Add("#define CORERT");
7676
else if (buildPartition.IsNetFramework)
7777
extraDefines.Add("#define NETFRAMEWORK");
78+
else if (buildPartition.IsWasm)
79+
extraDefines.Add("#define WASM");
7880

7981
string benchmarkProgramContent = new SmartStringBuilder(ResourceHelper.LoadTemplate("BenchmarkProgram.txt"))
8082
.Replace("$ShadowCopyDefines$", useShadowCopy ? "#define SHADOWCOPY" : null).Replace("$ShadowCopyFolderPath$", shadowCopyFolderPath)

src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,15 @@ public class CommandLineOptions
164164
[Option("envVars", Required = false, HelpText = "Colon separated environment variables (key:value)")]
165165
public IEnumerable<string> EnvironmentVariables { get; set; }
166166

167+
[Option("wasmJavascriptEnginePath", Required = false, Default = "v8", HelpText = "Full path to a java script engine used to run the benchmarks, if using the WASM runtime.")]
168+
public string WasmJavascriptEnginePath { get; set; }
169+
170+
[Option("wasmMainJS", Required = false, HelpText = "Path to the main.js file used for wasm apps.")]
171+
public string WasmMainJS { get; set; }
172+
173+
[Option("wasmJavaScriptEngineArguments", Required = false, Default = "--expose_wasm", HelpText = "Arguments for the javascript engine used by wasm.")]
174+
public string WasmJavaScriptEngineArguments { get; set; }
175+
167176
internal bool UserProvidedFilters => Filters.Any() || AttributeNames.Any() || AllCategories.Any() || AnyCategories.Any();
168177

169178
[Usage(ApplicationAlias = "")]

src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
using BenchmarkDotNet.Loggers;
1818
using BenchmarkDotNet.Portability;
1919
using BenchmarkDotNet.Reports;
20+
using BenchmarkDotNet.Toolchains;
2021
using BenchmarkDotNet.Toolchains.CoreRt;
2122
using BenchmarkDotNet.Toolchains.CoreRun;
2223
using BenchmarkDotNet.Toolchains.CsProj;
2324
using BenchmarkDotNet.Toolchains.DotNetCli;
2425
using BenchmarkDotNet.Toolchains.InProcess.Emit;
26+
using BenchmarkDotNet.Toolchains.MonoWasm;
2527
using CommandLine;
2628
using Perfolizer.Horology;
2729
using Perfolizer.Mathematics.OutlierDetection;
@@ -356,6 +358,21 @@ private static Job CreateJobForGivenRuntime(Job baseJob, string runtimeId, Comma
356358
builder.TargetFrameworkMoniker(runtime.MsBuildMoniker);
357359

358360
return baseJob.WithRuntime(runtime).WithToolchain(builder.ToToolchain());
361+
case RuntimeMoniker.Wasm:
362+
var wasmRuntime = runtimeMoniker.GetRuntime();
363+
364+
WasmSettings wasmSettings = new WasmSettings(wasmMainJS: options.WasmMainJS,
365+
wasmJavaScriptEngine: options.WasmJavascriptEnginePath,
366+
wasmjavaScriptEngineArguments: options.WasmJavaScriptEngineArguments);
367+
368+
IToolchain toolChain = new WasmToolChain(name: "Wasm",
369+
targetFrameworkMoniker: wasmRuntime.MsBuildMoniker,
370+
cliPath: options.CliPath.FullName,
371+
packagesPath: options.RestorePath?.FullName,
372+
wasmSettings: wasmSettings,
373+
timeout: timeOut ?? NetCoreAppSettings.DefaultBuildTimeout);
374+
375+
return baseJob.WithRuntime(wasmRuntime).WithToolchain(toolChain);
359376
default:
360377
throw new NotSupportedException($"Runtime {runtimeId} is not supported");
361378
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.IO;
3+
using BenchmarkDotNet.Validators;
4+
using JetBrains.Annotations;
5+
6+
namespace BenchmarkDotNet.Engines
7+
{
8+
/* This class is used by wasm because wasm cannot read from the console.
9+
* This potentially breaks communication with Diagnosers. */
10+
public sealed class NoAcknowledgementConsoleHost : IHost
11+
{
12+
private readonly TextWriter outWriter;
13+
14+
public NoAcknowledgementConsoleHost([NotNull]TextWriter outWriter)
15+
{
16+
this.outWriter = outWriter ?? throw new ArgumentNullException(nameof(outWriter));
17+
}
18+
19+
public void Write(string message) => outWriter.Write(message);
20+
21+
public void WriteLine() => outWriter.WriteLine();
22+
23+
public void WriteLine(string message) => outWriter.WriteLine(message);
24+
25+
public void SendSignal(HostSignal hostSignal) => WriteLine(Engine.Signals.ToMessage(hostSignal));
26+
27+
public void SendError(string message) => outWriter.WriteLine($"{ValidationErrorReporter.ConsoleErrorPrefix} {message}");
28+
29+
public void ReportResults(RunResults runResults) => runResults.Print(outWriter);
30+
}
31+
}

src/BenchmarkDotNet/Environments/Platform.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ public enum Platform
2525
/// <summary>
2626
/// ARM64
2727
/// </summary>
28-
Arm64
28+
Arm64,
29+
30+
/// <summary>
31+
/// Wasm
32+
/// </summary>
33+
Wasm
2934
}
3035
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using BenchmarkDotNet.Jobs;
3+
4+
namespace BenchmarkDotNet.Environments
5+
{
6+
public class WasmRuntime : Runtime, IEquatable<WasmRuntime>
7+
{
8+
public static readonly WasmRuntime Default = new WasmRuntime("Wasm");
9+
10+
public WasmRuntime(string name) : base(RuntimeMoniker.Wasm, "net5.0", name)
11+
{
12+
}
13+
14+
public WasmRuntime(string name, string msBuildMoniker) : base(RuntimeMoniker.Wasm, msBuildMoniker, name)
15+
{
16+
}
17+
18+
public override bool Equals(object obj) => obj is WasmRuntime other && Equals(other);
19+
20+
public bool Equals(WasmRuntime other) => base.Equals(other);
21+
}
22+
}

src/BenchmarkDotNet/Extensions/RuntimeMonikerExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ internal static Runtime GetRuntime(this RuntimeMoniker runtimeMoniker)
4848
return CoreRtRuntime.CoreRt31;
4949
case RuntimeMoniker.CoreRt50:
5050
return CoreRtRuntime.CoreRt50;
51+
case RuntimeMoniker.Wasm:
52+
return WasmRuntime.Default;
5153
default:
5254
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, "Runtime Moniker not supported");
5355
}

src/BenchmarkDotNet/Helpers/ConsoleExitHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ private void Attach()
2626
try
2727
{
2828
Console.CancelKeyPress += CancelKeyPressHandlerCallback;
29-
}
29+
}
3030
catch (PlatformNotSupportedException)
3131
{
3232
// Thrown when running in Xamarin

src/BenchmarkDotNet/Portability/RuntimeInformation.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public static bool IsCoreRT
4242
=> ((Environment.Version.Major >= 5) || FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase))
4343
&& string.IsNullOrEmpty(typeof(object).Assembly.Location); // but it's merged to a single .exe and .Location returns null here ;)
4444

45+
/// <summary>
46+
/// "Is the target where we will run the benchmarks WASM?"
47+
/// </summary>
48+
public static bool IsWasm => IsOSPlatform(OSPlatform.Create("BROWSER"));
49+
4550
public static bool IsRunningInContainer => string.Equals(Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER"), "true");
4651

4752
internal static string ExecutableExtension => IsWindows() ? ".exe" : string.Empty;
@@ -144,6 +149,10 @@ internal static string GetRuntimeVersion()
144149
{
145150
return FrameworkVersionHelper.GetFrameworkDescription();
146151
}
152+
else if (IsWasm)
153+
{
154+
return "wasm";
155+
}
147156
else if (IsNetCore)
148157
{
149158
string runtimeVersion = CoreRuntime.TryGetVersion(out var version) ? version.ToString() : "?";
@@ -168,6 +177,8 @@ internal static Runtime GetCurrentRuntime()
168177
return MonoRuntime.Default;
169178
if (IsFullFramework)
170179
return ClrRuntime.GetCurrentVersion();
180+
if (IsWasm)
181+
return WasmRuntime.Default;
171182
if (IsNetCore)
172183
return CoreRuntime.GetCurrentVersion();
173184
if (IsCoreRT)
@@ -189,6 +200,8 @@ public static Platform GetCurrentPlatform()
189200
case Architecture.X86:
190201
return Platform.X86;
191202
default:
203+
if (IsWasm)
204+
return Platform.Wasm;
192205
throw new ArgumentOutOfRangeException();
193206
}
194207
}

src/BenchmarkDotNet/Running/BuildPartition.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using BenchmarkDotNet.Portability;
77
using BenchmarkDotNet.Toolchains.CoreRt;
88
using BenchmarkDotNet.Toolchains.CsProj;
9+
using BenchmarkDotNet.Toolchains.MonoWasm;
910
using BenchmarkDotNet.Toolchains.Roslyn;
1011
using JetBrains.Annotations;
1112

@@ -46,6 +47,9 @@ public BuildPartition(BenchmarkBuildInfo[] benchmarks, IResolver resolver)
4647
// given job can have CoreRT toolchain set, but Runtime == default ;)
4748
|| (RepresentativeBenchmarkCase.Job.Infrastructure.TryGetToolchain(out var toolchain) && toolchain is CoreRtToolchain);
4849

50+
public bool IsWasm => Runtime is WasmRuntime // given job can have Wasm toolchain set, but Runtime == default ;)
51+
|| (RepresentativeBenchmarkCase.Job.Infrastructure.TryGetToolchain(out var toolchain) && toolchain is WasmToolChain);
52+
4953
public bool IsNetFramework => Runtime is ClrRuntime
5054
|| (RepresentativeBenchmarkCase.Job.Infrastructure.TryGetToolchain(out var toolchain) && (toolchain is RoslynToolchain || toolchain is CsProjClassicNetToolchain));
5155

src/BenchmarkDotNet/Templates/BenchmarkProgram.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ namespace BenchmarkDotNet.Autogenerated
2424

2525
private static System.Int32 AfterAssemblyLoadingAttached(System.String[] args)
2626
{
27+
#if WASM
28+
BenchmarkDotNet.Engines.NoAcknowledgementConsoleHost host = new BenchmarkDotNet.Engines.NoAcknowledgementConsoleHost(System.Console.Out); // this variable name is used by CodeGenerator.GetCoreRtSwitch, do NOT change it
29+
#else
2730
BenchmarkDotNet.Engines.ConsoleHost host = new BenchmarkDotNet.Engines.ConsoleHost(System.Console.Out, System.Console.In); // this variable name is used by CodeGenerator.GetCoreRtSwitch, do NOT change it
31+
#endif
2832

2933
// the first thing to do is to let diagnosers hook in before anything happens
3034
// so all jit-related diagnosers can catch first jit compilation!
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="$SDKNAME$" DefaultTargets="Publish">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<OutputPath>bin</OutputPath>
5+
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
6+
<TargetFramework>$TFM$</TargetFramework>
7+
<AppDir>$(MSBuildThisFileDirectory)\bin\$TFM$\browser-wasm\publish</AppDir>
8+
<AssemblyName>$PROGRAMNAME$</AssemblyName>
9+
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
10+
$COPIEDSETTINGS$
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<Compile Include="$CODEFILENAME$" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<ProjectReference Include="$CSPROJPATH$" />
19+
</ItemGroup>
20+
</Project>

src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public DotNetCliBuilder(string targetFrameworkMoniker, string customDotNetCliPat
2424
Timeout = timeout ?? NetCoreAppSettings.DefaultBuildTimeout;
2525
}
2626

27-
public BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger)
27+
public virtual BuildResult Build(GenerateResult generateResult, BuildPartition buildPartition, ILogger logger)
2828
=> new DotNetCliCommand(
2929
CustomDotNetCliPath,
3030
string.Empty,

src/BenchmarkDotNet/Toolchains/DotNetCli/NetCoreAppSettings.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ public NetCoreAppSettings(
4343
string name,
4444
string customDotNetCliPath = null,
4545
string packagesPath = null,
46-
TimeSpan? timeout = null)
46+
TimeSpan? timeout = null
47+
)
4748
{
4849
TargetFrameworkMoniker = targetFrameworkMoniker;
4950
RuntimeFrameworkVersion = runtimeFrameworkVersion;
5051
Name = name;
52+
5153
CustomDotNetCliPath = customDotNetCliPath;
5254
PackagesPath = packagesPath;
5355
Timeout = timeout ?? DefaultBuildTimeout;

src/BenchmarkDotNet/Toolchains/GeneratorBase.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ [PublicAPI] protected virtual void GenerateAppConfig(BuildPartition buildPartiti
111111
[PublicAPI] protected virtual void GenerateCode(BuildPartition buildPartition, ArtifactsPaths artifactsPaths)
112112
=> File.WriteAllText(artifactsPaths.ProgramCodePath, CodeGenerator.Generate(buildPartition));
113113

114+
protected virtual string GetExecutablePath(string binariesDirectoryPath, string programName) => Path.Combine(binariesDirectoryPath, $"{programName}{GetExecutableExtension()}");
115+
114116
private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string rootArtifactsFolderPath)
115117
{
116118
// its not ".cs" in order to avoid VS from displaying and compiling it with xprojs/csprojs that include all *.cs by default
@@ -119,7 +121,8 @@ private ArtifactsPaths GetArtifactsPaths(BuildPartition buildPartition, string r
119121
string programName = buildPartition.ProgramName;
120122
string buildArtifactsDirectoryPath = GetBuildArtifactsDirectoryPath(buildPartition, programName);
121123
string binariesDirectoryPath = GetBinariesDirectoryPath(buildArtifactsDirectoryPath, buildPartition.BuildConfiguration);
122-
string executablePath = Path.Combine(binariesDirectoryPath, $"{programName}{GetExecutableExtension()}");
124+
125+
string executablePath = GetExecutablePath(binariesDirectoryPath, programName);
123126

124127
return new ArtifactsPaths(
125128
rootArtifactsFolderPath: rootArtifactsFolderPath,
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Diagnostics;
9+
using System.IO;
10+
using System.Linq;
11+
using System.Text;
12+
using System.Reflection;
13+
using BenchmarkDotNet.Toolchains.MonoWasm;
14+
15+
public class WasmAppBuilder
16+
{
17+
private readonly WasmSettings WasmSettings;
18+
private readonly string TargetFrameworkMoniker;
19+
20+
public WasmAppBuilder(WasmSettings wasmSettings, string targetFrameworkMoniker)
21+
{
22+
WasmSettings = wasmSettings;
23+
TargetFrameworkMoniker = targetFrameworkMoniker;
24+
}
25+
26+
public bool BuildApp (string programName, string projectRoot)
27+
{
28+
string[] assemblies;
29+
string appDir = Path.Combine(projectRoot, $"bin", TargetFrameworkMoniker, "browser-wasm", "publish");
30+
string outputDir = Path.Combine(appDir, "output");
31+
32+
string mainAssemblyPath = Path.Combine(appDir, $"{programName}.dll");
33+
34+
if (!File.Exists(mainAssemblyPath))
35+
throw new ArgumentException($"File MainAssembly='{mainAssemblyPath}' doesn't exist.");
36+
if (!File.Exists(WasmSettings.WasmMainJS))
37+
throw new ArgumentException($"File MainJS='{WasmSettings.WasmMainJS}' doesn't exist.");
38+
39+
var paths = new List<string>();
40+
assemblies = Directory.GetFiles(appDir, "*.dll");
41+
42+
// Create app
43+
Directory.CreateDirectory(outputDir);
44+
Directory.CreateDirectory(Path.Combine(outputDir, "managed"));
45+
foreach (var assembly in assemblies)
46+
File.Copy(assembly, Path.Combine(outputDir, "managed", Path.GetFileName(assembly)), true);
47+
48+
foreach (var f in new string[] { "dotnet.wasm", "dotnet.js" })
49+
File.Copy(Path.Combine(appDir, f), Path.Combine(outputDir, f), true);
50+
51+
File.Copy(WasmSettings.WasmMainJS, Path.Combine(outputDir, "runtime.js"), true);
52+
53+
using (var sw = File.CreateText(Path.Combine(outputDir, "mono-config.js")))
54+
{
55+
sw.WriteLine("config = {");
56+
sw.WriteLine("\tvfs_prefix: \"managed\",");
57+
sw.WriteLine("\tdeploy_prefix: \"managed\",");
58+
sw.WriteLine("\tenable_debugging: 0,");
59+
sw.WriteLine("\tassembly_list: [");
60+
foreach (var assembly in assemblies)
61+
{
62+
sw.Write("\t\t\"" + Path.GetFileName(assembly) + "\"");
63+
sw.WriteLine(",");
64+
}
65+
sw.WriteLine ("\t],");
66+
sw.WriteLine("\tfiles_to_map: [],");
67+
68+
sw.WriteLine ("}");
69+
}
70+
71+
return true;
72+
}
73+
}

0 commit comments

Comments
 (0)