Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/native/libs/Common/JavaScript/cross-module/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ export function dotnetUpdateInternalsSubscriber() {
abortStartup: table[15],
quitNow: table[16],
normalizeException: table[17],
fetchSatelliteAssemblies: table[18],
fetchLazyAssembly: table[19],
};
Object.assign(dotnetLoaderExports, loaderExportsLocal);
Object.assign(logger, loggerLocal);
Expand Down
23 changes: 19 additions & 4 deletions src/native/libs/Common/JavaScript/host/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@ import { browserVirtualAppBase, sizeOfPtr } from "../per-module";
const hasInstantiateStreaming = typeof WebAssembly !== "undefined" && typeof WebAssembly.instantiateStreaming === "function";
const loadedAssemblies: Map<string, { ptr: number, length: number }> = new Map();

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function registerPdbBytes(bytes: Uint8Array, virtualPath: string) {
// WASM-TODO: https://github.com/dotnet/runtime/issues/122921
const lastSlash = virtualPath.lastIndexOf("/");
const parentDirectory = lastSlash > 0
? virtualPath.substring(0, lastSlash)
: browserVirtualAppBase;
Comment thread
pavelsavara marked this conversation as resolved.
Outdated
let fileName = lastSlash > 0 ? virtualPath.substring(lastSlash + 1) : virtualPath;
if (fileName.startsWith("/")) {
fileName = fileName.substring(1);
}

_ems_.dotnetLogger.debug(`Registering PDB '${fileName}' in directory '${parentDirectory}'`);
_ems_.FS.createPath("/", parentDirectory, true, true);
_ems_.FS.createDataFile(parentDirectory, fileName, bytes, true /* canRead */, true /* canWrite */, true /* canOwn */);
Comment thread
pavelsavara marked this conversation as resolved.
}

export function registerDllBytes(bytes: Uint8Array, virtualPath: string) {
Expand All @@ -25,7 +35,10 @@ export function registerDllBytes(bytes: Uint8Array, virtualPath: string) {
const ptr = _ems_.HEAPU32[ptrPtr as any >>> 2];
_ems_.HEAPU8.set(bytes, ptr >>> 0);

const name = virtualPath.substring(virtualPath.lastIndexOf("/") + 1);
const name = virtualPath.startsWith(browserVirtualAppBase)
? virtualPath.substring(browserVirtualAppBase.length)
: virtualPath.substring(virtualPath.lastIndexOf("/") + 1);

Comment thread
pavelsavara marked this conversation as resolved.
_ems_.dotnetLogger.debug(`Registered assembly '${virtualPath}' (name: '${name}') at ${ptr.toString(16)} length ${bytes.length}`);
loadedAssemblies.set(virtualPath, { ptr, length: bytes.length });
loadedAssemblies.set(name, { ptr, length: bytes.length });
Expand Down Expand Up @@ -69,7 +82,9 @@ export async function instantiateWebcilModule(webcilPromise: Promise<Response>,
const getWebcilPayload = instance.exports.getWebcilPayload as (ptr: number, size: number) => void;
getWebcilPayload(payloadPtr, payloadSize);

const name = virtualPath.substring(virtualPath.lastIndexOf("/") + 1);
const name = virtualPath.startsWith(browserVirtualAppBase)
? virtualPath.substring(browserVirtualAppBase.length)
: virtualPath.substring(virtualPath.lastIndexOf("/") + 1);
_ems_.dotnetLogger.debug(`Registered Webcil assembly '${virtualPath}' (name: '${name}') at ${payloadPtr.toString(16)} length ${payloadSize}`);
loadedAssemblies.set(virtualPath, { ptr: payloadPtr, length: payloadSize });
loadedAssemblies.set(name, { ptr: payloadPtr, length: payloadSize });
Expand Down
89 changes: 85 additions & 4 deletions src/native/libs/Common/JavaScript/loader/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,17 @@ export async function fetchDll(asset: AssemblyAsset): Promise<void> {
totalAssetsToDownload++;
const assetInternal = asset as AssetEntryInternal;
dotnetAssert.check(assetInternal.virtualPath, "Assembly asset must have virtualPath");
if (assetInternal.name && !asset.resolvedUrl) {
asset.resolvedUrl = locateFile(assetInternal.name);
const assetNameForUrl = assetInternal.culture
? `${assetInternal.culture}/${assetInternal.name}`
: assetInternal.name;
if (assetNameForUrl && !asset.resolvedUrl) {
asset.resolvedUrl = locateFile(assetNameForUrl);
}
assetInternal.virtualPath = assetInternal.virtualPath.startsWith("/")
? assetInternal.virtualPath
: browserVirtualAppBase + assetInternal.virtualPath;
: assetInternal.culture
? `${browserVirtualAppBase}${assetInternal.culture}/${assetInternal.virtualPath}`
: browserVirtualAppBase + assetInternal.virtualPath;
if (assetInternal.virtualPath.endsWith(".wasm")) {
assetInternal.behavior = "webcil10";
const webcilPromise = loadResource(assetInternal);
Expand All @@ -110,7 +115,6 @@ export async function fetchDll(asset: AssemblyAsset): Promise<void> {
assetInternal.behavior = "assembly";
const bytes = await fetchBytes(assetInternal);
onDownloadedAsset();

await nativeModulePromiseController.promise;
if (bytes) {
dotnetBrowserHostExports.registerDllBytes(bytes, assetInternal.virtualPath);
Expand Down Expand Up @@ -154,6 +158,83 @@ export async function fetchVfs(asset: AssemblyAsset): Promise<void> {
}
}

export async function fetchSatelliteAssemblies(culturesToLoad: string[]): Promise<void> {
const satelliteResources = loaderConfig.resources?.satelliteResources;
if (!satelliteResources) {
return;
}

const promises: Promise<void>[] = [];
for (const culture of culturesToLoad) {
if (!Object.prototype.hasOwnProperty.call(satelliteResources, culture)) {
continue;
}
for (const asset of satelliteResources[culture]) {
const assetInternal = asset as AssetEntryInternal;
Comment thread
pavelsavara marked this conversation as resolved.
assetInternal.culture = culture;
promises.push(fetchDll(asset));
}
}
await Promise.all(promises);
}

export async function fetchLazyAssembly(assemblyNameToLoad: string): Promise<boolean> {
const lazyAssemblies = loaderConfig.resources?.lazyAssembly;
if (!lazyAssemblies) {
throw new Error("No assemblies have been marked as lazy-loadable. Use the 'BlazorWebAssemblyLazyLoad' item group in your project file to enable lazy loading an assembly.");
Comment thread
pavelsavara marked this conversation as resolved.
}

let assemblyNameWithoutExtension = assemblyNameToLoad;
if (assemblyNameToLoad.endsWith(".dll"))
Comment thread
pavelsavara marked this conversation as resolved.
assemblyNameWithoutExtension = assemblyNameToLoad.substring(0, assemblyNameToLoad.length - 4);
else if (assemblyNameToLoad.endsWith(".wasm"))
assemblyNameWithoutExtension = assemblyNameToLoad.substring(0, assemblyNameToLoad.length - 5);

const assemblyNameToLoadDll = assemblyNameWithoutExtension + ".dll";
const assemblyNameToLoadWasm = assemblyNameWithoutExtension + ".wasm";

let dllAsset: AssemblyAsset | null = null;
for (const asset of lazyAssemblies) {
if (asset.virtualPath === assemblyNameToLoadDll || asset.virtualPath === assemblyNameToLoadWasm) {
dllAsset = asset;
break;
}
}

if (!dllAsset) {
throw new Error(`${assemblyNameToLoad} must be marked with 'BlazorWebAssemblyLazyLoad' item group in your project file to allow lazy-loading.`);
Comment thread
pavelsavara marked this conversation as resolved.
}

await fetchDll(dllAsset);

if (loaderConfig.debugLevel != 0) {
Comment thread
pavelsavara marked this conversation as resolved.
Outdated
const pdbNameToLoad = assemblyNameWithoutExtension + ".pdb";
const pdbAssets = loaderConfig.resources?.pdb;
let pdbAssetToLoad: SymbolsAsset | undefined;
if (pdbAssets) {
for (const pdbAsset of pdbAssets) {
if (pdbAsset.virtualPath === pdbNameToLoad) {
pdbAssetToLoad = pdbAsset;
break;
}
}
Comment thread
pavelsavara marked this conversation as resolved.
Outdated
}
if (!pdbAssetToLoad) {
for (const lazyAsset of lazyAssemblies) {
if (lazyAsset.virtualPath === pdbNameToLoad) {
pdbAssetToLoad = lazyAsset as SymbolsAsset;
break;
}
}
}
Comment thread
pavelsavara marked this conversation as resolved.
if (pdbAssetToLoad) {
await fetchPdb(pdbAssetToLoad);
}
}

return true;
}
Comment thread
pavelsavara marked this conversation as resolved.

export async function fetchNativeSymbols(asset: SymbolsAsset): Promise<void> {
totalAssetsToDownload++;
const assetInternal = asset as AssetEntryInternal;
Expand Down
5 changes: 5 additions & 0 deletions src/native/libs/Common/JavaScript/loader/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ function mergeResources(target: Assets, source: Assets): Assets {
for (const key in source.satelliteResources) {
source.satelliteResources![key] = [...target.satelliteResources![key] || [], ...source.satelliteResources![key] || []];
}
for (const key in target.satelliteResources) {
Comment thread
pavelsavara marked this conversation as resolved.
if (!Object.prototype.hasOwnProperty.call(source.satelliteResources, key)) {
source.satelliteResources![key] = target.satelliteResources![key] || [];
}
Comment thread
pavelsavara marked this conversation as resolved.
}
return Object.assign(target, source);
}

Expand Down
6 changes: 5 additions & 1 deletion src/native/libs/Common/JavaScript/loader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { check, error, info, warn, debug, fastCheck, normalizeException } from "
import { dotnetAssert, dotnetLoaderExports, dotnetLogger, dotnetUpdateInternals, dotnetUpdateInternalsSubscriber } from "./cross-module";
import { rejectRunMainPromise, resolveRunMainPromise, getRunMainPromise, abortStartup } from "./run";
import { createPromiseCompletionSource, getPromiseCompletionSource, isControllablePromise } from "./promise-completion-source";
import { instantiateMainWasm } from "./assets";
import { fetchLazyAssembly, fetchSatelliteAssemblies, instantiateMainWasm } from "./assets";

export function dotnetInitializeModule(): RuntimeAPI {

Expand Down Expand Up @@ -67,6 +67,8 @@ export function dotnetInitializeModule(): RuntimeAPI {
abortStartup,
quitNow,
normalizeException,
fetchSatelliteAssemblies,
fetchLazyAssembly,
};
Object.assign(dotnetLoaderExports, loaderFunctions);
const logger: LoggerType = {
Expand Down Expand Up @@ -114,6 +116,8 @@ export function dotnetInitializeModule(): RuntimeAPI {
dotnetLoaderExports.abortStartup,
dotnetLoaderExports.quitNow,
dotnetLoaderExports.normalizeException,
dotnetLoaderExports.fetchSatelliteAssemblies,
dotnetLoaderExports.fetchLazyAssembly,
];
}

Expand Down
8 changes: 5 additions & 3 deletions src/native/libs/Common/JavaScript/loader/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ import { exit, runtimeState } from "./exit";
import { createPromiseCompletionSource } from "./promise-completion-source";
import { getIcuResourceName } from "./icu";
import { loaderConfig, validateLoaderConfig } from "./config";
import { fetchDll, fetchIcu, fetchNativeSymbols, fetchPdb, fetchVfs, fetchWasm, loadDotnetModule, loadJSModule, nativeModulePromiseController, verifyAllAssetsDownloaded } from "./assets";
import { fetchDll, fetchIcu, fetchNativeSymbols, fetchPdb, fetchSatelliteAssemblies, fetchVfs, fetchWasm, loadDotnetModule, loadJSModule, nativeModulePromiseController, verifyAllAssetsDownloaded } from "./assets";
import { initPolyfills } from "./polyfills";
import { validateWasmFeatures } from "./bootstrap";

const runMainPromiseController = createPromiseCompletionSource<number>();

// WASM-TODO: downloadOnly - blazor render mode auto pre-download. Really no start.
// WASM-TODO: loadAllSatelliteResources
// WASM-TODO: debugLevel
// WASM-TODO: load symbolication json https://github.com/dotnet/runtime/issues/122647

// many things happen in parallel here, but order matters for performance!
// ideally we want to utilize network and CPU at the same time
Expand Down Expand Up @@ -56,6 +54,9 @@ export async function createRuntime(downloadOnly: boolean): Promise<any> {
const coreVfsPromise = Promise.all((loaderConfig.resources.coreVfs || []).map(fetchVfs));

const assembliesPromise = Promise.all(loaderConfig.resources.assembly.map(fetchDll));
const satelliteResourcesPromise = loaderConfig.loadAllSatelliteResources && loaderConfig.resources.satelliteResources
? fetchSatelliteAssemblies(Object.keys(loaderConfig.resources.satelliteResources))
: Promise.resolve();
const vfsPromise = Promise.all((loaderConfig.resources.vfs || []).map(fetchVfs));

const icuResourceName = getIcuResourceName();
Expand Down Expand Up @@ -87,6 +88,7 @@ export async function createRuntime(downloadOnly: boolean): Promise<any> {
}

await assembliesPromise;
await satelliteResourcesPromise;
await corePDBsPromise;
await pdbsPromise;
await runtimeModuleReady;
Expand Down
7 changes: 6 additions & 1 deletion src/native/libs/Common/JavaScript/types/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { addOnExitListener, isExited, isRuntimeRunning, quitNow } from "../
import type { initializeCoreCLR } from "../host/host";
import type { instantiateWasm, installVfsFile, registerDllBytes, loadIcuData, registerPdbBytes, instantiateWebcilModule } from "../host/assets";
import type { createPromiseCompletionSource, getPromiseCompletionSource, isControllablePromise } from "../loader/promise-completion-source";
import type { fetchSatelliteAssemblies, fetchLazyAssembly } from "../loader/assets";

import type { isSharedArrayBuffer, zeroRegion } from "../../../System.Native.Browser/utils/memory";
import type { stringToUTF16, stringToUTF16Ptr, stringToUTF8, stringToUTF8Ptr, utf16ToString } from "../../../System.Native.Browser/utils/strings";
Expand Down Expand Up @@ -71,7 +72,9 @@ export type LoaderExports = {
addOnExitListener: typeof addOnExitListener,
abortStartup: typeof abortStartup,
quitNow: typeof quitNow,
normalizeException: typeof normalizeException
normalizeException: typeof normalizeException,
fetchSatelliteAssemblies: typeof fetchSatelliteAssemblies,
fetchLazyAssembly: typeof fetchLazyAssembly,
}

export type LoaderExportsTable = [
Expand All @@ -93,6 +96,8 @@ export type LoaderExportsTable = [
typeof abortStartup,
typeof quitNow,
typeof normalizeException,
typeof fetchSatelliteAssemblies,
typeof fetchLazyAssembly,
]

export type BrowserHostExports = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { dotnetLoaderExports } from "./cross-module";

export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise<void> {
throw new Error("TODO: loadSatelliteAssemblies is not implemented yet");
await dotnetLoaderExports.fetchSatelliteAssemblies(culturesToLoad);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function loadLazyAssembly(assemblyNameToLoad: string): Promise<boolean> {
throw new Error("TODO: loadLazyAssembly is not implemented yet");
return dotnetLoaderExports.fetchLazyAssembly(assemblyNameToLoad);
}
Loading