Skip to content

Commit

Permalink
backport 90388 to Net8 (#91201)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara authored Aug 29, 2023
1 parent 776a845 commit dddf0d0
Show file tree
Hide file tree
Showing 15 changed files with 74 additions and 69 deletions.
5 changes: 1 addition & 4 deletions src/mono/sample/wasm/browser-advanced/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@
<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>
Expand Down
1 change: 1 addition & 0 deletions src/mono/sample/wasm/browser-advanced/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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": ""
Expand Down
3 changes: 0 additions & 3 deletions src/mono/sample/wasm/browser-bench/appstart-frame.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
5 changes: 4 additions & 1 deletion src/mono/sample/wasm/browser-bench/frame-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,17 @@ try {
}

const runtime = await dotnet
.withConfig({
maxParallelDownloads: 10000,
// diagnosticTracing:true,
})
.withModuleConfig({
printErr: () => undefined,
print: () => undefined,
onConfigLoaded: (config) => {
if (window.parent != window) {
window.parent.resolveAppStartEvent("onConfigLoaded");
}
// config.diagnosticTracing = true;
}
})
.create();
Expand Down
3 changes: 2 additions & 1 deletion src/mono/wasm/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/mono/wasm/runtime/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -143,5 +143,5 @@ class RuntimeList {

// export external API
export {
passEmscriptenInternals, initializeExports, initializeReplacements, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals
passEmscriptenInternals, initializeExports, initializeReplacements, configureRuntimeStartup, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals
};
1 change: 0 additions & 1 deletion src/mono/wasm/runtime/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>(),
Expand Down
18 changes: 7 additions & 11 deletions src/mono/wasm/runtime/loader/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ const containedInSnapshotByAssetTypes: {
"pdb": true,
"heap": true,
"icu": true,
...jsModulesAssetTypes,
"dotnetwasm": true,
};

// these assets are instantiated differently than the main flow
Expand All @@ -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`);

Expand All @@ -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;
}

Expand Down Expand Up @@ -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.
Expand All @@ -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>[] = [];
Expand All @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
19 changes: 11 additions & 8 deletions src/mono/wasm/runtime/loader/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();

Expand All @@ -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 });
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/loader/exit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/mono/wasm/runtime/loader/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export function setLoaderGlobals(
allDownloadsQueued: createPromiseController<void>(),
wasmDownloadPromise: createPromiseController<AssetEntryInternal>(),
runtimeModuleLoaded: createPromiseController<void>(),
memorySnapshotSkippedOrDone: createPromiseController<void>(),

is_exited,
is_runtime_running,
Expand Down
6 changes: 3 additions & 3 deletions src/mono/wasm/runtime/loader/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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);

Expand Down
23 changes: 18 additions & 5 deletions src/mono/wasm/runtime/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}

Expand Down
46 changes: 19 additions & 27 deletions src/mono/wasm/runtime/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");

Expand All @@ -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) {
Expand All @@ -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();
Expand Down
Loading

0 comments on commit dddf0d0

Please sign in to comment.