Skip to content

Commit b49fcc6

Browse files
authored
[wasm] making JSImport work on worker thread
- sample: added JSImport which would be called from thread (#78847) - fixed missing await on wasm instantiation - added preInitWorkerAsync - called from Module.preInit which is atually called - added preRunWorker - called after the thread is mono attached - remove forced lazy from cwraps (when called from preInit, the memory+wasm+Module is attached already)
1 parent e7ee837 commit b49fcc6

File tree

9 files changed

+78
-50
lines changed

9 files changed

+78
-50
lines changed

src/mono/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
mono_crash*
1+
mono_crash*
2+
wasi-sdk

src/mono/sample/wasm/browser-threads/Program.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ public static int Main(string[] args)
1818
return 0;
1919
}
2020

21+
[JSImport("globalThis.console.log")]
22+
public static partial void ConsoleLog(string status);
23+
2124
[JSImport("Sample.Test.updateProgress", "main.js")]
2225
static partial void updateProgress(string status);
2326

@@ -72,6 +75,7 @@ public void Start()
7275

7376
public void Run()
7477
{
78+
Sample.Test.ConsoleLog("Hello from ManagedThreadId " + Thread.CurrentThread.ManagedThreadId);
7579
long result = Fib(UpTo);
7680
if (result < (long)int.MaxValue)
7781
_tcs.SetResult((int)result);

src/mono/sample/wasm/browser-threads/Wasm.Browser.Threads.Sample.csproj

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22
<Import Project="..\DefaultBrowserSample.targets" />
33
<PropertyGroup>
44
<_SampleProject>Wasm.Browser.Threads.Sample.csproj</_SampleProject>
5+
<WasmEnableThreads>true</WasmEnableThreads>
56
</PropertyGroup>
67

7-
<Target Name="CheckThreadsEnabled" BeforeTargets="Compile" >
8-
<Warning Condition="'$(WasmEnableThreads)' != 'true'" Text="This sample requires threading" />
9-
</Target>
10-
118
<!-- set the condition to false and you will get a CA1416 error about the call to Thread.Start from a browser-wasm project -->
129
<ItemGroup Condition="true">
1310
<!-- TODO: some .props file that automates this. Unfortunately just adding a ProjectReference to Microsoft.NET.WebAssembly.Threading.proj doesn't work - it ends up bundling the ref assemblies into the publish directory and breaking the app. -->

src/mono/wasm/runtime/assets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ export async function instantiate_wasm_asset(
432432
pendingAsset: AssetEntryInternal,
433433
wasmModuleImports: WebAssembly.Imports,
434434
successCallback: InstantiateWasmSuccessCallback,
435-
) {
435+
): Promise<void> {
436436
mono_assert(pendingAsset && pendingAsset.pendingDownloadInternal, "Can't load dotnet.wasm");
437437
const response = await pendingAsset.pendingDownloadInternal.response;
438438
const contentType = response.headers ? response.headers.get("Content-Type") : undefined;

src/mono/wasm/runtime/cwraps.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
MonoMethod, MonoObject, MonoString,
77
MonoType, MonoObjectRef, MonoStringRef, JSMarshalerArguments
88
} from "./types";
9-
import { ENVIRONMENT_IS_PTHREAD, Module } from "./imports";
9+
import { Module } from "./imports";
1010
import { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr, ManagedPointer } from "./types/emscripten";
1111

1212
type SigLine = [lazy: boolean, name: string, returnType: string | null, argTypes?: string[], opts?: any];
@@ -235,19 +235,19 @@ export interface t_Cwraps {
235235
mono_jiterp_get_offset_of_vtable_initialized_flag(): number;
236236
mono_jiterp_get_offset_of_array_data(): number;
237237
// Returns bytes written (or 0 if writing failed)
238-
mono_jiterp_encode_leb52 (destination: VoidPtr, value: number, valueIsSigned: number): number;
238+
mono_jiterp_encode_leb52(destination: VoidPtr, value: number, valueIsSigned: number): number;
239239
// Returns bytes written (or 0 if writing failed)
240240
// Source is the address of a 64-bit int or uint
241-
mono_jiterp_encode_leb64_ref (destination: VoidPtr, source: VoidPtr, valueIsSigned: number): number;
242-
mono_jiterp_type_is_byref (type: MonoType): number;
243-
mono_jiterp_get_size_of_stackval (): number;
244-
mono_jiterp_type_get_raw_value_size (type: MonoType): number;
245-
mono_jiterp_parse_option (name: string): number;
246-
mono_jiterp_get_options_as_json (): number;
247-
mono_jiterp_get_options_version (): number;
248-
mono_jiterp_adjust_abort_count (opcode: number, delta: number): number;
249-
mono_jiterp_register_jit_call_thunk (cinfo: number, func: number): void;
250-
mono_jiterp_update_jit_call_dispatcher (fn: number): void;
241+
mono_jiterp_encode_leb64_ref(destination: VoidPtr, source: VoidPtr, valueIsSigned: number): number;
242+
mono_jiterp_type_is_byref(type: MonoType): number;
243+
mono_jiterp_get_size_of_stackval(): number;
244+
mono_jiterp_type_get_raw_value_size(type: MonoType): number;
245+
mono_jiterp_parse_option(name: string): number;
246+
mono_jiterp_get_options_as_json(): number;
247+
mono_jiterp_get_options_version(): number;
248+
mono_jiterp_adjust_abort_count(opcode: number, delta: number): number;
249+
mono_jiterp_register_jit_call_thunk(cinfo: number, func: number): void;
250+
mono_jiterp_update_jit_call_dispatcher(fn: number): void;
251251
}
252252

253253
const wrapped_c_functions: t_Cwraps = <any>{};
@@ -262,12 +262,10 @@ export const enum I52Error {
262262
}
263263

264264
export function init_c_exports(): void {
265-
// init_c_exports is called very early in a pthread before Module.cwrap is available
266-
const alwaysLazy = !!ENVIRONMENT_IS_PTHREAD;
267265
for (const sig of fn_signatures) {
268266
const wf: any = wrapped_c_functions;
269267
const [lazy, name, returnType, argTypes, opts] = sig;
270-
if (lazy || alwaysLazy) {
268+
if (lazy) {
271269
// lazy init on first run
272270
wf[name] = function (...args: any[]) {
273271
const fce = Module.cwrap(name, returnType, argTypes, opts);

src/mono/wasm/runtime/exports.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -133,19 +133,7 @@ function initializeImportsAndExports(
133133
list.registerRuntime(exportedRuntimeAPI);
134134

135135
if (MonoWasmThreads && ENVIRONMENT_IS_PTHREAD) {
136-
// eslint-disable-next-line no-inner-declarations
137-
async function workerInit(): Promise<DotnetModule> {
138-
await mono_wasm_pthread_worker_init();
139-
140-
// HACK: Emscripten's dotnet.worker.js expects the exports of dotnet.js module to be Module object
141-
// until we have our own fix for dotnet.worker.js file
142-
// we also skip all emscripten startup event and configuration of worker's JS state
143-
// note that emscripten events are not firing either
144-
145-
return exportedRuntimeAPI.Module;
146-
}
147-
// Emscripten pthread worker.js is ok with a Promise here.
148-
return <any>workerInit();
136+
return <any>mono_wasm_pthread_worker_init(module, exportedRuntimeAPI);
149137
}
150138

151139
configure_emscripten_startup(module, exportedRuntimeAPI);

src/mono/wasm/runtime/profiler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ export const enum MeasuredBlock {
3636
emscriptenStartup = "mono.emscriptenStartup",
3737
instantiateWasm = "mono.instantiateWasm",
3838
preInit = "mono.preInit",
39+
preInitWorker = "mono.preInitWorker",
3940
preRun = "mono.preRun",
41+
preRunWorker = "mono.preRunWorker",
4042
onRuntimeInitialized = "mono.onRuntimeInitialized",
4143
postRun = "mono.postRun",
4244
loadRuntime = "mono.loadRuntime",

src/mono/wasm/runtime/pthreads/worker/index.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/// <reference lib="webworker" />
55

66
import MonoWasmThreads from "consts:monoWasmThreads";
7-
import { Module, ENVIRONMENT_IS_PTHREAD } from "../../imports";
7+
import { Module, ENVIRONMENT_IS_PTHREAD, runtimeHelpers } from "../../imports";
88
import { isMonoThreadMessageApplyMonoConfig, makeChannelCreatedMonoMessage } from "../shared";
99
import type { pthread_ptr } from "../shared/types";
1010
import { mono_assert, is_nullish, MonoConfig } from "../../types";
@@ -17,6 +17,7 @@ import {
1717
WorkerThreadEventTarget
1818
} from "./events";
1919
import { setup_proxy_console } from "../../logging";
20+
import { afterConfigLoaded, preRunWorker } from "../../startup";
2021

2122
// re-export some of the events types
2223
export {
@@ -80,18 +81,22 @@ function setupChannelToMainThread(pthread_ptr: pthread_ptr): PThreadSelf {
8081
return pthread_self;
8182
}
8283

83-
// TODO: should we just assign to Module.config here?
84-
let workerMonoConfig: MonoConfig = null as unknown as MonoConfig;
84+
let workerMonoConfigReceived = false;
8585

8686
// called when the main thread sends us the mono config
8787
function onMonoConfigReceived(config: MonoConfig): void {
88-
if (workerMonoConfig !== null) {
88+
if (workerMonoConfigReceived) {
8989
console.debug("MONO_WASM: mono config already received");
9090
return;
9191
}
92-
workerMonoConfig = config;
93-
console.debug("MONO_WASM: mono config received", config);
94-
if (workerMonoConfig.diagnosticTracing) {
92+
93+
console.debug("MONO_WASM: mono config received");
94+
config = runtimeHelpers.config = Module.config = Object.assign(Module.config || {} as any, config);
95+
workerMonoConfigReceived = true;
96+
97+
afterConfigLoaded.promise_control.resolve(config);
98+
99+
if (config.diagnosticTracing) {
95100
setup_proxy_console("pthread-worker", console, self.location.href);
96101
}
97102
}
@@ -102,6 +107,7 @@ export function mono_wasm_pthread_on_pthread_attached(pthread_id: pthread_ptr):
102107
const self = pthread_self;
103108
mono_assert(self !== null && self.pthread_id == pthread_id, "expected pthread_self to be set already when attaching");
104109
console.debug("MONO_WASM: attaching pthread to runtime", pthread_id);
110+
preRunWorker();
105111
currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadAttached, self));
106112
}
107113

src/mono/wasm/runtime/startup.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ let config: MonoConfigInternal = undefined as any;
3434
let configLoaded = false;
3535
let isCustomStartup = false;
3636
export const dotnetReady = createPromiseController<any>();
37-
export const afterConfigLoaded = createPromiseController<void>();
37+
export const afterConfigLoaded = createPromiseController<MonoConfig>();
3838
export const afterInstantiateWasm = createPromiseController<void>();
3939
export const beforePreInit = createPromiseController<void>();
4040
export const afterPreInit = createPromiseController<void>();
@@ -159,6 +159,35 @@ function preInit(userPreInit: (() => void)[]) {
159159
})();
160160
}
161161

162+
async function preInitWorkerAsync() {
163+
const mark = startMeasure();
164+
try {
165+
if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: preInitWorker");
166+
beforePreInit.promise_control.resolve();
167+
mono_wasm_pre_init_essential();
168+
await init_polyfills_async();
169+
afterPreInit.promise_control.resolve();
170+
endMeasure(mark, MeasuredBlock.preInitWorker);
171+
} catch (err) {
172+
_print_error("MONO_WASM: user preInitWorker() failed", err);
173+
abort_startup(err, true);
174+
throw err;
175+
}
176+
}
177+
178+
export function preRunWorker() {
179+
const mark = startMeasure();
180+
try {
181+
bindings_init();
182+
endMeasure(mark, MeasuredBlock.preRunWorker);
183+
} catch (err) {
184+
abort_startup(err, true);
185+
throw err;
186+
}
187+
// signal next stage
188+
afterPreRun.promise_control.resolve();
189+
}
190+
162191
async function preRunAsync(userPreRun: (() => void)[]) {
163192
Module.addRunDependency("mono_pre_run_async");
164193
// wait for previous stages
@@ -419,7 +448,7 @@ async function instantiate_wasm_module(
419448
await start_asset_download_with_retries(assetToLoad, false);
420449
await beforePreInit.promise;
421450
Module.addRunDependency("instantiate_wasm_module");
422-
instantiate_wasm_asset(assetToLoad, imports, successCallback);
451+
await instantiate_wasm_asset(assetToLoad, imports, successCallback);
423452

424453
if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module done");
425454
afterInstantiateWasm.promise_control.resolve();
@@ -529,7 +558,7 @@ export async function mono_wasm_load_config(configFilePath?: string): Promise<vo
529558
configLoaded = true;
530559
if (!configFilePath) {
531560
normalize();
532-
afterConfigLoaded.promise_control.resolve();
561+
afterConfigLoaded.promise_control.resolve(runtimeHelpers.config);
533562
return;
534563
}
535564
if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_load_config");
@@ -557,7 +586,7 @@ export async function mono_wasm_load_config(configFilePath?: string): Promise<vo
557586
throw err;
558587
}
559588
}
560-
afterConfigLoaded.promise_control.resolve();
589+
afterConfigLoaded.promise_control.resolve(runtimeHelpers.config);
561590
} catch (err) {
562591
const errMessage = `Failed to load config file ${configFilePath} ${err}`;
563592
abort_startup(errMessage, true);
@@ -622,22 +651,25 @@ export function mono_wasm_set_main_args(name: string, allRuntimeArguments: strin
622651
}
623652

624653
/// Called when dotnet.worker.js receives an emscripten "load" event from the main thread.
654+
/// This method is comparable to configure_emscripten_startup function
625655
///
626656
/// Notes:
627657
/// 1. Emscripten skips a lot of initialization on the pthread workers, Module may not have everything you expect.
628-
/// 2. Emscripten does not run the preInit or preRun functions in the workers.
658+
/// 2. Emscripten does not run any event but preInit in the workers.
629659
/// 3. At the point when this executes there is no pthread assigned to the worker yet.
630-
export async function mono_wasm_pthread_worker_init(): Promise<void> {
660+
export async function mono_wasm_pthread_worker_init(module: DotnetModule, exportedAPI: RuntimeAPI): Promise<DotnetModule> {
631661
console.debug("MONO_WASM: worker initializing essential C exports and APIs");
632-
// FIXME: copy/pasted from mono_wasm_pre_init_essential - can we share this code? Any other global state that needs initialization?
633-
init_c_exports();
634-
// not initializing INTERNAL, MONO, or BINDING C wrappers here - those legacy APIs are not likely to be needed on pthread workers.
635662

636663
// This is a good place for subsystems to attach listeners for pthreads_worker.currentWorkerThreadEvents
637664
pthreads_worker.currentWorkerThreadEvents.addEventListener(pthreads_worker.dotnetPthreadCreated, (ev) => {
638665
console.debug("MONO_WASM: pthread created", ev.pthread_self.pthread_id);
639666
});
640667

668+
// this is the only event which is called on worker
669+
module.preInit = [() => preInitWorkerAsync()];
670+
671+
await afterPreInit.promise;
672+
return exportedAPI.Module;
641673
}
642674

643675
/**

0 commit comments

Comments
 (0)