Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wasm][mt] making JSImport work on worker thread #78847

Merged
merged 2 commits into from
Nov 30, 2022
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: 2 additions & 1 deletion src/mono/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mono_crash*
mono_crash*
wasi-sdk
4 changes: 4 additions & 0 deletions src/mono/sample/wasm/browser-threads/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public static int Main(string[] args)
return 0;
}

[JSImport("globalThis.console.log")]
public static partial void ConsoleLog(string status);

[JSImport("Sample.Test.updateProgress", "main.js")]
static partial void updateProgress(string status);

Expand Down Expand Up @@ -72,6 +75,7 @@ public void Start()

public void Run()
{
Sample.Test.ConsoleLog("Hello from ManagedThreadId " + Thread.CurrentThread.ManagedThreadId);
long result = Fib(UpTo);
if (result < (long)int.MaxValue)
_tcs.SetResult((int)result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@
<Import Project="..\DefaultBrowserSample.targets" />
<PropertyGroup>
<_SampleProject>Wasm.Browser.Threads.Sample.csproj</_SampleProject>
<WasmEnableThreads>true</WasmEnableThreads>
</PropertyGroup>

<Target Name="CheckThreadsEnabled" BeforeTargets="Compile" >
<Warning Condition="'$(WasmEnableThreads)' != 'true'" Text="This sample requires threading" />
</Target>

<!-- set the condition to false and you will get a CA1416 error about the call to Thread.Start from a browser-wasm project -->
<ItemGroup Condition="true">
<!-- 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. -->
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ export async function instantiate_wasm_asset(
pendingAsset: AssetEntryInternal,
wasmModuleImports: WebAssembly.Imports,
successCallback: InstantiateWasmSuccessCallback,
) {
): Promise<void> {
mono_assert(pendingAsset && pendingAsset.pendingDownloadInternal, "Can't load dotnet.wasm");
const response = await pendingAsset.pendingDownloadInternal.response;
const contentType = response.headers ? response.headers.get("Content-Type") : undefined;
Expand Down
28 changes: 13 additions & 15 deletions src/mono/wasm/runtime/cwraps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
MonoMethod, MonoObject, MonoString,
MonoType, MonoObjectRef, MonoStringRef, JSMarshalerArguments
} from "./types";
import { ENVIRONMENT_IS_PTHREAD, Module } from "./imports";
import { Module } from "./imports";
import { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr, ManagedPointer } from "./types/emscripten";

type SigLine = [lazy: boolean, name: string, returnType: string | null, argTypes?: string[], opts?: any];
Expand Down Expand Up @@ -235,19 +235,19 @@ export interface t_Cwraps {
mono_jiterp_get_offset_of_vtable_initialized_flag(): number;
mono_jiterp_get_offset_of_array_data(): number;
// Returns bytes written (or 0 if writing failed)
mono_jiterp_encode_leb52 (destination: VoidPtr, value: number, valueIsSigned: number): number;
mono_jiterp_encode_leb52(destination: VoidPtr, value: number, valueIsSigned: number): number;
// Returns bytes written (or 0 if writing failed)
// Source is the address of a 64-bit int or uint
mono_jiterp_encode_leb64_ref (destination: VoidPtr, source: VoidPtr, valueIsSigned: number): number;
mono_jiterp_type_is_byref (type: MonoType): number;
mono_jiterp_get_size_of_stackval (): number;
mono_jiterp_type_get_raw_value_size (type: MonoType): number;
mono_jiterp_parse_option (name: string): number;
mono_jiterp_get_options_as_json (): number;
mono_jiterp_get_options_version (): number;
mono_jiterp_adjust_abort_count (opcode: number, delta: number): number;
mono_jiterp_register_jit_call_thunk (cinfo: number, func: number): void;
mono_jiterp_update_jit_call_dispatcher (fn: number): void;
mono_jiterp_encode_leb64_ref(destination: VoidPtr, source: VoidPtr, valueIsSigned: number): number;
mono_jiterp_type_is_byref(type: MonoType): number;
mono_jiterp_get_size_of_stackval(): number;
mono_jiterp_type_get_raw_value_size(type: MonoType): number;
mono_jiterp_parse_option(name: string): number;
mono_jiterp_get_options_as_json(): number;
mono_jiterp_get_options_version(): number;
mono_jiterp_adjust_abort_count(opcode: number, delta: number): number;
mono_jiterp_register_jit_call_thunk(cinfo: number, func: number): void;
mono_jiterp_update_jit_call_dispatcher(fn: number): void;
}

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

export function init_c_exports(): void {
// init_c_exports is called very early in a pthread before Module.cwrap is available
const alwaysLazy = !!ENVIRONMENT_IS_PTHREAD;
for (const sig of fn_signatures) {
const wf: any = wrapped_c_functions;
const [lazy, name, returnType, argTypes, opts] = sig;
if (lazy || alwaysLazy) {
if (lazy) {
// lazy init on first run
wf[name] = function (...args: any[]) {
const fce = Module.cwrap(name, returnType, argTypes, opts);
Expand Down
14 changes: 1 addition & 13 deletions src/mono/wasm/runtime/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,7 @@ function initializeImportsAndExports(
list.registerRuntime(exportedRuntimeAPI);

if (MonoWasmThreads && ENVIRONMENT_IS_PTHREAD) {
// eslint-disable-next-line no-inner-declarations
async function workerInit(): Promise<DotnetModule> {
await mono_wasm_pthread_worker_init();

// HACK: Emscripten's dotnet.worker.js expects the exports of dotnet.js module to be Module object
// until we have our own fix for dotnet.worker.js file
// we also skip all emscripten startup event and configuration of worker's JS state
// note that emscripten events are not firing either

return exportedRuntimeAPI.Module;
}
// Emscripten pthread worker.js is ok with a Promise here.
return <any>workerInit();
return <any>mono_wasm_pthread_worker_init(module, exportedRuntimeAPI);
}

configure_emscripten_startup(module, exportedRuntimeAPI);
Expand Down
2 changes: 2 additions & 0 deletions src/mono/wasm/runtime/profiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export const enum MeasuredBlock {
emscriptenStartup = "mono.emscriptenStartup",
instantiateWasm = "mono.instantiateWasm",
preInit = "mono.preInit",
preInitWorker = "mono.preInitWorker",
preRun = "mono.preRun",
preRunWorker = "mono.preRunWorker",
onRuntimeInitialized = "mono.onRuntimeInitialized",
postRun = "mono.postRun",
loadRuntime = "mono.loadRuntime",
Expand Down
20 changes: 13 additions & 7 deletions src/mono/wasm/runtime/pthreads/worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/// <reference lib="webworker" />

import MonoWasmThreads from "consts:monoWasmThreads";
import { Module, ENVIRONMENT_IS_PTHREAD } from "../../imports";
import { Module, ENVIRONMENT_IS_PTHREAD, runtimeHelpers } from "../../imports";
import { isMonoThreadMessageApplyMonoConfig, makeChannelCreatedMonoMessage } from "../shared";
import type { pthread_ptr } from "../shared/types";
import { mono_assert, is_nullish, MonoConfig } from "../../types";
Expand All @@ -17,6 +17,7 @@ import {
WorkerThreadEventTarget
} from "./events";
import { setup_proxy_console } from "../../logging";
import { afterConfigLoaded, preRunWorker } from "../../startup";

// re-export some of the events types
export {
Expand Down Expand Up @@ -80,18 +81,22 @@ function setupChannelToMainThread(pthread_ptr: pthread_ptr): PThreadSelf {
return pthread_self;
}

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

// called when the main thread sends us the mono config
function onMonoConfigReceived(config: MonoConfig): void {
if (workerMonoConfig !== null) {
if (workerMonoConfigReceived) {
console.debug("MONO_WASM: mono config already received");
return;
}
workerMonoConfig = config;
console.debug("MONO_WASM: mono config received", config);
if (workerMonoConfig.diagnosticTracing) {

console.debug("MONO_WASM: mono config received");
config = runtimeHelpers.config = Module.config = Object.assign(Module.config || {} as any, config);
workerMonoConfigReceived = true;

afterConfigLoaded.promise_control.resolve(config);

if (config.diagnosticTracing) {
setup_proxy_console("pthread-worker", console, self.location.href);
}
}
Expand All @@ -102,6 +107,7 @@ export function mono_wasm_pthread_on_pthread_attached(pthread_id: pthread_ptr):
const self = pthread_self;
mono_assert(self !== null && self.pthread_id == pthread_id, "expected pthread_self to be set already when attaching");
console.debug("MONO_WASM: attaching pthread to runtime", pthread_id);
preRunWorker();
currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadAttached, self));
}

Expand Down
50 changes: 41 additions & 9 deletions src/mono/wasm/runtime/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ let config: MonoConfigInternal = undefined as any;
let configLoaded = false;
let isCustomStartup = false;
export const dotnetReady = createPromiseController<any>();
export const afterConfigLoaded = createPromiseController<void>();
export const afterConfigLoaded = createPromiseController<MonoConfig>();
export const afterInstantiateWasm = createPromiseController<void>();
export const beforePreInit = createPromiseController<void>();
export const afterPreInit = createPromiseController<void>();
Expand Down Expand Up @@ -159,6 +159,35 @@ function preInit(userPreInit: (() => void)[]) {
})();
}

async function preInitWorkerAsync() {
const mark = startMeasure();
try {
if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: preInitWorker");
beforePreInit.promise_control.resolve();
mono_wasm_pre_init_essential();
await init_polyfills_async();
afterPreInit.promise_control.resolve();
endMeasure(mark, MeasuredBlock.preInitWorker);
} catch (err) {
_print_error("MONO_WASM: user preInitWorker() failed", err);
abort_startup(err, true);
throw err;
}
}

export function preRunWorker() {
const mark = startMeasure();
try {
bindings_init();
endMeasure(mark, MeasuredBlock.preRunWorker);
} catch (err) {
abort_startup(err, true);
throw err;
}
// signal next stage
afterPreRun.promise_control.resolve();
}

async function preRunAsync(userPreRun: (() => void)[]) {
Module.addRunDependency("mono_pre_run_async");
// wait for previous stages
Expand Down Expand Up @@ -419,7 +448,7 @@ async function instantiate_wasm_module(
await start_asset_download_with_retries(assetToLoad, false);
await beforePreInit.promise;
Module.addRunDependency("instantiate_wasm_module");
instantiate_wasm_asset(assetToLoad, imports, successCallback);
await instantiate_wasm_asset(assetToLoad, imports, successCallback);

if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module done");
afterInstantiateWasm.promise_control.resolve();
Expand Down Expand Up @@ -529,7 +558,7 @@ export async function mono_wasm_load_config(configFilePath?: string): Promise<vo
configLoaded = true;
if (!configFilePath) {
normalize();
afterConfigLoaded.promise_control.resolve();
afterConfigLoaded.promise_control.resolve(runtimeHelpers.config);
return;
}
if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_load_config");
Expand Down Expand Up @@ -557,7 +586,7 @@ export async function mono_wasm_load_config(configFilePath?: string): Promise<vo
throw err;
}
}
afterConfigLoaded.promise_control.resolve();
afterConfigLoaded.promise_control.resolve(runtimeHelpers.config);
} catch (err) {
const errMessage = `Failed to load config file ${configFilePath} ${err}`;
abort_startup(errMessage, true);
Expand Down Expand Up @@ -622,22 +651,25 @@ export function mono_wasm_set_main_args(name: string, allRuntimeArguments: strin
}

/// Called when dotnet.worker.js receives an emscripten "load" event from the main thread.
/// This method is comparable to configure_emscripten_startup function
///
/// Notes:
/// 1. Emscripten skips a lot of initialization on the pthread workers, Module may not have everything you expect.
/// 2. Emscripten does not run the preInit or preRun functions in the workers.
/// 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(): Promise<void> {
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");
// FIXME: copy/pasted from mono_wasm_pre_init_essential - can we share this code? Any other global state that needs initialization?
init_c_exports();
// not initializing INTERNAL, MONO, or BINDING C wrappers here - those legacy APIs are not likely to be needed on pthread workers.

// 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()];

await afterPreInit.promise;
return exportedAPI.Module;
}

/**
Expand Down