backport 90388 to Net8 (#91201)
authorPavel Savara <pavel.savara@gmail.com>
Tue, 29 Aug 2023 23:26:43 +0000 (01:26 +0200)
committerGitHub <noreply@github.com>
Tue, 29 Aug 2023 23:26:43 +0000 (16:26 -0700)
15 files changed:
src/mono/sample/wasm/browser-advanced/index.html
src/mono/sample/wasm/browser-advanced/main.js
src/mono/sample/wasm/browser-bench/appstart-frame.html
src/mono/sample/wasm/browser-bench/frame-main.js
src/mono/wasm/features.md
src/mono/wasm/runtime/exports.ts
src/mono/wasm/runtime/globals.ts
src/mono/wasm/runtime/loader/assets.ts
src/mono/wasm/runtime/loader/config.ts
src/mono/wasm/runtime/loader/exit.ts
src/mono/wasm/runtime/loader/globals.ts
src/mono/wasm/runtime/loader/run.ts
src/mono/wasm/runtime/snapshot.ts
src/mono/wasm/runtime/startup.ts
src/mono/wasm/runtime/types/internal.ts

index c8961d7..24d51ea 100644 (file)
   <link rel="preload" href="./blazor.boot.json" as="fetch" crossorigin="use-credentials">
   <link rel="prefetch" href="./dotnet.native.js" as="fetch" crossorigin="anonymous">
   <link rel="prefetch" href="./dotnet.runtime.js" as="fetch" crossorigin="anonymous">
-  <link rel="prefetch" href="./dotnet.native.wasm" as="fetch" crossorigin="anonymous">
-  <!-- users should consider if they optimize for the first load or subsequent load from memory snapshot -->
-  <link rel="prefetch" href="./icudt.dat" as="fetch" crossorigin="anonymous">
-  <link rel="prefetch" href="./System.Private.CoreLib.wasm" as="fetch" crossorigin="anonymous">
+  <link rel="prefetch" href="./advanced-sample.lib.module.js" as="fetch" crossorigin="anonymous">
 </head>
 
 <body>
index f95fcbf..b5c4143 100644 (file)
@@ -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": ""
index 5bb75e3..481ffa4 100644 (file)
@@ -12,9 +12,6 @@
   <link rel="preload" href="./_framework/blazor.boot.json" as="fetch" crossorigin="use-credentials">
   <link rel="prefetch" href="./_framework/dotnet.native.js" as="fetch" crossorigin="anonymous">
   <link rel="prefetch" href="./_framework/dotnet.runtime.js" as="fetch" crossorigin="anonymous">
-  <link rel="prefetch" href="./_framework/dotnet.native.wasm" as="fetch" crossorigin="anonymous">
-  <!-- users should consider if they optimize for the first load or subsequent load from memory snapshot -->
-  <link rel="prefetch" href="./_framework/System.Private.CoreLib.dll" as="fetch" crossorigin="anonymous">
 </head>
 
 <body>
index c104292..88358e0 100644 (file)
@@ -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();
index 46aa26d..c131002 100644 (file)
@@ -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 `<head>` 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
 <link rel="preload" href="./_framework/blazor.boot.json" as="fetch" crossorigin="use-credentials">
 <link rel="prefetch" href="./_framework/dotnet.native.js" as="fetch" crossorigin="anonymous">
 <link rel="prefetch" href="./_framework/dotnet.runtime.js" as="fetch" crossorigin="anonymous">
-<link rel="prefetch" href="./_framework/dotnet.native.wasm" as="fetch" crossorigin="anonymous">
 ```
 
 See also [link rel prefetch on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/prefetch)
index a29fd2d..6e50e26 100644 (file)
@@ -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
index 5db69fc..b14bafd 100644 (file)
@@ -59,7 +59,6 @@ export function setRuntimeGlobals(globalObjects: GlobalObjects) {
         gitHash,
         allAssetsInMemory: createPromiseController<void>(),
         dotnetReady: createPromiseController<any>(),
-        memorySnapshotSkippedOrDone: createPromiseController<void>(),
         afterInstantiateWasm: createPromiseController<void>(),
         beforePreInit: createPromiseController<void>(),
         afterPreInit: createPromiseController<void>(),
index ba18815..be7c0e4 100644 (file)
@@ -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<void> {
             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<void> {
         }
 
         loaderHelpers.allDownloadsQueued.promise_control.resolve();
+
+        // continue after the dotnet.runtime.js was loaded
         await loaderHelpers.runtimeModuleLoaded.promise;
 
         const promises_of_asset_instantiation: Promise<void>[] = [];
@@ -211,7 +208,6 @@ export async function mono_download_assets(): Promise<void> {
                         // 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) {
index c35553a..097f6d1 100644 (file)
@@ -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 });
index e8cb2e4..6b6767e 100644 (file)
@@ -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);
index a710ea0..55974cf 100644 (file)
@@ -87,6 +87,7 @@ export function setLoaderGlobals(
         allDownloadsQueued: createPromiseController<void>(),
         wasmDownloadPromise: createPromiseController<AssetEntryInternal>(),
         runtimeModuleLoaded: createPromiseController<void>(),
+        memorySnapshotSkippedOrDone: createPromiseController<void>(),
 
         is_exited,
         is_runtime_running,
index a3cb797..b3437a5 100644 (file)
@@ -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<RuntimeAPI> {
         mono_exit(1, err);
     });
 
-    init_globalization();
-
     setTimeout(() => {
+        init_globalization();
         mono_download_assets(); // intentionally not awaited
     }, 0);
 
index 1d1df90..c234223 100644 (file)
@@ -44,22 +44,35 @@ async function openCache(): Promise<Cache | null> {
     }
 }
 
-export async function getMemorySnapshotSize(): Promise<number | undefined> {
+export async function checkMemorySnapshotSize(): Promise<void> {
     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();
+        }
     }
 }
 
index f67a8b7..e6f8d80 100644 (file)
@@ -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<void> {
+    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<void> {
     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<void> {
     // 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();
index fcbeb03..aedf803 100644 (file)
@@ -128,6 +128,7 @@ export type LoaderHelpers = {
     allDownloadsQueued: PromiseAndController<void>,
     wasmDownloadPromise: PromiseAndController<AssetEntryInternal>,
     runtimeModuleLoaded: PromiseAndController<void>,
+    memorySnapshotSkippedOrDone: PromiseAndController<void>,
 
     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<void>,
     dotnetReady: PromiseAndController<any>,
-    memorySnapshotSkippedOrDone: PromiseAndController<void>,
     afterInstantiateWasm: PromiseAndController<void>,
     beforePreInit: PromiseAndController<void>,
     afterPreInit: PromiseAndController<void>,
@@ -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<void>;
 export type configureWorkerStartupType = (module: DotnetModuleInternal) => Promise<void>
 
 
@@ -497,6 +498,7 @@ export type RuntimeModuleExportsInternal = {
     setRuntimeGlobals: setGlobalObjectsType,
     initializeExports: initializeExportsType,
     initializeReplacements: initializeReplacementsType,
+    configureRuntimeStartup: configureRuntimeStartupType,
     configureEmscriptenStartup: configureEmscriptenStartupType,
     configureWorkerStartup: configureWorkerStartupType,
     passEmscriptenInternals: passEmscriptenInternalsType,