(error: any): void;
};
}
-type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: WebAssembly.Module) => void;
+type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: WebAssembly.Module | undefined) => void;
type InstantiateWasmCallBack = (imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any;
declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array;
// -- this javascript file is evaluated by emcc during compilation! --
// we generate simple proxy for each exported function so that emcc will include them in the final output
for (let linked_function of linked_functions) {
- #if USE_PTHREADS
- const fn_template = `return __dotnet_runtime.__linker_exports.${linked_function}.apply(__dotnet_runtime, arguments)`;
- DotnetSupportLib[linked_function] = new Function(fn_template);
- #else
DotnetSupportLib[linked_function] = new Function('throw new Error("unreachable");');
- #endif
}
autoAddDeps(DotnetSupportLib, "$DOTNET");
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { MonoWorkerMessageChannelCreated, isMonoWorkerMessageChannelCreated, monoSymbol, makeMonoThreadMessageApplyMonoConfig } from "../shared";
+import { isMonoWorkerMessageChannelCreated, monoSymbol, makeMonoThreadMessageApplyMonoConfig, isMonoWorkerMessagePreload, MonoWorkerMessage } from "../shared";
import { pthread_ptr } from "../shared/types";
import { MonoThreadMessage } from "../shared";
import { PromiseController, createPromiseController } from "../../promise-controller";
}
// handler that runs in the main thread when a message is received from a pthread worker
-function monoWorkerMessageHandler(worker: Worker, ev: MessageEvent<MonoWorkerMessageChannelCreated<MessagePort>>): void {
+function monoWorkerMessageHandler(worker: Worker, ev: MessageEvent<MonoWorkerMessage<MessagePort>>): void {
/// N.B. important to ignore messages we don't recognize - Emscripten uses the message event to send internal messages
const data = ev.data;
- if (isMonoWorkerMessageChannelCreated(data)) {
+ if (isMonoWorkerMessagePreload(data)) {
+ const port = data[monoSymbol].port;
+ port.postMessage(makeMonoThreadMessageApplyMonoConfig(runtimeHelpers.config));
+ }
+ else if (isMonoWorkerMessageChannelCreated(data)) {
console.debug("MONO_WASM: received the channel created message", data, worker);
const port = data[monoSymbol].port;
const pthread_id = data[monoSymbol].thread_id;
const thread = addThread(pthread_id, worker, port);
port.addEventListener("message", (ev) => monoDedicatedChannelMessageFromWorkerToMain(ev, thread));
port.start();
- port.postMessage(makeMonoThreadMessageApplyMonoConfig(runtimeHelpers.config));
resolvePromises(pthread_id, thread);
}
}
return browser_thread_id_lazy;
}
+const enum WorkerMonoCommandType {
+ channel_created = "channel_created",
+ preload = "preload",
+}
+
/// Messages sent on the dedicated mono channel between a pthread and the browser thread
// We use a namespacing scheme to avoid collisions: type/command should be unique.
config: string;
}
-export function isMonoThreadMessageApplyMonoConfig(x: unknown): x is MonoThreadMessageApplyMonoConfig {
- if (!isMonoThreadMessage(x)) {
- return false;
- }
- const xmsg = x as MonoThreadMessageApplyMonoConfig;
- return xmsg.type === "pthread" && xmsg.cmd === "apply_mono_config" && typeof (xmsg.config) === "string";
-}
-
export function makeMonoThreadMessageApplyMonoConfig(config: MonoConfig): MonoThreadMessageApplyMonoConfig {
return {
type: "pthread",
/// Messages sent from the main thread using Worker.postMessage or from the worker using DedicatedWorkerGlobalScope.postMessage
/// should use this interface. The message event is also used by emscripten internals (and possibly by 3rd party libraries targeting Emscripten).
/// We should just use this to establish a dedicated MessagePort for Mono's uses.
-export interface MonoWorkerMessage {
- [monoSymbol]: object;
+export interface MonoWorkerMessage<TPort> {
+ [monoSymbol]: {
+ mono_cmd: WorkerMonoCommandType;
+ port: TPort;
+ };
}
/// The message sent early during pthread creation to set up a dedicated MessagePort for Mono between the main thread and the pthread.
-export interface MonoWorkerMessageChannelCreated<TPort> extends MonoWorkerMessage {
+export interface MonoWorkerMessageChannelCreated<TPort> extends MonoWorkerMessage<TPort> {
[monoSymbol]: {
- mono_cmd: "channel_created";
+ mono_cmd: WorkerMonoCommandType.channel_created;
thread_id: pthread_ptr;
port: TPort;
};
}
+export interface MonoWorkerMessagePreload<TPort> extends MonoWorkerMessage<TPort> {
+ [monoSymbol]: {
+ mono_cmd: WorkerMonoCommandType.preload;
+ port: TPort;
+ };
+}
+
export function makeChannelCreatedMonoMessage<TPort>(thread_id: pthread_ptr, port: TPort): MonoWorkerMessageChannelCreated<TPort> {
return {
[monoSymbol]: {
- mono_cmd: "channel_created",
+ mono_cmd: WorkerMonoCommandType.channel_created,
thread_id,
port
}
};
}
-export function isMonoWorkerMessage(message: unknown): message is MonoWorkerMessage {
+export function makePreloadMonoMessage<TPort>(port: TPort): MonoWorkerMessagePreload<TPort> {
+ return {
+ [monoSymbol]: {
+ mono_cmd: WorkerMonoCommandType.preload,
+ port
+ }
+ };
+}
+
+export function isMonoWorkerMessage(message: unknown): message is MonoWorkerMessage<any> {
return message !== undefined && typeof message === "object" && message !== null && monoSymbol in message;
}
-export function isMonoWorkerMessageChannelCreated<TPort>(message: MonoWorkerMessageChannelCreated<TPort>): message is MonoWorkerMessageChannelCreated<TPort> {
+export function isMonoWorkerMessageChannelCreated<TPort>(message: MonoWorkerMessage<TPort>): message is MonoWorkerMessageChannelCreated<TPort> {
+ if (isMonoWorkerMessage(message)) {
+ const monoMessage = message[monoSymbol];
+ if (monoMessage.mono_cmd === WorkerMonoCommandType.channel_created) {
+ return true;
+ }
+ }
+ return false;
+}
+
+export function isMonoWorkerMessagePreload<TPort>(message: MonoWorkerMessage<TPort>): message is MonoWorkerMessagePreload<TPort> {
if (isMonoWorkerMessage(message)) {
const monoMessage = message[monoSymbol];
- if (monoMessage.mono_cmd === "channel_created") {
+ if (monoMessage.mono_cmd === WorkerMonoCommandType.preload) {
return true;
}
}
/// <reference lib="webworker" />
import MonoWasmThreads from "consts:monoWasmThreads";
-import { Module, ENVIRONMENT_IS_PTHREAD, runtimeHelpers } from "../../imports";
-import { isMonoThreadMessageApplyMonoConfig, makeChannelCreatedMonoMessage } from "../shared";
+import { Module, ENVIRONMENT_IS_PTHREAD, runtimeHelpers, ENVIRONMENT_IS_WEB } from "../../imports";
+import { makeChannelCreatedMonoMessage, makePreloadMonoMessage } from "../shared";
import type { pthread_ptr } from "../shared/types";
-import { mono_assert, is_nullish, MonoConfig } from "../../types";
+import { mono_assert, is_nullish, MonoConfig, MonoConfigInternal } from "../../types";
import type { MonoThreadMessage } from "../shared";
import {
PThreadSelf,
// this is the message handler for the worker that receives messages from the main thread
// extend this with new cases as needed
function monoDedicatedChannelMessageFromMainToWorker(event: MessageEvent<string>): void {
- if (isMonoThreadMessageApplyMonoConfig(event.data)) {
+ console.debug("MONO_WASM: got message from main on the dedicated channel", event.data);
+}
+
+export function setupPreloadChannelToMainThread() {
+ const channel = new MessageChannel();
+ const workerPort = channel.port1;
+ const mainPort = channel.port2;
+ workerPort.addEventListener("message", (event) => {
const config = JSON.parse(event.data.config) as MonoConfig;
console.debug("MONO_WASM: applying mono config from main", event.data.config);
onMonoConfigReceived(config);
- return;
- }
- console.debug("MONO_WASM: got message from main on the dedicated channel", event.data);
+ workerPort.close();
+ mainPort.close();
+ }, { once: true });
+ workerPort.start();
+ self.postMessage(makePreloadMonoMessage(mainPort), [mainPort]);
}
function setupChannelToMainThread(pthread_ptr: pthread_ptr): PThreadSelf {
let workerMonoConfigReceived = false;
// called when the main thread sends us the mono config
-function onMonoConfigReceived(config: MonoConfig): void {
+function onMonoConfigReceived(config: MonoConfigInternal): void {
if (workerMonoConfigReceived) {
console.debug("MONO_WASM: mono config already received");
return;
afterConfigLoaded.promise_control.resolve(config);
- if (config.diagnosticTracing) {
+ if (ENVIRONMENT_IS_WEB && config.forwardConsoleLogsToWS && typeof globalThis.WebSocket != "undefined") {
setup_proxy_console("pthread-worker", console, self.location.href);
}
}
const mark = startMeasure();
// these all could be overridden on DotnetModuleConfig, we are chaing them to async below, as opposed to emscripten
// when user set configSrc or config, we are running our default startup sequence.
- const userInstantiateWasm: undefined | ((imports: WebAssembly.Imports, successCallback: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void) => any) = module.instantiateWasm;
+ const userInstantiateWasm: undefined | ((imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any) = module.instantiateWasm;
const userPreInit: (() => void)[] = !module.preInit ? [] : typeof module.preInit === "function" ? [module.preInit] : module.preInit;
const userPreRun: (() => void)[] = !module.preRun ? [] : typeof module.preRun === "function" ? [module.preRun] : module.preRun as any;
const userpostRun: (() => void)[] = !module.postRun ? [] : typeof module.postRun === "function" ? [module.postRun] : module.postRun as any;
if (!Module.configSrc && !Module.config && !userInstantiateWasm) {
Module.print("MONO_WASM: configSrc nor config was specified");
}
- if (Module.config) {
- config = runtimeHelpers.config = Module.config as MonoConfig;
- } else {
- config = runtimeHelpers.config = Module.config = {} as any;
- }
- runtimeHelpers.diagnosticTracing = !!config.diagnosticTracing;
+ normalizeConfig();
const mark = startMeasure();
if (userInstantiateWasm) {
- const exports = userInstantiateWasm(imports, (instance: WebAssembly.Instance, module: WebAssembly.Module) => {
+ const exports = userInstantiateWasm(imports, (instance: WebAssembly.Instance, module: WebAssembly.Module | undefined) => {
endMeasure(mark, MeasuredBlock.instantiateWasm);
afterInstantiateWasm.promise_control.resolve();
successCallback(instance, module);
return []; // No exports
}
+async function instantiateWasmWorker(
+ imports: WebAssembly.Imports,
+ successCallback: InstantiateWasmSuccessCallback
+): Promise<void> {
+ // wait for the config to arrive by message from the main thread
+ await afterConfigLoaded.promise;
+
+ const anyModule = Module as any;
+ normalizeConfig();
+ replace_linker_placeholders(imports, export_linker());
+
+ // Instantiate from the module posted from the main thread.
+ // We can just use sync instantiation in the worker.
+ const instance = new WebAssembly.Instance(anyModule.wasmModule, imports);
+ successCallback(instance, undefined);
+ anyModule.wasmModule = null;
+}
+
function preInit(userPreInit: (() => void)[]) {
Module.addRunDependency("mono_pre_init");
const mark = startMeasure();
}
async function preInitWorkerAsync() {
+ console.debug("MONO_WASM: worker initializing essential C exports and APIs");
const mark = startMeasure();
try {
if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: preInitWorker");
}
configLoaded = true;
if (!configFilePath) {
- normalize();
+ normalizeConfig();
afterConfigLoaded.promise_control.resolve(runtimeHelpers.config);
return;
}
try {
const resolveSrc = runtimeHelpers.locateFile(configFilePath);
const configResponse = await runtimeHelpers.fetch_like(resolveSrc);
- const loadedConfig: MonoConfig = (await configResponse.json()) || {};
+ const loadedConfig: MonoConfigInternal = (await configResponse.json()) || {};
if (loadedConfig.environmentVariables && typeof (loadedConfig.environmentVariables) !== "object")
throw new Error("Expected config.environmentVariables to be unset or a dictionary-style object");
// merge
loadedConfig.assets = [...(loadedConfig.assets || []), ...(config.assets || [])];
loadedConfig.environmentVariables = { ...(loadedConfig.environmentVariables || {}), ...(config.environmentVariables || {}) };
+ loadedConfig.runtimeOptions = [...(loadedConfig.runtimeOptions || []), ...(config.runtimeOptions || [])];
config = runtimeHelpers.config = Module.config = Object.assign(Module.config as any, loadedConfig);
- normalize();
+ normalizeConfig();
if (Module.onConfigLoaded) {
try {
await Module.onConfigLoaded(<MonoConfig>runtimeHelpers.config);
- normalize();
+ normalizeConfig();
}
catch (err: any) {
_print_error("MONO_WASM: onConfigLoaded() failed", err);
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;
+}
- runtimeHelpers.enablePerfMeasure = !!config.browserProfilerOptions
- && globalThis.performance
- && typeof globalThis.performance.measure === "function";
+function normalizeConfig() {
+ // normalize
+ Module.config = config = runtimeHelpers.config = Object.assign(runtimeHelpers.config, Module.config || {});
+ 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;
+
+ runtimeHelpers.enablePerfMeasure = !!config.browserProfilerOptions
+ && globalThis.performance
+ && typeof globalThis.performance.measure === "function";
}
+
export function mono_wasm_asm_loaded(assembly_name: CharPtr, assembly_ptr: number, assembly_len: number, pdb_ptr: number, pdb_len: number): void {
// Only trigger this codepath for assemblies loaded after app is ready
if (runtimeHelpers.mono_wasm_runtime_is_ready !== true)
/// 2. Emscripten does not run any event but preInit in the workers.
/// 3. At the point when this executes there is no pthread assigned to the worker yet.
export async function mono_wasm_pthread_worker_init(module: DotnetModule, exportedAPI: RuntimeAPI): Promise<DotnetModule> {
- console.debug("MONO_WASM: worker initializing essential C exports and APIs");
-
+ pthreads_worker.setupPreloadChannelToMainThread();
// This is a good place for subsystems to attach listeners for pthreads_worker.currentWorkerThreadEvents
pthreads_worker.currentWorkerThreadEvents.addEventListener(pthreads_worker.dotnetPthreadCreated, (ev) => {
console.debug("MONO_WASM: pthread created", ev.pthread_self.pthread_id);
// this is the only event which is called on worker
module.preInit = [() => preInitWorkerAsync()];
+ module.instantiateWasm = instantiateWasmWorker;
await afterPreInit.promise;
return exportedAPI.Module;
onAbort?: { (error: any): void };
}
-export type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: WebAssembly.Module) => void;
+export type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: WebAssembly.Module | undefined) => void;
export type InstantiateWasmCallBack = (imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any;
export declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array;