Skip to content

[wasm-mt] Add MessageChannel between browser thread and new pthreads #70908

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

Merged
merged 35 commits into from
Jul 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
474b50d
outline of a dedicated channel between JS main thread and pthreads
lambdageek Jun 15, 2022
cfe28f5
add JS entrypoints for pthread-channel
lambdageek Jun 17, 2022
f1faae9
WIP: wire up the MessageChannel to native thread attach
lambdageek Jun 15, 2022
b948e7b
debug printfs etc
lambdageek Jun 17, 2022
5ebe918
more printfs
lambdageek Jun 17, 2022
6caafa5
comment out the thread start.
lambdageek Jun 17, 2022
f47f173
split up into pthread-channel module into worker, browser and shared
lambdageek Jun 18, 2022
4d21b70
WIP: pthreads modules
lambdageek Jun 19, 2022
cca2c90
add ENVIRONMENT_IS_PTHREAD; mono_wasm_pthread_worker_init
lambdageek Jun 20, 2022
b7754db
Fixup names; call MessagePort.start; remove printfs
lambdageek Jun 20, 2022
76765e2
remove whitespace and extra printfs
lambdageek Jun 20, 2022
7634ea4
Exclude threading exports in non-threaded runtime
lambdageek Jun 20, 2022
064013e
Add replacement for PThread.loadWasmModuleToWorker
lambdageek Jun 21, 2022
ed1fb70
Simplify the dedicated channel creation
lambdageek Jun 21, 2022
8ef3be4
Don't forget the GC transition out to JS
lambdageek Jun 21, 2022
1162d2f
fixup rebase
lambdageek Jun 22, 2022
8aaf524
less code golf
lambdageek Jun 23, 2022
d65c1d9
Merge remote-tracking branch 'origin/main' into wasm-mt-channels
lambdageek Jun 24, 2022
fe65255
fix browser-eventpipe default import
lambdageek Jun 24, 2022
60a4d0f
move mono_threads_wasm_on_thread_attached later in register_thread
lambdageek Jun 24, 2022
75b70fa
also fix default import in browser-mt-eventpipe
lambdageek Jun 24, 2022
60e3cf5
Add replacement for Module.PThread.threadInit
lambdageek Jun 27, 2022
72c84f9
Cleanup mono_wasm_pthread_on_pthread_created
lambdageek Jun 28, 2022
954b2b8
fix comment
lambdageek Jun 28, 2022
f92cf3f
Share tsconfig parts using "extends" property
lambdageek Jun 28, 2022
72ae535
Use an EventTarget and custom events for worker thread lifecycle noti…
lambdageek Jun 29, 2022
4977e4d
pass portToMain in ThreadEvents; update README
lambdageek Jun 29, 2022
9113b62
make pthread/worker/events friendlier to tree shaking and node
lambdageek Jun 29, 2022
1b432c1
remove comment out old code
lambdageek Jun 29, 2022
17cef55
another approach to tree shaking
lambdageek Jun 29, 2022
6c820f0
more treeshaking
lambdageek Jun 30, 2022
37841da
fix annoying VSCode ESLint toast
lambdageek Jun 30, 2022
ae21694
Add Event and EventTarget polyfill for v8
lambdageek Jun 30, 2022
a4a0e3c
fix whitespace and comments
lambdageek Jun 30, 2022
9f6e7b0
Merge remote-tracking branch 'origin/main' into wasm-mt-channels
lambdageek Jun 30, 2022
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: 3 additions & 0 deletions src/mono/mono/component/event_pipe-stub.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#include "mono/component/event_pipe.h"
#include "mono/component/event_pipe-wasm.h"
#include "mono/metadata/components.h"
#ifdef HOST_WASM
#include <emscripten/emscripten.h>
#endif

static EventPipeSessionID _dummy_session_id;

Expand Down
3 changes: 3 additions & 0 deletions src/mono/mono/component/event_pipe.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
#include <eventpipe/ep-event-instance.h>
#include <eventpipe/ep-session.h>

#ifdef HOST_WASM
#include <emscripten/emscripten.h>
#endif

extern void ep_rt_mono_component_init (void);
static bool _event_pipe_component_inited = false;
Expand Down
31 changes: 31 additions & 0 deletions src/mono/mono/utils/mono-threads-wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,29 @@ mono_threads_platform_stw_defer_initial_suspend (MonoThreadInfo *info)
return mono_native_thread_id_equals (mono_thread_info_get_tid (info), mono_threads_wasm_browser_thread_tid ());
}

#ifndef DISABLE_THREADS
extern void
mono_wasm_pthread_on_pthread_attached (gpointer pthread_id);
#endif

void
mono_threads_wasm_on_thread_attached (void)
{
#ifdef DISABLE_THREADS
return;
#else
if (mono_threads_wasm_is_browser_thread ()) {
return;
}
// Notify JS that the pthread attachd to Mono
pthread_t id = pthread_self ();
MONO_ENTER_GC_SAFE;
mono_wasm_pthread_on_pthread_attached (id);
MONO_EXIT_GC_SAFE;
#endif
}


#ifndef DISABLE_THREADS
void
mono_threads_wasm_async_run_in_main_thread (void (*func) (void))
Expand All @@ -423,6 +446,14 @@ mono_threads_wasm_async_run_in_main_thread_vi (void (*func) (gpointer), gpointer
{
emscripten_async_run_in_main_runtime_thread (EM_FUNC_SIG_VI, func, user_data);
}

void
mono_threads_wasm_async_run_in_main_thread_vii (void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2)
{
emscripten_async_run_in_main_runtime_thread (EM_FUNC_SIG_VII, func, user_data1, user_data2);
}


#endif /* DISABLE_THREADS */

#endif /* HOST_BROWSER */
Expand Down
7 changes: 7 additions & 0 deletions src/mono/mono/utils/mono-threads-wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,15 @@ mono_threads_wasm_async_run_in_main_thread (void (*func) (void));
*/
void
mono_threads_wasm_async_run_in_main_thread_vi (void (*func)(gpointer), gpointer user_data);

void
mono_threads_wasm_async_run_in_main_thread_vii (void (*func)(gpointer, gpointer), gpointer user_data1, gpointer user_data2);
#endif /* DISABLE_THREADS */

// Called from register_thread when a pthread attaches to the runtime
void
mono_threads_wasm_on_thread_attached (void);

#endif /* HOST_WASM*/

#endif /* __MONO_THREADS_WASM_H__ */
5 changes: 5 additions & 0 deletions src/mono/mono/utils/mono-threads.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <mono/utils/mono-coop-semaphore.h>
#include <mono/utils/mono-threads-coop.h>
#include <mono/utils/mono-threads-debug.h>
#include <mono/utils/mono-threads-wasm.h>
#include <mono/utils/os-event.h>
#include <mono/utils/w32api.h>
#include <glib.h>
Expand Down Expand Up @@ -552,6 +553,10 @@ register_thread (MonoThreadInfo *info)
g_assert (result);
mono_thread_info_suspend_unlock ();

#ifdef HOST_BROWSER
mono_threads_wasm_on_thread_attached ();
#endif

return TRUE;
}

Expand Down
14 changes: 14 additions & 0 deletions src/mono/sample/wasm/browser-eventpipe/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,23 @@ private static long recursiveFib (int n)
return recursiveFib (n - 1) + recursiveFib (n - 2);
}

#if false
// dead code to prove that starting user threads isn't possible on the perftracing runtime
public static void Meth() {
Thread.Sleep (500);
while (!GetCancellationToken().IsCancellationRequested) {
Console.WriteLine ("ping");
Thread.Sleep (500);
}
}
#endif

public static async Task<double> StartAsyncWork(int N)
{
CancellationToken ct = GetCancellationToken();
#if false
new Thread(new ThreadStart(Meth)).Start();
#endif
await Task.Delay(1);
long b;
WasmHelloEventSource.Instance.NewCallsCounter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<GenerateRunScriptForSample Condition="'$(ArchiveTests)' == 'true'">true</GenerateRunScriptForSample>
<RunScriptCommand>$(ExecXHarnessCmd) wasm test-browser --app=. --browser=Chrome $(XHarnessBrowserPathArg) --html-file=index.html --output-directory=$(XHarnessOutput) -- $(MSBuildProjectName).dll</RunScriptCommand>
<FeatureWasmPerfTracing>true</FeatureWasmPerfTracing>
<WasmEnablePerfTracing>true</WasmEnablePerfTracing>
<FeatureWasmThreads Condition="false">true</FeatureWasmThreads>
<NoWarn>CA2007</NoWarn> <!-- consider ConfigureAwait() -->
</PropertyGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/mono/sample/wasm/browser-eventpipe/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createDotnetRuntime } from "./dotnet.js";
import createDotnetRuntime from "./dotnet.js";

function downloadData(dataURL, filename) {
// make an `<a download="filename" href="data:..."/>` link and click on it to trigger a download with the given name
Expand Down
2 changes: 1 addition & 1 deletion src/mono/sample/wasm/browser-mt-eventpipe/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createDotnetRuntime } from "./dotnet.js";
import createDotnetRuntime from "./dotnet.js";

function wasm_exit(exit_code, reason) {
/* Set result in a tests_done element, to be read by xharness in runonly CI test */
Expand Down
5 changes: 4 additions & 1 deletion src/mono/wasm/runtime/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ module.exports = {
"indent": [
"error",
4,
{ SwitchCase: 1 }
{
SwitchCase: 1,
"ignoredNodes": ["VariableDeclaration[declarations.length=0]"] // fixes https://github.com/microsoft/vscode-eslint/issues/1149
}
],
"linebreak-style": "off",
"quotes": [
Expand Down
26 changes: 24 additions & 2 deletions src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,39 @@

"use strict";

#if USE_PTHREADS
const usePThreads = `true`;
const isPThread = `ENVIRONMENT_IS_PTHREAD`;
#else
const usePThreads = `false`;
const isPThread = `false`;
#endif

const DotnetSupportLib = {
$DOTNET: {},
// these lines will be placed early on emscripten runtime creation, passing import and export objects into __dotnet_runtime IFFE
// we replace implementation of readAsync and fetch
// replacement of require is there for consistency with ES6 code
$DOTNET__postset: `
let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require, updateGlobalBufferAndViews};
let __dotnet_replacement_PThread = ${usePThreads} ? {} : undefined;
if (${usePThreads}) {
__dotnet_replacement_PThread.loadWasmModuleToWorker = PThread.loadWasmModuleToWorker;
__dotnet_replacement_PThread.threadInit = PThread.threadInit;
}
let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require, updateGlobalBufferAndViews, pthreadReplacements: __dotnet_replacement_PThread};
let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports(
{ isESM:false, isGlobal:ENVIRONMENT_IS_GLOBAL, isNode:ENVIRONMENT_IS_NODE, isWorker:ENVIRONMENT_IS_WORKER, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, ExitStatus, requirePromise:Promise.resolve(require)},
{ isESM:false, isGlobal:ENVIRONMENT_IS_GLOBAL, isNode:ENVIRONMENT_IS_NODE, isWorker:ENVIRONMENT_IS_WORKER, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, isPThread:${isPThread}, locateFile, quit_, ExitStatus, requirePromise:Promise.resolve(require)},
{ mono:MONO, binding:BINDING, internal:INTERNAL, module:Module, marshaled_exports: EXPORTS, marshaled_imports: IMPORTS },
__dotnet_replacements);
updateGlobalBufferAndViews = __dotnet_replacements.updateGlobalBufferAndViews;
readAsync = __dotnet_replacements.readAsync;
var fetch = __dotnet_replacements.fetch;
require = __dotnet_replacements.requireOut;
var noExitRuntime = __dotnet_replacements.noExitRuntime;
if (${usePThreads}) {
PThread.loadWasmModuleToWorker = __dotnet_replacements.pthreadReplacements.loadWasmModuleToWorker;
PThread.threadInit = __dotnet_replacements.pthreadReplacements.threadInit;
}
`,
};

Expand Down Expand Up @@ -73,6 +90,11 @@ const linked_functions = [
"dotnet_browser_can_use_subtle_crypto_impl",
"dotnet_browser_simple_digest_hash",
"dotnet_browser_sign",

/// mono-threads-wasm.c
#if USE_PTHREADS
"mono_wasm_pthread_on_pthread_attached",
#endif
];

// -- this javascript file is evaluated by emcc during compilation! --
Expand Down
26 changes: 24 additions & 2 deletions src/mono/wasm/runtime/es6/dotnet.es6.lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

"use strict";

#if USE_PTHREADS
const usePThreads = `true`;
const isPThread = `ENVIRONMENT_IS_PTHREAD`;
#else
const usePThreads = `false`;
const isPThread = `false`;
#endif

const DotnetSupportLib = {
$DOTNET: {},
// this line will be placed early on emscripten runtime creation, passing import and export objects into __dotnet_runtime IFFE
Expand All @@ -14,7 +22,12 @@ const DotnetSupportLib = {
// Emscripten's getBinaryPromise is not async for NodeJs, but we would like to have it async, so we replace it.
// We also replace implementation of readAsync and fetch
$DOTNET__postset: `
let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require, updateGlobalBufferAndViews};
let __dotnet_replacement_PThread = ${usePThreads} ? {} : undefined;
if (${usePThreads}) {
__dotnet_replacement_PThread.loadWasmModuleToWorker = PThread.loadWasmModuleToWorker;
__dotnet_replacement_PThread.threadInit = PThread.threadInit;
}
let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require, updateGlobalBufferAndViews, pthreadReplacements: __dotnet_replacement_PThread};
if (ENVIRONMENT_IS_NODE) {
__dotnet_replacements.requirePromise = import(/* webpackIgnore: true */'module').then(mod => {
const require = mod.createRequire(import.meta.url);
Expand Down Expand Up @@ -49,14 +62,18 @@ if (ENVIRONMENT_IS_NODE) {
}
}
let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports(
{ isESM:true, isGlobal:false, isNode:ENVIRONMENT_IS_NODE, isWorker:ENVIRONMENT_IS_WORKER, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, ExitStatus, requirePromise:__dotnet_replacements.requirePromise },
{ isESM:true, isGlobal:false, isNode:ENVIRONMENT_IS_NODE, isWorker:ENVIRONMENT_IS_WORKER, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, isPThread:${isPThread}, locateFile, quit_, ExitStatus, requirePromise:__dotnet_replacements.requirePromise },
{ mono:MONO, binding:BINDING, internal:INTERNAL, module:Module, marshaled_exports: EXPORTS, marshaled_imports: IMPORTS },
__dotnet_replacements);
updateGlobalBufferAndViews = __dotnet_replacements.updateGlobalBufferAndViews;
readAsync = __dotnet_replacements.readAsync;
var fetch = __dotnet_replacements.fetch;
require = __dotnet_replacements.requireOut;
var noExitRuntime = __dotnet_replacements.noExitRuntime;
if (${usePThreads}) {
PThread.loadWasmModuleToWorker = __dotnet_replacements.pthreadReplacements.loadWasmModuleToWorker;
PThread.threadInit = __dotnet_replacements.pthreadReplacements.threadInit;
}
`,
};

Expand Down Expand Up @@ -110,6 +127,11 @@ const linked_functions = [
"dotnet_browser_can_use_subtle_crypto_impl",
"dotnet_browser_simple_digest_hash",
"dotnet_browser_sign",

/// mono-threads-wasm.c
#if USE_PTHREADS
"mono_wasm_pthread_on_pthread_attached",
#endif
];

// -- this javascript file is evaluated by emcc during compilation! --
Expand Down
40 changes: 36 additions & 4 deletions src/mono/wasm/runtime/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import ProductVersion from "consts:productVersion";
import Configuration from "consts:configuration";
import MonoWasmThreads from "consts:monoWasmThreads";

import {
mono_wasm_new_root, mono_wasm_release_roots, mono_wasm_new_external_root,
Expand Down Expand Up @@ -74,6 +75,8 @@ import {
} from "./crypto-worker";
import { mono_wasm_cancel_promise_ref } from "./cancelable-promise";
import { mono_wasm_web_socket_open_ref, mono_wasm_web_socket_send, mono_wasm_web_socket_receive, mono_wasm_web_socket_close_ref, mono_wasm_web_socket_abort } from "./web-socket";
import { mono_wasm_pthread_on_pthread_attached, afterThreadInit } from "./pthreads/worker";
import { afterLoadWasmModuleToWorker } from "./pthreads/browser";

const MONO = {
// current "public" MONO API
Expand Down Expand Up @@ -184,14 +187,20 @@ export type BINDINGType = typeof BINDING;

let exportedAPI: DotnetPublicAPI;

// We need to replace some of the methods in the Emscripten PThreads support with our own
type PThreadReplacements = {
loadWasmModuleToWorker: Function,
threadInit: Function
}

// this is executed early during load of emscripten runtime
// it exports methods to global objects MONO, BINDING and Module in backward compatible way
// At runtime this will be referred to as 'createDotnetRuntime'
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
function initializeImportsAndExports(
imports: { isESM: boolean, isGlobal: boolean, isNode: boolean, isWorker: boolean, isShell: boolean, isWeb: boolean, locateFile: Function, quit_: Function, ExitStatus: ExitStatusError, requirePromise: Promise<Function> },
imports: { isESM: boolean, isGlobal: boolean, isNode: boolean, isWorker: boolean, isShell: boolean, isWeb: boolean, isPThread: boolean, locateFile: Function, quit_: Function, ExitStatus: ExitStatusError, requirePromise: Promise<Function> },
exports: { mono: any, binding: any, internal: any, module: any, marshaled_exports: any, marshaled_imports: any },
replacements: { fetch: any, readAsync: any, require: any, requireOut: any, noExitRuntime: boolean, updateGlobalBufferAndViews: Function },
replacements: { fetch: any, readAsync: any, require: any, requireOut: any, noExitRuntime: boolean, updateGlobalBufferAndViews: Function, pthreadReplacements: PThreadReplacements | undefined | null },
): DotnetPublicAPI {
const module = exports.module as DotnetModule;
const globalThisAny = globalThis as any;
Expand Down Expand Up @@ -258,6 +267,19 @@ function initializeImportsAndExports(

replacements.noExitRuntime = ENVIRONMENT_IS_WEB;

if (replacements.pthreadReplacements) {
const originalLoadWasmModuleToWorker = replacements.pthreadReplacements.loadWasmModuleToWorker;
replacements.pthreadReplacements.loadWasmModuleToWorker = (worker: Worker, onFinishedLoading: Function): void => {
originalLoadWasmModuleToWorker(worker, onFinishedLoading);
afterLoadWasmModuleToWorker(worker);
};
const originalThreadInit = replacements.pthreadReplacements.threadInit;
replacements.pthreadReplacements.threadInit = (): void => {
originalThreadInit();
afterThreadInit();
};
}

if (typeof module.disableDotnet6Compatibility === "undefined") {
module.disableDotnet6Compatibility = imports.isESM;
}
Expand Down Expand Up @@ -328,6 +350,13 @@ function initializeImportsAndExports(

export const __initializeImportsAndExports: any = initializeImportsAndExports; // don't want to export the type

// the methods would be visible to EMCC linker
// --- keep in sync with dotnet.cjs.lib.js ---
const mono_wasm_threads_exports = !MonoWasmThreads ? undefined : {
// mono-threads-wasm.c
mono_wasm_pthread_on_pthread_attached,
};

// the methods would be visible to EMCC linker
// --- keep in sync with dotnet.cjs.lib.js ---
export const __linker_exports: any = {
Expand Down Expand Up @@ -376,7 +405,10 @@ export const __linker_exports: any = {
// pal_crypto_webworker.c
dotnet_browser_can_use_subtle_crypto_impl,
dotnet_browser_simple_digest_hash,
dotnet_browser_sign
dotnet_browser_sign,

// threading exports, if threading is enabled
...mono_wasm_threads_exports,
};

const INTERNAL: any = {
Expand Down Expand Up @@ -450,4 +482,4 @@ class RuntimeList {

export function get_dotnet_instance(): DotnetPublicAPI {
return exportedAPI;
}
}
4 changes: 3 additions & 1 deletion src/mono/wasm/runtime/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export let ENVIRONMENT_IS_NODE: boolean;
export let ENVIRONMENT_IS_SHELL: boolean;
export let ENVIRONMENT_IS_WEB: boolean;
export let ENVIRONMENT_IS_WORKER: boolean;
export let ENVIRONMENT_IS_PTHREAD: boolean;
export let locateFile: Function;
export let quit: Function;
export let ExitStatus: ExitStatusError;
Expand All @@ -33,7 +34,7 @@ export interface ExitStatusError {

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function setImportsAndExports(
imports: { isESM: boolean, isNode: boolean, isShell: boolean, isWeb: boolean, isWorker: boolean, locateFile: Function, ExitStatus: ExitStatusError, quit_: Function, requirePromise: Promise<Function> },
imports: { isESM: boolean, isNode: boolean, isShell: boolean, isWeb: boolean, isWorker: boolean, isPThread: boolean, locateFile: Function, ExitStatus: ExitStatusError, quit_: Function, requirePromise: Promise<Function> },
exports: { mono: any, binding: any, internal: any, module: any, marshaled_exports: any, marshaled_imports: any },
): void {
MONO = exports.mono;
Expand All @@ -49,6 +50,7 @@ export function setImportsAndExports(
ENVIRONMENT_IS_SHELL = imports.isShell;
ENVIRONMENT_IS_WEB = imports.isWeb;
ENVIRONMENT_IS_WORKER = imports.isWorker;
ENVIRONMENT_IS_PTHREAD = imports.isPThread;
locateFile = imports.locateFile;
quit = imports.quit_;
ExitStatus = imports.ExitStatus;
Expand Down
Loading