diff --git a/eng/testing/linker/trimmingTests.targets b/eng/testing/linker/trimmingTests.targets index d474e19b69acc..7ff8fccc2c017 100644 --- a/eng/testing/linker/trimmingTests.targets +++ b/eng/testing/linker/trimmingTests.targets @@ -165,7 +165,7 @@ - + diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml index d1f5af506a247..5561632ceaaaa 100644 --- a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml @@ -11,6 +11,7 @@ to be trimmed when Invariant=true, and allows for the Settings static cctor (on Unix) to be preserved when Invariant=false. --> + diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs index 37d32f9cd57f4..32d284e85989c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs @@ -628,7 +628,7 @@ private static CultureData CreateCultureWithInvariantData() // all available calendar type(s). The first one is the default calendar invariant._waCalendars = new CalendarId[] { CalendarId.GREGORIAN }; - if (!GlobalizationMode.Invariant) + if (!GlobalizationMode.InvariantNoLoad) { // Store for specific data about each calendar invariant._calendars = new CalendarData[CalendarData.MAX_CALENDARS]; @@ -646,7 +646,7 @@ private static CultureData CreateCultureWithInvariantData() invariant._iDefaultMacCodePage = 10000; // default macintosh code page invariant._iDefaultEbcdicCodePage = 037; // default EBCDIC code page - if (GlobalizationMode.Invariant) + if (GlobalizationMode.InvariantNoLoad) { invariant._sLocalizedCountry = invariant._sNativeCountry; } @@ -2228,7 +2228,7 @@ private string[] GetNativeDigits() internal void GetNFIValues(NumberFormatInfo nfi) { - if (GlobalizationMode.Invariant || IsInvariantCulture) + if (GlobalizationMode.InvariantNoLoad || IsInvariantCulture) { nfi._positiveSign = _sPositiveSign!; nfi._negativeSign = _sNegativeSign!; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs index 314bb33692dc7..78cebb45b9a00 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs @@ -25,6 +25,21 @@ private static partial class Settings // This allows for the whole Settings nested class to be trimmed when Invariant=true, and allows for the Settings // static cctor (on Unix) to be preserved when Invariant=false. internal static bool Invariant => Settings.Invariant; + + // same as GlobalizationMode.Invariant but doesn't trigger ICU load in GlobalizationMode.Settings.cctor + // during runtime startup on Browser platform + internal static bool InvariantNoLoad + { + get + { +#if TARGET_BROWSER + return AppContextConfigHelper.GetBooleanConfig("System.Globalization.Invariant", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"); +#else + return Settings.Invariant; +#endif + } + } + #if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS || TARGET_BROWSER internal static bool Hybrid => Settings.Hybrid; #endif diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets index d75513604304c..c92fd9d6e185d 100644 --- a/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/mono/browser/build/BrowserWasmApp.targets @@ -156,6 +156,8 @@ RuntimeAssetsLocation="$(WasmRuntimeAssetsLocation)" CacheBootResources="$(BlazorCacheBootResources)" RuntimeConfigJsonPath="$(_WasmRuntimeConfigFilePath)" + IsAot="$(RunAOTCompilation)" + IsMultiThreaded="$(WasmEnableThreads)" > diff --git a/src/mono/browser/runtime/assets.ts b/src/mono/browser/runtime/assets.ts index 5c3f7ecce8822..f8725a36e1250 100644 --- a/src/mono/browser/runtime/assets.ts +++ b/src/mono/browser/runtime/assets.ts @@ -52,6 +52,9 @@ export function instantiate_asset (asset: AssetEntry, url: string, bytes: Uint8A if (fileName.startsWith("/")) fileName = fileName.substring(1); if (parentDirectory) { + if (!parentDirectory.startsWith("/")) + parentDirectory = "/" + parentDirectory; + mono_log_debug(`Creating directory '${parentDirectory}'`); Module.FS_createPath( @@ -85,8 +88,7 @@ export function instantiate_asset (asset: AssetEntry, url: string, bytes: Uint8A } else if (asset.behavior === "pdb") { cwraps.mono_wasm_add_assembly(virtualName, offset!, bytes.length); } else if (asset.behavior === "icu") { - if (!mono_wasm_load_icu_data(offset!)) - Module.err(`Error loading ICU asset ${asset.name}`); + mono_wasm_load_icu_data(offset!); } else if (asset.behavior === "resource") { cwraps.mono_wasm_add_satellite_assembly(virtualName, asset.culture || "", offset!, bytes.length); } diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts index 510aa61fcc91c..aa38e6ef9916d 100644 --- a/src/mono/browser/runtime/dotnet.d.ts +++ b/src/mono/browser/runtime/dotnet.d.ts @@ -243,8 +243,10 @@ type ResourceExtensions = { }; interface ResourceGroups { hash?: string; + coreAssembly?: ResourceList; assembly?: ResourceList; lazyAssembly?: ResourceList; + corePdb?: ResourceList; pdb?: ResourceList; jsModuleWorker?: ResourceList; jsModuleNative: ResourceList; @@ -258,6 +260,9 @@ interface ResourceGroups { modulesAfterConfigLoaded?: ResourceList; modulesAfterRuntimeReady?: ResourceList; extensions?: ResourceExtensions; + coreVfs?: { + [virtualPath: string]: ResourceList; + }; vfs?: { [virtualPath: string]: ResourceList; }; diff --git a/src/mono/browser/runtime/globals.ts b/src/mono/browser/runtime/globals.ts index 047fcb60c9502..e1eeed5ddce1d 100644 --- a/src/mono/browser/runtime/globals.ts +++ b/src/mono/browser/runtime/globals.ts @@ -57,6 +57,7 @@ export function setRuntimeGlobals (globalObjects: GlobalObjects) { const rh: Partial = { gitHash, + coreAssetsInMemory: createPromiseController(), allAssetsInMemory: createPromiseController(), dotnetReady: createPromiseController(), afterInstantiateWasm: createPromiseController(), @@ -64,7 +65,8 @@ export function setRuntimeGlobals (globalObjects: GlobalObjects) { afterPreInit: createPromiseController(), afterPreRun: createPromiseController(), beforeOnRuntimeInitialized: createPromiseController(), - afterMonoStarted: createPromiseController(), + afterMonoStarted: createPromiseController(), + afterDeputyReady: createPromiseController(), afterIOStarted: createPromiseController(), afterOnRuntimeInitialized: createPromiseController(), afterPostRun: createPromiseController(), diff --git a/src/mono/browser/runtime/icu.ts b/src/mono/browser/runtime/icu.ts index 16b23871f9871..b6c789f1138f6 100644 --- a/src/mono/browser/runtime/icu.ts +++ b/src/mono/browser/runtime/icu.ts @@ -4,8 +4,8 @@ import cwraps from "./cwraps"; import { VoidPtr } from "./types/emscripten"; -// @offset must be the address of an ICU data archive in the native heap. -// returns true on success. -export function mono_wasm_load_icu_data (offset: VoidPtr): boolean { - return (cwraps.mono_wasm_load_icu_data(offset)) === 1; +export function mono_wasm_load_icu_data (offset: VoidPtr) { + if (!cwraps.mono_wasm_load_icu_data(offset)) { + throw new Error("Failed to load ICU data"); + } } diff --git a/src/mono/browser/runtime/loader/assets.ts b/src/mono/browser/runtime/loader/assets.ts index ad9d3650791e4..926042d57e7dd 100644 --- a/src/mono/browser/runtime/loader/assets.ts +++ b/src/mono/browser/runtime/loader/assets.ts @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { PThreadPtrNull, type AssetEntryInternal, type PThreadWorker, type PromiseAndController } from "../types/internal"; import type { AssetBehaviors, AssetEntry, LoadingResource, ResourceList, SingleAssetBehaviors as SingleAssetBehaviors, WebAssemblyBootResourceType } from "../types"; -import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; +import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { createPromiseController } from "./promise-controller"; import { mono_log_debug, mono_log_warn } from "./logging"; import { mono_exit } from "./exit"; @@ -18,6 +18,7 @@ import { mono_log_info } from "./logging"; let throttlingPromise: PromiseAndController | undefined; // in order to prevent net::ERR_INSUFFICIENT_RESOURCES if we start downloading too many files at same time let parallel_count = 0; +const coreAssetsToLoad: AssetEntryInternal[] = []; const assetsToLoad: AssetEntryInternal[] = []; const singleAssets: Map = new Map(); @@ -152,21 +153,25 @@ export function resolve_single_asset_path (behavior: SingleAssetBehaviors): Asse export async function mono_download_assets (): Promise { mono_log_debug("mono_download_assets"); try { - const promises_of_assets: Promise[] = []; + const promises_of_assets_core: Promise[] = []; + const promises_of_assets_remaining: Promise[] = []; - const countAndStartDownload = (asset: AssetEntryInternal) => { + const countAndStartDownload = (asset: AssetEntryInternal, promises_list: Promise[]) => { if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { loaderHelpers.expected_instantiated_assets_count++; } if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { loaderHelpers.expected_downloaded_assets_count++; - promises_of_assets.push(start_asset_download(asset)); + promises_list.push(start_asset_download(asset)); } }; // start fetching assets in parallel + for (const asset of coreAssetsToLoad) { + countAndStartDownload(asset, promises_of_assets_core); + } for (const asset of assetsToLoad) { - countAndStartDownload(asset); + countAndStartDownload(asset, promises_of_assets_remaining); } loaderHelpers.allDownloadsQueued.promise_control.resolve(); @@ -174,55 +179,73 @@ export async function mono_download_assets (): Promise { // continue after the dotnet.runtime.js was loaded await loaderHelpers.runtimeModuleLoaded.promise; - const promises_of_asset_instantiation: Promise[] = []; - for (const downloadPromise of promises_of_assets) { - promises_of_asset_instantiation.push((async () => { - const asset = await downloadPromise; - if (asset.buffer) { - if (!skipInstantiateByAssetTypes[asset.behavior]) { - mono_assert(asset.buffer && typeof asset.buffer === "object", "asset buffer must be array-like or buffer-like or promise of these"); - mono_assert(typeof asset.resolvedUrl === "string", "resolvedUrl must be string"); - const url = asset.resolvedUrl!; - const buffer = await asset.buffer; - const data = new Uint8Array(buffer); - cleanupAsset(asset); - - // wait till after onRuntimeInitialized - - await runtimeHelpers.beforeOnRuntimeInitialized.promise; - runtimeHelpers.instantiate_asset(asset, url, data); + const instantiate = async (downloadPromise: Promise) => { + const asset = await downloadPromise; + if (asset.buffer) { + if (!skipInstantiateByAssetTypes[asset.behavior]) { + mono_assert(asset.buffer && typeof asset.buffer === "object", "asset buffer must be array-like or buffer-like or promise of these"); + mono_assert(typeof asset.resolvedUrl === "string", "resolvedUrl must be string"); + const url = asset.resolvedUrl!; + const buffer = await asset.buffer; + const data = new Uint8Array(buffer); + cleanupAsset(asset); + + // wait till after onRuntimeInitialized + + await runtimeHelpers.beforeOnRuntimeInitialized.promise; + runtimeHelpers.instantiate_asset(asset, url, data); + } + } else { + const headersOnly = skipBufferByAssetTypes[asset.behavior]; + if (!headersOnly) { + mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); + if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { + loaderHelpers.expected_downloaded_assets_count--; + } + if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { + loaderHelpers.expected_instantiated_assets_count--; } } else { - const headersOnly = skipBufferByAssetTypes[asset.behavior]; - if (!headersOnly) { - mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); - if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { - loaderHelpers.expected_downloaded_assets_count--; - } - if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { - loaderHelpers.expected_instantiated_assets_count--; - } - } else { - if (asset.behavior === "symbols") { - await runtimeHelpers.instantiate_symbols_asset(asset); - cleanupAsset(asset); - } else if (asset.behavior === "segmentation-rules") { - await runtimeHelpers.instantiate_segmentation_rules_asset(asset); - cleanupAsset(asset); - } - - if (skipBufferByAssetTypes[asset.behavior]) { - ++loaderHelpers.actual_downloaded_assets_count; - } + if (asset.behavior === "symbols") { + await runtimeHelpers.instantiate_symbols_asset(asset); + cleanupAsset(asset); + } else if (asset.behavior === "segmentation-rules") { + await runtimeHelpers.instantiate_segmentation_rules_asset(asset); + cleanupAsset(asset); + } + + if (skipBufferByAssetTypes[asset.behavior]) { + ++loaderHelpers.actual_downloaded_assets_count; } } - })()); + } + }; + + const promises_of_asset_instantiation_core: Promise[] = []; + const promises_of_asset_instantiation_remaining: Promise[] = []; + for (const downloadPromise of promises_of_assets_core) { + promises_of_asset_instantiation_core.push(instantiate(downloadPromise)); + } + for (const downloadPromise of promises_of_assets_remaining) { + promises_of_asset_instantiation_remaining.push(instantiate(downloadPromise)); } // this await will get past the onRuntimeInitialized because we are not blocking via addRunDependency - // and we are not awating it here - Promise.all(promises_of_asset_instantiation).then(() => { - runtimeHelpers.allAssetsInMemory.promise_control.resolve(); + // and we are not awaiting it here + Promise.all(promises_of_asset_instantiation_core).then(() => { + if (!ENVIRONMENT_IS_WORKER) { + runtimeHelpers.coreAssetsInMemory.promise_control.resolve(); + } + }).catch(err => { + loaderHelpers.err("Error in mono_download_assets: " + err); + mono_exit(1, err); + throw err; + }); + Promise.all(promises_of_asset_instantiation_remaining).then(async () => { + if (!ENVIRONMENT_IS_WORKER) { + await runtimeHelpers.coreAssetsInMemory.promise; + runtimeHelpers.allAssetsInMemory.promise_control.resolve(); + } }).catch(err => { loaderHelpers.err("Error in mono_download_assets: " + err); mono_exit(1, err); @@ -251,7 +274,11 @@ export function prepareAssets () { mono_assert(!asset.resolvedUrl || typeof asset.resolvedUrl === "string", "asset resolvedUrl could be string"); mono_assert(!asset.hash || typeof asset.hash === "string", "asset resolvedUrl could be string"); mono_assert(!asset.pendingDownload || typeof asset.pendingDownload === "object", "asset pendingDownload could be object"); - assetsToLoad.push(asset); + if (asset.isCore) { + coreAssetsToLoad.push(asset); + } else { + assetsToLoad.push(asset); + } set_single_asset(asset); } } else if (config.resources) { @@ -268,23 +295,55 @@ export function prepareAssets () { convert_single_asset(modulesAssets, resources.jsModuleWorker, "js-module-threads"); } + const addAsset = (asset: AssetEntryInternal, isCore: boolean) => { + if (isCore) { + asset.isCore = true; + coreAssetsToLoad.push(asset); + } else { + assetsToLoad.push(asset); + } + }; + + if (resources.coreAssembly) { + for (const name in resources.coreAssembly) { + addAsset({ + name, + hash: resources.coreAssembly[name], + behavior: "assembly" + }, true); + } + } + if (resources.assembly) { for (const name in resources.assembly) { - assetsToLoad.push({ + addAsset({ name, hash: resources.assembly[name], behavior: "assembly" - }); + }, !resources.coreAssembly); // if there are no core assemblies, then all assemblies are core } } - if (config.debugLevel != 0 && loaderHelpers.isDebuggingSupported() && resources.pdb) { - for (const name in resources.pdb) { - assetsToLoad.push({ - name, - hash: resources.pdb[name], - behavior: "pdb" - }); + + if (config.debugLevel != 0 && loaderHelpers.isDebuggingSupported()) { + if (resources.corePdb) { + for (const name in resources.corePdb) { + addAsset({ + name, + hash: resources.corePdb[name], + behavior: "pdb" + }, true); + } + } + + if (resources.pdb) { + for (const name in resources.pdb) { + addAsset({ + name, + hash: resources.pdb[name], + behavior: "pdb" + }, !resources.corePdb); // if there are no core pdbs, then all pdbs are core + } } } @@ -301,15 +360,28 @@ export function prepareAssets () { } } + if (resources.coreVfs) { + for (const virtualPath in resources.coreVfs) { + for (const name in resources.coreVfs[virtualPath]) { + addAsset({ + name, + hash: resources.coreVfs[virtualPath][name], + behavior: "vfs", + virtualPath + }, true); + } + } + } + if (resources.vfs) { for (const virtualPath in resources.vfs) { for (const name in resources.vfs[virtualPath]) { - assetsToLoad.push({ + addAsset({ name, hash: resources.vfs[virtualPath][name], behavior: "vfs", virtualPath - }); + }, !resources.coreVfs); } } } @@ -336,7 +408,7 @@ export function prepareAssets () { if (resources.wasmSymbols) { for (const name in resources.wasmSymbols) { - assetsToLoad.push({ + coreAssetsToLoad.push({ name, hash: resources.wasmSymbols[name], behavior: "symbols" @@ -363,7 +435,7 @@ export function prepareAssets () { } } - config.assets = [...assetsToLoad, ...modulesAssets]; + config.assets = [...coreAssetsToLoad, ...assetsToLoad, ...modulesAssets]; } export function prepareAssetsWorker () { diff --git a/src/mono/browser/runtime/pthreads/deputy-thread.ts b/src/mono/browser/runtime/pthreads/deputy-thread.ts index 4b514b28a4aed..1e2041fa44dae 100644 --- a/src/mono/browser/runtime/pthreads/deputy-thread.ts +++ b/src/mono/browser/runtime/pthreads/deputy-thread.ts @@ -8,8 +8,10 @@ import { mono_log_error, mono_log_info } from "../logging"; import { monoThreadInfo, postMessageToMain, update_thread_info } from "./shared"; import { Module, loaderHelpers, runtimeHelpers } from "../globals"; import { start_runtime } from "../startup"; -import { WorkerToMainMessageType } from "../types/internal"; +import { MainToWorkerMessageType, WorkerToMainMessageType } from "../types/internal"; import { forceThreadMemoryViewRefresh } from "../memory"; +import { install_main_synchronization_context } from "../managed-exports"; +import { pthread_self } from "./worker-thread"; export function mono_wasm_start_deputy_thread_async () { if (!WasmEnableThreads) return; @@ -31,11 +33,26 @@ export function mono_wasm_start_deputy_thread_async () { try { forceThreadMemoryViewRefresh(); + pthread_self.addEventListenerFromBrowser((message) => { + if (message.data.cmd == MainToWorkerMessageType.allAssetsLoaded) { + runtimeHelpers.allAssetsInMemory.promise_control.resolve(); + } + }); + await start_runtime(); postMessageToMain({ monoCmd: WorkerToMainMessageType.deputyStarted, info: monoThreadInfo, + }); + + await runtimeHelpers.allAssetsInMemory.promise; + + runtimeHelpers.proxyGCHandle = install_main_synchronization_context(runtimeHelpers.config.jsThreadBlockingMode!); + + postMessageToMain({ + monoCmd: WorkerToMainMessageType.deputyReady, + info: monoThreadInfo, deputyProxyGCHandle: runtimeHelpers.proxyGCHandle, }); } catch (err) { diff --git a/src/mono/browser/runtime/pthreads/ui-thread.ts b/src/mono/browser/runtime/pthreads/ui-thread.ts index 0dc2b80c57e0f..94016a529424c 100644 --- a/src/mono/browser/runtime/pthreads/ui-thread.ts +++ b/src/mono/browser/runtime/pthreads/ui-thread.ts @@ -96,7 +96,11 @@ function monoWorkerMessageHandler (worker: PThreadWorker, ev: MessageEvent) worker.info = Object.assign(worker.info!, message.info, {}); break; case WorkerToMainMessageType.deputyStarted: - runtimeHelpers.afterMonoStarted.promise_control.resolve(message.deputyProxyGCHandle); + runtimeHelpers.deputyWorker = worker; + runtimeHelpers.afterMonoStarted.promise_control.resolve(); + break; + case WorkerToMainMessageType.deputyReady: + runtimeHelpers.afterDeputyReady.promise_control.resolve(message.deputyProxyGCHandle); break; case WorkerToMainMessageType.ioStarted: runtimeHelpers.afterIOStarted.promise_control.resolve(); diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index b89522caff198..4d07e712358b0 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -4,7 +4,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import BuildConfiguration from "consts:configuration"; -import { DotnetModuleInternal, CharPtrNull } from "./types/internal"; +import { DotnetModuleInternal, CharPtrNull, MainToWorkerMessageType } from "./types/internal"; import { exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers, createPromiseController, mono_assert } from "./globals"; import cwraps, { init_c_exports, threads_c_functions as tcwraps } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; @@ -14,7 +14,7 @@ import { initialize_marshalers_to_cs } from "./marshal-to-cs"; import { initialize_marshalers_to_js } from "./marshal-to-js"; import { init_polyfills_async } from "./polyfills"; import { strings_init, utf8ToString } from "./strings"; -import { init_managed_exports, install_main_synchronization_context } from "./managed-exports"; +import { init_managed_exports } from "./managed-exports"; import { cwraps_internal } from "./exports-internal"; import { CharPtr, InstantiateWasmCallBack, InstantiateWasmSuccessCallback } from "./types/emscripten"; import { wait_for_all_assets } from "./assets"; @@ -255,16 +255,21 @@ async function onRuntimeInitializedAsync (userOnRuntimeInitialized: () => void) threadsReady = mono_wasm_init_threads(); } - await wait_for_all_assets(); + await runtimeHelpers.coreAssetsInMemory.promise; if (runtimeHelpers.config.virtualWorkingDirectory) { const FS = Module.FS; const cwd = runtimeHelpers.config.virtualWorkingDirectory; - const wds = FS.stat(cwd); - if (!wds) { + try { + const wds = FS.stat(cwd); + if (!wds) { + Module.FS_createPath("/", cwd, true, true); + } else { + mono_assert(wds && FS.isDir(wds.mode), () => `FS.chdir: ${cwd} is not a directory`); + } + } catch (e) { Module.FS_createPath("/", cwd, true, true); } - mono_assert(wds && FS.isDir(wds.mode), () => `FS.chdir: ${cwd} is not a directory`); FS.chdir(cwd); } @@ -287,7 +292,7 @@ async function onRuntimeInitializedAsync (userOnRuntimeInitialized: () => void) runtimeHelpers.managedThreadTID = tcwraps.mono_wasm_create_deputy_thread(); // await mono started on deputy thread - runtimeHelpers.proxyGCHandle = await runtimeHelpers.afterMonoStarted.promise; + await runtimeHelpers.afterMonoStarted.promise; runtimeHelpers.ioThreadTID = tcwraps.mono_wasm_create_io_thread(); // TODO make UI thread not managed/attached https://github.com/dotnet/runtime/issues/100411 @@ -311,6 +316,16 @@ async function onRuntimeInitializedAsync (userOnRuntimeInitialized: () => void) await runtimeHelpers.afterIOStarted.promise; } + await wait_for_all_assets(); + + if (WasmEnableThreads) { + runtimeHelpers.deputyWorker.thread!.postMessageToWorker({ + type:"deputyThread", + cmd: MainToWorkerMessageType.allAssetsLoaded, + }); + runtimeHelpers.proxyGCHandle = await runtimeHelpers.afterDeputyReady.promise; + } + runtimeList.registerRuntime(exportedRuntimeAPI); if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready(); @@ -545,12 +560,11 @@ export async function start_runtime () { monoThreadInfo.isRegistered = true; runtimeHelpers.currentThreadTID = monoThreadInfo.pthreadId = runtimeHelpers.managedThreadTID = mono_wasm_pthread_ptr(); update_thread_info(); - runtimeHelpers.proxyGCHandle = install_main_synchronization_context(runtimeHelpers.config.jsThreadBlockingMode!); runtimeHelpers.isManagedRunningOnCurrentThread = true; } // get GCHandle of the ctx - runtimeHelpers.afterMonoStarted.promise_control.resolve(runtimeHelpers.proxyGCHandle); + runtimeHelpers.afterMonoStarted.promise_control.resolve(); if (runtimeHelpers.config.interpreterPgo) { await interp_pgo_load_data(); diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts index dad313a47a07e..df2fb6b69c81b 100644 --- a/src/mono/browser/runtime/types/index.ts +++ b/src/mono/browser/runtime/types/index.ts @@ -199,8 +199,10 @@ export type ResourceExtensions = { [extensionName: string]: ResourceList }; export interface ResourceGroups { hash?: string; + coreAssembly?: ResourceList; // nullable only temporarily assembly?: ResourceList; // nullable only temporarily lazyAssembly?: ResourceList; // nullable only temporarily + corePdb?: ResourceList; pdb?: ResourceList; jsModuleWorker?: ResourceList; @@ -216,6 +218,7 @@ export interface ResourceGroups { modulesAfterRuntimeReady?: ResourceList extensions?: ResourceExtensions + coreVfs?: { [virtualPath: string]: ResourceList }; vfs?: { [virtualPath: string]: ResourceList }; } diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index 3e83f751c0b8a..6b9f16cafa05f 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -111,6 +111,7 @@ export interface AssetEntryInternal extends AssetEntry { pendingDownloadInternal?: LoadingResource noCache?: boolean useCredentials?: boolean + isCore?: boolean } export type LoaderHelpers = { @@ -209,11 +210,13 @@ export type RuntimeHelpers = { proxyGCHandle: GCHandle | undefined, managedThreadTID: PThreadPtr, ioThreadTID: PThreadPtr, + deputyWorker: PThreadWorker, currentThreadTID: PThreadPtr, isManagedRunningOnCurrentThread: boolean, isPendingSynchronousCall: boolean, // true when we are in the middle of a synchronous call from managed code from same thread cspPolicy: boolean, + coreAssetsInMemory: PromiseAndController, allAssetsInMemory: PromiseAndController, dotnetReady: PromiseAndController, afterInstantiateWasm: PromiseAndController, @@ -221,7 +224,8 @@ export type RuntimeHelpers = { afterPreInit: PromiseAndController, afterPreRun: PromiseAndController, beforeOnRuntimeInitialized: PromiseAndController, - afterMonoStarted: PromiseAndController, + afterMonoStarted: PromiseAndController, + afterDeputyReady: PromiseAndController, afterIOStarted: PromiseAndController, afterOnRuntimeInitialized: PromiseAndController, afterPostRun: PromiseAndController, @@ -501,12 +505,14 @@ export const enum WorkerToMainMessageType { deputyCreated = "createdDeputy", deputyFailed = "deputyFailed", deputyStarted = "monoStarted", + deputyReady = "deputyReady", ioStarted = "ioStarted", preload = "preload", } export const enum MainToWorkerMessageType { - applyConfig = "apply_mono_config", + applyConfig = "applyConfig", + allAssetsLoaded = "allAssetsLoaded", } export interface PThreadWorker extends Worker { diff --git a/src/mono/mono/metadata/bundled-resources.c b/src/mono/mono/metadata/bundled-resources.c index 7b2f80ef3edfd..17ddedb19c012 100644 --- a/src/mono/mono/metadata/bundled-resources.c +++ b/src/mono/mono/metadata/bundled-resources.c @@ -140,9 +140,6 @@ bundled_resources_get (const char *id); void mono_bundled_resources_add (MonoBundledResource **resources_to_bundle, uint32_t len) { - MonoDomain *domain = mono_get_root_domain (); - g_assert (!domain); - if (!bundled_resources) // FIXME: Choose a good initial capacity to avoid rehashes during startup. I picked one at random bundled_resources = dn_simdhash_ght_new_full ((GHashFunc)bundled_resources_resource_id_hash, (GEqualFunc)bundled_resources_resource_id_equal, NULL, bundled_resources_value_destroy_func, 2048, NULL); diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index 3b236888404d1..c7b62fbe4ebf2 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -355,7 +355,9 @@ Copyright (c) .NET Foundation. All rights reserved. TargetFrameworkVersion="$(TargetFrameworkVersion)" ModuleAfterConfigLoaded="@(WasmModuleAfterConfigLoaded)" ModuleAfterRuntimeReady="@(WasmModuleAfterRuntimeReady)" - IsPublish="false" /> + IsPublish="false" + IsAot="$(RunAOTCompilation)" + IsMultiThreaded="$(WasmEnableThreads)" /> @@ -641,7 +643,9 @@ Copyright (c) .NET Foundation. All rights reserved. TargetFrameworkVersion="$(TargetFrameworkVersion)" ModuleAfterConfigLoaded="@(WasmModuleAfterConfigLoaded)" ModuleAfterRuntimeReady="@(WasmModuleAfterRuntimeReady)" - IsPublish="true" /> + IsPublish="true" + IsAot="$(RunAOTCompilation)" + IsMultiThreaded="$(WasmEnableThreads)" /> diff --git a/src/mono/sample/wasm/browser-minimal-config/main.js b/src/mono/sample/wasm/browser-minimal-config/main.js index acd81b2899a39..702234c984df5 100644 --- a/src/mono/sample/wasm/browser-minimal-config/main.js +++ b/src/mono/sample/wasm/browser-minimal-config/main.js @@ -34,11 +34,13 @@ const assets = [ }, { name: "System.Private.CoreLib.wasm", - behavior: "assembly" + behavior: "assembly", + isCore: true, }, { name: "System.Runtime.InteropServices.JavaScript.wasm", - behavior: "assembly" + behavior: "assembly", + isCore: true, }, { name: "Wasm.Browser.Config.Sample.wasm", @@ -62,10 +64,12 @@ const resources = { "wasmNative": { "dotnet.native.wasm": "" }, - "assembly": { - "System.Console.wasm": "", + "coreAssembly": { "System.Private.CoreLib.wasm": "", "System.Runtime.InteropServices.JavaScript.wasm": "", + }, + "assembly": { + "System.Console.wasm": "", "Wasm.Browser.Config.Sample.wasm": "" }, }; diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index 581a187270ae4..8e725222cbca7 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -404,7 +404,7 @@ public void AssertBootJson(AssertBundleOptionsBase options) BootJsonData bootJson = ParseBootData(bootJsonPath); string spcExpectedFilename = $"System.Private.CoreLib{WasmAssemblyExtension}"; - string? spcActualFilename = bootJson.resources.assembly.Keys + string? spcActualFilename = bootJson.resources.coreAssembly.Keys .Where(a => Path.GetFileNameWithoutExtension(a) == "System.Private.CoreLib") .SingleOrDefault(); if (spcActualFilename is null) diff --git a/src/mono/wasm/build/WasmApp.Common.targets b/src/mono/wasm/build/WasmApp.Common.targets index 501a98264538c..54a202840b41f 100644 --- a/src/mono/wasm/build/WasmApp.Common.targets +++ b/src/mono/wasm/build/WasmApp.Common.targets @@ -435,7 +435,7 @@ - + diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js index 3eb9e50aedeb3..e426886485d9d 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js @@ -89,7 +89,7 @@ switch (testCase) { const { setModuleImports, getAssemblyExports, getConfig, INTERNAL } = await dotnet.create(); const config = getConfig(); const exports = await getAssemblyExports(config.mainAssemblyName); -const assemblyExtension = config.resources.assembly['System.Private.CoreLib.wasm'] !== undefined ? ".wasm" : ".dll"; +const assemblyExtension = config.resources.coreAssembly['System.Private.CoreLib.wasm'] !== undefined ? ".wasm" : ".dll"; // Run the test case try { diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs index 17760e84aad06..6436fc1aecb93 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -10,8 +10,29 @@ namespace Microsoft.NET.Sdk.WebAssembly { - public class BootJsonBuilderHelper(TaskLoggingHelper Log) + public class BootJsonBuilderHelper(TaskLoggingHelper Log, bool IsMultiThreaded) { + private static readonly string[] coreAssemblyNames = [ + "System.Private.CoreLib", + "System.Runtime.InteropServices.JavaScript", + ]; + + private static readonly string[] extraMultiThreadedCoreAssemblyName = [ + "System.Threading.Channels" + ]; + + public bool IsCoreAssembly(string fileName) + { + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); + if (coreAssemblyNames.Contains(fileNameWithoutExtension)) + return true; + + if (IsMultiThreaded && extraMultiThreadedCoreAssemblyName.Contains(fileNameWithoutExtension)) + return true; + + return false; + } + public void ComputeResourcesHash(BootJsonData bootConfig) { var sb = new StringBuilder(); @@ -26,6 +47,7 @@ static void AddDictionary(StringBuilder sb, Dictionary? res) } AddDictionary(sb, bootConfig.resources.assembly); + AddDictionary(sb, bootConfig.resources.coreAssembly); AddDictionary(sb, bootConfig.resources.jsModuleWorker); AddDictionary(sb, bootConfig.resources.jsModuleNative); @@ -48,6 +70,12 @@ static void AddDictionary(StringBuilder sb, Dictionary? res) AddDictionary(sb, entry.Value); } + if (bootConfig.resources.coreVfs != null) + { + foreach (var entry in bootConfig.resources.coreVfs) + AddDictionary(sb, entry.Value); + } + bootConfig.resources.hash = Utils.ComputeTextIntegrity(sb.ToString()); } diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index 22edc2c68fbe1..8c28db9b2d4c3 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -152,11 +152,22 @@ public class ResourcesData [DataMember(EmitDefaultValue = false)] public ResourceHashesByNameDictionary icu { get; set; } + /// + /// "assembly" (.dll) resources needed to start MonoVM + /// + public ResourceHashesByNameDictionary coreAssembly { get; set; } = new ResourceHashesByNameDictionary(); + /// /// "assembly" (.dll) resources /// public ResourceHashesByNameDictionary assembly { get; set; } = new ResourceHashesByNameDictionary(); + /// + /// "debug" (.pdb) resources needed to start MonoVM + /// + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary corePdb { get; set; } + /// /// "debug" (.pdb) resources /// @@ -201,6 +212,9 @@ public class ResourcesData [DataMember(EmitDefaultValue = false)] public Dictionary runtimeAssets { get; set; } + [DataMember(EmitDefaultValue = false)] + public Dictionary coreVfs { get; set; } + [DataMember(EmitDefaultValue = false)] public Dictionary vfs { get; set; } diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index d5dc83e4252df..fbe0aa7e3733e 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -22,7 +22,11 @@ namespace Microsoft.NET.Sdk.WebAssembly; public class GenerateWasmBootJson : Task { - private static readonly string[] jiterpreterOptions = new[] { "jiterpreter-traces-enabled", "jiterpreter-interp-entry-enabled", "jiterpreter-jit-call-enabled" }; + private static readonly string[] jiterpreterOptions = [ + "jiterpreter-traces-enabled", + "jiterpreter-interp-entry-enabled", + "jiterpreter-jit-call-enabled" + ]; [Required] public string AssemblyPath { get; set; } @@ -73,6 +77,10 @@ public class GenerateWasmBootJson : Task public bool IsPublish { get; set; } + public bool IsAot { get; set; } + + public bool IsMultiThreaded { get; set; } + public override bool Execute() { using var fileStream = File.Create(OutputPath); @@ -93,7 +101,7 @@ public override bool Execute() // Internal for tests public void WriteBootJson(Stream output, string entryAssemblyName) { - var helper = new BootJsonBuilderHelper(Log); + var helper = new BootJsonBuilderHelper(Log, IsMultiThreaded); var result = new BootJsonData { @@ -205,15 +213,32 @@ public void WriteBootJson(Stream output, string entryAssemblyName) } else { - Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as symbols file.", resource.ItemSpec); - resourceData.pdb ??= new ResourceHashesByNameDictionary(); - resourceList = resourceData.pdb; + if (IsTargeting90OrLater() && (IsAot || helper.IsCoreAssembly(resourceName))) + { + Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as core symbols file.", resource.ItemSpec); + resourceData.corePdb ??= new ResourceHashesByNameDictionary(); + resourceList = resourceData.corePdb; + } + else + { + Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as symbols file.", resource.ItemSpec); + resourceData.pdb ??= new ResourceHashesByNameDictionary(); + resourceList = resourceData.pdb; + } } } else if (string.Equals("runtime", assetTraitValue, StringComparison.OrdinalIgnoreCase)) { - Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as an app assembly.", resource.ItemSpec); - resourceList = resourceData.assembly; + if (IsTargeting90OrLater() && (IsAot || helper.IsCoreAssembly(resourceName))) + { + Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as core assembly.", resource.ItemSpec); + resourceList = resourceData.coreAssembly; + } + else + { + Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as an app assembly.", resource.ItemSpec); + resourceList = resourceData.assembly; + } } else if (string.Equals(assetTraitName, "WasmResource", StringComparison.OrdinalIgnoreCase) && string.Equals(assetTraitValue, "native", StringComparison.OrdinalIgnoreCase)) @@ -445,8 +470,15 @@ private bool TryGetLazyLoadedAssembly(string fileName, out ITaskItem lazyLoadedA private Version? parsedTargetFrameworkVersion; private static readonly Version version80 = new Version(8, 0); + private static readonly Version version90 = new Version(9, 0); private bool IsTargeting80OrLater() + => IsTargetingVersionOrLater(version80); + + private bool IsTargeting90OrLater() + => IsTargetingVersionOrLater(version90); + + private bool IsTargetingVersionOrLater(Version version) { if (parsedTargetFrameworkVersion == null) { @@ -457,6 +489,6 @@ private bool IsTargeting80OrLater() parsedTargetFrameworkVersion = Version.Parse(tfv); } - return parsedTargetFrameworkVersion >= version80; + return parsedTargetFrameworkVersion >= version; } } diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 7847039163b11..fa5cf03f9a560 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -31,6 +31,8 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask public string? RuntimeAssetsLocation { get; set; } public bool CacheBootResources { get; set; } public int DebugLevel { get; set; } + public bool IsAot { get; set; } + public bool IsMultiThreaded { get; set; } private static readonly JsonSerializerOptions s_jsonOptions = new JsonSerializerOptions { @@ -95,7 +97,7 @@ private GlobalizationMode GetGlobalizationMode() protected override bool ExecuteInternal() { - var helper = new BootJsonBuilderHelper(Log); + var helper = new BootJsonBuilderHelper(Log, IsMultiThreaded); var logAdapter = new LogAdapter(Log); if (!ValidateArguments()) @@ -206,16 +208,30 @@ protected override bool ExecuteInternal() bytes = File.ReadAllBytes(assemblyPath); } - bootConfig.resources.assembly[Path.GetFileName(assemblyPath)] = Utils.ComputeIntegrity(bytes); + var assemblyName = Path.GetFileName(assemblyPath); + bool isCoreAssembly = IsAot || helper.IsCoreAssembly(assemblyName); + + var assemblyList = isCoreAssembly ? bootConfig.resources.coreAssembly : bootConfig.resources.assembly; + assemblyList[assemblyName] = Utils.ComputeIntegrity(bytes); + if (DebugLevel != 0) { var pdb = Path.ChangeExtension(assembly, ".pdb"); if (File.Exists(pdb)) { - if (bootConfig.resources.pdb == null) - bootConfig.resources.pdb = new(); - - bootConfig.resources.pdb[Path.GetFileName(pdb)] = Utils.ComputeIntegrity(pdb); + if (isCoreAssembly) + { + if (bootConfig.resources.corePdb == null) + bootConfig.resources.corePdb = new(); + } + else + { + if (bootConfig.resources.pdb == null) + bootConfig.resources.pdb = new(); + } + + var pdbList = isCoreAssembly ? bootConfig.resources.corePdb : bootConfig.resources.pdb; + pdbList[Path.GetFileName(pdb)] = Utils.ComputeIntegrity(pdb); } } } @@ -268,9 +284,11 @@ protected override bool ExecuteInternal() var i = 0; StringDictionary targetPathTable = new(); var vfs = new Dictionary>(); + var coreVfs = new Dictionary>(); foreach (var item in FilesToIncludeInFileSystem) { string? targetPath = item.GetMetadata("TargetPath"); + string? loadingStage = item.GetMetadata("LoadingStage"); if (string.IsNullOrEmpty(targetPath)) { targetPath = Path.GetFileName(item.ItemSpec); @@ -298,7 +316,14 @@ protected override bool ExecuteInternal() var vfsPath = Path.Combine(supportFilesDir, generatedFileName); FileCopyChecked(item.ItemSpec, vfsPath, "FilesToIncludeInFileSystem"); - vfs[targetPath] = new() + var vfsDict = loadingStage switch + { + null => vfs, + "" => vfs, + "Core" => coreVfs, + _ => throw new LogAsErrorException($"The WasmFilesToIncludeInFileSystem '{item.ItemSpec}' has LoadingStage set to unsupported '{loadingStage}' (empty or 'Core' is currently supported).") + }; + vfsDict[targetPath] = new() { [$"supportFiles/{generatedFileName}"] = Utils.ComputeIntegrity(vfsPath) }; @@ -306,6 +331,9 @@ protected override bool ExecuteInternal() if (vfs.Count > 0) bootConfig.resources.vfs = vfs; + + if (coreVfs.Count > 0) + bootConfig.resources.coreVfs = coreVfs; } if (!InvariantGlobalization)