Skip to content

Commit 764f774

Browse files
pavelsavararadical
andauthored
[browser] detect SIMD and EH support (#89387)
Co-authored-by: Ankit Jain <[email protected]>
1 parent 6269d2b commit 764f774

18 files changed

+185
-37
lines changed

eng/testing/performance/performance-setup.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ if [[ -n "$wasm_bundle_directory" ]]; then
403403
wasm_bundle_directory_path=$payload_directory
404404
mv $wasm_bundle_directory/* $wasm_bundle_directory_path
405405
find $wasm_bundle_directory_path -type d
406-
wasm_args="--expose_wasm"
406+
wasm_args="--experimental-wasm-eh --expose_wasm"
407407
if [ "$javascript_engine" == "v8" ]; then
408408
# for es6 module support
409409
wasm_args="$wasm_args --module"

src/mono/sample/wasm/Directory.Build.targets

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@
5555
<Exec WorkingDirectory="bin/$(Configuration)/AppBundle" Command="v8 --expose_wasm --module $(_WasmMainJSFileName) -- $(DOTNET_MONO_LOG_LEVEL) --run $(_SampleAssembly) $(Args)" IgnoreExitCode="true" />
5656
</Target>
5757
<Target Name="RunSampleWithNode" DependsOnTargets="BuildSampleInTree;_ComputeMainJSFileName">
58-
<Exec WorkingDirectory="bin/$(Configuration)/AppBundle" Command="node --expose_wasm $(_WasmMainJSFileName) -- $(DOTNET_MONO_LOG_LEVEL) --run $(_SampleAssembly) $(Args)" IgnoreExitCode="true" />
58+
<Exec WorkingDirectory="bin/$(Configuration)/AppBundle" Command="node --experimental-wasm-eh --expose_wasm $(_WasmMainJSFileName) -- $(DOTNET_MONO_LOG_LEVEL) --run $(_SampleAssembly) $(Args)" IgnoreExitCode="true" />
5959
</Target>
6060
<Target Name="DebugSampleWithNode" DependsOnTargets="BuildSampleInTree;_ComputeMainJSFileName">
61-
<Exec WorkingDirectory="bin/$(Configuration)/AppBundle" Command="node --expose_wasm $(_WasmMainJSFileName) -- $(DOTNET_MONO_LOG_LEVEL) --run $(_SampleAssembly) $(Args) --inspect=9222" IgnoreExitCode="true" />
61+
<Exec WorkingDirectory="bin/$(Configuration)/AppBundle" Command="node --experimental-wasm-eh --expose_wasm $(_WasmMainJSFileName) -- $(DOTNET_MONO_LOG_LEVEL) --run $(_SampleAssembly) $(Args) --inspect=9222" IgnoreExitCode="true" />
6262
</Target>
6363
<Target Name="CheckServe">
6464
<Exec Command="dotnet tool install -g dotnet-serve" IgnoreExitCode="true" />

src/mono/sample/wasm/wasm.mk

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ run-console:
4545
cd bin/$(CONFIG)/AppBundle && $(V8_PATH) --stack-trace-limit=1000 --single-threaded --expose_wasm --module $(MAIN_JS) -- $(ARGS)
4646

4747
run-console-node:
48-
cd bin/$(CONFIG)/AppBundle && node --stack-trace-limit=1000 --single-threaded --expose_wasm $(MAIN_JS) $(ARGS)
48+
cd bin/$(CONFIG)/AppBundle && node --stack-trace-limit=1000 --single-threaded --experimental-wasm-eh --expose_wasm $(MAIN_JS) $(ARGS)
4949

5050
debug-console-node:
51-
cd bin/$(CONFIG)/AppBundle && node --inspect=9222 --stack-trace-limit=1000 --single-threaded --expose_wasm $(MAIN_JS) $(ARGS)
51+
cd bin/$(CONFIG)/AppBundle && node --inspect=9222 --stack-trace-limit=1000 --single-threaded --experimental-wasm-eh --expose_wasm $(MAIN_JS) $(ARGS)

src/mono/wasm/build/WasmApp.Native.targets

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,10 @@
275275
<EmscriptenEnvVars Include="EM_FROZEN_CACHE=True" />
276276
<EmscriptenEnvVars Include="DISABLE_LEGACY_JS_INTEROP=1" Condition="'$(WasmEnableLegacyJsInterop)' == 'false'" />
277277
<EmscriptenEnvVars Include="DISABLE_LEGACY_JS_INTEROP=0" Condition="'$(WasmEnableLegacyJsInterop)' != 'false'" />
278+
<EmscriptenEnvVars Include="WASM_ENABLE_SIMD=1" Condition="'$(WasmEnableSIMD)' != 'false'" />
279+
<EmscriptenEnvVars Include="WASM_ENABLE_SIMD=0" Condition="'$(WasmEnableSIMD)' == 'false'" />
280+
<EmscriptenEnvVars Include="WASM_ENABLE_EH=1" Condition="'$(WasmEnableExceptionHandling)' != 'false'" />
281+
<EmscriptenEnvVars Include="WASM_ENABLE_EH=0" Condition="'$(WasmEnableExceptionHandling)' == 'false'" />
278282
<EmscriptenEnvVars Include="ENABLE_AOT_PROFILER=$([System.Convert]::ToInt32($(WasmProfilers.Contains('aot'))))" />
279283
<EmscriptenEnvVars Include="ENABLE_BROWSER_PROFILER=$([System.Convert]::ToInt32($(WasmProfilers.Contains('browser'))))" />
280284
</ItemGroup>

src/mono/wasm/features.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Browser or JS engine features
2+
3+
dotnet for wasm can be compiled with various MSBuild flags which enable the use of browser features. If you need to target an older version of the browser, then you may need to disable some of the dotnet features or optimizations.
4+
5+
For full set of MSBuild properties [see top of](src\mono\wasm\build\WasmApp.targets)
6+
For set of [browser WASM features see](https://webassembly.org/roadmap/)
7+
8+
# Multi-threading
9+
Is enabled by `<WasmEnableThreads>true</WasmEnableThreads>`.
10+
It requires HTTP headers similar to `Cross-Origin-Embedder-Policy:require-corp` and `Cross-Origin-Opener-Policy:same-origin`.
11+
See also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements
12+
13+
# SIMD - Single instruction, multiple data
14+
Is performance optimization enabled by default. It requires recent version of browser.
15+
You can disable it by `<WasmEnableSIMD>false</WasmEnableSIMD><WasmBuildNative>true</WasmBuildNative>`.
16+
[See also](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md)
17+
18+
# EH - Exception handling
19+
Is performance optimization enabled by default. It requires recent version of browser.
20+
You can disable it by `<WasmEnableExceptionHandling>false</WasmEnableExceptionHandling><WasmBuildNative>true</WasmBuildNative>`.
21+
[See also](https://github.com/WebAssembly/exception-handling/blob/master/proposals/exception-handling/Exceptions.md)
22+
23+
# BigInt
24+
Is required if the application uses Int64 marshaling in JS interop.
25+
[See also](https://github.com/WebAssembly/JS-BigInt-integration)
26+
27+
# fetch browser API
28+
Is required if the application uses [HttpClient](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient)
29+
NodeJS needs to install `node-fetch` and `node-abort-controller` npm packages.
30+
31+
# WebSocket browser API
32+
Is required if the application uses [WebSocketClient](https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocket)
33+
NodeJS needs to install `ws` npm package.
34+
35+
# Shell environments - NodeJS & V8
36+
We pass most of the unit tests with NodeJS v 14 but it's not fully supported target platform. We would like to hear about community use-cases.
37+
We also use v8 engine version 11 or higher to run some of the tests. The engine is lacking many APIs and features.

src/mono/wasm/runtime/es6/dotnet.es6.lib.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
// because we can't pass custom define symbols to acorn optimizer, we use environment variables to pass other build options
99
const DISABLE_LEGACY_JS_INTEROP = process.env.DISABLE_LEGACY_JS_INTEROP === "1";
10+
const WASM_ENABLE_SIMD = process.env.WASM_ENABLE_SIMD === "1";
11+
const WASM_ENABLE_EH = process.env.WASM_ENABLE_EH === "1";
1012
const ENABLE_BROWSER_PROFILER = process.env.ENABLE_BROWSER_PROFILER === "1";
1113
const ENABLE_AOT_PROFILER = process.env.ENABLE_AOT_PROFILER === "1";
1214

@@ -62,6 +64,8 @@ function setup(linkerSetup) {
6264
const postset = `
6365
DOTNET.setup({ `+
6466
`linkerDisableLegacyJsInterop: ${DISABLE_LEGACY_JS_INTEROP ? "true" : "false"},` +
67+
`linkerWasmEnableSIMD: ${WASM_ENABLE_SIMD ? "true" : "false"},` +
68+
`linkerWasmEnableEH: ${WASM_ENABLE_EH ? "true" : "false"},` +
6569
`linkerEnableAotProfiler: ${ENABLE_AOT_PROFILER ? "true" : "false"}, ` +
6670
`linkerEnableBrowserProfiler: ${ENABLE_BROWSER_PROFILER ? "true" : "false"}` +
6771
`});

src/mono/wasm/runtime/globals.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@ export let runtimeHelpers: RuntimeHelpers = null as any;
2424
export let loaderHelpers: LoaderHelpers = null as any;
2525
// this is when we link with workload tools. The consts:wasmEnableLegacyJsInterop is when we compile with rollup.
2626
export let linkerDisableLegacyJsInterop = false;
27+
export let linkerWasmEnableSIMD = true;
28+
export let linkerWasmEnableEH = true;
2729
export let linkerEnableAotProfiler = false;
2830
export let linkerEnableBrowserProfiler = false;
2931
export let _runtimeModuleLoaded = false; // please keep it in place also as rollup guard
3032

3133
export function passEmscriptenInternals(internals: EmscriptenInternals): void {
3234
ENVIRONMENT_IS_PTHREAD = internals.isPThread;
3335
linkerDisableLegacyJsInterop = internals.linkerDisableLegacyJsInterop;
36+
linkerWasmEnableSIMD = internals.linkerWasmEnableSIMD;
37+
linkerWasmEnableEH = internals.linkerWasmEnableEH;
3438
linkerEnableAotProfiler = internals.linkerEnableAotProfiler;
3539
linkerEnableBrowserProfiler = internals.linkerEnableBrowserProfiler;
3640
runtimeHelpers.quit = internals.quit_;

src/mono/wasm/runtime/http.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import type { VoidPtr } from "./types/emscripten";
1010
function verifyEnvironment() {
1111
if (typeof globalThis.fetch !== "function" || typeof globalThis.AbortController !== "function") {
1212
const message = ENVIRONMENT_IS_NODE
13-
? "Please install `node-fetch` and `node-abort-controller` npm packages to enable HTTP client support."
14-
: "This browser doesn't support fetch API. Please use a modern browser.";
13+
? "Please install `node-fetch` and `node-abort-controller` npm packages to enable HTTP client support. See also https://aka.ms/dotnet-wasm-features"
14+
: "This browser doesn't support fetch API. Please use a modern browser. See also https://aka.ms/dotnet-wasm-features";
1515
throw new Error(message);
1616
}
1717
}

src/mono/wasm/runtime/loader/globals.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
import { exceptions, simd } from "wasm-feature-detect";
5+
46
import type { AssetEntryInternal, GlobalObjects, LoaderHelpers, RuntimeHelpers } from "../types/internal";
57
import type { MonoConfig, RuntimeAPI } from "../types";
68
import { assert_runtime_running, is_exited, is_runtime_running, mono_exit } from "./exit";
@@ -94,6 +96,10 @@ export function setLoaderGlobals(
9496
hasDebuggingEnabled,
9597
invokeLibraryInitializers,
9698

99+
// from wasm-feature-detect npm package
100+
exceptions,
101+
simd,
102+
97103
} as Partial<LoaderHelpers>);
98104
}
99105

src/mono/wasm/runtime/loader/polyfills.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,25 @@ const URLPolyfill = class URL {
1919
};
2020

2121
export function verifyEnvironment() {
22-
mono_assert(ENVIRONMENT_IS_SHELL || typeof globalThis.URL === "function", "This browser/engine doesn't support URL API. Please use a modern version.");
23-
mono_assert(typeof globalThis.BigInt64Array === "function", "This browser/engine doesn't support BigInt64Array API. Please use a modern version.");
22+
mono_assert(ENVIRONMENT_IS_SHELL || typeof globalThis.URL === "function", "This browser/engine doesn't support URL API. Please use a modern version. See also https://aka.ms/dotnet-wasm-features");
23+
mono_assert(typeof globalThis.BigInt64Array === "function", "This browser/engine doesn't support BigInt64Array API. Please use a modern version. See also https://aka.ms/dotnet-wasm-features");
2424
if (MonoWasmThreads) {
25-
mono_assert(!ENVIRONMENT_IS_SHELL && !ENVIRONMENT_IS_NODE, "This build of dotnet is multi-threaded, it doesn't support shell environments like V8 or NodeJS.");
26-
mono_assert(globalThis.SharedArrayBuffer !== undefined, "SharedArrayBuffer is not enabled on this page. Please use a modern browser and set Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy http headers. See also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements");
27-
mono_assert(typeof globalThis.EventTarget === "function", "This browser/engine doesn't support EventTarget API. Please use a modern version.");
25+
mono_assert(!ENVIRONMENT_IS_SHELL && !ENVIRONMENT_IS_NODE, "This build of dotnet is multi-threaded, it doesn't support shell environments like V8 or NodeJS. See also https://aka.ms/dotnet-wasm-features");
26+
mono_assert(globalThis.SharedArrayBuffer !== undefined, "SharedArrayBuffer is not enabled on this page. Please use a modern browser and set Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy http headers. See also https://aka.ms/dotnet-wasm-features");
27+
mono_assert(typeof globalThis.EventTarget === "function", "This browser/engine doesn't support EventTarget API. Please use a modern version. See also https://aka.ms/dotnet-wasm-features");
2828
}
29-
30-
// TODO detect other (WASM) features that are required for the runtime
31-
// See https://github.com/dotnet/runtime/issues/84574
3229
}
3330

3431
export async function detect_features_and_polyfill(module: DotnetModuleInternal): Promise<void> {
32+
if (ENVIRONMENT_IS_NODE) {
33+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
34+
// @ts-ignore:
35+
const process = await import(/* webpackIgnore: true */"process");
36+
const minNodeVersion = 14;
37+
if (process.versions.node.split(".")[0] < minNodeVersion) {
38+
throw new Error(`NodeJS at '${process.execPath}' has too low version '${process.versions.node}', please use at least ${minNodeVersion}. See also https://aka.ms/dotnet-wasm-features`);
39+
}
40+
}
3541

3642
const scriptUrlQuery =/* webpackIgnore: true */import.meta.url;
3743
const queryIndex = scriptUrlQuery.indexOf("?");

src/mono/wasm/runtime/loader/run.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -363,14 +363,6 @@ export class HostBuilder implements DotnetHostBuilder {
363363
if (ENVIRONMENT_IS_WEB && (module.config! as MonoConfigInternal).forwardConsoleLogsToWS && typeof globalThis.WebSocket != "undefined") {
364364
setup_proxy_console("main", globalThis.console, globalThis.location.origin);
365365
}
366-
if (ENVIRONMENT_IS_NODE) {
367-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
368-
// @ts-ignore:
369-
const process = await import(/* webpackIgnore: true */"process");
370-
if (process.versions.node.split(".")[0] < 14) {
371-
throw new Error(`NodeJS at '${process.execPath}' has too low version '${process.versions.node}'`);
372-
}
373-
}
374366
mono_assert(module, "Null moduleConfig");
375367
mono_assert(module.config, "Null moduleConfig.config");
376368
await createEmscripten(module);
@@ -452,7 +444,7 @@ function importModules() {
452444
];
453445
}
454446

455-
function initializeModules(es6Modules: [RuntimeModuleExportsInternal, NativeModuleExportsInternal]) {
447+
async function initializeModules(es6Modules: [RuntimeModuleExportsInternal, NativeModuleExportsInternal]) {
456448
const { initializeExports, initializeReplacements, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals, passEmscriptenInternals } = es6Modules[0];
457449
const { default: emscriptenFactory } = es6Modules[1];
458450
setRuntimeGlobals(globalObjectsRoot);
@@ -491,7 +483,7 @@ async function createEmscriptenMain(): Promise<RuntimeAPI> {
491483

492484
// TODO call mono_download_assets(); here in parallel ?
493485
const es6Modules = await Promise.all(promises);
494-
initializeModules(es6Modules as any);
486+
await initializeModules(es6Modules as any);
495487

496488
await runtimeHelpers.dotnetReady.promise;
497489

@@ -507,7 +499,7 @@ async function createEmscriptenWorker(): Promise<EmscriptenModuleInternal> {
507499

508500
const promises = importModules();
509501
const es6Modules = await Promise.all(promises);
510-
initializeModules(es6Modules as any);
502+
await initializeModules(es6Modules as any);
511503

512504
return module;
513505
}

src/mono/wasm/runtime/package-lock.json

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/mono/wasm/runtime/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,23 @@
2222
"author": "Microsoft",
2323
"license": "MIT",
2424
"devDependencies": {
25+
"@rollup/plugin-node-resolve": "^15.1.0",
2526
"@rollup/plugin-terser": "0.4.3",
2627
"@rollup/plugin-typescript": "11.1.2",
2728
"@rollup/plugin-virtual": "3.0.1",
2829
"@typescript-eslint/eslint-plugin": "5.59.1",
2930
"@typescript-eslint/parser": "5.59.1",
30-
"magic-string": "0.30.1",
3131
"eslint": "8.44.0",
3232
"fast-glob": "3.3.0",
3333
"git-commit-info": "2.0.2",
34+
"magic-string": "0.30.1",
3435
"rollup": "3.26.2",
3536
"rollup-plugin-dts": "5.3.0",
3637
"terser": "5.19.0",
3738
"tslib": "2.6.0",
3839
"typescript": "5.1.6"
40+
},
41+
"dependencies": {
42+
"wasm-feature-detect": "1.5.1"
3943
}
4044
}

0 commit comments

Comments
 (0)