Skip to content

Commit

Permalink
feat: Add support for WebAssembly threading
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromelaban committed Jun 8, 2022
1 parent e7f0a08 commit f0098ba
Show file tree
Hide file tree
Showing 14 changed files with 117 additions and 19 deletions.
5 changes: 5 additions & 0 deletions src/Uno.Wasm.Bootstrap.Cli/Server/Startup.cs
Expand Up @@ -62,6 +62,11 @@ public void Configure(IApplicationBuilder app, IConfiguration configuration)
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
context.Response.Headers.Add("Access-Control-Allow-Methods", "*");
context.Response.Headers.Add("Access-Control-Allow-Headers", "*");
// Required for SharedArrayBuffer: https://developer.chrome.com/blog/enabling-shared-array-buffer/
context.Response.Headers.Add("Cross-Origin-Embedder-Policy", "require-corp");
context.Response.Headers.Add("Cross-Origin-Opener-Policy", "same-origin");
await next();
});

Expand Down
2 changes: 1 addition & 1 deletion src/Uno.Wasm.Bootstrap/Constants.cs
Expand Up @@ -7,7 +7,7 @@ namespace Uno.Wasm.Bootstrap
internal class Constants
{
public const string DefaultDotnetRuntimeSdkUrl = "https://unowasmbootstrap.azureedge.net/runtime/"
+ "dotnet-runtime-wasm-linux-0aba92c-be4d292eaf2-2449169995-Release.zip";
+ "dotnet-runtime-wasm-linux-696f07f-be4d292eaf2-2458842755-Release.zip";

/// <summary>
/// Min version of the emscripten SDK. Must be aligned with dotnet/runtime SDK build in <see cref="NetCoreWasmSDKUri"/>.
Expand Down
11 changes: 11 additions & 0 deletions src/Uno.Wasm.Bootstrap/RuntimeConfiguration.cs
@@ -0,0 +1,11 @@
using System;

namespace Uno.Wasm.Bootstrap
{
[Flags]
public enum RuntimeConfiguration
{
Default = 0,
Threads = 1 << 0,
}
}
14 changes: 12 additions & 2 deletions src/Uno.Wasm.Bootstrap/ShellTask.cs
Expand Up @@ -212,6 +212,11 @@ public partial class ShellTask_v0 : Microsoft.Build.Utilities.Task
private Version ActualTargetFrameworkVersion
=> Version.TryParse(TargetFrameworkVersion.Substring(1), out var v) ? v : new Version("0.0");

private RuntimeConfiguration RuntimeConfigurationAsEnum
=> Enum.TryParse<RuntimeConfiguration>(RuntimeConfiguration, out var result)
? result
: throw new InvalidOperationException($"RuntimeConfiguration {RuntimeConfiguration} is not a valid value");

public override bool Execute()
{
try
Expand Down Expand Up @@ -741,9 +746,14 @@ private void RunPackager()

ValidateDotnet();

var runtimeConfigurationParam = $"--runtime-config=" +
(RuntimeConfigurationAsEnum.HasFlag(Bootstrap.RuntimeConfiguration.Threads)
? "release-threads"
: "release");

var enableICUParam = EnableNetCoreICU ? "--icu" : "";
var monovmparams = $"--framework=net5 --runtimepack-dir=\"{AlignPath(MonoWasmSDKPath)}\" {enableICUParam} ";
var pass1ResponseContent = $"--runtime-config={RuntimeConfiguration} {appDirParm} {monovmparams} --zlib {debugOption} {referencePathsParameter} \"{AlignPath(TryConvertLongPath(Path.GetFullPath(Assembly)))}\"";
var pass1ResponseContent = $"{runtimeConfigurationParam} {appDirParm} {monovmparams} --zlib {debugOption} {referencePathsParameter} \"{AlignPath(TryConvertLongPath(Path.GetFullPath(Assembly)))}\"";

var packagerPass1ResponseFile = Path.Combine(workAotPath, "packager-pass1.rsp");
File.WriteAllText(packagerPass1ResponseFile, pass1ResponseContent);
Expand Down Expand Up @@ -839,7 +849,7 @@ private void RunPackager()
packagerParams.Add("--enable-fs ");
packagerParams.Add($"--extra-emccflags=\"{extraEmccFlagsPararm} -l idbfs.js\" ");
packagerParams.Add($"--extra-linkerflags=\"{extraLinkerFlags}\"");
packagerParams.Add($"--runtime-config={RuntimeConfiguration} ");
packagerParams.Add(runtimeConfigurationParam);
packagerParams.Add(aotOptions);
packagerParams.Add(aotProfile);
packagerParams.Add($"--aot-compiler-opts=\"{AotCompilerOptions}\"");
Expand Down
19 changes: 17 additions & 2 deletions src/Uno.Wasm.Bootstrap/UnoInstallSDKTask.cs
Expand Up @@ -19,6 +19,8 @@ namespace Uno.Wasm.Bootstrap
{
public class UnoInstallSDKTask_v0 : Microsoft.Build.Utilities.Task, ICancelableTask
{
private readonly CancellationTokenSource _cts = new();

private static readonly TimeSpan _SDKFolderLockTimeout = TimeSpan.FromMinutes(2);
private static readonly TimeSpan _SDKLockRetryDelay = TimeSpan.FromSeconds(10);

Expand All @@ -33,6 +35,9 @@ public class UnoInstallSDKTask_v0 : Microsoft.Build.Utilities.Task, ICancelableT

public string TargetFrameworkVersion { get; set; } = "0.0";

[Required]
public string RuntimeConfiguration { get; set; } = "";

[Required]
public string PackagerOverrideFolderPath { get; set; } = "";

Expand Down Expand Up @@ -70,11 +75,16 @@ public class UnoInstallSDKTask_v0 : Microsoft.Build.Utilities.Task, ICancelableT
[Output]
public string? PackagerProjectFile { get; private set; }

private CancellationTokenSource _cts = new CancellationTokenSource();

public override bool Execute()
=> ExecuteAsync(_cts.Token).Result;


private RuntimeConfiguration RuntimeConfigurationAsEnum
=> Enum.TryParse<RuntimeConfiguration>(RuntimeConfiguration, out var result)
? result
: throw new InvalidOperationException($"RuntimeConfiguration {RuntimeConfiguration} is not a valid value");


private async Task<bool> ExecuteAsync(CancellationToken ct)
{
if(TargetFramework == "netstandard2.0")
Expand Down Expand Up @@ -104,6 +114,11 @@ private async Task InstallNetCoreWasmSdk(CancellationToken ct)
sdkUri = sdkUri.Replace("linux", "windows");
}

if (RuntimeConfigurationAsEnum.HasFlag(Bootstrap.RuntimeConfiguration.Threads))
{
sdkUri = sdkUri.Replace(".zip", "-threads.zip");
}

var sdkName = Path.GetFileNameWithoutExtension(new Uri(sdkUri).AbsolutePath.Replace('/', Path.DirectorySeparatorChar));
Log.LogMessage("NetCore-Wasm SDK: " + sdkName);
SdkPath = Path.Combine(GetMonoTempPath(), sdkName);
Expand Down
3 changes: 2 additions & 1 deletion src/Uno.Wasm.Bootstrap/build/Uno.Wasm.Bootstrap.targets
Expand Up @@ -12,7 +12,7 @@
<WasmShellContentExtensionsToExclude Condition="'$(WasmShellContentExtensionsToExclude)' == ''">.a;.bc;.o</WasmShellContentExtensionsToExclude>

<WasmShellMode Condition="'$(WasmShellMode)'==''">browser</WasmShellMode>
<MonoWasmRuntimeConfiguration Condition="'$(MonoWasmRuntimeConfiguration)'==''">release</MonoWasmRuntimeConfiguration>
<MonoWasmRuntimeConfiguration Condition="'$(MonoWasmRuntimeConfiguration)'==''">Default</MonoWasmRuntimeConfiguration>
<MonoRuntimeDebuggerEnabled Condition="'$(MonoRuntimeDebuggerEnabled)'==''">false</MonoRuntimeDebuggerEnabled>
<WasmShellILLinkerEnabled Condition="'$(WasmShellILLinkerEnabled)'==''">true</WasmShellILLinkerEnabled>
<WasmShellGenerateCompressedFiles Condition="'$(WasmShellGenerateCompressedFiles)'=='' and '$(Configuration)'=='Debug'">false</WasmShellGenerateCompressedFiles>
Expand Down Expand Up @@ -169,6 +169,7 @@
MonoTempFolder="$(WasmShellMonoTempFolder)"
NetCoreWasmSDKUri="$(NetCoreWasmSDKUri)"
PackagerOverrideFolderPath="$(MSBuildThisFileDirectory)/packager/$(_WasmShellToolSuffix)"
RuntimeConfiguration="$(MonoWasmRuntimeConfiguration)"
TargetFramework="$(TargetFramework)"
TargetFrameworkIdentifier="$(TargetFrameworkIdentifier)"
TargetFrameworkVersion="$(TargetFrameworkVersion)"
Expand Down
2 changes: 2 additions & 0 deletions src/Uno.Wasm.Bootstrap/ts/Uno/WebAssembly/Bootstrapper.ts
Expand Up @@ -100,6 +100,8 @@ namespace Uno.WebAssembly.Bootstrap {
return <DotnetModuleConfig>{
disableDotnet6Compatibility: this.disableDotnet6Compatibility,
configSrc: this.configSrc,
baseUrl: this._unoConfig.uno_app_base + "/",
mainScriptPath: "dotnet.js",
onConfigLoaded: this.onConfigLoaded,
onDotnetReady: this.onDotnetReady,
onAbort: this.onAbort,
Expand Down
4 changes: 3 additions & 1 deletion src/Uno.Wasm.Bootstrap/ts/types/dotnet/index.d.ts
Expand Up @@ -205,7 +205,9 @@ declare type CoverageProfilerOptions = {
declare type DotnetModuleConfig = {
disableDotnet6Compatibility?: boolean;
config?: MonoConfig | MonoConfigError;
configSrc?: string;
configSrc?: string;
baseUrl?: string,
mainScriptPath?: string,
scriptDirectory?: string;
onConfigLoaded?: () => void;
onDotnetReady?: () => void;
Expand Down
21 changes: 17 additions & 4 deletions src/Uno.Wasm.Packager/packager.cs
Expand Up @@ -596,11 +596,11 @@ class WasmOptions {
case "release":
break;

case "threads-release":
case "release-threads":
enable_threads = true;
break;

case "threads-debug":
case "debug-threads":
enable_threads = true;
enable_debug = true;
break;
Expand Down Expand Up @@ -897,7 +897,8 @@ class WasmOptions {
File.WriteAllText(config_json, config);

if (!emit_ninja) {
var interp_files = new List<string> { "dotnet.js", "dotnet.wasm" };
var interp_files = new List<string> { "dotnet.js", "dotnet.wasm", "dotnet-crypto-worker.js" };

if (enable_threads) {
interp_files.Add ("dotnet.worker.js");
}
Expand Down Expand Up @@ -1054,6 +1055,17 @@ class WasmOptions {
emcc_link_flags.Add(linker_optimization_level);
}

if (enable_threads)
{
emcc_link_flags.Add("-s USE_PTHREADS=1");
emcc_link_flags.Add("-s PTHREAD_POOL_SIZE=2");
emcc_link_flags.Add("-Wno-pthreads-mem-growth");

emcc_flags += "-s USE_PTHREADS=1 ";
emcc_flags += "-s PTHREAD_POOL_SIZE=2 ";
emcc_flags += "-Wno-pthreads-mem-growth ";
}

if (opts.EnableWasmExceptions)
{
emcc_link_flags.Add("-fwasm-exceptions");
Expand Down Expand Up @@ -1094,7 +1106,7 @@ class WasmOptions {
var commandSeparator = is_windows ? ";" : "&&";
if (opts.NativeStrip)
{
strip_cmd = $" {commandSeparator} \"$wasm_opt\" --strip-dwarf --mvp-features --enable-mutable-globals --enable-exception-handling \'$out_wasm\' -o \'$out_wasm\' {failOnError}";
strip_cmd = $" {commandSeparator} \"$wasm_opt\" --strip-dwarf --mvp-features --all-features --enable-threads --enable-bulk-memory --enable-mutable-globals --enable-exception-handling \'$out_wasm\' -o \'$out_wasm\' {failOnError}";
}
if (enable_simd) {
aot_args += "mattr=simd,";
Expand Down Expand Up @@ -1347,6 +1359,7 @@ class WasmOptions {
}
} else {
ninja.WriteLine ("build $appdir/dotnet.js: cpifdiff $wasm_runtime_dir/dotnet.js");
ninja.WriteLine ("build $appdir/dotnet-crypto-worker.js: cpifdiff $wasm_runtime_dir/dotnet-crypto-worker.js");
ninja.WriteLine ("build $appdir/dotnet.wasm: cpifdiff $wasm_runtime_dir/dotnet.wasm");
if (enable_threads) {
ninja.WriteLine ("build $appdir/dotnet.worker.js: cpifdiff $wasm_runtime_dir/dotnet.worker.js");
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.Wasm.Threading.UITests/app.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Uno.Wasm.Threading.UITests/app.ts
Expand Up @@ -55,7 +55,7 @@ function delay(time) {
console.log(`Results: ${value}`);
}

const expected = "StartupWorking...Done 10000 results";
const expected = "StartupDone 10000 results (_mainThreadInvoked:True)";

if (value !== expected) {
console.log(`Invalid results got ${value}, expected ${expected}`);
Expand Down
38 changes: 34 additions & 4 deletions src/Uno.Wasm.Threads.Shared/Program.cs
Expand Up @@ -19,6 +19,7 @@
using System.IO;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -31,12 +32,14 @@ public static class Program
private static ManualResetEvent _event = new ManualResetEvent(false);
private static object _gate = new object();
private static List<string> _messages = new List<string>();
private static bool _mainThreadInvoked = false;
private static Timer _timer;

static void Main()
{
var runtimeMode = Environment.GetEnvironmentVariable("UNO_BOOTSTRAP_MONO_RUNTIME_MODE");
Console.WriteLine($"Mono Runtime Mode: " + runtimeMode);
Console.WriteLine($"Runtime Version: " + RuntimeInformation.FrameworkDescription);
Console.WriteLine($"Runtime Mode: " + runtimeMode);
Console.WriteLine($"TID: {Thread.CurrentThread.ManagedThreadId}");

Runtime.InvokeJS("Interop.appendResult('Startup')");
Expand All @@ -51,16 +54,23 @@ private static void OnTick(object state)
{
if (_event.WaitOne(10))
{
var r = $"Done {_messages.Count} results";
var r = $"Done {_messages.Count} results (_mainThreadInvoked:{_mainThreadInvoked})";
Runtime.InvokeJS($"Interop.appendResult('{r}')");
Console.WriteLine($"[tid:{Thread.CurrentThread.ManagedThreadId}]: {r}");
_timer.Dispose();
}
else
{
Runtime.InvokeJS("Interop.appendResult('Working...')");
Console.WriteLine("Working...");
}
}

public static void MainThreadCallback()
{
Console.WriteLine($"[tid:{Thread.CurrentThread.ManagedThreadId}]: MainThreadCallback");
_mainThreadInvoked = true;
}

private static async Task Run()
{
int counter = 0;
Expand All @@ -79,12 +89,23 @@ void DoWork(string name)
{
for (int i = 0; i < 10000; i++) {
logMessage($"{name}: {i}");

if ((i % 2000) == 0)
{
WebAssembly.JSInterop.InternalCalls.InvokeOnMainThread();
}
}
}

new Thread(_ => {
new Thread(_ =>
{
Console.WriteLine($"Starting thread [tid:{Thread.CurrentThread.ManagedThreadId}]");
WebAssembly.JSInterop.InternalCalls.InvokeOnMainThread();
DoWork("thread1");
tcs.TrySetResult(true);
Console.WriteLine($"Stopping thread [tid:{Thread.CurrentThread.ManagedThreadId}]");
}).Start();

await tcs.Task;
Expand All @@ -93,3 +114,12 @@ void DoWork(string name)
}
}
}

namespace WebAssembly.JSInterop
{
public static class InternalCalls
{
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static extern void InvokeOnMainThread();
}
}
7 changes: 7 additions & 0 deletions src/Uno.Wasm.Threads.Shared/WasmScripts/test.js
Expand Up @@ -9,3 +9,10 @@ var Interop = {
parent.appendChild(txt, parent.lastChild);
}
};

var DotNet = {
invokeOnMainThread: function (str) {
let getApplyUpdateCapabilitiesMethod = BINDING.bind_static_method("[Uno.Wasm.Threads] Uno.Wasm.Sample.Program:MainThreadCallback");
getApplyUpdateCapabilitiesMethod();
}
}
6 changes: 4 additions & 2 deletions src/Uno.Wasm.Threads/Uno.Wasm.Threads.csproj
Expand Up @@ -7,8 +7,10 @@
<StartupObject>Uno.Wasm.Sample.Program</StartupObject>
<MonoRuntimeDebuggerEnabled Condition="'$(Configuration)'=='Debug'">true</MonoRuntimeDebuggerEnabled>
<!--<WasmShellMonoRuntimeExecutionMode Condition="$([MSBuild]::IsOsPlatform('Linux'))">FullAOT</WasmShellMonoRuntimeExecutionMode>-->
<!--<MonoWasmRuntimeConfiguration>threads-release</MonoWasmRuntimeConfiguration>-->
<MonoWasmRuntimeConfiguration>Threads</MonoWasmRuntimeConfiguration>
<!--<WasmShellILLinkerEnabled>false</WasmShellILLinkerEnabled>-->

<NetCoreWasmSDKUri>C:\temp\net7-test\2458654031-wasm\dotnet-runtime-wasm-linux-2bc3185-be4d292eaf2-2458654031-Release.zip</NetCoreWasmSDKUri>
</PropertyGroup>

<ItemGroup>
Expand All @@ -29,7 +31,7 @@

<Import Project="..\Uno.Wasm.Tests.Shared\Uno.Wasm.Tests.Shared.projitems" Label="Shared" />
<Import Project="..\Uno.Wasm.Threads.Shared\Uno.Wasm.Threads.Shared.projitems" Label="Shared" />

<ItemGroup>
<None Remove="side.wasm" />
<None Remove="side.bc" />
Expand Down

0 comments on commit f0098ba

Please sign in to comment.