From: Pavel Savara Date: Tue, 31 Jan 2023 18:07:33 +0000 (+0100) Subject: [browser][MT] Calling JS functions from workers + config loading (#81273) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055536~4310 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e831dab5a5e2ee58682613ba18800cb26045f36d;p=platform%2Fupstream%2Fdotnet%2Fruntime.git [browser][MT] Calling JS functions from workers + config loading (#81273) * calling JS functions from workers * remove workaround * config loading earlier --- diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index c510643..80fab37 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -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; diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index 19f877f..8eafb1d 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -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"); diff --git a/src/mono/wasm/runtime/pthreads/browser/index.ts b/src/mono/wasm/runtime/pthreads/browser/index.ts index b7c62e9..7816adf 100644 --- a/src/mono/wasm/runtime/pthreads/browser/index.ts +++ b/src/mono/wasm/runtime/pthreads/browser/index.ts @@ -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"; @@ -85,17 +85,20 @@ function monoDedicatedChannelMessageFromWorkerToMain(event: MessageEvent>): void { +function monoWorkerMessageHandler(worker: Worker, ev: MessageEvent>): 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); } } diff --git a/src/mono/wasm/runtime/pthreads/shared/index.ts b/src/mono/wasm/runtime/pthreads/shared/index.ts index b03207a..c71a27f 100644 --- a/src/mono/wasm/runtime/pthreads/shared/index.ts +++ b/src/mono/wasm/runtime/pthreads/shared/index.ts @@ -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. @@ -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", @@ -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 { + [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 extends MonoWorkerMessage { +export interface MonoWorkerMessageChannelCreated extends MonoWorkerMessage { [monoSymbol]: { - mono_cmd: "channel_created"; + mono_cmd: WorkerMonoCommandType.channel_created; thread_id: pthread_ptr; port: TPort; }; } +export interface MonoWorkerMessagePreload extends MonoWorkerMessage { + [monoSymbol]: { + mono_cmd: WorkerMonoCommandType.preload; + port: TPort; + }; +} + export function makeChannelCreatedMonoMessage(thread_id: pthread_ptr, port: TPort): MonoWorkerMessageChannelCreated { 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(port: TPort): MonoWorkerMessagePreload { + return { + [monoSymbol]: { + mono_cmd: WorkerMonoCommandType.preload, + port + } + }; +} + +export function isMonoWorkerMessage(message: unknown): message is MonoWorkerMessage { return message !== undefined && typeof message === "object" && message !== null && monoSymbol in message; } -export function isMonoWorkerMessageChannelCreated(message: MonoWorkerMessageChannelCreated): message is MonoWorkerMessageChannelCreated { +export function isMonoWorkerMessageChannelCreated(message: MonoWorkerMessage): message is MonoWorkerMessageChannelCreated { + if (isMonoWorkerMessage(message)) { + const monoMessage = message[monoSymbol]; + if (monoMessage.mono_cmd === WorkerMonoCommandType.channel_created) { + return true; + } + } + return false; +} + +export function isMonoWorkerMessagePreload(message: MonoWorkerMessage): message is MonoWorkerMessagePreload { if (isMonoWorkerMessage(message)) { const monoMessage = message[monoSymbol]; - if (monoMessage.mono_cmd === "channel_created") { + if (monoMessage.mono_cmd === WorkerMonoCommandType.preload) { return true; } } diff --git a/src/mono/wasm/runtime/pthreads/worker/index.ts b/src/mono/wasm/runtime/pthreads/worker/index.ts index 1322a11..ba411a7 100644 --- a/src/mono/wasm/runtime/pthreads/worker/index.ts +++ b/src/mono/wasm/runtime/pthreads/worker/index.ts @@ -4,10 +4,10 @@ /// 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, @@ -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): 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 { @@ -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; @@ -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); } } diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 71a51bc..25bf207 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -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; @@ -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); @@ -123,6 +118,24 @@ function instantiateWasm( return []; // No exports } +async function instantiateWasmWorker( + imports: WebAssembly.Imports, + successCallback: InstantiateWasmSuccessCallback +): Promise { + // 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(); @@ -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"); @@ -564,7 +578,7 @@ export async function mono_wasm_load_config(configFilePath?: string): PromiseruntimeHelpers.config); - normalize(); + normalizeConfig(); } catch (err: any) { _print_error("MONO_WASM: onConfigLoaded() failed", err); @@ -601,26 +616,29 @@ export async function mono_wasm_load_config(configFilePath?: string): Promise { - 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); @@ -674,6 +691,7 @@ export async function mono_wasm_pthread_worker_init(module: DotnetModule, export // this is the only event which is called on worker module.preInit = [() => preInitWorkerAsync()]; + module.instantiateWasm = instantiateWasmWorker; await afterPreInit.promise; return exportedAPI.Module; diff --git a/src/mono/wasm/runtime/types/emscripten.ts b/src/mono/wasm/runtime/types/emscripten.ts index 5dfef89..3cc8933 100644 --- a/src/mono/wasm/runtime/types/emscripten.ts +++ b/src/mono/wasm/runtime/types/emscripten.ts @@ -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;