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
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/dotnet.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ declare interface EmscriptenModule {
(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;

Expand Down
5 changes: 0 additions & 5 deletions src/mono/wasm/runtime/es6/dotnet.es6.lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,7 @@ const linked_functions = [
// -- 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");
Expand Down
11 changes: 7 additions & 4 deletions src/mono/wasm/runtime/pthreads/browser/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

import { 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";
Expand Down Expand Up @@ -85,17 +85,20 @@ function monoDedicatedChannelMessageFromWorkerToMain(event: MessageEvent<unknown
}

// 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);
}
}
Expand Down
58 changes: 42 additions & 16 deletions src/mono/wasm/runtime/pthreads/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export function getBrowserThreadID(): pthread_ptr {
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.
Expand All @@ -50,14 +55,6 @@ export interface MonoThreadMessageApplyMonoConfig extends MonoThreadMessage {
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",
Expand All @@ -75,37 +72,66 @@ export const monoSymbol = "__mono_message_please_dont_collide__"; //Symbol("mono
/// 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;
}
}
Expand Down
27 changes: 18 additions & 9 deletions src/mono/wasm/runtime/pthreads/worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
/// <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,
Expand Down Expand Up @@ -60,13 +60,22 @@ export const currentWorkerThreadEvents: WorkerThreadEventTarget =
// 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);
Comment thread
pavelsavara marked this conversation as resolved.
}

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 {
Expand All @@ -84,7 +93,7 @@ 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;
Expand All @@ -96,7 +105,7 @@ function onMonoConfigReceived(config: MonoConfig): void {

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);
}
}
Expand Down
78 changes: 48 additions & 30 deletions src/mono/wasm/runtime/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI:
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;
Expand Down Expand Up @@ -102,16 +102,11 @@ function instantiateWasm(
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);
Expand All @@ -123,6 +118,24 @@ function instantiateWasm(
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();
Expand Down Expand Up @@ -160,6 +173,7 @@ function preInit(userPreInit: (() => void)[]) {
}

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");
Expand Down Expand Up @@ -564,29 +578,30 @@ export async function mono_wasm_load_config(configFilePath?: string): Promise<vo
}
configLoaded = true;
if (!configFilePath) {
normalize();
normalizeConfig();
afterConfigLoaded.promise_control.resolve(runtimeHelpers.config);
return;
}
if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_load_config");
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);
Expand All @@ -601,26 +616,29 @@ export async function mono_wasm_load_config(configFilePath?: string): Promise<vo
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)
Expand Down Expand Up @@ -665,15 +683,15 @@ export function mono_wasm_set_main_args(name: string, allRuntimeArguments: strin
/// 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;
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/types/emscripten.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export declare interface EmscriptenModule {
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;