diff --git a/src/mono/sample/wasm/browser-advanced/index.html b/src/mono/sample/wasm/browser-advanced/index.html
index c8961d7c71540..24d51ea29672d 100644
--- a/src/mono/sample/wasm/browser-advanced/index.html
+++ b/src/mono/sample/wasm/browser-advanced/index.html
@@ -13,10 +13,7 @@
-
-
-
-
+
diff --git a/src/mono/sample/wasm/browser-advanced/main.js b/src/mono/sample/wasm/browser-advanced/main.js
index f95fcbf9903be..b5c414322fefd 100644
--- a/src/mono/sample/wasm/browser-advanced/main.js
+++ b/src/mono/sample/wasm/browser-advanced/main.js
@@ -31,6 +31,7 @@ try {
// here we show how emscripten could be further configured
// It is preferred to use specific 'with***' methods instead in all other cases.
.withConfig({
+ startupMemoryCache: true,
resources: {
modulesAfterConfigLoaded: {
"advanced-sample.lib.module.js": ""
diff --git a/src/mono/sample/wasm/browser-bench/appstart-frame.html b/src/mono/sample/wasm/browser-bench/appstart-frame.html
index 5bb75e32aa1b7..481ffa4e27301 100644
--- a/src/mono/sample/wasm/browser-bench/appstart-frame.html
+++ b/src/mono/sample/wasm/browser-bench/appstart-frame.html
@@ -12,9 +12,6 @@
-
-
-
diff --git a/src/mono/sample/wasm/browser-bench/frame-main.js b/src/mono/sample/wasm/browser-bench/frame-main.js
index c1042928d78d0..88358e0310a10 100644
--- a/src/mono/sample/wasm/browser-bench/frame-main.js
+++ b/src/mono/sample/wasm/browser-bench/frame-main.js
@@ -31,6 +31,10 @@ try {
}
const runtime = await dotnet
+ .withConfig({
+ maxParallelDownloads: 10000,
+ // diagnosticTracing:true,
+ })
.withModuleConfig({
printErr: () => undefined,
print: () => undefined,
@@ -38,7 +42,6 @@ try {
if (window.parent != window) {
window.parent.resolveAppStartEvent("onConfigLoaded");
}
- // config.diagnosticTracing = true;
}
})
.create();
diff --git a/src/mono/wasm/features.md b/src/mono/wasm/features.md
index 46aa26d85038e..c131002dde796 100644
--- a/src/mono/wasm/features.md
+++ b/src/mono/wasm/features.md
@@ -193,12 +193,13 @@ See also [fetch integrity on MDN](https://developer.mozilla.org/en-US/docs/Web/A
### Pre-fetching
In order to start downloading application resources as soon as possible you can add HTML elements to `` of your page similar to:
+Adding too many files into prefetch could be counterproductive.
+Please benchmark your startup performance on real target devices and with realistic network conditions.
```html
-
```
See also [link rel prefetch on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/prefetch)
diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts
index a29fd2da59d84..6e50e26e164c0 100644
--- a/src/mono/wasm/runtime/exports.ts
+++ b/src/mono/wasm/runtime/exports.ts
@@ -8,7 +8,7 @@ import type { RuntimeAPI } from "./types";
import { Module, linkerDisableLegacyJsInterop, exportedRuntimeAPI, passEmscriptenInternals, runtimeHelpers, setRuntimeGlobals, } from "./globals";
import { GlobalObjects, is_nullish } from "./types/internal";
-import { configureEmscriptenStartup, configureWorkerStartup } from "./startup";
+import { configureEmscriptenStartup, configureRuntimeStartup, configureWorkerStartup } from "./startup";
import { create_weak_ref } from "./weak-ref";
import { export_internal } from "./exports-internal";
@@ -143,5 +143,5 @@ class RuntimeList {
// export external API
export {
- passEmscriptenInternals, initializeExports, initializeReplacements, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals
+ passEmscriptenInternals, initializeExports, initializeReplacements, configureRuntimeStartup, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals
};
\ No newline at end of file
diff --git a/src/mono/wasm/runtime/globals.ts b/src/mono/wasm/runtime/globals.ts
index 5db69fc91bf61..b14bafd5ed2d7 100644
--- a/src/mono/wasm/runtime/globals.ts
+++ b/src/mono/wasm/runtime/globals.ts
@@ -59,7 +59,6 @@ export function setRuntimeGlobals(globalObjects: GlobalObjects) {
gitHash,
allAssetsInMemory: createPromiseController(),
dotnetReady: createPromiseController(),
- memorySnapshotSkippedOrDone: createPromiseController(),
afterInstantiateWasm: createPromiseController(),
beforePreInit: createPromiseController(),
afterPreInit: createPromiseController(),
diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts
index ba18815a08db8..be7c0e4592938 100644
--- a/src/mono/wasm/runtime/loader/assets.ts
+++ b/src/mono/wasm/runtime/loader/assets.ts
@@ -78,8 +78,6 @@ const containedInSnapshotByAssetTypes: {
"pdb": true,
"heap": true,
"icu": true,
- ...jsModulesAssetTypes,
- "dotnetwasm": true,
};
// these assets are instantiated differently than the main flow
@@ -95,7 +93,7 @@ export function shouldLoadIcuAsset(asset: AssetEntryInternal): boolean {
return !(asset.behavior == "icu" && asset.name != loaderHelpers.preferredIcuAsset);
}
-function convert_single_asset(modulesAssets: AssetEntryInternal[], resource: ResourceList | undefined, behavior: SingleAssetBehaviors): AssetEntryInternal {
+function convert_single_asset(assetsCollection: AssetEntryInternal[], resource: ResourceList | undefined, behavior: SingleAssetBehaviors): AssetEntryInternal {
const keys = Object.keys(resource || {});
mono_assert(keys.length == 1, `Expect to have one ${behavior} asset in resources`);
@@ -110,7 +108,7 @@ function convert_single_asset(modulesAssets: AssetEntryInternal[], resource: Res
set_single_asset(asset);
// so that we can use it on the worker too
- modulesAssets.push(asset);
+ assetsCollection.push(asset);
return asset;
}
@@ -168,15 +166,12 @@ export async function mono_download_assets(): Promise {
countAndStartDownload(asset);
}
- // continue after the dotnet.runtime.js was loaded
- await loaderHelpers.runtimeModuleLoaded.promise;
-
// continue after we know if memory snapshot is available or not
- await runtimeHelpers.memorySnapshotSkippedOrDone.promise;
+ await loaderHelpers.memorySnapshotSkippedOrDone.promise;
// start fetching assets in parallel, only if memory snapshot is not available.
for (const asset of containedInSnapshotAssets) {
- if (!runtimeHelpers.loadedMemorySnapshot) {
+ if (!runtimeHelpers.loadedMemorySnapshotSize) {
countAndStartDownload(asset);
} else {
// Otherwise cleanup in case we were given pending download. It would be even better if we could abort the download.
@@ -193,6 +188,8 @@ export async function mono_download_assets(): Promise {
}
loaderHelpers.allDownloadsQueued.promise_control.resolve();
+
+ // continue after the dotnet.runtime.js was loaded
await loaderHelpers.runtimeModuleLoaded.promise;
const promises_of_asset_instantiation: Promise[] = [];
@@ -211,7 +208,6 @@ export async function mono_download_assets(): Promise {
// wait till after onRuntimeInitialized and after memory snapshot is loaded or skipped
await runtimeHelpers.beforeOnRuntimeInitialized.promise;
- await runtimeHelpers.memorySnapshotSkippedOrDone.promise;
runtimeHelpers.instantiate_asset(asset, url, data);
}
} else {
@@ -284,7 +280,7 @@ export function prepareAssets() {
mono_assert(resources.jsModuleNative, "resources.jsModuleNative must be defined");
mono_assert(resources.jsModuleRuntime, "resources.jsModuleRuntime must be defined");
mono_assert(!MonoWasmThreads || resources.jsModuleWorker, "resources.jsModuleWorker must be defined");
- convert_single_asset(modulesAssets, resources.wasmNative, "dotnetwasm");
+ convert_single_asset(alwaysLoadedAssets, resources.wasmNative, "dotnetwasm");
convert_single_asset(modulesAssets, resources.jsModuleNative, "js-module-native");
convert_single_asset(modulesAssets, resources.jsModuleRuntime, "js-module-runtime");
if (MonoWasmThreads) {
diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts
index c35553ae593e8..097f6d1ca2cb1 100644
--- a/src/mono/wasm/runtime/loader/config.ts
+++ b/src/mono/wasm/runtime/loader/config.ts
@@ -10,6 +10,7 @@ import { importLibraryInitializers, invokeLibraryInitializers } from "./libraryI
import { mono_exit } from "./exit";
import { makeURLAbsoluteWithApplicationBase } from "./polyfills";
import { appendUniqueQuery } from "./assets";
+import { mono_assert } from "./globals";
export function deep_merge_config(target: MonoConfigInternal, source: MonoConfigInternal): MonoConfigInternal {
// no need to merge the same object
@@ -220,15 +221,12 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi
await loaderHelpers.afterConfigLoaded.promise;
return;
}
- configLoaded = true;
- if (!configFilePath) {
- normalizeConfig();
- loaderHelpers.afterConfigLoaded.promise_control.resolve(loaderHelpers.config);
- return;
- }
- mono_log_debug("mono_wasm_load_config");
try {
- await loadBootConfig(module);
+ configLoaded = true;
+ if (configFilePath) {
+ mono_log_debug("mono_wasm_load_config");
+ await loadBootConfig(module);
+ }
normalizeConfig();
@@ -249,7 +247,12 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi
normalizeConfig();
+ mono_assert(!loaderHelpers.config.startupMemoryCache || !module.instantiateWasm, "startupMemoryCache is not supported with Module.instantiateWasm");
+
loaderHelpers.afterConfigLoaded.promise_control.resolve(loaderHelpers.config);
+ if (!loaderHelpers.config.startupMemoryCache) {
+ loaderHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
+ }
} catch (err) {
const errMessage = `Failed to load config file ${configFilePath} ${err} ${(err as Error)?.stack}`;
loaderHelpers.config = module.config = Object.assign(loaderHelpers.config, { message: errMessage, error: err, isError: true });
diff --git a/src/mono/wasm/runtime/loader/exit.ts b/src/mono/wasm/runtime/loader/exit.ts
index e8cb2e42eb2ef..6b6767e89672e 100644
--- a/src/mono/wasm/runtime/loader/exit.ts
+++ b/src/mono/wasm/runtime/loader/exit.ts
@@ -122,9 +122,9 @@ function abort_promises(reason: any) {
loaderHelpers.afterConfigLoaded.promise_control.reject(reason);
loaderHelpers.wasmDownloadPromise.promise_control.reject(reason);
loaderHelpers.runtimeModuleLoaded.promise_control.reject(reason);
+ loaderHelpers.memorySnapshotSkippedOrDone.promise_control.reject(reason);
if (runtimeHelpers.dotnetReady) {
runtimeHelpers.dotnetReady.promise_control.reject(reason);
- runtimeHelpers.memorySnapshotSkippedOrDone.promise_control.reject(reason);
runtimeHelpers.afterInstantiateWasm.promise_control.reject(reason);
runtimeHelpers.beforePreInit.promise_control.reject(reason);
runtimeHelpers.afterPreInit.promise_control.reject(reason);
diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts
index a710ea0cb3b92..55974cf4c5177 100644
--- a/src/mono/wasm/runtime/loader/globals.ts
+++ b/src/mono/wasm/runtime/loader/globals.ts
@@ -87,6 +87,7 @@ export function setLoaderGlobals(
allDownloadsQueued: createPromiseController(),
wasmDownloadPromise: createPromiseController(),
runtimeModuleLoaded: createPromiseController(),
+ memorySnapshotSkippedOrDone: createPromiseController(),
is_exited,
is_runtime_running,
diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts
index a3cb7977a30f0..b3437a5a81d61 100644
--- a/src/mono/wasm/runtime/loader/run.ts
+++ b/src/mono/wasm/runtime/loader/run.ts
@@ -454,10 +454,11 @@ function importModules() {
}
async function initializeModules(es6Modules: [RuntimeModuleExportsInternal, NativeModuleExportsInternal]) {
- const { initializeExports, initializeReplacements, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals, passEmscriptenInternals } = es6Modules[0];
+ const { initializeExports, initializeReplacements, configureRuntimeStartup, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals, passEmscriptenInternals } = es6Modules[0];
const { default: emscriptenFactory } = es6Modules[1];
setRuntimeGlobals(globalObjectsRoot);
initializeExports(globalObjectsRoot);
+ await configureRuntimeStartup();
loaderHelpers.runtimeModuleLoaded.promise_control.resolve();
emscriptenFactory((originalModule: EmscriptenModuleInternal) => {
@@ -494,9 +495,8 @@ async function createEmscriptenMain(): Promise {
mono_exit(1, err);
});
- init_globalization();
-
setTimeout(() => {
+ init_globalization();
mono_download_assets(); // intentionally not awaited
}, 0);
diff --git a/src/mono/wasm/runtime/snapshot.ts b/src/mono/wasm/runtime/snapshot.ts
index 1d1df904643f5..c23422353a7dd 100644
--- a/src/mono/wasm/runtime/snapshot.ts
+++ b/src/mono/wasm/runtime/snapshot.ts
@@ -44,22 +44,35 @@ async function openCache(): Promise {
}
}
-export async function getMemorySnapshotSize(): Promise {
+export async function checkMemorySnapshotSize(): Promise {
try {
+ if (!runtimeHelpers.config.startupMemoryCache) {
+ // we could start downloading DLLs because snapshot is disabled
+ return;
+ }
+
const cacheKey = await getCacheKey();
if (!cacheKey) {
- return undefined;
+ return;
}
const cache = await openCache();
if (!cache) {
- return undefined;
+ return;
}
const res = await cache.match(cacheKey);
const contentLength = res?.headers.get("content-length");
- return contentLength ? parseInt(contentLength) : undefined;
+ const memorySize = contentLength ? parseInt(contentLength) : undefined;
+
+ runtimeHelpers.loadedMemorySnapshotSize = memorySize;
+ runtimeHelpers.storeMemorySnapshotPending = !memorySize;
} catch (ex) {
mono_log_warn("Failed find memory snapshot in the cache", ex);
- return undefined;
+ }
+ finally {
+ if (!runtimeHelpers.loadedMemorySnapshotSize) {
+ // we could start downloading DLLs because there is no snapshot yet
+ loaderHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
+ }
}
}
diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts
index 4200b65503768..e3520777d175c 100644
--- a/src/mono/wasm/runtime/startup.ts
+++ b/src/mono/wasm/runtime/startup.ts
@@ -21,7 +21,7 @@ import { instantiate_wasm_asset, wait_for_all_assets } from "./assets";
import { mono_wasm_init_diagnostics } from "./diagnostics";
import { replace_linker_placeholders } from "./exports-binding";
import { endMeasure, MeasuredBlock, startMeasure } from "./profiler";
-import { getMemorySnapshot, storeMemorySnapshot, getMemorySnapshotSize } from "./snapshot";
+import { checkMemorySnapshotSize, getMemorySnapshot, storeMemorySnapshot } from "./snapshot";
import { mono_log_debug, mono_log_error, mono_log_warn, mono_set_thread_id } from "./logging";
// threads
@@ -40,6 +40,19 @@ import { assertNoProxies } from "./gc-handles";
// default size if MonoConfig.pthreadPoolSize is undefined
const MONO_PTHREAD_POOL_SIZE = 4;
+export async function configureRuntimeStartup(): Promise {
+ if (linkerWasmEnableSIMD) {
+ mono_assert(await loaderHelpers.simd(), "This browser/engine doesn't support WASM SIMD. Please use a modern version. See also https://aka.ms/dotnet-wasm-features");
+ }
+ if (linkerWasmEnableEH) {
+ mono_assert(await loaderHelpers.exceptions(), "This browser/engine doesn't support WASM exception handling. Please use a modern version. See also https://aka.ms/dotnet-wasm-features");
+ }
+
+ await init_polyfills_async();
+
+ await checkMemorySnapshotSize();
+}
+
// we are making emscripten startup async friendly
// emscripten is executing the events without awaiting it and so we need to block progress via PromiseControllers above
export function configureEmscriptenStartup(module: DotnetModuleInternal): void {
@@ -118,8 +131,6 @@ function instantiateWasm(
const mark = startMeasure();
if (userInstantiateWasm) {
- // user wasm instantiation doesn't support memory snapshots
- runtimeHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
const exports = userInstantiateWasm(imports, (instance: WebAssembly.Instance, module: WebAssembly.Module | undefined) => {
endMeasure(mark, MeasuredBlock.instantiateWasm);
runtimeHelpers.afterInstantiateWasm.promise_control.resolve();
@@ -377,14 +388,6 @@ async function mono_wasm_pre_init_essential_async(): Promise {
mono_log_debug("mono_wasm_pre_init_essential_async");
Module.addRunDependency("mono_wasm_pre_init_essential_async");
- if (linkerWasmEnableSIMD) {
- mono_assert(await loaderHelpers.simd(), "This browser/engine doesn't support WASM SIMD. Please use a modern version. See also https://aka.ms/dotnet-wasm-features");
- }
- if (linkerWasmEnableEH) {
- mono_assert(await loaderHelpers.exceptions(), "This browser/engine doesn't support WASM exception handling. Please use a modern version. See also https://aka.ms/dotnet-wasm-features");
- }
-
- await init_polyfills_async();
if (MonoWasmThreads) {
preAllocatePThreadWorkerPool(MONO_PTHREAD_POOL_SIZE, runtimeHelpers.config);
@@ -459,20 +462,9 @@ async function instantiate_wasm_module(
): Promise {
// this is called so early that even Module exports like addRunDependency don't exist yet
try {
- let memorySize: number | undefined = undefined;
await loaderHelpers.afterConfigLoaded;
mono_log_debug("instantiate_wasm_module");
- if (runtimeHelpers.config.startupMemoryCache) {
- memorySize = await getMemorySnapshotSize();
- runtimeHelpers.loadedMemorySnapshot = !!memorySize;
- runtimeHelpers.storeMemorySnapshotPending = !runtimeHelpers.loadedMemorySnapshot;
- }
- if (!runtimeHelpers.loadedMemorySnapshot) {
- // we should start downloading DLLs etc as they are not in the snapshot
- runtimeHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
- }
-
await runtimeHelpers.beforePreInit.promise;
Module.addRunDependency("instantiate_wasm_module");
@@ -486,19 +478,19 @@ async function instantiate_wasm_module(
mono_log_debug("instantiate_wasm_module done");
- if (runtimeHelpers.loadedMemorySnapshot) {
+ if (runtimeHelpers.loadedMemorySnapshotSize) {
try {
const wasmMemory = Module.getMemory();
// .grow() takes a delta compared to the previous size
- wasmMemory.grow((memorySize! - wasmMemory.buffer.byteLength + 65535) >>> 16);
+ wasmMemory.grow((runtimeHelpers.loadedMemorySnapshotSize! - wasmMemory.buffer.byteLength + 65535) >>> 16);
runtimeHelpers.updateMemoryViews();
} catch (err) {
mono_log_warn("failed to resize memory for the snapshot", err);
- runtimeHelpers.loadedMemorySnapshot = false;
+ runtimeHelpers.loadedMemorySnapshotSize = undefined;
}
// now we know if the loading of memory succeeded or not, we can start loading the rest of the assets
- runtimeHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
+ loaderHelpers.memorySnapshotSkippedOrDone.promise_control.resolve();
}
runtimeHelpers.afterInstantiateWasm.promise_control.resolve();
} catch (err) {
@@ -511,7 +503,7 @@ async function instantiate_wasm_module(
async function mono_wasm_before_memory_snapshot() {
const mark = startMeasure();
- if (runtimeHelpers.loadedMemorySnapshot) {
+ if (runtimeHelpers.loadedMemorySnapshotSize) {
// get the bytes after we re-sized the memory, so that we don't have too much memory in use at the same time
const memoryBytes = await getMemorySnapshot();
const heapU8 = localHeapViewU8();
diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts
index 2588115e9eff9..edad8b9bd0bf8 100644
--- a/src/mono/wasm/runtime/types/internal.ts
+++ b/src/mono/wasm/runtime/types/internal.ts
@@ -128,6 +128,7 @@ export type LoaderHelpers = {
allDownloadsQueued: PromiseAndController,
wasmDownloadPromise: PromiseAndController,
runtimeModuleLoaded: PromiseAndController,
+ memorySnapshotSkippedOrDone: PromiseAndController,
is_exited: () => boolean,
is_runtime_running: () => boolean,
@@ -176,7 +177,7 @@ export type RuntimeHelpers = {
mono_wasm_runtime_is_ready: boolean;
mono_wasm_bindings_is_ready: boolean;
- loadedMemorySnapshot: boolean,
+ loadedMemorySnapshotSize?: number,
enablePerfMeasure: boolean;
waitForDebugger?: number;
ExitStatus: ExitStatusError;
@@ -194,7 +195,6 @@ export type RuntimeHelpers = {
allAssetsInMemory: PromiseAndController,
dotnetReady: PromiseAndController,
- memorySnapshotSkippedOrDone: PromiseAndController,
afterInstantiateWasm: PromiseAndController,
beforePreInit: PromiseAndController,
afterPreInit: PromiseAndController,
@@ -490,6 +490,7 @@ export type setGlobalObjectsType = (globalObjects: GlobalObjects) => void;
export type initializeExportsType = (globalObjects: GlobalObjects) => RuntimeAPI;
export type initializeReplacementsType = (replacements: EmscriptenReplacements) => void;
export type configureEmscriptenStartupType = (module: DotnetModuleInternal) => void;
+export type configureRuntimeStartupType = () => Promise;
export type configureWorkerStartupType = (module: DotnetModuleInternal) => Promise
@@ -497,6 +498,7 @@ export type RuntimeModuleExportsInternal = {
setRuntimeGlobals: setGlobalObjectsType,
initializeExports: initializeExportsType,
initializeReplacements: initializeReplacementsType,
+ configureRuntimeStartup: configureRuntimeStartupType,
configureEmscriptenStartup: configureEmscriptenStartupType,
configureWorkerStartup: configureWorkerStartupType,
passEmscriptenInternals: passEmscriptenInternalsType,