diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index 6979b8536abaa5..be23863e311932 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -38,7 +38,7 @@ - + diff --git a/src/mono/sample/wasm/browser-bench/frame-main.js b/src/mono/sample/wasm/browser-bench/frame-main.js index bf767c9e7ebc5f..9045a9544988da 100644 --- a/src/mono/sample/wasm/browser-bench/frame-main.js +++ b/src/mono/sample/wasm/browser-bench/frame-main.js @@ -38,11 +38,11 @@ try { console.error(...arguments); } }, - onConfigLoaded: () => { + onConfigLoaded: (config) => { if (window.parent != window) { window.parent.resolveAppStartEvent("onConfigLoaded"); } - // Module.config.diagnosticTracing = true; + // config.diagnosticTracing = true; }, onAbort: (error) => { wasm_exit(1, error); diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts new file mode 100644 index 00000000000000..362d6ba0f38a55 --- /dev/null +++ b/src/mono/wasm/runtime/assets.ts @@ -0,0 +1,415 @@ +import cwraps from "./cwraps"; +import { mono_wasm_load_icu_data } from "./icu"; +import { ENVIRONMENT_IS_WEB, Module, runtimeHelpers } from "./imports"; +import { mono_wasm_load_bytes_into_heap } from "./memory"; +import { MONO } from "./net6-legacy/imports"; +import { createPromiseController, PromiseAndController } from "./promise-controller"; +import { delay } from "./promise-utils"; +import { beforeOnRuntimeInitialized } from "./startup"; +import { AssetBehaviours, AssetEntry, LoadingResource, mono_assert, ResourceRequest } from "./types"; +import { InstantiateWasmSuccessCallback, VoidPtr } from "./types/emscripten"; + +const allAssetsInMemory = createPromiseController(); +const allDownloadsQueued = createPromiseController(); +let downloded_assets_count = 0; +let instantiated_assets_count = 0; +const loaded_files: { url: string, file: string }[] = []; +const loaded_assets: { [id: string]: [VoidPtr, number] } = Object.create(null); +// in order to prevent net::ERR_INSUFFICIENT_RESOURCES if we start downloading too many files at same time +let parallel_count = 0; +let throttling: PromiseAndController | undefined; +const skipDownloadsAssetTypes: { + [k: string]: boolean +} = { + "js-module-crypto": true, + "js-module-threads": true, +}; + +export function resolve_asset_path(behavior: AssetBehaviours) { + const asset: AssetEntry | undefined = runtimeHelpers.config.assets?.find(a => a.behavior == behavior); + mono_assert(asset, () => `Can't find asset for ${behavior}`); + if (!asset.resolvedUrl) { + asset.resolvedUrl = resolve_path(asset, ""); + } + return asset; +} + +export async function mono_download_assets(): Promise { + if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_download_assets"); + runtimeHelpers.maxParallelDownloads = runtimeHelpers.config.maxParallelDownloads || runtimeHelpers.maxParallelDownloads; + try { + const download_promises: Promise[] = []; + // start fetching and instantiating all assets in parallel + for (const asset of runtimeHelpers.config.assets!) { + if (!asset.pending && !skipDownloadsAssetTypes[asset.behavior]) { + download_promises.push(start_asset_download(asset)); + } + } + allDownloadsQueued.promise_control.resolve(); + + const asset_promises: Promise[] = []; + for (const downloadPromise of download_promises) { + const downloadedAsset = await downloadPromise; + if (downloadedAsset) { + asset_promises.push((async () => { + const url = downloadedAsset.pending!.url; + const response = await downloadedAsset.pending!.response; + downloadedAsset.pending = undefined; //GC + const buffer = await response.arrayBuffer(); + await beforeOnRuntimeInitialized.promise; + // this is after onRuntimeInitialized + _instantiate_asset(downloadedAsset, url, new Uint8Array(buffer)); + })()); + } + } + + // this await will get past the onRuntimeInitialized because we are not blocking via addRunDependency + // and we are not awating it here + Promise.all(asset_promises).then(() => allAssetsInMemory.promise_control.resolve()); + // OPTIMIZATION explained: + // we do it this way so that we could allocate memory immediately after asset is downloaded (and after onRuntimeInitialized which happened already) + // spreading in time + // rather than to block all downloads after onRuntimeInitialized or block onRuntimeInitialized after all downloads are done. That would create allocation burst. + } catch (err: any) { + Module.printErr("MONO_WASM: Error in mono_download_assets: " + err); + throw err; + } +} + +export async function start_asset_download(asset: AssetEntry): Promise { + try { + return await start_asset_download_throttle(asset); + } catch (err: any) { + if (err && err.status == 404) { + throw err; + } + // second attempt only after all first attempts are queued + await allDownloadsQueued.promise; + try { + return await start_asset_download_throttle(asset); + } catch (err) { + // third attempt after small delay + await delay(100); + return await start_asset_download_throttle(asset); + } + } +} + +function resolve_path(asset: AssetEntry, sourcePrefix: string): string { + let attemptUrl; + const assemblyRootFolder = runtimeHelpers.config.assemblyRootFolder; + if (!asset.resolvedUrl) { + if (sourcePrefix === "") { + if (asset.behavior === "assembly" || asset.behavior === "pdb") + attemptUrl = assemblyRootFolder + "/" + asset.name; + else if (asset.behavior === "resource") { + const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; + attemptUrl = assemblyRootFolder + "/" + path; + } + else { + attemptUrl = asset.name; + } + } else { + attemptUrl = sourcePrefix + asset.name; + } + attemptUrl = runtimeHelpers.locateFile(attemptUrl); + } + else { + attemptUrl = asset.resolvedUrl; + } + return attemptUrl; +} + +function download_resource(request: ResourceRequest): LoadingResource { + if (typeof Module.downloadResource === "function") { + const loading = Module.downloadResource(request); + if (loading) return loading; + } + const options: any = {}; + if (request.hash) { + options.integrity = request.hash; + } + const response = runtimeHelpers.fetch_like(request.resolvedUrl!, options); + return { + name: request.name, url: request.resolvedUrl!, response + }; +} + +async function start_asset_download_sources(asset: AssetEntry): Promise { + // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! + if (asset.buffer) { + ++downloded_assets_count; + const buffer = asset.buffer; + asset.buffer = undefined;//GC later + asset.pending = { + url: "undefined://" + asset.name, + name: asset.name, + response: Promise.resolve({ + arrayBuffer: () => buffer, + headers: { + get: () => undefined, + } + }) as any + }; + return Promise.resolve(asset); + } + if (asset.pending) { + ++downloded_assets_count; + return asset; + } + + const sourcesList = asset.loadRemote && runtimeHelpers.config.remoteSources ? runtimeHelpers.config.remoteSources : [""]; + let response: Response | undefined = undefined; + for (let sourcePrefix of sourcesList) { + sourcePrefix = sourcePrefix.trim(); + // HACK: Special-case because MSBuild doesn't allow "" as an attribute + if (sourcePrefix === "./") + sourcePrefix = ""; + + const attemptUrl = resolve_path(asset, sourcePrefix); + if (asset.name === attemptUrl) { + if (runtimeHelpers.diagnosticTracing) + console.debug(`MONO_WASM: Attempting to download '${attemptUrl}'`); + } else { + if (runtimeHelpers.diagnosticTracing) + console.debug(`MONO_WASM: Attempting to download '${attemptUrl}' for ${asset.name}`); + } + try { + const loadingResource = download_resource({ + name: asset.name, + resolvedUrl: attemptUrl, + hash: asset.hash, + behavior: asset.behavior + }); + asset.pending = loadingResource; + response = await loadingResource.response; + if (!response.ok) { + continue;// next source + } + ++downloded_assets_count; + return asset; + } + catch (err) { + continue; //next source + } + } + throw response; +} + +async function start_asset_download_throttle(asset: AssetEntry): Promise { + // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! + while (throttling) { + await throttling.promise; + } + try { + ++parallel_count; + if (parallel_count == runtimeHelpers.maxParallelDownloads) { + if (runtimeHelpers.diagnosticTracing) + console.debug("MONO_WASM: Throttling further parallel downloads"); + throttling = createPromiseController(); + } + return await start_asset_download_sources(asset); + } + catch (response: any) { + const isOkToFail = asset.isOptional || (asset.name.match(/\.pdb$/) && runtimeHelpers.config.ignorePdbLoadErrors); + if (!isOkToFail) { + const err: any = new Error(`MONO_WASM: download '${response.url}' for ${asset.name} failed ${response.status} ${response.statusText}`); + err.status = response.status; + throw err; + } + } + finally { + --parallel_count; + if (throttling && parallel_count == runtimeHelpers.maxParallelDownloads - 1) { + if (runtimeHelpers.diagnosticTracing) + console.debug("MONO_WASM: Resuming more parallel downloads"); + const old_throttling = throttling; + throttling = undefined; + old_throttling.promise_control.resolve(); + } + } +} + +// this need to be run only after onRuntimeInitialized event, when the memory is ready +function _instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array) { + if (runtimeHelpers.diagnosticTracing) + console.debug(`MONO_WASM: Loaded:${asset.name} as ${asset.behavior} size ${bytes.length} from ${url}`); + + const virtualName: string = typeof (asset.virtualPath) === "string" + ? asset.virtualPath + : asset.name; + let offset: VoidPtr | null = null; + + switch (asset.behavior) { + case "dotnetwasm": + case "js-module-crypto": + case "js-module-threads": + // do nothing + break; + case "resource": + case "assembly": + case "pdb": + loaded_files.push({ url: url, file: virtualName }); + // falls through + case "heap": + case "icu": + offset = mono_wasm_load_bytes_into_heap(bytes); + loaded_assets[virtualName] = [offset, bytes.length]; + break; + + case "vfs": { + // FIXME + const lastSlash = virtualName.lastIndexOf("/"); + let parentDirectory = (lastSlash > 0) + ? virtualName.substr(0, lastSlash) + : null; + let fileName = (lastSlash > 0) + ? virtualName.substr(lastSlash + 1) + : virtualName; + if (fileName.startsWith("/")) + fileName = fileName.substr(1); + if (parentDirectory) { + if (runtimeHelpers.diagnosticTracing) + console.debug(`MONO_WASM: Creating directory '${parentDirectory}'`); + + Module.FS_createPath( + "/", parentDirectory, true, true // fixme: should canWrite be false? + ); + } else { + parentDirectory = "/"; + } + + if (runtimeHelpers.diagnosticTracing) + console.debug(`MONO_WASM: Creating file '${fileName}' in directory '${parentDirectory}'`); + + if (!mono_wasm_load_data_archive(bytes, parentDirectory)) { + Module.FS_createDataFile( + parentDirectory, fileName, + bytes, true /* canRead */, true /* canWrite */, true /* canOwn */ + ); + } + break; + } + default: + throw new Error(`Unrecognized asset behavior:${asset.behavior}, for asset ${asset.name}`); + } + + if (asset.behavior === "assembly") { + // this is reading flag inside the DLL about the existence of PDB + // it doesn't relate to whether the .pdb file is downloaded at all + const hasPpdb = cwraps.mono_wasm_add_assembly(virtualName, offset!, bytes.length); + + if (!hasPpdb) { + const index = loaded_files.findIndex(element => element.file == virtualName); + loaded_files.splice(index, 1); + } + } + else if (asset.behavior === "icu") { + if (!mono_wasm_load_icu_data(offset!)) + Module.printErr(`MONO_WASM: Error loading ICU asset ${asset.name}`); + } + else if (asset.behavior === "resource") { + cwraps.mono_wasm_add_satellite_assembly(virtualName, asset.culture!, offset!, bytes.length); + } + ++instantiated_assets_count; +} + +export async function instantiate_wasm_asset( + pendingAsset: AssetEntry, + wasmModuleImports: WebAssembly.Imports, + successCallback: InstantiateWasmSuccessCallback, +) { + mono_assert(pendingAsset && pendingAsset.pending, "Can't load dotnet.wasm"); + const response = await pendingAsset.pending.response; + const contentType = response.headers ? response.headers.get("Content-Type") : undefined; + let compiledInstance: WebAssembly.Instance; + let compiledModule: WebAssembly.Module; + if (typeof WebAssembly.instantiateStreaming === "function" && contentType === "application/wasm") { + if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module streaming"); + const streamingResult = await WebAssembly.instantiateStreaming(response, wasmModuleImports!); + compiledInstance = streamingResult.instance; + compiledModule = streamingResult.module; + } else { + if (ENVIRONMENT_IS_WEB && contentType !== "application/wasm") { + console.warn("MONO_WASM: WebAssembly resource does not have the expected content type \"application/wasm\", so falling back to slower ArrayBuffer instantiation."); + } + const arrayBuffer = await response.arrayBuffer(); + if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module buffered"); + const arrayBufferResult = await WebAssembly.instantiate(arrayBuffer, wasmModuleImports!); + compiledInstance = arrayBufferResult.instance; + compiledModule = arrayBufferResult.module; + } + ++instantiated_assets_count; + successCallback(compiledInstance, compiledModule); +} + +// used from Blazor +export function mono_wasm_load_data_archive(data: Uint8Array, prefix: string): boolean { + if (data.length < 8) + return false; + + const dataview = new DataView(data.buffer); + const magic = dataview.getUint32(0, true); + // get magic number + if (magic != 0x626c6174) { + return false; + } + const manifestSize = dataview.getUint32(4, true); + if (manifestSize == 0 || data.length < manifestSize + 8) + return false; + + let manifest; + try { + const manifestContent = Module.UTF8ArrayToString(data, 8, manifestSize); + manifest = JSON.parse(manifestContent); + if (!(manifest instanceof Array)) + return false; + } catch (exc) { + return false; + } + + data = data.slice(manifestSize + 8); + + // Create the folder structure + // /usr/share/zoneinfo + // /usr/share/zoneinfo/Africa + // /usr/share/zoneinfo/Asia + // .. + + const folders = new Set(); + manifest.filter(m => { + const file = m[0]; + const last = file.lastIndexOf("/"); + const directory = file.slice(0, last + 1); + folders.add(directory); + }); + folders.forEach(folder => { + Module["FS_createPath"](prefix, folder, true, true); + }); + + for (const row of manifest) { + const name = row[0]; + const length = row[1]; + const bytes = data.slice(0, length); + Module["FS_createDataFile"](prefix, name, bytes, true, true); + data = data.slice(length); + } + return true; +} + +export async function wait_for_all_assets() { + // wait for all assets in memory + await allAssetsInMemory.promise; + if (runtimeHelpers.config.assets) { + const expected_asset_count = runtimeHelpers.config.assets.filter(a => !skipDownloadsAssetTypes[a.behavior]).length; + mono_assert(downloded_assets_count == expected_asset_count, "Expected assets to be downloaded"); + mono_assert(instantiated_assets_count == expected_asset_count, "Expected assets to be in memory"); + loaded_files.forEach(value => MONO.loaded_files.push(value.url)); + if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: all assets are loaded in wasm memory"); + } +} + +// Used by the debugger to enumerate loaded dlls and pdbs +export function mono_wasm_get_loaded_files(): string[] { + return MONO.loaded_files; +} diff --git a/src/mono/wasm/runtime/crypto-worker.ts b/src/mono/wasm/runtime/crypto-worker.ts index adde81a9ec9bca..82fafbf3e6f050 100644 --- a/src/mono/wasm/runtime/crypto-worker.ts +++ b/src/mono/wasm/runtime/crypto-worker.ts @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { resolve_asset_path } from "./assets"; import { Module, runtimeHelpers } from "./imports"; import { mono_assert } from "./types"; @@ -107,22 +108,24 @@ export function init_crypto(): void { console.debug("MONO_WASM: Initializing Crypto WebWorker"); const chan = LibraryChannel.create(1024); // 1024 is the buffer size in char units. - const worker = new Worker("dotnet-crypto-worker.js"); + const asset = resolve_asset_path("js-module-crypto"); + mono_assert(asset && asset.resolvedUrl, "Can't find js-module-crypto"); + const worker = new Worker(asset.resolvedUrl); mono_wasm_crypto = { channel: chan, worker: worker, }; const messageData: InitCryptoMessageData = { - config: JSON.stringify(runtimeHelpers.config), + config: JSON.stringify(runtimeHelpers.config),// there could be things in config which could not be cloned to worker comm_buf: chan.get_comm_buffer(), msg_buf: chan.get_msg_buffer(), msg_char_len: chan.get_msg_len() }; - worker.postMessage(messageData); worker.onerror = event => { console.warn(`MONO_WASM: Error in Crypto WebWorker. Cryptography digest calls will fallback to managed implementation. Error: ${event.message}`); mono_wasm_crypto = null; }; + worker.postMessage(messageData); } } diff --git a/src/mono/wasm/runtime/debug.ts b/src/mono/wasm/runtime/debug.ts index 0367fc71f59620..43e9ffb83d3e11 100644 --- a/src/mono/wasm/runtime/debug.ts +++ b/src/mono/wasm/runtime/debug.ts @@ -1,14 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import Configuration from "consts:configuration"; +import BuildConfiguration from "consts:configuration"; import { INTERNAL, Module, runtimeHelpers } from "./imports"; import { toBase64StringImpl } from "./base64"; import cwraps from "./cwraps"; import { VoidPtr, CharPtr } from "./types/emscripten"; -import { MONO } from "./net6-legacy/imports"; const commands_received: any = new Map(); -const wasm_func_map = new Map(); commands_received.remove = function (key: number): CommandResponse { const value = this.get(key); this.delete(key); return value; }; let _call_function_res_cache: any = {}; let _next_call_function_res_id = 0; @@ -17,23 +15,6 @@ let _debugger_buffer: VoidPtr; let _assembly_name_str: string; //keep this variable, it's used by BrowserDebugProxy let _entrypoint_method_token: number; //keep this variable, it's used by BrowserDebugProxy -const regexes: any[] = []; - -// V8 -// at :wasm-function[1900]:0x83f63 -// at dlfree (:wasm-function[18739]:0x2328ef) -regexes.push(/at (?[^:()]+:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)((?![^)a-fA-F\d])|$)/); - -//# 5: WASM [009712b2], function #111 (''), pc=0x7c16595c973 (+0x53), pos=38740 (+11) -regexes.push(/(?:WASM \[[\da-zA-Z]+\], (?function #(?[\d]+) \(''\)))/); - -//# chrome -//# at http://127.0.0.1:63817/dotnet.wasm:wasm-function[8963]:0x1e23f4 -regexes.push(/(?[a-z]+:\/\/[^ )]*:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)/); - -//# .wasm-function[8962] -regexes.push(/(?<[^ >]+>[.:]wasm-function\[(?[0-9]+)\])/); - export function mono_wasm_runtime_ready(): void { INTERNAL.mono_wasm_runtime_is_ready = runtimeHelpers.mono_wasm_runtime_is_ready = true; @@ -49,7 +30,6 @@ export function mono_wasm_runtime_ready(): void { else console.debug("mono_wasm_runtime_ready", "fe00e07a-5519-4dfe-b35a-f867dbaf2e28"); - _readSymbolMapFile("dotnet.js.symbols"); } export function mono_wasm_fire_debugger_agent_message(): void { @@ -143,11 +123,6 @@ export function mono_wasm_raise_debug_event(event: WasmEvent, args = {}): void { console.debug("mono_wasm_debug_event_raised:aef14bca-5519-4dfe-b35a-f867abc123ae", JSON.stringify(event), JSON.stringify(args)); } -// Used by the debugger to enumerate loaded dlls and pdbs -export function mono_wasm_get_loaded_files(): string[] { - return MONO.loaded_files; -} - export function mono_wasm_wait_for_debugger(): Promise { return new Promise((resolve) => { const interval = setInterval(() => { @@ -365,193 +340,11 @@ export function mono_wasm_debugger_log(level: number, message_ptr: CharPtr): voi return; } - if (Configuration === "Debug") { + if (BuildConfiguration === "Debug") { console.debug(`MONO_WASM: Debugger.Debug: ${message}`); } } -function _readSymbolMapFile(filename: string): void { - try { - const res = Module.FS_readFile(filename, { flags: "r", encoding: "utf8" }); - res.split(/[\r\n]/).forEach((line: string) => { - const parts: string[] = line.split(/:/); - if (parts.length < 2) - return; - - parts[1] = parts.splice(1).join(":"); - wasm_func_map.set(Number(parts[0]), parts[1]); - }); - if (Configuration === "Debug") { - console.debug(`MONO_WASM: Loaded ${wasm_func_map.size} symbols`); - } - } catch (error: any) { - if (error.errno == 44) {// NOENT - if (Configuration === "Debug") { - console.debug(`MONO_WASM: Could not find symbols file ${filename}. Ignoring.`); - } - } - else { - console.log(`MONO_WASM: Error loading symbol file ${filename}: ${JSON.stringify(error)}`); - } - return; - } -} - -export function mono_wasm_symbolicate_string(message: string): string { - try { - if (wasm_func_map.size == 0) - return message; - - const origMessage = message; - - for (let i = 0; i < regexes.length; i++) { - const newRaw = message.replace(new RegExp(regexes[i], "g"), (substring, ...args) => { - const groups = args.find(arg => { - return typeof (arg) == "object" && arg.replaceSection !== undefined; - }); - - if (groups === undefined) - return substring; - - const funcNum = groups.funcNum; - const replaceSection = groups.replaceSection; - const name = wasm_func_map.get(Number(funcNum)); - - if (name === undefined) - return substring; - - return substring.replace(replaceSection, `${name} (${replaceSection})`); - }); - - if (newRaw !== origMessage) - return newRaw; - } - - return origMessage; - } catch (error) { - console.debug(`MONO_WASM: failed to symbolicate: ${error}`); - return message; - } -} - -export function mono_wasm_stringify_as_error_with_stack(err: Error | string): string { - let errObj: any = err; - if (!(err instanceof Error)) - errObj = new Error(err); - - // Error - return mono_wasm_symbolicate_string(errObj.stack); -} - -export function mono_wasm_trace_logger(log_domain_ptr: CharPtr, log_level_ptr: CharPtr, message_ptr: CharPtr, fatal: number, user_data: VoidPtr): void { - const origMessage = Module.UTF8ToString(message_ptr); - const isFatal = !!fatal; - const domain = Module.UTF8ToString(log_domain_ptr); - const dataPtr = user_data; - const log_level = Module.UTF8ToString(log_level_ptr); - - const message = `[MONO] ${origMessage}`; - - if (INTERNAL["logging"] && typeof INTERNAL.logging["trace"] === "function") { - INTERNAL.logging.trace(domain, log_level, message, isFatal, dataPtr); - return; - } - - switch (log_level) { - case "critical": - case "error": - console.error(mono_wasm_stringify_as_error_with_stack(message)); - break; - case "warning": - console.warn(message); - break; - case "message": - console.log(message); - break; - case "info": - console.info(message); - break; - case "debug": - console.debug(message); - break; - default: - console.log(message); - break; - } -} - -export function setup_proxy_console(id: string, console: any, origin: string): void { - function proxyConsoleMethod(prefix: string, func: any, asJson: boolean) { - return function (...args: any[]) { - try { - let payload = args[0]; - if (payload === undefined) payload = "undefined"; - else if (payload === null) payload = "null"; - else if (typeof payload === "function") payload = payload.toString(); - else if (typeof payload !== "string") { - try { - payload = JSON.stringify(payload); - } catch (e) { - payload = payload.toString(); - } - } - - if (typeof payload === "string") - payload = `[${id}] ${payload}`; - - if (asJson) { - func(JSON.stringify({ - method: prefix, - payload: payload, - arguments: args - })); - } else { - func([prefix + payload, ...args.slice(1)]); - } - } catch (err) { - originalConsole.error(`proxyConsole failed: ${err}`); - } - }; - } - - const originalConsole = { - log: console.log, - error: console.error - }; - const methods = ["debug", "trace", "warn", "info", "error"]; - for (const m of methods) { - if (typeof (console[m]) !== "function") { - console[m] = proxyConsoleMethod(`console.${m}: `, originalConsole.log, false); - } - } - - const consoleUrl = `${origin}/console`.replace("https://", "wss://").replace("http://", "ws://"); - - const consoleWebSocket = new WebSocket(consoleUrl); - consoleWebSocket.onopen = function () { - originalConsole.log(`browser: [${id}] Console websocket connected.`); - }; - consoleWebSocket.onerror = function (event) { - originalConsole.error(`[${id}] websocket error: ${event}`, event); - }; - consoleWebSocket.onclose = function (event) { - originalConsole.error(`[${id}] websocket closed: ${event}`, event); - }; - - const send = (msg: string) => { - if (consoleWebSocket.readyState === WebSocket.OPEN) { - consoleWebSocket.send(msg); - } - else { - originalConsole.log(msg); - } - }; - - // redirect output early, so that when emscripten starts it's already redirected - for (const m of ["log", ...methods]) - console[m] = proxyConsoleMethod(`console.${m}`, send, true); -} - type CallDetails = { value: string } diff --git a/src/mono/wasm/runtime/dotnet-legacy.d.ts b/src/mono/wasm/runtime/dotnet-legacy.d.ts index a3b7591887d3ac..6bfdd64e14bcc7 100644 --- a/src/mono/wasm/runtime/dotnet-legacy.d.ts +++ b/src/mono/wasm/runtime/dotnet-legacy.d.ts @@ -304,4 +304,4 @@ declare type MONOType = { getF64: (offset: MemOffset) => number; }; -export { BINDINGType, MONOType }; +export { BINDINGType, MONOType, MonoArray, MonoObject, MonoString }; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 131f2bb4da5442..eacddc7a017bbe 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -47,7 +47,7 @@ declare interface EmscriptenModule { stackRestore(stack: VoidPtr): void; stackAlloc(size: number): VoidPtr; ready: Promise; - instantiateWasm?: (imports: WebAssembly.Imports, successCallback: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void) => any; + instantiateWasm?: InstantiateWasmCallBack; preInit?: (() => any)[] | (() => any); preRun?: (() => any)[] | (() => any); onRuntimeInitialized?: () => any; @@ -56,6 +56,8 @@ declare interface EmscriptenModule { (error: any): void; }; } +declare type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: WebAssembly.Module) => void; +declare type InstantiateWasmCallBack = (imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any; declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; declare type MonoConfig = { @@ -94,7 +96,7 @@ interface AssetEntry extends ResourceRequest { buffer?: ArrayBuffer; pending?: LoadingResource; } -declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm"; +declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-crypto" | "js-module-threads"; declare type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". "invariant" | // operate in invariant globalization mode. "auto"; diff --git a/src/mono/wasm/runtime/exports-internal.ts b/src/mono/wasm/runtime/exports-internal.ts index a5c9219b2933e3..b2b8c4b958332c 100644 --- a/src/mono/wasm/runtime/exports-internal.ts +++ b/src/mono/wasm/runtime/exports-internal.ts @@ -1,6 +1,6 @@ import { mono_wasm_cancel_promise } from "./cancelable-promise"; import cwraps from "./cwraps"; -import { mono_wasm_symbolicate_string, mono_wasm_stringify_as_error_with_stack, mono_wasm_get_loaded_files, mono_wasm_send_dbg_command_with_parms, mono_wasm_send_dbg_command, mono_wasm_get_dbg_command_info, mono_wasm_get_details, mono_wasm_release_object, mono_wasm_call_function_on, mono_wasm_debugger_resume, mono_wasm_detach_debugger, mono_wasm_raise_debug_event, mono_wasm_change_debugger_log_level, mono_wasm_debugger_attached } from "./debug"; +import { mono_wasm_send_dbg_command_with_parms, mono_wasm_send_dbg_command, mono_wasm_get_dbg_command_info, mono_wasm_get_details, mono_wasm_release_object, mono_wasm_call_function_on, mono_wasm_debugger_resume, mono_wasm_detach_debugger, mono_wasm_raise_debug_event, mono_wasm_change_debugger_log_level, mono_wasm_debugger_attached } from "./debug"; import { get_dotnet_instance } from "./exports"; import { http_wasm_supports_streaming_response, http_wasm_create_abort_controler, http_wasm_abort_request, http_wasm_abort_response, http_wasm_fetch, http_wasm_fetch_bytes, http_wasm_get_response_header_names, http_wasm_get_response_header_values, http_wasm_get_response_bytes, http_wasm_get_response_length, http_wasm_get_streamed_response_bytes } from "./http"; import { Module, runtimeHelpers } from "./imports"; @@ -8,7 +8,9 @@ import { get_property, set_property, has_property, get_typeof_property, get_glob import { mono_method_resolve } from "./net6-legacy/method-binding"; import { mono_wasm_set_runtime_options } from "./startup"; import { mono_intern_string } from "./strings"; +import { mono_wasm_stringify_as_error_with_stack } from "./logging"; import { ws_wasm_create, ws_wasm_open, ws_wasm_send, ws_wasm_receive, ws_wasm_close, ws_wasm_abort } from "./web-socket"; +import { mono_wasm_get_loaded_files } from "./assets"; export function export_internal(): any { return { @@ -24,8 +26,6 @@ export function export_internal(): any { // with mono_wasm_debugger_log and mono_wasm_trace_logger logging: undefined, - // - mono_wasm_symbolicate_string, mono_wasm_stringify_as_error_with_stack, // used in debugger DevToolsHelper.cs diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts index a8d2c08453d75d..6a97b55421ef73 100644 --- a/src/mono/wasm/runtime/exports-linker.ts +++ b/src/mono/wasm/runtime/exports-linker.ts @@ -1,6 +1,6 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import { dotnet_browser_can_use_subtle_crypto_impl, dotnet_browser_simple_digest_hash, dotnet_browser_sign, dotnet_browser_encrypt_decrypt, dotnet_browser_derive_bits } from "./crypto-worker"; -import { mono_wasm_fire_debugger_agent_message, mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_trace_logger, mono_wasm_set_entrypoint_breakpoint } from "./debug"; +import { mono_wasm_fire_debugger_agent_message, mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint } from "./debug"; import { mono_wasm_release_cs_owned_object } from "./gc-handles"; import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from "./icu"; import { mono_wasm_bind_cs_function } from "./invoke-cs"; @@ -19,6 +19,7 @@ import { mono_wasm_diagnostic_server_on_runtime_server_init, mono_wasm_event_pip import { mono_wasm_diagnostic_server_stream_signal_work_available } from "./diagnostics/server_pthread/stream-queue"; import { mono_wasm_create_cs_owned_object_ref } from "./net6-legacy/cs-to-js"; import { mono_wasm_typed_array_to_array_ref } from "./net6-legacy/js-to-cs"; +import { mono_wasm_trace_logger } from "./logging"; // the methods would be visible to EMCC linker // --- keep in sync with dotnet.cjs.lib.js --- diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index 46eede120336b0..ea46886cfe674d 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. import ProductVersion from "consts:productVersion"; -import BuildConfiguration from "consts:configuration"; import MonoWasmThreads from "consts:monoWasmThreads"; +import BuildConfiguration from "consts:configuration"; import { ENVIRONMENT_IS_PTHREAD, set_imports_exports } from "./imports"; import { DotnetModule, is_nullish, EarlyImports, EarlyExports, EarlyReplacements, MonoConfig } from "./types"; diff --git a/src/mono/wasm/runtime/icu.ts b/src/mono/wasm/runtime/icu.ts index 93ea54d9d9fab0..3e6e7105335c48 100644 --- a/src/mono/wasm/runtime/icu.ts +++ b/src/mono/wasm/runtime/icu.ts @@ -3,7 +3,6 @@ import cwraps from "./cwraps"; import { Module, runtimeHelpers } from "./imports"; -import { MonoConfig } from "./types"; import { VoidPtr } from "./types/emscripten"; let num_icu_assets_loaded_successfully = 0; @@ -30,7 +29,7 @@ export function mono_wasm_get_icudt_name(culture: string): string { // "auto" will use "icu" if any ICU data archives have been loaded, // otherwise "invariant". export function mono_wasm_globalization_init(): void { - const config = Module.config as MonoConfig; + const config = runtimeHelpers.config; let invariantMode = false; if (!config.globalizationMode) config.globalizationMode = "auto"; diff --git a/src/mono/wasm/runtime/imports.ts b/src/mono/wasm/runtime/imports.ts index e12bc3857f6a2f..2e4a619238fd7b 100644 --- a/src/mono/wasm/runtime/imports.ts +++ b/src/mono/wasm/runtime/imports.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/triple-slash-reference */ /// +/// import { DotnetModule, EarlyExports, EarlyImports, RuntimeHelpers } from "./types"; import { EmscriptenModule } from "./types/emscripten"; diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index f6a65f43f1d3f8..e078ef8e52ea7f 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -11,7 +11,7 @@ import { Int32Ptr } from "./types/emscripten"; import { IMPORTS, INTERNAL, Module, runtimeHelpers } from "./imports"; import { generate_arg_marshal_to_js } from "./marshal-to-js"; import { mono_wasm_new_external_root } from "./roots"; -import { mono_wasm_symbolicate_string } from "./debug"; +import { mono_wasm_symbolicate_string } from "./logging"; export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_name: MonoStringRef, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void { const function_name_root = mono_wasm_new_external_root(function_name), diff --git a/src/mono/wasm/runtime/logging.ts b/src/mono/wasm/runtime/logging.ts new file mode 100644 index 00000000000000..34de60da7929d2 --- /dev/null +++ b/src/mono/wasm/runtime/logging.ts @@ -0,0 +1,210 @@ +//! Licensed to the .NET Foundation under one or more agreements. +//! The .NET Foundation licenses this file to you under the MIT license. + +import BuildConfiguration from "consts:configuration"; +import { INTERNAL, Module, runtimeHelpers } from "./imports"; +import { CharPtr, VoidPtr } from "./types/emscripten"; + +const wasm_func_map = new Map(); +const regexes: any[] = []; + +// V8 +// at :wasm-function[1900]:0x83f63 +// at dlfree (:wasm-function[18739]:0x2328ef) +regexes.push(/at (?[^:()]+:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)((?![^)a-fA-F\d])|$)/); + +//# 5: WASM [009712b2], function #111 (''), pc=0x7c16595c973 (+0x53), pos=38740 (+11) +regexes.push(/(?:WASM \[[\da-zA-Z]+\], (?function #(?[\d]+) \(''\)))/); + +//# chrome +//# at http://127.0.0.1:63817/dotnet.wasm:wasm-function[8963]:0x1e23f4 +regexes.push(/(?[a-z]+:\/\/[^ )]*:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)/); + +//# .wasm-function[8962] +regexes.push(/(?<[^ >]+>[.:]wasm-function\[(?[0-9]+)\])/); + +export function mono_wasm_symbolicate_string(message: string): string { + try { + if (wasm_func_map.size == 0) + return message; + + const origMessage = message; + + for (let i = 0; i < regexes.length; i++) { + const newRaw = message.replace(new RegExp(regexes[i], "g"), (substring, ...args) => { + const groups = args.find(arg => { + return typeof (arg) == "object" && arg.replaceSection !== undefined; + }); + + if (groups === undefined) + return substring; + + const funcNum = groups.funcNum; + const replaceSection = groups.replaceSection; + const name = wasm_func_map.get(Number(funcNum)); + + if (name === undefined) + return substring; + + return substring.replace(replaceSection, `${name} (${replaceSection})`); + }); + + if (newRaw !== origMessage) + return newRaw; + } + + return origMessage; + } catch (error) { + console.debug(`MONO_WASM: failed to symbolicate: ${error}`); + return message; + } +} + +export function mono_wasm_stringify_as_error_with_stack(err: Error | string): string { + let errObj: any = err; + if (!(err instanceof Error)) + errObj = new Error(err); + + // Error + return mono_wasm_symbolicate_string(errObj.stack); +} + +export function mono_wasm_trace_logger(log_domain_ptr: CharPtr, log_level_ptr: CharPtr, message_ptr: CharPtr, fatal: number, user_data: VoidPtr): void { + const origMessage = Module.UTF8ToString(message_ptr); + const isFatal = !!fatal; + const domain = Module.UTF8ToString(log_domain_ptr); + const dataPtr = user_data; + const log_level = Module.UTF8ToString(log_level_ptr); + + const message = `[MONO] ${origMessage}`; + + if (INTERNAL["logging"] && typeof INTERNAL.logging["trace"] === "function") { + INTERNAL.logging.trace(domain, log_level, message, isFatal, dataPtr); + return; + } + + switch (log_level) { + case "critical": + case "error": + console.error(mono_wasm_stringify_as_error_with_stack(message)); + break; + case "warning": + console.warn(message); + break; + case "message": + console.log(message); + break; + case "info": + console.info(message); + break; + case "debug": + console.debug(message); + break; + default: + console.log(message); + break; + } +} + +export function setup_proxy_console(id: string, console: Console, origin: string): void { + // this need to be copy, in order to keep reference to original methods + const originalConsole = { + log: console.log, + error: console.error + }; + const anyConsole = console as any; + + function proxyConsoleMethod(prefix: string, func: any, asJson: boolean) { + return function (...args: any[]) { + try { + let payload = args[0]; + if (payload === undefined) payload = "undefined"; + else if (payload === null) payload = "null"; + else if (typeof payload === "function") payload = payload.toString(); + else if (typeof payload !== "string") { + try { + payload = JSON.stringify(payload); + } catch (e) { + payload = payload.toString(); + } + } + + if (typeof payload === "string") + payload = `[${id}] ${payload}`; + + if (asJson) { + func(JSON.stringify({ + method: prefix, + payload: payload, + arguments: args + })); + } else { + func([prefix + payload, ...args.slice(1)]); + } + } catch (err) { + originalConsole.error(`proxyConsole failed: ${err}`); + } + }; + } + + const methods = ["debug", "trace", "warn", "info", "error"]; + for (const m of methods) { + if (typeof (anyConsole[m]) !== "function") { + anyConsole[m] = proxyConsoleMethod(`console.${m}: `, console.log, false); + } + } + + const consoleUrl = `${origin}/console`.replace("https://", "wss://").replace("http://", "ws://"); + + const consoleWebSocket = new WebSocket(consoleUrl); + consoleWebSocket.addEventListener("open", () => { + originalConsole.log(`browser: [${id}] Console websocket connected.`); + }); + consoleWebSocket.addEventListener("error", (event) => { + originalConsole.error(`[${id}] websocket error: ${event}`, event); + }); + consoleWebSocket.addEventListener("close", (event) => { + originalConsole.error(`[${id}] websocket closed: ${event}`, event); + }); + + const send = (msg: string) => { + if (consoleWebSocket.readyState === WebSocket.OPEN) { + consoleWebSocket.send(msg); + } + else { + originalConsole.log(msg); + } + }; + + for (const m of ["log", ...methods]) + anyConsole[m] = proxyConsoleMethod(`console.${m}`, send, true); +} + +export function readSymbolMapFile(filename: string): void { + if (runtimeHelpers.mono_wasm_symbols_are_ready) return; + runtimeHelpers.mono_wasm_symbols_are_ready = true; + try { + const res = Module.FS_readFile(filename, { flags: "r", encoding: "utf8" }); + res.split(/[\r\n]/).forEach((line: string) => { + const parts: string[] = line.split(/:/); + if (parts.length < 2) + return; + + parts[1] = parts.splice(1).join(":"); + wasm_func_map.set(Number(parts[0]), parts[1]); + }); + if (BuildConfiguration === "Debug") { + console.debug(`MONO_WASM: Loaded ${wasm_func_map.size} symbols`); + } + } catch (error: any) { + if (error.errno == 44) {// NOENT + if (BuildConfiguration === "Debug") { + console.debug(`MONO_WASM: Could not find symbols file ${filename}. Ignoring.`); + } + } + else { + console.log(`MONO_WASM: Error loading symbol file ${filename}: ${JSON.stringify(error)}`); + } + return; + } +} diff --git a/src/mono/wasm/runtime/net6-legacy/export-types.ts b/src/mono/wasm/runtime/net6-legacy/export-types.ts index 22291a91491802..84c8171cbb67d1 100644 --- a/src/mono/wasm/runtime/net6-legacy/export-types.ts +++ b/src/mono/wasm/runtime/net6-legacy/export-types.ts @@ -1,4 +1,4 @@ -import { MonoArray, MonoObject, MonoObjectRef, MonoString, WasmRoot, WasmRootBuffer, MemOffset, NumberOrPointer } from "../types"; +import { MemOffset, MonoArray, MonoObject, MonoObjectRef, MonoString, NumberOrPointer, WasmRoot, WasmRootBuffer } from "../types"; import { VoidPtr } from "../types/emscripten"; /** @@ -247,4 +247,6 @@ export type MONOType = { * @deprecated Please use getHeapF64 */ getF64: (offset: MemOffset) => number; -}; \ No newline at end of file +}; + +export { MonoArray, MonoObject, MonoString }; \ No newline at end of file diff --git a/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts b/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts index 4202cf7ebc3406..7ead0a1000dd17 100644 --- a/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts +++ b/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts @@ -5,7 +5,7 @@ import { runtimeHelpers } from "../imports"; import { mono_wasm_load_bytes_into_heap, setB32, setI8, setI16, setI32, setI52, setU52, setI64Big, setU8, setU16, setU32, setF32, setF64, getB32, getI8, getI16, getI32, getI52, getU52, getI64Big, getU8, getU16, getU32, getF32, getF64 } from "../memory"; import { mono_wasm_new_root_buffer, mono_wasm_new_root, mono_wasm_new_external_root, mono_wasm_release_roots } from "../roots"; import { mono_run_main, mono_run_main_and_exit } from "../run"; -import { mono_wasm_setenv, mono_wasm_load_data_archive, mono_wasm_load_config, mono_load_runtime_and_bcl_args } from "../startup"; +import { mono_wasm_setenv, mono_wasm_load_config, mono_load_runtime_and_bcl_args } from "../startup"; import { js_string_to_mono_string, conv_string, js_string_to_mono_string_root, conv_string_root } from "../strings"; import { MonoConfig, MonoConfigError } from "../types"; import { mono_array_to_js_array, unbox_mono_obj, unbox_mono_obj_root, mono_array_root_to_js_array } from "./cs-to-js"; @@ -13,6 +13,7 @@ import { js_typed_array_to_array, js_to_mono_obj, js_typed_array_to_array_root, import { mono_bind_static_method, mono_call_assembly_entry_point } from "./method-calls"; import { mono_wasm_load_runtime } from "../startup"; import { BINDINGType, MONOType } from "./export-types"; +import { mono_wasm_load_data_archive } from "../assets"; export function export_mono_api(): MONOType { return { diff --git a/src/mono/wasm/runtime/polyfills.ts b/src/mono/wasm/runtime/polyfills.ts index 1e56ac4e813a5f..afd92c71074896 100644 --- a/src/mono/wasm/runtime/polyfills.ts +++ b/src/mono/wasm/runtime/polyfills.ts @@ -1,4 +1,4 @@ -import Configuration from "consts:configuration"; +import BuildConfiguration from "consts:configuration"; import MonoWasmThreads from "consts:monoWasmThreads"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, INTERNAL, Module, runtimeHelpers } from "./imports"; import { afterUpdateGlobalBufferAndViews } from "./memory"; @@ -144,7 +144,7 @@ export function init_polyfills(replacements: EarlyReplacements): void { // script location runtimeHelpers.scriptDirectory = replacements.scriptDirectory = detectScriptDirectory(replacements); anyModule.mainScriptUrlOrBlob = replacements.scriptUrl;// this is needed by worker threads - if (Configuration === "Debug") { + if (BuildConfiguration === "Debug") { console.debug(`MONO_WASM: starting script ${replacements.scriptUrl}`); console.debug(`MONO_WASM: starting in ${runtimeHelpers.scriptDirectory}`); } diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index c26bdf352a0bb3..4e35254d7e456b 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -1,49 +1,45 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import BuildConfiguration from "consts:configuration"; import MonoWasmThreads from "consts:monoWasmThreads"; -import { mono_assert, CharPtrNull, DotnetModule, MonoConfig, MonoConfigError, LoadingResource, AssetEntry, ResourceRequest } from "./types"; +import { CharPtrNull, DotnetModule, MonoConfig, MonoConfigError } from "./types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, Module, runtimeHelpers } from "./imports"; import cwraps, { init_c_exports } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; -import { mono_wasm_globalization_init, mono_wasm_load_icu_data } from "./icu"; +import { mono_wasm_globalization_init } from "./icu"; import { toBase64StringImpl } from "./base64"; import { mono_wasm_init_aot_profiler, mono_wasm_init_coverage_profiler } from "./profiler"; -import { VoidPtr, CharPtr } from "./types/emscripten"; import { mono_on_abort, set_exit_code } from "./run"; import { initialize_marshalers_to_cs } from "./marshal-to-cs"; import { initialize_marshalers_to_js } from "./marshal-to-js"; import { init_crypto } from "./crypto-worker"; import { init_polyfills_async } from "./polyfills"; import * as pthreads_worker from "./pthreads/worker"; -import { createPromiseController, PromiseAndController } from "./promise-controller"; +import { createPromiseController } from "./promise-controller"; import { string_decoder } from "./strings"; -import { delay } from "./promise-utils"; import { init_managed_exports } from "./managed-exports"; import { init_legacy_exports } from "./net6-legacy/corebindings"; -import { mono_wasm_load_bytes_into_heap } from "./memory"; import { cwraps_internal } from "./exports-internal"; import { cwraps_binding_api, cwraps_mono_api } from "./net6-legacy/exports-legacy"; -import { DotnetPublicAPI } from "./export-types"; +import { DotnetPublicAPI } from "./exports"; +import { CharPtr, InstantiateWasmCallBack, InstantiateWasmSuccessCallback } from "./types/emscripten"; +import { instantiate_wasm_asset, mono_download_assets, resolve_asset_path, start_asset_download, wait_for_all_assets } from "./assets"; import { BINDING, MONO } from "./net6-legacy/imports"; +import { readSymbolMapFile } from "./logging"; import { mono_wasm_init_diagnostics } from "./diagnostics"; -let all_assets_loaded_in_memory: Promise | null = null; -const loaded_files: { url: string, file: string }[] = []; -const loaded_assets: { [id: string]: [VoidPtr, number] } = Object.create(null); -let instantiated_assets_count = 0; -let downloded_assets_count = 0; -// in order to prevent net::ERR_INSUFFICIENT_RESOURCES if we start downloading too many files at same time -let parallel_count = 0; let config: MonoConfig = undefined as any; - -const afterInstantiateWasm = createPromiseController(); -const beforePreInit = createPromiseController(); -const afterPreInit = createPromiseController(); -const afterPreRun = createPromiseController(); -const beforeOnRuntimeInitialized = createPromiseController(); -const afterOnRuntimeInitialized = createPromiseController(); -const afterPostRun = createPromiseController(); +let configLoaded = false; +let isCustomStartup = false; +export const afterConfigLoaded = createPromiseController(); +export const afterInstantiateWasm = createPromiseController(); +export const beforePreInit = createPromiseController(); +export const afterPreInit = createPromiseController(); +export const afterPreRun = createPromiseController(); +export const beforeOnRuntimeInitialized = createPromiseController(); +export const afterOnRuntimeInitialized = createPromiseController(); +export const afterPostRun = createPromiseController(); // 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 @@ -56,17 +52,18 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: const userpostRun: (() => void)[] = !module.postRun ? [] : typeof module.postRun === "function" ? [module.postRun] : module.postRun as any; // eslint-disable-next-line @typescript-eslint/no-empty-function const userOnRuntimeInitialized: () => void = module.onRuntimeInitialized ? module.onRuntimeInitialized : () => { }; - const isCustomStartup = !module.configSrc && !module.config; // like blazor + // when assets don't contain DLLs it means this is Blazor or another custom startup + isCustomStartup = !module.configSrc && (!module.config || !module.config.assets || module.config.assets.findIndex(a => a.behavior === "assembly") != -1); // like blazor // execution order == [0] == // - default or user Module.instantiateWasm (will start downloading dotnet.wasm) module.instantiateWasm = (imports, callback) => instantiateWasm(imports, callback, userInstantiateWasm); // execution order == [1] == - module.preInit = [() => preInit(isCustomStartup, userPreInit)]; + module.preInit = [() => preInit(userPreInit)]; // execution order == [2] == module.preRun = [() => preRunAsync(userPreRun)]; // execution order == [4] == - module.onRuntimeInitialized = () => onRuntimeInitializedAsync(isCustomStartup, userOnRuntimeInitialized); + module.onRuntimeInitialized = () => onRuntimeInitializedAsync(userOnRuntimeInitialized); // execution order == [5] == module.postRun = [() => postRunAsync(userpostRun)]; // execution order == [6] == @@ -83,13 +80,11 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: } } -let wasm_module_imports: WebAssembly.Imports | null = null; -let wasm_success_callback: null | ((instance: WebAssembly.Instance, module: WebAssembly.Module) => void) = null; function instantiateWasm( imports: WebAssembly.Imports, - successCallback: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void, - userInstantiateWasm?: (imports: WebAssembly.Imports, successCallback: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void) => any): any[] { + successCallback: InstantiateWasmSuccessCallback, + userInstantiateWasm?: InstantiateWasmCallBack): any[] { // this is called so early that even Module exports like addRunDependency don't exist yet if (!Module.configSrc && !Module.config && !userInstantiateWasm) { @@ -101,9 +96,6 @@ function instantiateWasm( config = runtimeHelpers.config = Module.config = {} as any; } runtimeHelpers.diagnosticTracing = !!config.diagnosticTracing; - if (!config.assets) { - config.assets = []; - } if (userInstantiateWasm) { const exports = userInstantiateWasm(imports, (instance: WebAssembly.Instance, module: WebAssembly.Module) => { @@ -113,13 +105,11 @@ function instantiateWasm( return exports; } - wasm_module_imports = imports; - wasm_success_callback = successCallback; - instantiate_wasm_module(); + instantiate_wasm_module(imports, successCallback); return []; // No exports } -function preInit(isCustomStartup: boolean, userPreInit: (() => void)[]) { +function preInit(userPreInit: (() => void)[]) { Module.addRunDependency("mono_pre_init"); try { mono_wasm_pre_init_essential(); @@ -172,7 +162,7 @@ async function preRunAsync(userPreRun: (() => void)[]) { Module.removeRunDependency("mono_pre_run_async"); } -async function onRuntimeInitializedAsync(isCustomStartup: boolean, userOnRuntimeInitialized: () => void) { +async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { // wait for previous stage await afterPreRun.promise; if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: onRuntimeInitialized"); @@ -180,13 +170,7 @@ async function onRuntimeInitializedAsync(isCustomStartup: boolean, userOnRuntime beforeOnRuntimeInitialized.promise_control.resolve(); try { if (!isCustomStartup) { - // wait for all assets in memory - await all_assets_loaded_in_memory; - const expected_asset_count = config.assets ? config.assets.length : 0; - mono_assert(downloded_assets_count == expected_asset_count, "Expected assets to be downloaded"); - mono_assert(instantiated_assets_count == expected_asset_count, "Expected assets to be in memory"); - if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: all assets are loaded in wasm memory"); - + await wait_for_all_assets(); // load runtime await mono_wasm_before_user_runtime_initialized(); } @@ -246,7 +230,6 @@ function mono_wasm_pre_init_essential(): void { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_pre_init_essential"); // init_polyfills() is already called from export.ts - init_crypto(); init_c_exports(); cwraps_internal(INTERNAL); cwraps_mono_api(MONO); @@ -261,6 +244,8 @@ async function mono_wasm_pre_init_essential_async(): Promise { Module.addRunDependency("mono_wasm_pre_init_essential_async"); await init_polyfills_async(); + await mono_wasm_load_config(Module.configSrc); + init_crypto(); Module.removeRunDependency("mono_wasm_pre_init_essential_async"); } @@ -270,9 +255,6 @@ async function mono_wasm_pre_init_full(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_pre_init_full"); Module.addRunDependency("mono_wasm_pre_init_full"); - if (Module.configSrc) { - await mono_wasm_load_config(Module.configSrc); - } await mono_download_assets(); Module.removeRunDependency("mono_wasm_pre_init_full"); @@ -282,21 +264,14 @@ async function mono_wasm_pre_init_full(): Promise { async function mono_wasm_before_user_runtime_initialized(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_before_user_runtime_initialized"); - if (!Module.config) { - return; - } - try { - loaded_files.forEach(value => MONO.loaded_files.push(value.url)); - if (!loaded_files || loaded_files.length == 0) { - Module.print("MONO_WASM: no files were loaded into runtime"); - } - await _apply_configuration_from_args(); mono_wasm_globalization_init(); if (!runtimeHelpers.mono_wasm_load_runtime_done) mono_wasm_load_runtime("unused", config.debugLevel || 0); if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready(); + if (!runtimeHelpers.mono_wasm_symbols_are_ready) readSymbolMapFile("dotnet.js.symbols"); + setTimeout(() => { // when there are free CPU cycles string_decoder.init_fields(); @@ -382,137 +357,30 @@ export function mono_wasm_set_runtime_options(options: string[]): void { } -async function instantiate_wasm_module(): Promise { +async function instantiate_wasm_module( + imports: WebAssembly.Imports, + successCallback: InstantiateWasmSuccessCallback, +): Promise { // this is called so early that even Module exports like addRunDependency don't exist yet try { - if (!config.assets && Module.configSrc) { - // when we are starting with mono-config,json, it could have dotnet.wasm location in it, we have to wait for it - await mono_wasm_load_config(Module.configSrc); - } + await mono_wasm_load_config(Module.configSrc); if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module"); - let assetToLoad: AssetEntry = { - name: "dotnet.wasm", - behavior: "dotnetwasm" - }; - const assetfromConfig = config.assets!.find(a => a.behavior === "dotnetwasm"); - if (assetfromConfig) { - assetToLoad = assetfromConfig; - } else { - config.assets!.push(assetToLoad); - } - + const assetToLoad = resolve_asset_path("dotnetwasm"); const pendingAsset = await start_asset_download(assetToLoad); await beforePreInit.promise; Module.addRunDependency("instantiate_wasm_module"); - mono_assert(pendingAsset && pendingAsset.pending, () => `Can't load ${assetToLoad.name}`); - - const response = await pendingAsset.pending.response; - const contentType = response.headers ? response.headers.get("Content-Type") : undefined; - let compiledInstance: WebAssembly.Instance; - let compiledModule: WebAssembly.Module; - if (typeof WebAssembly.instantiateStreaming === "function" && contentType === "application/wasm") { - if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module streaming"); - const streamingResult = await WebAssembly.instantiateStreaming(response, wasm_module_imports!); - compiledInstance = streamingResult.instance; - compiledModule = streamingResult.module; - } else { - const arrayBuffer = await response.arrayBuffer(); - if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module buffered"); - const arrayBufferResult = await WebAssembly.instantiate(arrayBuffer, wasm_module_imports!); - compiledInstance = arrayBufferResult.instance; - compiledModule = arrayBufferResult.module; - } - ++instantiated_assets_count; - wasm_success_callback!(compiledInstance, compiledModule); + instantiate_wasm_asset(pendingAsset!, imports, successCallback); + if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module done"); afterInstantiateWasm.promise_control.resolve(); - wasm_success_callback = null; - wasm_module_imports = null; } catch (err) { - _print_error("MONO_WASM: _instantiate_wasm_module() failed", err); + _print_error("MONO_WASM: instantiate_wasm_module() failed", err); abort_startup(err, true); throw err; } Module.removeRunDependency("instantiate_wasm_module"); } -// this need to be run only after onRuntimeInitialized event, when the memory is ready -function _instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array) { - if (runtimeHelpers.diagnosticTracing) - console.debug(`MONO_WASM: Loaded:${asset.name} as ${asset.behavior} size ${bytes.length} from ${url}`); - - const virtualName: string = typeof (asset.virtualPath) === "string" - ? asset.virtualPath - : asset.name; - let offset: VoidPtr | null = null; - - switch (asset.behavior) { - case "resource": - case "assembly": - case "pdb": - loaded_files.push({ url: url, file: virtualName }); - // falls through - case "heap": - case "icu": - offset = mono_wasm_load_bytes_into_heap(bytes); - loaded_assets[virtualName] = [offset, bytes.length]; - break; - - case "vfs": { - // FIXME - const lastSlash = virtualName.lastIndexOf("/"); - let parentDirectory = (lastSlash > 0) - ? virtualName.substr(0, lastSlash) - : null; - let fileName = (lastSlash > 0) - ? virtualName.substr(lastSlash + 1) - : virtualName; - if (fileName.startsWith("/")) - fileName = fileName.substr(1); - if (parentDirectory) { - if (runtimeHelpers.diagnosticTracing) - console.debug(`MONO_WASM: Creating directory '${parentDirectory}'`); - - Module.FS_createPath( - "/", parentDirectory, true, true // fixme: should canWrite be false? - ); - } else { - parentDirectory = "/"; - } - - if (runtimeHelpers.diagnosticTracing) - console.debug(`MONO_WASM: Creating file '${fileName}' in directory '${parentDirectory}'`); - - if (!mono_wasm_load_data_archive(bytes, parentDirectory)) { - Module.FS_createDataFile( - parentDirectory, fileName, - bytes, true /* canRead */, true /* canWrite */, true /* canOwn */ - ); - } - break; - } - default: - throw new Error(`Unrecognized asset behavior:${asset.behavior}, for asset ${asset.name}`); - } - - if (asset.behavior === "assembly") { - const hasPpdb = cwraps.mono_wasm_add_assembly(virtualName, offset!, bytes.length); - - if (!hasPpdb) { - const index = loaded_files.findIndex(element => element.file == virtualName); - loaded_files.splice(index, 1); - } - } - else if (asset.behavior === "icu") { - if (!mono_wasm_load_icu_data(offset!)) - Module.printErr(`MONO_WASM: Error loading ICU asset ${asset.name}`); - } - else if (asset.behavior === "resource") { - cwraps.mono_wasm_add_satellite_assembly(virtualName, asset.culture!, offset!, bytes.length); - } - ++instantiated_assets_count; -} - // runs just in non-blazor async function _apply_configuration_from_args() { try { @@ -592,251 +460,6 @@ export function bindings_init(): void { } } -function downloadResource(request: ResourceRequest): LoadingResource { - if (typeof Module.downloadResource === "function") { - const loading = Module.downloadResource(request); - if (loading) return loading; - } - const options: any = {}; - if (request.hash) { - options.integrity = request.hash; - } - const response = runtimeHelpers.fetch_like(request.resolvedUrl!, options); - return { - name: request.name, url: request.resolvedUrl!, response - }; -} -async function start_asset_download_sources(asset: AssetEntry): Promise { - if (asset.buffer) { - ++downloded_assets_count; - const buffer = asset.buffer; - asset.buffer = undefined;//GC later - asset.pending = { - url: "undefined://" + asset.name, - name: asset.name, - response: Promise.resolve({ - arrayBuffer: () => buffer, - headers: { - get: () => undefined, - } - }) as any - }; - return Promise.resolve(asset); - } - if (asset.pending) { - ++downloded_assets_count; - return asset; - } - - const sourcesList = asset.loadRemote && config.remoteSources ? config.remoteSources : [""]; - let response: Response | undefined = undefined; - for (let sourcePrefix of sourcesList) { - sourcePrefix = sourcePrefix.trim(); - // HACK: Special-case because MSBuild doesn't allow "" as an attribute - if (sourcePrefix === "./") - sourcePrefix = ""; - - let attemptUrl; - const assemblyRootFolder = config.assemblyRootFolder; - if (!asset.resolvedUrl) { - if (sourcePrefix === "") { - if (asset.behavior === "assembly" || asset.behavior === "pdb") - attemptUrl = assemblyRootFolder + "/" + asset.name; - else if (asset.behavior === "resource") { - const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; - attemptUrl = assemblyRootFolder + "/" + path; - } - else - attemptUrl = asset.name; - } else { - attemptUrl = sourcePrefix + asset.name; - } - attemptUrl = runtimeHelpers.locateFile(attemptUrl); - } - else { - attemptUrl = asset.resolvedUrl; - } - if (asset.name === attemptUrl) { - if (runtimeHelpers.diagnosticTracing) - console.debug(`MONO_WASM: Attempting to download '${attemptUrl}'`); - } else { - if (runtimeHelpers.diagnosticTracing) - console.debug(`MONO_WASM: Attempting to download '${attemptUrl}' for ${asset.name}`); - } - try { - const loadingResource = downloadResource({ - name: asset.name, - resolvedUrl: attemptUrl, - hash: asset.hash, - behavior: asset.behavior - }); - response = await loadingResource.response; - if (!response.ok) { - continue;// next source - } - asset.pending = loadingResource; - ++downloded_assets_count; - return asset; - } - catch (err) { - continue; //next source - } - } - throw response; -} - -let throttling: PromiseAndController | undefined; -async function start_asset_download_throttle(asset: AssetEntry): Promise { - // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! - while (throttling) { - await throttling.promise; - } - try { - ++parallel_count; - if (parallel_count == runtimeHelpers.maxParallelDownloads) { - if (runtimeHelpers.diagnosticTracing) - console.debug("MONO_WASM: Throttling further parallel downloads"); - throttling = createPromiseController(); - } - return await start_asset_download_sources(asset); - } - catch (response: any) { - const isOkToFail = asset.isOptional || (asset.name.match(/\.pdb$/) && config.ignorePdbLoadErrors); - if (!isOkToFail) { - const err: any = new Error(`MONO_WASM: download '${response.url}' for ${asset.name} failed ${response.status} ${response.statusText}`); - err.status = response.status; - throw err; - } - } - finally { - --parallel_count; - if (throttling && parallel_count == runtimeHelpers.maxParallelDownloads - 1) { - if (runtimeHelpers.diagnosticTracing) - console.debug("MONO_WASM: Resuming more parallel downloads"); - const old_throttling = throttling; - throttling = undefined; - old_throttling.promise_control.resolve(); - } - } -} - -async function start_asset_download(asset: AssetEntry): Promise { - try { - return await start_asset_download_throttle(asset); - } catch (err: any) { - if (err && err.status == 404) { - throw err; - } - // second attempt only after all first attempts are queued - await allDownloadsQueued.promise; - try { - return await start_asset_download_throttle(asset); - } catch (err) { - // third attempt after small delay - await delay(100); - return await start_asset_download_throttle(asset); - } - } -} - -const allDownloadsQueued = createPromiseController(); -async function mono_download_assets(): Promise { - if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_download_assets"); - runtimeHelpers.maxParallelDownloads = runtimeHelpers.config.maxParallelDownloads || runtimeHelpers.maxParallelDownloads; - try { - const download_promises: Promise[] = []; - // start fetching and instantiating all assets in parallel - for (const asset of config.assets || []) { - if (asset.behavior != "dotnetwasm") { - download_promises.push(start_asset_download(asset)); - } - } - allDownloadsQueued.promise_control.resolve(); - - const asset_promises: Promise[] = []; - for (const downloadPromise of download_promises) { - const downloadedAsset = await downloadPromise; - if (downloadedAsset) { - asset_promises.push((async () => { - const url = downloadedAsset.pending!.url; - const response = await downloadedAsset.pending!.response; - downloadedAsset.pending = undefined; //GC - const buffer = await response.arrayBuffer(); - await beforeOnRuntimeInitialized.promise; - // this is after onRuntimeInitialized - _instantiate_asset(downloadedAsset, url, new Uint8Array(buffer)); - })()); - } - } - - // this await will get past the onRuntimeInitialized because we are not blocking via addRunDependency - // and we are not awating it here - all_assets_loaded_in_memory = Promise.all(asset_promises) as any; - // OPTIMIZATION explained: - // we do it this way so that we could allocate memory immediately after asset is downloaded (and after onRuntimeInitialized which happened already) - // spreading in time - // rather than to block all downloads after onRuntimeInitialized or block onRuntimeInitialized after all downloads are done. That would create allocation burst. - } catch (err: any) { - Module.printErr("MONO_WASM: Error in mono_download_assets: " + err); - throw err; - } -} - -// used from Blazor -export function mono_wasm_load_data_archive(data: Uint8Array, prefix: string): boolean { - if (data.length < 8) - return false; - - const dataview = new DataView(data.buffer); - const magic = dataview.getUint32(0, true); - // get magic number - if (magic != 0x626c6174) { - return false; - } - const manifestSize = dataview.getUint32(4, true); - if (manifestSize == 0 || data.length < manifestSize + 8) - return false; - - let manifest; - try { - const manifestContent = Module.UTF8ArrayToString(data, 8, manifestSize); - manifest = JSON.parse(manifestContent); - if (!(manifest instanceof Array)) - return false; - } catch (exc) { - return false; - } - - data = data.slice(manifestSize + 8); - - // Create the folder structure - // /usr/share/zoneinfo - // /usr/share/zoneinfo/Africa - // /usr/share/zoneinfo/Asia - // .. - - const folders = new Set(); - manifest.filter(m => { - const file = m[0]; - const last = file.lastIndexOf("/"); - const directory = file.slice(0, last + 1); - folders.add(directory); - }); - folders.forEach(folder => { - Module["FS_createPath"](prefix, folder, true, true); - }); - - for (const row of manifest) { - const name = row[0]; - const length = row[1]; - const bytes = data.slice(0, length); - Module["FS_createDataFile"](prefix, name, bytes, true, true); - data = data.slice(length); - } - return true; -} - -let configLoaded = false; /** * Loads the mono config file (typically called mono-config.json) asynchroniously * Note: the run dependencies are so emsdk actually awaits it in order. @@ -844,8 +467,15 @@ let configLoaded = false; * @param {string} configFilePath - relative path to the config file * @throws Will throw an error if the config file loading fails */ -export async function mono_wasm_load_config(configFilePath: string): Promise { +export async function mono_wasm_load_config(configFilePath?: string): Promise { if (configLoaded) { + await afterConfigLoaded.promise; + return; + } + configLoaded = true; + if (!configFilePath) { + normalize(); + afterConfigLoaded.promise_control.resolve(); return; } if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_load_config"); @@ -853,36 +483,48 @@ export async function mono_wasm_load_config(configFilePath: string): PromiseruntimeHelpers.config); + normalize(); } catch (err: any) { _print_error("MONO_WASM: onConfigLoaded() failed", err); throw err; } } - runtimeHelpers.diagnosticTracing = !!runtimeHelpers.config.diagnosticTracing; - configLoaded = true; + afterConfigLoaded.promise_control.resolve(); } catch (err) { const errMessage = `Failed to load config file ${configFilePath} ${err}`; abort_startup(errMessage, true); config = runtimeHelpers.config = Module.config = { message: errMessage, error: err, isError: true }; throw err; } + + function normalize() { + // normalize + config.environmentVariables = config.environmentVariables || {}; + config.assets = config.assets || []; + config.runtimeOptions = config.runtimeOptions || []; + config.globalizationMode = config.globalizationMode || "auto"; + if (config.debugLevel === undefined && BuildConfiguration === "Debug") { + config.debugLevel = -1; + } + if (config.diagnosticTracing === undefined && BuildConfiguration === "Debug") { + config.diagnosticTracing = true; + } + runtimeHelpers.diagnosticTracing = !!runtimeHelpers.config.diagnosticTracing; + } } export function mono_wasm_asm_loaded(assembly_name: CharPtr, assembly_ptr: number, assembly_len: number, pdb_ptr: number, pdb_len: number): void { @@ -946,5 +588,7 @@ export async function mono_wasm_pthread_worker_init(): Promise { export async function mono_load_runtime_and_bcl_args(cfg?: MonoConfig | MonoConfigError | undefined): Promise { config = Module.config = runtimeHelpers.config = Object.assign(runtimeHelpers.config || {}, cfg || {}) as any; await mono_download_assets(); - await all_assets_loaded_in_memory; + if (!isCustomStartup) { + await wait_for_all_assets(); + } } diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 271edd7f14595f..d9edc82f9587b6 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -116,7 +116,9 @@ export type AssetBehaviours = | "heap" // store asset into the native heap | "icu" // load asset as an ICU data archive | "vfs" // load asset into the virtual filesystem (for fopen, File.Open, etc) - | "dotnetwasm"; // the binary of the dotnet runtime + | "dotnetwasm" // the binary of the dotnet runtime + | "js-module-crypto" // the javascript module for subtle crypto + | "js-module-threads" // the javascript module for threads export type RuntimeHelpers = { runtime_interop_module: MonoAssembly; @@ -128,6 +130,7 @@ export type RuntimeHelpers = { mono_wasm_load_runtime_done: boolean; mono_wasm_runtime_is_ready: boolean; mono_wasm_bindings_is_ready: boolean; + mono_wasm_symbols_are_ready: boolean; loaded_files: string[]; maxParallelDownloads: number; diff --git a/src/mono/wasm/runtime/types/emscripten.ts b/src/mono/wasm/runtime/types/emscripten.ts index 3c90efaac757a0..5ddd3f6ceb6223 100644 --- a/src/mono/wasm/runtime/types/emscripten.ts +++ b/src/mono/wasm/runtime/types/emscripten.ts @@ -58,7 +58,7 @@ export declare interface EmscriptenModule { ready: Promise; - instantiateWasm?: (imports: WebAssembly.Imports, successCallback: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void) => any; + instantiateWasm?: InstantiateWasmCallBack; preInit?: (() => any)[] | (() => any); preRun?: (() => any)[] | (() => any); onRuntimeInitialized?: () => any; @@ -66,4 +66,7 @@ export declare interface EmscriptenModule { onAbort?: { (error: any): void }; } +export type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: WebAssembly.Module) => void; +export type InstantiateWasmCallBack = (imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any; + export declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; diff --git a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts index 6b4dcde69c12d1..59866c1c500a7e 100644 --- a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts +++ b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { setup_proxy_console } from "../debug"; +import { setup_proxy_console } from "../logging"; import type { InitCryptoMessageData } from "../crypto-worker"; import type { MonoConfig } from "../types"; diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index de25059d66d03c..65f1a9a5b0f1ad 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -74,7 +74,7 @@ public class WasmAppBuilder : Task private sealed class WasmAppConfig { [JsonPropertyName("assemblyRootFolder")] - public string AssemblyRoot { get; set; } = "managed"; + public string AssemblyRootFolder { get; set; } = "managed"; [JsonPropertyName("debugLevel")] public int DebugLevel { get; set; } = 0; [JsonPropertyName("assets")] @@ -96,6 +96,23 @@ protected AssetEntry (string name, string behavior) public string Behavior { get; init; } [JsonPropertyName("name")] public string Name { get; init; } + // TODO [JsonPropertyName("hash")] + // TODO public string? Hash { get; set; } + } + + private sealed class WasmEntry : AssetEntry + { + public WasmEntry(string name) : base(name, "dotnetwasm") { } + } + + private sealed class CryptoWorkerEntry : AssetEntry + { + public CryptoWorkerEntry(string name) : base(name, "js-module-crypto") { } + } + + private sealed class ThreadsWorkerEntry : AssetEntry + { + public ThreadsWorkerEntry(string name) : base(name, "js-module-threads") { } } private sealed class AssemblyEntry : AssetEntry @@ -165,7 +182,7 @@ private bool ExecuteInternal () var config = new WasmAppConfig (); // Create app - var asmRootPath = Path.Combine(AppDir, config.AssemblyRoot); + var asmRootPath = Path.Combine(AppDir, config.AssemblyRootFolder); Directory.CreateDirectory(AppDir!); Directory.CreateDirectory(asmRootPath); foreach (var assembly in _assemblies) @@ -240,7 +257,7 @@ private bool ExecuteInternal () // FIXME: validate the culture? string name = Path.GetFileName(fullPath); - string directory = Path.Combine(AppDir, config.AssemblyRoot, culture); + string directory = Path.Combine(AppDir, config.AssemblyRootFolder, culture); Directory.CreateDirectory(directory); FileCopyChecked(fullPath, Path.Combine(directory, name), "SatelliteAssemblies"); config.Assets.Add(new SatelliteAssemblyEntry(name, culture)); @@ -295,6 +312,8 @@ private bool ExecuteInternal () config.Assets.Add(new IcuData(IcuDataFileName!) { LoadRemote = RemoteSources?.Length > 0 }); config.Assets.Add(new VfsEntry ("dotnet.timezones.blat") { VirtualPath = "/usr/share/zoneinfo/"}); + config.Assets.Add(new WasmEntry ("dotnet.wasm") ); + config.Assets.Add(new CryptoWorkerEntry ("dotnet-crypto-worker.js") ); if (RemoteSources?.Length > 0) {