diff --git a/src/mono/sample/wasm/browser-advanced/index.html b/src/mono/sample/wasm/browser-advanced/index.html index c8961d7c71540819ed6addd4b8861527786f9ab4..24d51ea29672d66fbe7f626fc9e99f23b30d0401 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 f95fcbf9903be6d16b6e73512f1cce16f9bf9fe6..b5c414322fefd00ab2870e35d1e0b2d321caa65a 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 5bb75e32aa1b751b0ffed1dab678f40fba1d108a..481ffa4e27301fd78adf32d5b6845c83180e9404 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 c1042928d78d076e86cfc7178e12a017a1c07f60..88358e0310a10b30f5a0aec9949a84f986124e13 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 46aa26d85038e1063e1cfb9480192d0e9e701c00..c131002dde7968954d36ef0ebfec65b4ac993002 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 a29fd2da59d84f96d9ec4981f000f5155a8bc5c0..6e50e26e164c0c07db5495b3ebd571d46efe7d52 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 5db69fc91bf61946b8d28eda81ff36851c8b5116..b14bafd5ed2d7b8b99f3848245ecd4401d7c224b 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 ba18815a08db80f99f8f63c1d45ce8e3b2218bb5..be7c0e4592938bccabd52d7c2f5bb080f208a344 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 c35553ae593e8c2bebab8dc4453e7474c1376ca3..097f6d1ca2cb15a65413630448b632accaf03211 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 e8cb2e42eb2ef13d672ed1b4e28533e98596234b..6b6767e89672e6635dcfb2498aa27581213047e2 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 a710ea0cb3b92d74d70e83d1ba3efdc0eb42f7a9..55974cf4c517776001c128fcbb237884a156e076 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 a3cb7977a30f0cf941e9942408bce09a6135e9f1..b3437a5a81d61b4dd1a76e902af41115f05ecb43 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 1d1df904643f51c07a7987faa11334bff8644494..c23422353a7dd3a66f0f511d7c2ae06f7360e2a6 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 f67a8b7c18ba903ad1cd4ecb1459dceb5b34b68d..e6f8d80ef82f4882e90737fee281606c8c20eb85 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 @@ -39,6 +39,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 { @@ -117,8 +130,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(); @@ -375,14 +386,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); @@ -457,20 +460,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"); @@ -484,19 +476,19 @@ async function instantiate_wasm_module( mono_log_debug("instantiate_wasm_module done"); - if (runtimeHelpers.loadedMemorySnapshot) { + if (runtimeHelpers.loadedMemorySnapshotSize) { try { const wasmMemory = (Module.asm?.memory || Module.wasmMemory)!; // .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) { @@ -509,7 +501,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 fcbeb03eadefddecafdb7f5d11fa67f9136b500e..aedf803aceff1ffa6bf9e383feac0e3a007c9207 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,