Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions src/native/libs/Common/JavaScript/cross-module/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export function dotnetUpdateInternalsSubscriber() {
initializeCoreCLR: table[3],
registerPdbBytes: table[4],
instantiateWasm: table[5],
instantiateWebcilModule: table[6],
};
Object.assign(native, nativeLocal);
}
Expand All @@ -172,6 +173,8 @@ export function dotnetUpdateInternalsSubscriber() {
// keep in sync with nativeBrowserExportsToTable()
function nativeBrowserExportsFromTable(table: NativeBrowserExportsTable, interop: NativeBrowserExports): void {
const interopLocal: NativeBrowserExports = {
getWasmMemory: table[0],
getWasmTable: table[1],
};
Object.assign(interop, interopLocal);
}
Expand Down
64 changes: 49 additions & 15 deletions src/native/libs/Common/JavaScript/host/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@
import type { CharPtr, VfsAsset, VoidPtr, VoidPtrPtr } from "../types";
import { _ems_ } from "../ems-ambient";

import { browserVirtualAppBase, ENVIRONMENT_IS_WEB } from "../per-module";
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
let wasmMemory: WebAssembly.Memory = undefined as any;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let wasmMainTable: WebAssembly.Table = undefined as any;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function registerPdbBytes(bytes: Uint8Array, virtualPath: string) {
Expand All @@ -21,16 +17,15 @@ export function registerPdbBytes(bytes: Uint8Array, virtualPath: string) {
export function registerDllBytes(bytes: Uint8Array, virtualPath: string) {
const sp = _ems_.stackSave();
try {
const sizeOfPtr = 4;
const ptrPtr = _ems_.stackAlloc(sizeOfPtr);
if (_ems_._posix_memalign(ptrPtr as any, 16, bytes.length)) {
throw new Error("posix_memalign failed");
}

const ptr = _ems_.HEAPU32[ptrPtr as any >>> 2];
_ems_.HEAPU8.set(bytes, ptr >>> 0);
const name = virtualPath.substring(virtualPath.lastIndexOf("/") + 1);

const name = virtualPath.substring(virtualPath.lastIndexOf("/") + 1);
_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 All @@ -39,6 +34,50 @@ export function registerDllBytes(bytes: Uint8Array, virtualPath: string) {
}
}

export async function instantiateWebcilModule(webcilPromise: Promise<Response>, memory: WebAssembly.Memory, virtualPath: string): Promise<void> {

const imports: WebAssembly.Imports = {
webcil: {
memory,
}
};

const { instance } = await instantiateWasm(webcilPromise, imports);
const webcilVersion = (instance.exports.webcilVersion as WebAssembly.Global).value;
if (webcilVersion !== 0) {
throw new Error(`Unsupported Webcil version: ${webcilVersion}`);
}

const sp = _ems_.stackSave();
try {
const sizePtr = _ems_.stackAlloc(sizeOfPtr);
const getWebcilSize = instance.exports.getWebcilSize as (destPtr: number) => void;
getWebcilSize(sizePtr as any);
const payloadSize = _ems_.HEAPU32[sizePtr as any >>> 2];

if (payloadSize === 0) {
throw new Error("Webcil payload size is 0");
}

const ptrPtr = _ems_.stackAlloc(sizeOfPtr);
if (_ems_._posix_memalign(ptrPtr as any, 16, payloadSize)) {
throw new Error("posix_memalign failed for Webcil payload");
}

const payloadPtr = _ems_.HEAPU32[ptrPtr as any >>> 2];

const getWebcilPayload = instance.exports.getWebcilPayload as (ptr: number, size: number) => void;
getWebcilPayload(payloadPtr, payloadSize);

const name = 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 });
} finally {
_ems_.stackRestore(sp);
}
}

export function BrowserHost_ExternalAssemblyProbe(pathPtr: CharPtr, outDataStartPtr: VoidPtrPtr, outSize: VoidPtr) {
const path = _ems_.UTF8ToString(pathPtr);
const assembly = loadedAssemblies.get(path);
Expand All @@ -59,7 +98,6 @@ export function BrowserHost_ExternalAssemblyProbe(pathPtr: CharPtr, outDataStart
export function loadIcuData(bytes: Uint8Array) {
const sp = _ems_.stackSave();
try {
const sizeOfPtr = 4;
const ptrPtr = _ems_.stackAlloc(sizeOfPtr);
if (_ems_._posix_memalign(ptrPtr as any, 16, bytes.length)) {
throw new Error("posix_memalign failed for ICU data");
Expand Down Expand Up @@ -100,11 +138,11 @@ export function installVfsFile(bytes: Uint8Array, asset: VfsAsset) {
_ems_.FS.createDataFile(parentDirectory, fileName, bytes, true /* canRead */, true /* canWrite */, true /* canOwn */);
}

export async function instantiateWasm(wasmPromise: Promise<Response>, imports: WebAssembly.Imports, isStreaming: boolean, isMainModule: boolean): Promise<{ instance: WebAssembly.Instance; module: WebAssembly.Module; }> {
export async function instantiateWasm(wasmPromise: Promise<Response>, imports: WebAssembly.Imports): Promise<{ instance: WebAssembly.Instance; module: WebAssembly.Module; }> {
let instance: WebAssembly.Instance;
let module: WebAssembly.Module;
const res = await checkResponseOk(wasmPromise);
if (!hasInstantiateStreaming || !isStreaming || !res.isStreamingOk) {
if (!hasInstantiateStreaming || !res.isStreamingOk) {
const data = await res.arrayBuffer();
module = await WebAssembly.compile(data);
instance = await WebAssembly.instantiate(module, imports);
Expand All @@ -113,10 +151,6 @@ export async function instantiateWasm(wasmPromise: Promise<Response>, imports: W
instance = instantiated.instance;
module = instantiated.module;
}
if (isMainModule) {
wasmMemory = instance.exports.memory as WebAssembly.Memory;
wasmMainTable = instance.exports.__indirect_function_table as WebAssembly.Table;
}
return { instance, module };

async function checkResponseOk(wasmPromise: Promise<Response> | undefined): Promise<Response & { isStreamingOk?: boolean }> {
Expand All @@ -127,7 +161,7 @@ export async function instantiateWasm(wasmPromise: Promise<Response>, imports: W
}
res.isStreamingOk = typeof globalThis.Response === "function" && res instanceof globalThis.Response;
const contentType = res.headers && res.headers.get ? res.headers.get("Content-Type") : undefined;
if (ENVIRONMENT_IS_WEB && contentType !== "application/wasm") {
if (_ems_.ENVIRONMENT_IS_WEB && contentType !== "application/wasm") {
_ems_.dotnetLogger.warn("WebAssembly resource does not have the expected content type \"application/wasm\", so falling back to slower ArrayBuffer instantiation.");
res.isStreamingOk = false;
}
Expand Down
16 changes: 8 additions & 8 deletions src/native/libs/Common/JavaScript/host/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import type { CharPtrPtr, VoidPtr } from "../types";
import { _ems_ } from "../ems-ambient";
import { browserVirtualAppBase } from "../per-module";
import { browserVirtualAppBase, sizeOfPtr } from "../per-module";

const HOST_PROPERTY_RUNTIME_CONTRACT = "HOST_RUNTIME_CONTRACT";
const HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES = "TRUSTED_PLATFORM_ASSEMBLIES";
Expand All @@ -23,8 +23,8 @@ export function initializeCoreCLR(): number {
}
}

const assemblyPaths = loaderConfig.resources!.assembly.map(asset => asset.virtualPath);
const coreAssemblyPaths = loaderConfig.resources!.coreAssembly.map(asset => asset.virtualPath);
const assemblyPaths = loaderConfig.resources!.assembly.map(asset => asset.virtualPath.replace(/\.wasm$/, ".dll"));
const coreAssemblyPaths = loaderConfig.resources!.coreAssembly.map(asset => asset.virtualPath.replace(/\.wasm$/, ".dll"));
const tpa = [...coreAssemblyPaths, ...assemblyPaths].join(":");
runtimeConfigProperties.set(HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES, tpa);
runtimeConfigProperties.set(HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES, loaderConfig.virtualWorkingDirectory!);
Expand All @@ -35,17 +35,17 @@ export function initializeCoreCLR(): number {
runtimeConfigProperties.set(HOST_PROPERTY_RUNTIME_CONTRACT, `0x${(hostContractPtr as unknown as number).toString(16)}`);

const buffers: VoidPtr[] = [];
const appctx_keys = _ems_._malloc(4 * runtimeConfigProperties.size) as any as CharPtrPtr;
const appctx_values = _ems_._malloc(4 * runtimeConfigProperties.size) as any as CharPtrPtr;
const appctx_keys = _ems_._malloc(sizeOfPtr * runtimeConfigProperties.size) as any as CharPtrPtr;
const appctx_values = _ems_._malloc(sizeOfPtr * runtimeConfigProperties.size) as any as CharPtrPtr;
buffers.push(appctx_keys as any);
buffers.push(appctx_values as any);

let propertyCount = 0;
for (const [key, value] of runtimeConfigProperties.entries()) {
const keyPtr = _ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(key);
const valuePtr = _ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(value);
_ems_.dotnetApi.setHeapU32((appctx_keys as any) + (propertyCount * 4), keyPtr);
_ems_.dotnetApi.setHeapU32((appctx_values as any) + (propertyCount * 4), valuePtr);
_ems_.dotnetApi.setHeapU32((appctx_keys as any) + (propertyCount * sizeOfPtr), keyPtr);
_ems_.dotnetApi.setHeapU32((appctx_values as any) + (propertyCount * sizeOfPtr), valuePtr);
propertyCount++;
buffers.push(keyPtr as any);
buffers.push(valuePtr as any);
Expand All @@ -72,7 +72,7 @@ export async function runMain(mainAssemblyName?: string, args?: string[]): Promi
args ??= [];

const sp = _ems_.stackSave();
const argsvPtr: number = _ems_.stackAlloc((args.length + 1) * 4) as any;
const argsvPtr: number = _ems_.stackAlloc((args.length + 1) * sizeOfPtr) as any;
const ptrs: VoidPtr[] = [];
try {

Expand Down
4 changes: 3 additions & 1 deletion src/native/libs/Common/JavaScript/host/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { _ems_ } from "../ems-ambient";
import GitHash from "consts:gitHash";

import { runMain, runMainAndExit, initializeCoreCLR } from "./host";
import { registerPdbBytes, registerDllBytes, installVfsFile, loadIcuData, instantiateWasm, } from "./assets";
import { registerPdbBytes, instantiateWebcilModule, registerDllBytes, installVfsFile, loadIcuData, instantiateWasm, } from "./assets";

export function dotnetInitializeModule(internals: InternalExchange): void {
if (!Array.isArray(internals)) throw new Error("Expected internals to be an array");
Expand All @@ -30,6 +30,7 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
initializeCoreCLR,
registerPdbBytes,
instantiateWasm,
instantiateWebcilModule,
});
_ems_.dotnetUpdateInternals(internals, _ems_.dotnetUpdateInternalsSubscriber);

Expand All @@ -44,6 +45,7 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
map.initializeCoreCLR,
map.registerPdbBytes,
map.instantiateWasm,
map.instantiateWebcilModule,
];
}
}
Expand Down
37 changes: 25 additions & 12 deletions src/native/libs/Common/JavaScript/loader/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import type { JsModuleExports, JsAsset, AssemblyAsset, WasmAsset, IcuAsset, EmscriptenModuleInternal, WebAssemblyBootResourceType, AssetEntryInternal, PromiseCompletionSource, LoadBootResourceCallback, InstantiateWasmSuccessCallback, SymbolsAsset } from "./types";

import { dotnetAssert, dotnetLogger, dotnetInternals, dotnetBrowserHostExports, dotnetUpdateInternals, Module, dotnetDiagnosticsExports } from "./cross-module";
import { dotnetAssert, dotnetLogger, dotnetInternals, dotnetBrowserHostExports, dotnetUpdateInternals, Module, dotnetDiagnosticsExports, dotnetNativeBrowserExports } from "./cross-module";
import { ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_NODE, browserVirtualAppBase } from "./per-module";
import { createPromiseCompletionSource, delay } from "./promise-completion-source";
import { locateFile, makeURLAbsoluteWithApplicationBase } from "./bootstrap";
Expand All @@ -20,7 +20,7 @@ export function setLoadBootResourceCallback(callback: LoadBootResourceCallback |
loadBootResourceCallback = callback;
}
export let wasmBinaryPromise: Promise<Response> | undefined = undefined;
export const mainModulePromiseController = createPromiseCompletionSource<WebAssembly.Instance>();
export const wasmMemoryPromiseController = createPromiseCompletionSource<WebAssembly.Memory>();
export const nativeModulePromiseController = createPromiseCompletionSource<EmscriptenModuleInternal>(() => {
dotnetUpdateInternals(dotnetInternals);
});
Expand Down Expand Up @@ -66,10 +66,11 @@ export function fetchWasm(asset: WasmAsset): Promise<Response> {
}

export async function instantiateMainWasm(imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback): Promise<void> {
const { instance, module } = await dotnetBrowserHostExports.instantiateWasm(wasmBinaryPromise!, imports, true, true);
const { instance, module } = await dotnetBrowserHostExports.instantiateWasm(wasmBinaryPromise!, imports);
onDownloadedAsset();
successCallback(instance, module);
mainModulePromiseController.resolve(instance);
const memory = dotnetNativeBrowserExports.getWasmMemory();
wasmMemoryPromiseController.resolve(memory);
}

export async function fetchIcu(asset: IcuAsset): Promise<void> {
Expand All @@ -94,17 +95,26 @@ export async function fetchDll(asset: AssemblyAsset): Promise<void> {
if (assetInternal.name && !asset.resolvedUrl) {
asset.resolvedUrl = locateFile(assetInternal.name);
}
assetInternal.behavior = "assembly";
assetInternal.virtualPath = assetInternal.virtualPath.startsWith("/")
? assetInternal.virtualPath
: browserVirtualAppBase + assetInternal.virtualPath;
if (assetInternal.virtualPath.endsWith(".wasm")) {
assetInternal.behavior = "webcil10";
const webcilPromise = loadResource(assetInternal);

const memory = await wasmMemoryPromiseController.promise;
const virtualPath = assetInternal.virtualPath.replace(/\.wasm$/, ".dll");
await dotnetBrowserHostExports.instantiateWebcilModule(webcilPromise, memory, virtualPath);
onDownloadedAsset();
} else {
assetInternal.behavior = "assembly";
const bytes = await fetchBytes(assetInternal);
onDownloadedAsset();

const bytes = await fetchBytes(assetInternal);
await nativeModulePromiseController.promise;

onDownloadedAsset();
if (bytes) {
dotnetBrowserHostExports.registerDllBytes(bytes, asset.virtualPath);
await nativeModulePromiseController.promise;
if (bytes) {
dotnetBrowserHostExports.registerDllBytes(bytes, assetInternal.virtualPath);
}
}
}

Expand All @@ -125,7 +135,7 @@ export async function fetchPdb(asset: AssemblyAsset): Promise<void> {

onDownloadedAsset();
if (bytes) {
dotnetBrowserHostExports.registerPdbBytes(bytes, asset.virtualPath);
dotnetBrowserHostExports.registerPdbBytes(bytes, assetInternal.virtualPath);
}
}

Expand Down Expand Up @@ -344,6 +354,7 @@ const behaviorToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType
"manifest": "manifest",
"symbols": "pdb",
"dotnetwasm": "dotnetwasm",
"webcil10": "assembly",
"js-module-dotnet": "dotnetjs",
"js-module-native": "dotnetjs",
"js-module-runtime": "dotnetjs",
Expand All @@ -359,9 +370,11 @@ const behaviorToContentTypeMap: { [key: string]: string | undefined } = {
"manifest": "application/json",
"symbols": "text/plain; charset=utf-8",
"dotnetwasm": "application/wasm",
"webcil10": "application/wasm",
};

const noThrottleNoRetry: { [key: string]: number | undefined } = {
"dotnetwasm": 1,
"symbols": 1,
"webcil10": 1,
};
6 changes: 5 additions & 1 deletion src/native/libs/Common/JavaScript/loader/dotnet.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,11 @@ type AssetBehaviors = SingleAssetBehaviors |
/**
* The javascript module that came from nuget package .
*/
| "js-module-library-initializer";
| "js-module-library-initializer"
/**
* Managed assembly packaged as Webcil v 1.0
*/
| "webcil10";
declare const enum GlobalizationMode {
/**
* Load sharded ICU data.
Expand Down
1 change: 0 additions & 1 deletion src/native/libs/Common/JavaScript/loader/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { validateWasmFeatures } from "./bootstrap";

const runMainPromiseController = createPromiseCompletionSource<number>();

// WASM-TODO: webCIL
// WASM-TODO: downloadOnly - blazor render mode auto pre-download. Really no start.
// WASM-TODO: loadAllSatelliteResources
// WASM-TODO: debugLevel
Expand Down
1 change: 1 addition & 0 deletions src/native/libs/Common/JavaScript/per-module/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export const VoidPtrNull: VoidPtr = <VoidPtr><any>0;
export const CharPtrNull: CharPtr = <CharPtr><any>0;
export const NativePointerNull: NativePointer = <NativePointer><any>0;
export const browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase
export const sizeOfPtr = 4;
3 changes: 3 additions & 0 deletions src/native/libs/Common/JavaScript/types/ems-ambient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,7 @@ export type EmsAmbientSymbolsType = EmscriptenModuleInternal & {
writeI53ToI64(ptr: MemOffset, value: number): void;
readI53FromI64(ptr: MemOffset): number;
readI53FromU64(ptr: MemOffset): number;

wasmMemory: WebAssembly.Memory;
wasmTable: WebAssembly.Table;
}
12 changes: 11 additions & 1 deletion src/native/libs/Common/JavaScript/types/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { resolveRunMainPromise, rejectRunMainPromise, getRunMainPromise, ab
import type { addOnExitListener, isExited, isRuntimeRunning, quitNow } from "../loader/exit";

import type { initializeCoreCLR } from "../host/host";
import type { instantiateWasm, installVfsFile, registerDllBytes, loadIcuData, registerPdbBytes } from "../host/assets";
import type { instantiateWasm, installVfsFile, registerDllBytes, loadIcuData, registerPdbBytes, instantiateWebcilModule } from "../host/assets";
import type { createPromiseCompletionSource, getPromiseCompletionSource, isControllablePromise } from "../loader/promise-completion-source";

import type { isSharedArrayBuffer, zeroRegion } from "../../../System.Native.Browser/utils/memory";
Expand All @@ -21,6 +21,10 @@ import type { abortInteropTimers } from "../../../System.Runtime.InteropServices
import type { installNativeSymbols, symbolicateStackTrace } from "../../../System.Native.Browser/diagnostics/symbolicate";
import type { EmsAmbientSymbolsType } from "../types";


type getWasmMemoryType = () => WebAssembly.Memory;
type getWasmTableType = () => WebAssembly.Table;

export type RuntimeExports = {
bindJSImportST: typeof bindJSImportST,
invokeJSImportST: typeof invokeJSImportST,
Expand Down Expand Up @@ -98,6 +102,7 @@ export type BrowserHostExports = {
initializeCoreCLR: typeof initializeCoreCLR
registerPdbBytes: typeof registerPdbBytes
instantiateWasm: typeof instantiateWasm
instantiateWebcilModule: typeof instantiateWebcilModule
}

export type BrowserHostExportsTable = [
Expand All @@ -107,6 +112,7 @@ export type BrowserHostExportsTable = [
typeof initializeCoreCLR,
typeof registerPdbBytes,
typeof instantiateWasm,
typeof instantiateWebcilModule,
]

export type InteropJavaScriptExports = {
Expand All @@ -128,9 +134,13 @@ export type InteropJavaScriptExportsTable = [
]

export type NativeBrowserExports = {
getWasmMemory: getWasmMemoryType,
getWasmTable: getWasmTableType,
}

export type NativeBrowserExportsTable = [
getWasmMemoryType,
getWasmTableType,
]

export type BrowserUtilsExports = {
Expand Down
Loading
Loading