diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs index f96e0f4f0cf0a2..fc6fe9ce0c6115 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs @@ -45,7 +45,7 @@ public static void CancelPromise(Task promise) // FIXME: race condition // we know that holder.GCHandle is still valid because we hold the ProxyContext lock // but the message may arrive to the target thread after it was resolved, making GCHandle invalid - Interop.Runtime.CancelPromisePost(holder.ProxyContext.NativeTID, holder.GCHandle); + Interop.Runtime.CancelPromisePost(holder.ProxyContext.JSNativeTID, holder.GCHandle); } } #endif diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index 74a67b477648c9..f4a6efb3c333f3 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -297,10 +297,23 @@ public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer) // this is here temporarily, until JSWebWorker becomes public API [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")] // the marshaled signature is: - // void InstallMainSynchronizationContext() - public static void InstallMainSynchronizationContext() + // void InstallMainSynchronizationContext(nint jsNativeTID, out GCHandle contextHandle) + public static void InstallMainSynchronizationContext(JSMarshalerArgument* arguments_buffer) { - JSSynchronizationContext.InstallWebWorkerInterop(true, CancellationToken.None); + ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() + ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller + ref JSMarshalerArgument arg_2 = ref arguments_buffer[3];// initialized and set by caller + + try + { + var jsSynchronizationContext = JSSynchronizationContext.InstallWebWorkerInterop(true, CancellationToken.None); + jsSynchronizationContext.ProxyContext.JSNativeTID = arg_1.slot.IntPtrValue; + arg_2.slot.GCHandle = jsSynchronizationContext.ProxyContext.ContextHandle; + } + catch (Exception ex) + { + arg_exc.ToJS(ex); + } } #endif diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs index 5761fe0010bb00..acbd4a92ef9042 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs @@ -253,7 +253,7 @@ internal static unsafe void DispatchJSFunctionSync(JSObject jsFunction, Span true; +#pragma warning restore CA1822 // Mark members as static #else public nint ContextHandle; - public nint NativeTID; + public nint JSNativeTID; // target thread where JavaScript is running public int ManagedTID; public bool IsMainThread; public JSSynchronizationContext SynchronizationContext; @@ -53,7 +58,7 @@ public static IntPtr GetNativeThreadId() public JSProxyContext(bool isMainThread, JSSynchronizationContext synchronizationContext) { SynchronizationContext = synchronizationContext; - NativeTID = GetNativeThreadId(); + JSNativeTID = GetNativeThreadId(); ManagedTID = Environment.CurrentManagedThreadId; IsMainThread = isMainThread; ContextHandle = (nint)GCHandle.Alloc(this, GCHandleType.Normal); @@ -467,7 +472,7 @@ public static void ReleaseCSOwnedObject(JSObject jso, bool skipJS) // this is async message, we need to call this as the last thing // the same jsHandle would not be re-used until JS side considers it free - Interop.Runtime.ReleaseCSOwnedObjectPost(ctx.NativeTID, jsHandle); + Interop.Runtime.ReleaseCSOwnedObjectPost(ctx.JSNativeTID, jsHandle); } #else Interop.Runtime.ReleaseCSOwnedObject(jsHandle); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs index a1e6b6c93b093d..3953633580811f 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs @@ -179,7 +179,7 @@ private unsafe void ScheduleJSPump() { // While we COULD pump here, we don't want to. We want the pump to happen on the next event loop turn. // Otherwise we could get a chain where a pump generates a new work item and that makes us pump again, forever. - TargetThreadScheduleBackgroundJob(ProxyContext.NativeTID, (void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler); + TargetThreadScheduleBackgroundJob(ProxyContext.JSNativeTID, (delegate* unmanaged[Cdecl])&BackgroundJobHandler); } public override void Post(SendOrPostCallback d, object? state) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs index 5721c67d5ea241..928339b8062061 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs @@ -66,6 +66,7 @@ public JSWebWorkerInstance(Func> body, CancellationToken cancellationTok // TODO TaskCreationOptions.HideScheduler ? _taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _thread = new Thread(ThreadMain); + _thread.Name = "JSWebWorker"; _resultTask = null; _cancellationToken = cancellationToken; _cancellationRegistration = null; diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs index 4d8b10cfd452a9..eee6b5f3037cf9 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs @@ -228,13 +228,19 @@ static void MarshalResult(ref JSMarshalerArgument arg, object? taskResult) public void ToJS(Task? value) { Task? task = value; + var ctx = ToJSContext; + var isCurrentThread = ctx.IsCurrentThread(); if (task == null) { + if (!isCurrentThread) + { + Environment.FailFast("Marshalling null task to JS is not supported in MT"); + } slot.Type = MarshalerType.None; return; } - if (task.IsCompleted) + if (isCurrentThread && task.IsCompleted) { if (task.Exception != null) { @@ -252,8 +258,6 @@ public void ToJS(Task? value) } } - var ctx = ToJSContext; - if (slot.Type != MarshalerType.TaskPreCreated) { // this path should only happen when the Task is passed as argument of JSImport @@ -298,14 +302,20 @@ static void Complete(Task task, object? th) public void ToJS(Task? value, ArgumentToJSCallback marshaler) { Task? task = value; + var ctx = ToJSContext; + var isCurrentThread = ctx.IsCurrentThread(); if (task == null) { + if (!isCurrentThread) + { + Environment.FailFast("NULL not supported in MT"); + } slot.Type = MarshalerType.None; return; } - if (task.IsCompleted) + if (isCurrentThread && task.IsCompleted) { if (task.Exception != null) { @@ -325,7 +335,6 @@ public void ToJS(Task? value, ArgumentToJSCallback marshaler) } } - var ctx = ToJSContext; if (slot.Type != MarshalerType.TaskPreCreated) { // this path should only happen when the Task is passed as argument of JSImport diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs index c88d32277da8bb..514741a6b8753b 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs @@ -439,7 +439,7 @@ await executor.Execute(async () => [Theory, MemberData(nameof(GetTargetThreadsAndBlockingCalls))] public async Task WaitAssertsOnJSInteropThreads(Executor executor, NamedCall method) { - var cts = CreateTestCaseTimeoutSource(); + using var cts = CreateTestCaseTimeoutSource(); await executor.Execute(Task () => { Exception? exception = null; diff --git a/src/mono/browser/debugger/tests/debugger-test/debugger-main.js b/src/mono/browser/debugger/tests/debugger-test/debugger-main.js index 6849e490de36aa..58688dfc8a470e 100644 --- a/src/mono/browser/debugger/tests/debugger-test/debugger-main.js +++ b/src/mono/browser/debugger/tests/debugger-test/debugger-main.js @@ -36,18 +36,18 @@ try { } } - // this is fake implementation of legacy `bind_static_method` which uses `mono_wasm_invoke_method_raw` + // this is fake implementation of legacy `bind_static_method` which uses `mono_wasm_invoke_method` // We have unit tests that stop on unhandled managed exceptions. - // as opposed to [JSExport], the `mono_wasm_invoke_method_raw` doesn't handle managed exceptions. + // as opposed to [JSExport], the `mono_wasm_invoke_method` doesn't handle managed exceptions. // Same way as old `bind_static_method` didn't App.bind_static_method_native = (method_name) => { try { const monoMethodPtr = App.exports.DebuggerTests.BindStaticMethod.GetMonoMethodPtr(method_name); // this is only implemented for void methods with no arguments - const invoker = runtime.Module.cwrap("mono_wasm_invoke_method_raw", "number", ["number", "number"]); + const invoker = runtime.Module.cwrap("mono_wasm_invoke_method", "number", ["number", "number", "number"]); return function () { try { - return invoker(monoMethodPtr); + return invoker(monoMethodPtr, 0, 0); } catch (err) { console.error(err); diff --git a/src/mono/browser/runtime/cwraps.ts b/src/mono/browser/runtime/cwraps.ts index 71fbf7202342a4..7c38a34ee4e852 100644 --- a/src/mono/browser/runtime/cwraps.ts +++ b/src/mono/browser/runtime/cwraps.ts @@ -64,8 +64,7 @@ const fn_signatures: SigLine[] = [ [() => !runtimeHelpers.emscriptenBuildOptions.enableBrowserProfiler, "mono_wasm_profiler_init_aot", "void", ["string"]], [true, "mono_wasm_profiler_init_browser", "void", ["number"]], [false, "mono_wasm_exec_regression", "number", ["number", "string"]], - [false, "mono_wasm_invoke_method_bound", "number", ["number", "number", "number"]], - [false, "mono_wasm_invoke_method_raw", "number", ["number", "number"]], + [false, "mono_wasm_invoke_method", "number", ["number", "number", "number"]], [true, "mono_wasm_write_managed_pointer_unsafe", "void", ["number", "number"]], [true, "mono_wasm_copy_managed_pointer", "void", ["number", "number"]], [true, "mono_wasm_i52_to_f64", "number", ["number", "number"]], @@ -182,8 +181,7 @@ export interface t_Cwraps { mono_wasm_getenv(name: string): CharPtr; mono_wasm_set_main_args(argc: number, argv: VoidPtr): void; mono_wasm_exec_regression(verbose_level: number, image: string): number; - mono_wasm_invoke_method_bound(method: MonoMethod, args: JSMarshalerArguments, fail: MonoStringRef): number; - mono_wasm_invoke_method_raw(method: MonoMethod, fail: MonoStringRef): number; + mono_wasm_invoke_method(method: MonoMethod, args: JSMarshalerArguments, fail: MonoStringRef): number; mono_wasm_write_managed_pointer_unsafe(destination: VoidPtr | MonoObjectRef, pointer: ManagedPointer): void; mono_wasm_copy_managed_pointer(destination: VoidPtr | MonoObjectRef, source: VoidPtr | MonoObjectRef): void; mono_wasm_i52_to_f64(source: VoidPtr, error: Int32Ptr): number; diff --git a/src/mono/browser/runtime/diagnostics/browser/controller.ts b/src/mono/browser/runtime/diagnostics/browser/controller.ts index 8cc60f7742c4cf..7bcdf62b868505 100644 --- a/src/mono/browser/runtime/diagnostics/browser/controller.ts +++ b/src/mono/browser/runtime/diagnostics/browser/controller.ts @@ -10,6 +10,7 @@ import { withStackAlloc, getI32 } from "../../memory"; import { Thread, waitForThread } from "../../pthreads/browser"; import { isDiagnosticMessage, makeDiagnosticServerControlCommand } from "../shared/controller-commands"; import monoDiagnosticsMock from "consts:monoDiagnosticsMock"; +import { PThreadPtr } from "../../pthreads/shared/types"; /// An object that can be used to control the diagnostic server. export interface ServerController { @@ -57,10 +58,10 @@ export async function startDiagnosticServer(websocket_url: string): Promise { + const result: PThreadPtr | undefined = withStackAlloc(sizeOfPthreadT, (pthreadIdPtr) => { if (!cwraps.mono_wasm_diagnostic_server_create_thread(websocket_url, pthreadIdPtr)) return undefined; - const pthreadId = getI32(pthreadIdPtr); + const pthreadId = getI32(pthreadIdPtr) as any as PThreadPtr; return pthreadId; }); if (result === undefined) { diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts index 23f55cacc455ba..e60265f04a2813 100644 --- a/src/mono/browser/runtime/dotnet.d.ts +++ b/src/mono/browser/runtime/dotnet.d.ts @@ -352,7 +352,15 @@ type SingleAssetBehaviors = /** * Typically blazor.boot.json */ - | "manifest"; + | "manifest" +/** + * The debugging symbols + */ + | "symbols" +/** + * Load segmentation rules file for Hybrid Globalization. + */ + | "segmentation-rules"; type AssetBehaviors = SingleAssetBehaviors | /** * Load asset as a managed resource assembly. @@ -381,15 +389,7 @@ type AssetBehaviors = SingleAssetBehaviors | /** * The javascript module that came from nuget package . */ - | "js-module-library-initializer" -/** - * The javascript module for threads. - */ - | "symbols" -/** - * Load segmentation rules file for Hybrid Globalization. - */ - | "segmentation-rules"; + | "js-module-library-initializer"; declare const enum GlobalizationMode { /** * Load sharded ICU data. diff --git a/src/mono/browser/runtime/driver.c b/src/mono/browser/runtime/driver.c index eea015cb0ea5a9..215eaca4dfe85e 100644 --- a/src/mono/browser/runtime/driver.c +++ b/src/mono/browser/runtime/driver.c @@ -228,7 +228,7 @@ mono_wasm_load_runtime (int debug_level) } EMSCRIPTEN_KEEPALIVE int -mono_wasm_invoke_method_bound (MonoMethod *method, void* args /*JSMarshalerArguments*/, MonoString **out_exc) +mono_wasm_invoke_method (MonoMethod *method, void* args, MonoString **out_exc) { PVOLATILE(MonoObject) temp_exc = NULL; @@ -236,7 +236,7 @@ mono_wasm_invoke_method_bound (MonoMethod *method, void* args /*JSMarshalerArgum int is_err = 0; MONO_ENTER_GC_UNSAFE; - mono_runtime_invoke (method, NULL, invoke_args, (MonoObject **)&temp_exc); + mono_runtime_invoke (method, NULL, args ? invoke_args : NULL, (MonoObject **)&temp_exc); // this failure is unlikely because it would be runtime error, not application exception. // the application exception is passed inside JSMarshalerArguments `args` @@ -251,27 +251,6 @@ mono_wasm_invoke_method_bound (MonoMethod *method, void* args /*JSMarshalerArgum return is_err; } -EMSCRIPTEN_KEEPALIVE int -mono_wasm_invoke_method_raw (MonoMethod *method, MonoString **out_exc) -{ - PVOLATILE(MonoObject) temp_exc = NULL; - - int is_err = 0; - - MONO_ENTER_GC_UNSAFE; - mono_runtime_invoke (method, NULL, NULL, (MonoObject **)&temp_exc); - - if (temp_exc && out_exc) { - PVOLATILE(MonoObject) exc2 = NULL; - store_volatile((MonoObject**)out_exc, (MonoObject*)mono_object_to_string ((MonoObject*)temp_exc, (MonoObject **)&exc2)); - if (exc2) - store_volatile((MonoObject**)out_exc, (MonoObject*)mono_string_new (root_domain, "Exception Double Fault")); - is_err = 1; - } - MONO_EXIT_GC_UNSAFE; - return is_err; -} - EMSCRIPTEN_KEEPALIVE MonoMethod* mono_wasm_assembly_get_entry_point (MonoAssembly *assembly, int auto_insert_breakpoint) { diff --git a/src/mono/browser/runtime/gc-handles.ts b/src/mono/browser/runtime/gc-handles.ts index c192891e514236..5fe3a27a50fe1a 100644 --- a/src/mono/browser/runtime/gc-handles.ts +++ b/src/mono/browser/runtime/gc-handles.ts @@ -4,13 +4,14 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import BuildConfiguration from "consts:configuration"; -import { loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; +import { loaderHelpers, mono_assert } from "./globals"; import { assert_js_interop, js_import_wrapper_by_fn_handle } from "./invoke-js"; import { mono_log_info, mono_log_warn } from "./logging"; import { bound_cs_function_symbol, imported_js_function_symbol, proxy_debug_symbol } from "./marshal"; import { GCHandle, GCHandleNull, JSHandle, WeakRefInternal } from "./types/internal"; import { _use_weak_ref, create_weak_ref } from "./weak-ref"; import { exportsByAssembly } from "./invoke-cs"; +import { release_js_owned_object_by_gc_handle } from "./managed-exports"; const _use_finalization_registry = typeof globalThis.FinalizationRegistry === "function"; let _js_owned_object_registry: FinalizationRegistry; @@ -152,7 +153,7 @@ export function teardown_managed_proxy(owner: any, gc_handle: GCHandle, skipMana } if (gc_handle !== GCHandleNull && _js_owned_object_table.delete(gc_handle) && !skipManaged) { if (loaderHelpers.is_runtime_running()) { - runtimeHelpers.javaScriptExports.release_js_owned_object_by_gc_handle(gc_handle); + release_js_owned_object_by_gc_handle(gc_handle); } } if (is_gcv_handle(gc_handle)) { diff --git a/src/mono/browser/runtime/globals.ts b/src/mono/browser/runtime/globals.ts index be2ae8f86bee95..15ea06b82c6d07 100644 --- a/src/mono/browser/runtime/globals.ts +++ b/src/mono/browser/runtime/globals.ts @@ -9,7 +9,7 @@ import gitHash from "consts:gitHash"; import { RuntimeAPI } from "./types/index"; -import type { GlobalObjects, EmscriptenInternals, RuntimeHelpers, LoaderHelpers, DotnetModuleInternal, PromiseAndController, EmscriptenBuildOptions } from "./types/internal"; +import type { GlobalObjects, EmscriptenInternals, RuntimeHelpers, LoaderHelpers, DotnetModuleInternal, PromiseAndController, EmscriptenBuildOptions, GCHandle } from "./types/internal"; import { mono_log_error } from "./logging"; // these are our public API (except internal) @@ -55,7 +55,7 @@ export function setRuntimeGlobals(globalObjects: GlobalObjects) { loaderHelpers = globalObjects.loaderHelpers; exportedRuntimeAPI = globalObjects.api; - Object.assign(runtimeHelpers, { + const rh: Partial = { gitHash, allAssetsInMemory: createPromiseController(), dotnetReady: createPromiseController(), @@ -64,15 +64,13 @@ export function setRuntimeGlobals(globalObjects: GlobalObjects) { afterPreInit: createPromiseController(), afterPreRun: createPromiseController(), beforeOnRuntimeInitialized: createPromiseController(), + afterMonoStarted: createPromiseController(), afterOnRuntimeInitialized: createPromiseController(), afterPostRun: createPromiseController(), - mono_wasm_exit: () => { - throw new Error("Mono shutdown"); - }, - abort: (reason: any) => { - throw reason; - } - }); + nativeAbort: (reason: any) => { throw reason || new Error("abort"); }, + nativeExit: (code: number) => { throw new Error("exit:" + code); }, + }; + Object.assign(runtimeHelpers, rh); Object.assign(globalObjects.module.config!, {}) as any; Object.assign(globalObjects.api, { diff --git a/src/mono/browser/runtime/invoke-cs.ts b/src/mono/browser/runtime/invoke-cs.ts index fd0af4ca25dc42..d45063ab30bbb6 100644 --- a/src/mono/browser/runtime/invoke-cs.ts +++ b/src/mono/browser/runtime/invoke-cs.ts @@ -16,7 +16,7 @@ import { monoStringToString } from "./strings"; import { MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull, MarshalerType, MonoAssembly } from "./types/internal"; import { Int32Ptr } from "./types/emscripten"; import cwraps from "./cwraps"; -import { assert_c_interop, assert_js_interop, wrap_error_root, wrap_no_error_root } from "./invoke-js"; +import { assert_js_interop, wrap_error_root, wrap_no_error_root } from "./invoke-js"; import { startMeasure, MeasuredBlock, endMeasure } from "./profiler"; import { mono_log_debug } from "./logging"; @@ -140,7 +140,7 @@ function bind_fn_0V(closure: BindingClosure) { try { const args = alloc_stack_frame(2); // call C# side - invoke_method_and_handle_exception(method, args); + invoke_sync_method(method, args); } finally { Module.stackRestore(sp); endMeasure(mark, MeasuredBlock.callCsFunction, fqn); @@ -163,7 +163,7 @@ function bind_fn_1V(closure: BindingClosure) { marshaler1(args, arg1); // call C# side - invoke_method_and_handle_exception(method, args); + invoke_sync_method(method, args); } finally { Module.stackRestore(sp); endMeasure(mark, MeasuredBlock.callCsFunction, fqn); @@ -187,7 +187,7 @@ function bind_fn_1R(closure: BindingClosure) { marshaler1(args, arg1); // call C# side - invoke_method_and_handle_exception(method, args); + invoke_sync_method(method, args); const js_result = res_converter(args); return js_result; @@ -217,7 +217,7 @@ function bind_fn_1RA(closure: BindingClosure) { let promise = res_converter(args); // call C# side - invoke_method_and_handle_exception(method, args); + invoke_sync_method(method, args); // in case the C# side returned synchronously promise = end_marshal_task_to_js(args, undefined, promise); @@ -248,7 +248,7 @@ function bind_fn_2R(closure: BindingClosure) { marshaler2(args, arg2); // call C# side - invoke_method_and_handle_exception(method, args); + invoke_sync_method(method, args); const js_result = res_converter(args); return js_result; @@ -280,7 +280,7 @@ function bind_fn_2RA(closure: BindingClosure) { let promise = res_converter(args); // call C# side - invoke_method_and_handle_exception(method, args); + invoke_sync_method(method, args); // in case the C# side returned synchronously promise = end_marshal_task_to_js(args, undefined, promise); @@ -322,7 +322,7 @@ function bind_fn(closure: BindingClosure) { } // call C# side - invoke_method_and_handle_exception(method, args); + invoke_sync_method(method, args); if (is_async) { // in case the C# side returned synchronously js_result = end_marshal_task_to_js(args, undefined, js_result); @@ -348,12 +348,12 @@ type BindingClosure = { isDisposed: boolean, } -export function invoke_method_and_handle_exception(method: MonoMethod, args: JSMarshalerArguments): void { +export function invoke_sync_method(method: MonoMethod, args: JSMarshalerArguments): void { assert_js_interop(); const fail_root = mono_wasm_new_root(); try { set_args_context(args); - const fail = cwraps.mono_wasm_invoke_method_bound(method, args, fail_root.address); + const fail = cwraps.mono_wasm_invoke_method(method, args, fail_root.address); if (fail) runtimeHelpers.nativeAbort("ERR24: Unexpected error: " + monoStringToString(fail_root)); if (is_args_exception(args)) { const exc = get_arg(args, 0); @@ -365,18 +365,6 @@ export function invoke_method_and_handle_exception(method: MonoMethod, args: JSM } } -export function invoke_method_raw(method: MonoMethod): void { - assert_c_interop(); - const fail_root = mono_wasm_new_root(); - try { - const fail = cwraps.mono_wasm_invoke_method_raw(method, fail_root.address); - if (fail) runtimeHelpers.nativeAbort("ERR24: Unexpected error: " + monoStringToString(fail_root)); - } - finally { - fail_root.release(); - } -} - export const exportsByAssembly: Map = new Map(); function _walk_exports_to_set_function(assembly: string, namespace: string, classname: string, methodname: string, signature_hash: number, fn: Function): void { const parts = `${namespace}.${classname}`.replace(/\//g, ".").split("."); diff --git a/src/mono/browser/runtime/invoke-js.ts b/src/mono/browser/runtime/invoke-js.ts index 79f676ee08b437..ccd7e294a3f883 100644 --- a/src/mono/browser/runtime/invoke-js.ts +++ b/src/mono/browser/runtime/invoke-js.ts @@ -462,7 +462,7 @@ export function wrap_no_error_root(is_exception: Int32Ptr | null, result?: WasmR export function assert_js_interop(): void { loaderHelpers.assert_runtime_running(); if (WasmEnableThreads) { - mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready && runtimeHelpers.proxy_context_gc_handle, "Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads"); + mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready && runtimeHelpers.proxyGCHandle, "Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads"); } else { mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "The runtime must be initialized."); } diff --git a/src/mono/browser/runtime/lazyLoading.ts b/src/mono/browser/runtime/lazyLoading.ts index 85058f89819b7c..6d7ea7852f8c52 100644 --- a/src/mono/browser/runtime/lazyLoading.ts +++ b/src/mono/browser/runtime/lazyLoading.ts @@ -1,7 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { loaderHelpers, runtimeHelpers } from "./globals"; +import { loaderHelpers } from "./globals"; +import { load_lazy_assembly } from "./managed-exports"; import { AssetEntry } from "./types"; export async function loadLazyAssembly(assemblyNameToLoad: string): Promise { @@ -51,7 +52,7 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise = { mono_wasm_bindings_is_ready: false, - javaScriptExports: {} as any, config: globalObjects.module.config, diagnosticTracing: false, - nativeAbort: (reason: any) => { throw reason; }, + nativeAbort: (reason: any) => { throw reason || new Error("abort"); }, nativeExit: (code: number) => { throw new Error("exit:" + code); } }; const lh: Partial = { diff --git a/src/mono/browser/runtime/loader/icu.ts b/src/mono/browser/runtime/loader/icu.ts index 46a75726702e93..2efc00f398eda5 100644 --- a/src/mono/browser/runtime/loader/icu.ts +++ b/src/mono/browser/runtime/loader/icu.ts @@ -7,14 +7,14 @@ import { mono_log_info, mono_log_debug } from "./logging"; export function init_globalization() { loaderHelpers.preferredIcuAsset = getIcuResourceName(loaderHelpers.config); - loaderHelpers.invariantMode = loaderHelpers.config.globalizationMode == GlobalizationMode.Invariant; + let invariantMode = loaderHelpers.config.globalizationMode == GlobalizationMode.Invariant; - if (!loaderHelpers.invariantMode) { + if (!invariantMode) { if (loaderHelpers.preferredIcuAsset) { mono_log_debug("ICU data archive(s) available, disabling invariant mode"); } else if (loaderHelpers.config.globalizationMode !== GlobalizationMode.Custom && loaderHelpers.config.globalizationMode !== GlobalizationMode.All && loaderHelpers.config.globalizationMode !== GlobalizationMode.Sharded) { mono_log_debug("ICU data archive(s) not available, using invariant globalization mode"); - loaderHelpers.invariantMode = true; + invariantMode = true; loaderHelpers.preferredIcuAsset = null; } else { const msg = "invariant globalization mode is inactive and no ICU data archives are available"; @@ -29,7 +29,7 @@ export function init_globalization() { if (env_variables[hybridEnv] === undefined && loaderHelpers.config.globalizationMode === GlobalizationMode.Hybrid) { env_variables[hybridEnv] = "1"; } - else if (env_variables[invariantEnv] === undefined && loaderHelpers.invariantMode) { + else if (env_variables[invariantEnv] === undefined && invariantMode) { env_variables[invariantEnv] = "1"; } if (env_variables["TZ"] === undefined) { diff --git a/src/mono/browser/runtime/loader/run.ts b/src/mono/browser/runtime/loader/run.ts index 9a19aa6c5217fb..1f85b4e2412002 100644 --- a/src/mono/browser/runtime/loader/run.ts +++ b/src/mono/browser/runtime/loader/run.ts @@ -513,6 +513,16 @@ async function createEmscriptenWorker(): Promise { prepareAssetsWorker(); + setTimeout(async () => { + try { + // load subset which is on JS heap rather than in WASM linear memory + await mono_download_assets(); + } + catch (err) { + mono_exit(1, err); + } + }, 0); + const promises = importModules(); const es6Modules = await Promise.all(promises); await initializeModules(es6Modules as any); diff --git a/src/mono/browser/runtime/managed-exports.ts b/src/mono/browser/runtime/managed-exports.ts index 9ff79b65c210e4..4a95d0ca362a55 100644 --- a/src/mono/browser/runtime/managed-exports.ts +++ b/src/mono/browser/runtime/managed-exports.ts @@ -3,14 +3,19 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; -import { GCHandle, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod } from "./types/internal"; +import { GCHandle, GCHandleNull, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod } from "./types/internal"; import cwraps from "./cwraps"; import { runtimeHelpers, Module, loaderHelpers, mono_assert } from "./globals"; -import { alloc_stack_frame, get_arg, set_arg_type, set_gc_handle } from "./marshal"; -import { invoke_method_and_handle_exception, invoke_method_raw } from "./invoke-cs"; +import { JavaScriptMarshalerArgSize, alloc_stack_frame, get_arg, get_arg_gc_handle, is_args_exception, set_arg_intptr, set_arg_type, set_gc_handle } from "./marshal"; +import { invoke_sync_method } from "./invoke-cs"; import { marshal_array_to_cs, marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs"; -import { marshal_int32_to_js, end_marshal_task_to_js, marshal_string_to_js, begin_marshal_task_to_js } from "./marshal-to-js"; +import { marshal_int32_to_js, end_marshal_task_to_js, marshal_string_to_js, begin_marshal_task_to_js, marshal_exception_to_js } from "./marshal-to-js"; import { do_not_force_dispose } from "./gc-handles"; +import { assert_c_interop } from "./invoke-js"; +import { mono_wasm_main_thread_ptr } from "./pthreads/shared"; +import { _zero_region } from "./memory"; + +const managedExports: ManagedExports = {} as any; export function init_managed_exports(): void { const exports_fqn_asm = "System.Runtime.InteropServices.JavaScript"; @@ -24,183 +29,223 @@ export function init_managed_exports(): void { if (!runtimeHelpers.runtime_interop_exports_class) throw "Can't find " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + " class"; - const install_main_synchronization_context = WasmEnableThreads ? get_method("InstallMainSynchronizationContext") : undefined; - mono_assert(!WasmEnableThreads || install_main_synchronization_context, "Can't find InstallMainSynchronizationContext method"); - const call_entry_point = get_method("CallEntrypoint"); - mono_assert(call_entry_point, "Can't find CallEntrypoint method"); - const release_js_owned_object_by_gc_handle_method = get_method("ReleaseJSOwnedObjectByGCHandle"); - mono_assert(release_js_owned_object_by_gc_handle_method, "Can't find ReleaseJSOwnedObjectByGCHandle method"); - const complete_task_method = get_method("CompleteTask"); - mono_assert(complete_task_method, "Can't find CompleteTask method"); - const call_delegate_method = get_method("CallDelegate"); - mono_assert(call_delegate_method, "Can't find CallDelegate method"); - const get_managed_stack_trace_method = get_method("GetManagedStackTrace"); - mono_assert(get_managed_stack_trace_method, "Can't find GetManagedStackTrace method"); - const load_satellite_assembly_method = get_method("LoadSatelliteAssembly"); - mono_assert(load_satellite_assembly_method, "Can't find LoadSatelliteAssembly method"); - const load_lazy_assembly_method = get_method("LoadLazyAssembly"); - mono_assert(load_lazy_assembly_method, "Can't find LoadLazyAssembly method"); - - runtimeHelpers.javaScriptExports.call_entry_point = async (entry_point: MonoMethod, program_args?: string[]): Promise => { - loaderHelpers.assert_runtime_running(); - const sp = Module.stackSave(); - try { - Module.runtimeKeepalivePush(); - const args = alloc_stack_frame(4); - const res = get_arg(args, 1); - const arg1 = get_arg(args, 2); - const arg2 = get_arg(args, 3); - marshal_intptr_to_cs(arg1, entry_point); - if (program_args && program_args.length == 0) { - program_args = undefined; - } - marshal_array_to_cs_impl(arg2, program_args, MarshalerType.String); + managedExports.InstallMainSynchronizationContext = WasmEnableThreads ? get_method("InstallMainSynchronizationContext") : undefined; + managedExports.CallEntrypoint = get_method("CallEntrypoint"); + managedExports.ReleaseJSOwnedObjectByGCHandle = get_method("ReleaseJSOwnedObjectByGCHandle"); + managedExports.CompleteTask = get_method("CompleteTask"); + managedExports.CallDelegate = get_method("CallDelegate"); + managedExports.GetManagedStackTrace = get_method("GetManagedStackTrace"); + managedExports.LoadSatelliteAssembly = get_method("LoadSatelliteAssembly"); + managedExports.LoadLazyAssembly = get_method("LoadLazyAssembly"); +} - // because this is async, we could pre-allocate the promise - let promise = begin_marshal_task_to_js(res, MarshalerType.TaskPreCreated, marshal_int32_to_js); +// the marshaled signature is: Task? CallEntrypoint(MonoMethod* entrypointPtr, string[] args) +export async function call_entry_point(entry_point: MonoMethod, program_args?: string[]): Promise { + loaderHelpers.assert_runtime_running(); + const sp = Module.stackSave(); + try { + Module.runtimeKeepalivePush(); + const args = alloc_stack_frame(4); + const res = get_arg(args, 1); + const arg1 = get_arg(args, 2); + const arg2 = get_arg(args, 3); + marshal_intptr_to_cs(arg1, entry_point); + if (program_args && program_args.length == 0) { + program_args = undefined; + } + marshal_array_to_cs_impl(arg2, program_args, MarshalerType.String); - // NOTE: at the moment this is synchronous call on the same thread and therefore we could marshal (null) result synchronously - invoke_method_and_handle_exception(call_entry_point, args); + // because this is async, we could pre-allocate the promise + let promise = begin_marshal_task_to_js(res, MarshalerType.TaskPreCreated, marshal_int32_to_js); - // in case the C# side returned synchronously - promise = end_marshal_task_to_js(args, marshal_int32_to_js, promise); + // NOTE: at the moment this is synchronous call on the same thread and therefore we could marshal (null) result synchronously + invoke_sync_method(managedExports.CallEntrypoint, args); - if (promise === null || promise === undefined) { - promise = Promise.resolve(0); - } - (promise as any)[do_not_force_dispose] = true; // prevent disposing the task in forceDisposeProxies() - return await promise; - } finally { - Module.runtimeKeepalivePop();// after await promise ! - Module.stackRestore(sp); + // in case the C# side returned synchronously + promise = end_marshal_task_to_js(args, marshal_int32_to_js, promise); + + if (promise === null || promise === undefined) { + promise = Promise.resolve(0); } - }; - runtimeHelpers.javaScriptExports.load_satellite_assembly = (dll: Uint8Array): void => { - const sp = Module.stackSave(); - try { - const args = alloc_stack_frame(3); - const arg1 = get_arg(args, 2); - set_arg_type(arg1, MarshalerType.Array); - marshal_array_to_cs(arg1, dll, MarshalerType.Byte); - invoke_method_and_handle_exception(load_satellite_assembly_method, args); - } finally { - Module.stackRestore(sp); + (promise as any)[do_not_force_dispose] = true; // prevent disposing the task in forceDisposeProxies() + return await promise; + } finally { + Module.runtimeKeepalivePop();// after await promise ! + Module.stackRestore(sp); + } +} + +// the marshaled signature is: void LoadSatelliteAssembly(byte[] dll) +export function load_satellite_assembly(dll: Uint8Array): void { + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(3); + const arg1 = get_arg(args, 2); + set_arg_type(arg1, MarshalerType.Array); + marshal_array_to_cs(arg1, dll, MarshalerType.Byte); + invoke_sync_method(managedExports.LoadSatelliteAssembly, args); + } finally { + Module.stackRestore(sp); + } +} + +// the marshaled signature is: void LoadLazyAssembly(byte[] dll, byte[] pdb) +export function load_lazy_assembly(dll: Uint8Array, pdb: Uint8Array | null): void { + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(4); + const arg1 = get_arg(args, 2); + const arg2 = get_arg(args, 3); + set_arg_type(arg1, MarshalerType.Array); + set_arg_type(arg2, MarshalerType.Array); + marshal_array_to_cs(arg1, dll, MarshalerType.Byte); + marshal_array_to_cs(arg2, pdb, MarshalerType.Byte); + invoke_sync_method(managedExports.LoadLazyAssembly, args); + } finally { + Module.stackRestore(sp); + } +} + +// the marshaled signature is: void ReleaseJSOwnedObjectByGCHandle(GCHandle gcHandle) +export function release_js_owned_object_by_gc_handle(gc_handle: GCHandle) { + mono_assert(gc_handle, "Must be valid gc_handle"); + loaderHelpers.assert_runtime_running(); + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(3); + const arg1 = get_arg(args, 2); + set_arg_type(arg1, MarshalerType.Object); + set_gc_handle(arg1, gc_handle); + invoke_sync_method(managedExports.ReleaseJSOwnedObjectByGCHandle, args); + } finally { + Module.stackRestore(sp); + } +} + +// the marshaled signature is: void CompleteTask(GCHandle holder, Exception? exceptionResult, T? result) +export function complete_task(holder_gc_handle: GCHandle, isCanceling: boolean, error?: any, data?: any, res_converter?: MarshalerToCs) { + loaderHelpers.assert_runtime_running(); + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(5); + const res = get_arg(args, 1); + const arg1 = get_arg(args, 2); + set_arg_type(arg1, MarshalerType.Object); + set_gc_handle(arg1, holder_gc_handle); + const arg2 = get_arg(args, 3); + if (error) { + marshal_exception_to_cs(arg2, error); + if (isCanceling) { + set_arg_type(res, MarshalerType.Discard); + } + } else { + set_arg_type(arg2, MarshalerType.None); + const arg3 = get_arg(args, 4); + mono_assert(res_converter, "res_converter missing"); + res_converter(arg3, data); } - }; - runtimeHelpers.javaScriptExports.load_lazy_assembly = (dll: Uint8Array, pdb: Uint8Array | null): void => { - const sp = Module.stackSave(); - try { - const args = alloc_stack_frame(4); - const arg1 = get_arg(args, 2); + invoke_sync_method(managedExports.CompleteTask, args); + } finally { + Module.stackRestore(sp); + } +} + +// the marshaled signature is: TRes? CallDelegate(GCHandle callback, T1? arg1, T2? arg2, T3? arg3) +export function call_delegate(callback_gc_handle: GCHandle, arg1_js: any, arg2_js: any, arg3_js: any, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) { + loaderHelpers.assert_runtime_running(); + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(6); + + const arg1 = get_arg(args, 2); + set_arg_type(arg1, MarshalerType.Object); + set_gc_handle(arg1, callback_gc_handle); + // payload arg numbers are shifted by one, the real first is a gc handle of the callback + + if (arg1_converter) { const arg2 = get_arg(args, 3); - set_arg_type(arg1, MarshalerType.Array); - set_arg_type(arg2, MarshalerType.Array); - marshal_array_to_cs(arg1, dll, MarshalerType.Byte); - marshal_array_to_cs(arg2, pdb, MarshalerType.Byte); - invoke_method_and_handle_exception(load_lazy_assembly_method, args); - } finally { - Module.stackRestore(sp); + arg1_converter(arg2, arg1_js); } - }; - runtimeHelpers.javaScriptExports.release_js_owned_object_by_gc_handle = (gc_handle: GCHandle) => { - mono_assert(gc_handle, "Must be valid gc_handle"); - loaderHelpers.assert_runtime_running(); - const sp = Module.stackSave(); - try { - const args = alloc_stack_frame(3); - const arg1 = get_arg(args, 2); - set_arg_type(arg1, MarshalerType.Object); - set_gc_handle(arg1, gc_handle); - invoke_method_and_handle_exception(release_js_owned_object_by_gc_handle_method, args); - } finally { - Module.stackRestore(sp); + if (arg2_converter) { + const arg3 = get_arg(args, 4); + arg2_converter(arg3, arg2_js); } - }; - runtimeHelpers.javaScriptExports.complete_task = (holder_gc_handle: GCHandle, isCanceling: boolean, error?: any, data?: any, res_converter?: MarshalerToCs) => { - loaderHelpers.assert_runtime_running(); - const sp = Module.stackSave(); - try { - const args = alloc_stack_frame(5); - const res = get_arg(args, 1); - const arg1 = get_arg(args, 2); - set_arg_type(arg1, MarshalerType.Object); - set_gc_handle(arg1, holder_gc_handle); - const arg2 = get_arg(args, 3); - if (error) { - marshal_exception_to_cs(arg2, error); - if (isCanceling) { - set_arg_type(res, MarshalerType.Discard); - } - } else { - set_arg_type(arg2, MarshalerType.None); - const arg3 = get_arg(args, 4); - mono_assert(res_converter, "res_converter missing"); - res_converter(arg3, data); - } - invoke_method_and_handle_exception(complete_task_method, args); - } finally { - Module.stackRestore(sp); + if (arg3_converter) { + const arg4 = get_arg(args, 5); + arg3_converter(arg4, arg3_js); } - }; - runtimeHelpers.javaScriptExports.call_delegate = (callback_gc_handle: GCHandle, arg1_js: any, arg2_js: any, arg3_js: any, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) => { - loaderHelpers.assert_runtime_running(); - const sp = Module.stackSave(); - try { - const args = alloc_stack_frame(6); - - const arg1 = get_arg(args, 2); - set_arg_type(arg1, MarshalerType.Object); - set_gc_handle(arg1, callback_gc_handle); - // payload arg numbers are shifted by one, the real first is a gc handle of the callback - - if (arg1_converter) { - const arg2 = get_arg(args, 3); - arg1_converter(arg2, arg1_js); - } - if (arg2_converter) { - const arg3 = get_arg(args, 4); - arg2_converter(arg3, arg2_js); - } - if (arg3_converter) { - const arg4 = get_arg(args, 5); - arg3_converter(arg4, arg3_js); - } - invoke_method_and_handle_exception(call_delegate_method, args); + invoke_sync_method(managedExports.CallDelegate, args); - if (res_converter) { - const res = get_arg(args, 1); - return res_converter(res); - } - } finally { - Module.stackRestore(sp); - } - }; - runtimeHelpers.javaScriptExports.get_managed_stack_trace = (exception_gc_handle: GCHandle) => { - loaderHelpers.assert_runtime_running(); - const sp = Module.stackSave(); - try { - const args = alloc_stack_frame(3); - - const arg1 = get_arg(args, 2); - set_arg_type(arg1, MarshalerType.Exception); - set_gc_handle(arg1, exception_gc_handle); - - invoke_method_and_handle_exception(get_managed_stack_trace_method, args); + if (res_converter) { const res = get_arg(args, 1); - return marshal_string_to_js(res); - } finally { - Module.stackRestore(sp); + return res_converter(res); + } + } finally { + Module.stackRestore(sp); + } +} + +// the marshaled signature is: string GetManagedStackTrace(GCHandle exception) +export function get_managed_stack_trace(exception_gc_handle: GCHandle) { + loaderHelpers.assert_runtime_running(); + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(3); + + const arg1 = get_arg(args, 2); + set_arg_type(arg1, MarshalerType.Exception); + set_gc_handle(arg1, exception_gc_handle); + + invoke_sync_method(managedExports.GetManagedStackTrace, args); + const res = get_arg(args, 1); + return marshal_string_to_js(res); + } finally { + Module.stackRestore(sp); + } +} + +// void InstallMainSynchronizationContext(nint jsNativeTID, out GCHandle contextHandle) +export function install_main_synchronization_context(): GCHandle { + if (!WasmEnableThreads) return GCHandleNull; + assert_c_interop(); + + const sp = Module.stackSave(); + try { + // tid in, gc_handle out + const bytes = JavaScriptMarshalerArgSize * 4; + const args = Module.stackAlloc(bytes) as any; + _zero_region(args, bytes); + const arg1 = get_arg(args, 2); + const arg2 = get_arg(args, 3); + set_arg_intptr(arg1, mono_wasm_main_thread_ptr() as any); + const fail = cwraps.mono_wasm_invoke_method(managedExports.InstallMainSynchronizationContext!, args, 0 as any); + if (fail) runtimeHelpers.nativeAbort("ERR24: Unexpected error"); + if (is_args_exception(args)) { + const exc = get_arg(args, 0); + throw marshal_exception_to_js(exc); } - }; - if (WasmEnableThreads && install_main_synchronization_context) { - runtimeHelpers.javaScriptExports.install_main_synchronization_context = () => invoke_method_raw(install_main_synchronization_context); + return get_arg_gc_handle(arg2) as any; + } finally { + Module.stackRestore(sp); } } -export function get_method(method_name: string): MonoMethod { +function get_method(method_name: string): MonoMethod { const res = cwraps.mono_wasm_assembly_find_method(runtimeHelpers.runtime_interop_exports_class, method_name, -1); if (!res) throw "Can't find method " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + "." + method_name; return res; } + +type ManagedExports = { + InstallMainSynchronizationContext: MonoMethod | undefined, + entry_point: MonoMethod, + CallEntrypoint: MonoMethod, + ReleaseJSOwnedObjectByGCHandle: MonoMethod, + CompleteTask: MonoMethod, + CallDelegate: MonoMethod, + GetManagedStackTrace: MonoMethod, + LoadSatelliteAssembly: MonoMethod, + LoadLazyAssembly: MonoMethod, +} diff --git a/src/mono/browser/runtime/marshal-to-cs.ts b/src/mono/browser/runtime/marshal-to-cs.ts index e1baec112ab388..63657a122195e7 100644 --- a/src/mono/browser/runtime/marshal-to-cs.ts +++ b/src/mono/browser/runtime/marshal-to-cs.ts @@ -8,7 +8,7 @@ import WasmEnableJsInteropByValue from "consts:wasmEnableJsInteropByValue"; import { isThenable } from "./cancelable-promise"; import cwraps from "./cwraps"; import { alloc_gcv_handle, assert_not_disposed, cs_owned_js_handle_symbol, js_owned_gc_handle_symbol, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles"; -import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; +import { Module, loaderHelpers, mono_assert } from "./globals"; import { ManagedError, set_gc_handle, set_js_handle, set_arg_type, set_arg_i32, set_arg_f64, set_arg_i52, set_arg_f32, set_arg_i16, set_arg_u8, set_arg_b8, set_arg_date, @@ -24,6 +24,7 @@ import { JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerTo import { TypedArray } from "./types/emscripten"; import { addUnsettledPromise, settleUnsettledPromise } from "./pthreads/shared/eventloop"; import { mono_log_debug } from "./logging"; +import { complete_task } from "./managed-exports"; export const jsinteropDoc = "For more information see https://aka.ms/dotnet-wasm-jsinterop"; @@ -358,7 +359,7 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: teardown_managed_proxy(holder, gc_handle, /*skipManaged: */ true); // order of operations with teardown_managed_proxy matters // so that managed user code running in the continuation could allocate the same GCHandle number and the local registry would be already ok with that - runtimeHelpers.javaScriptExports.complete_task(gc_handle, false, null, data, res_converter || _marshal_cs_object_to_cs); + complete_task(gc_handle, false, null, data, res_converter || _marshal_cs_object_to_cs); } catch (ex) { try { @@ -385,7 +386,7 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: // we can unregister the GC handle just on JS side teardown_managed_proxy(holder, gc_handle, /*skipManaged: */ true); // order of operations with teardown_managed_proxy matters - runtimeHelpers.javaScriptExports.complete_task(gc_handle, holder.isCanceled, reason, null, undefined); + complete_task(gc_handle, holder.isCanceled, reason, null, undefined); } catch (ex) { try { diff --git a/src/mono/browser/runtime/marshal-to-js.ts b/src/mono/browser/runtime/marshal-to-js.ts index b1eca560532e9f..e40c83899bc07f 100644 --- a/src/mono/browser/runtime/marshal-to-js.ts +++ b/src/mono/browser/runtime/marshal-to-js.ts @@ -7,7 +7,7 @@ import WasmEnableJsInteropByValue from "consts:wasmEnableJsInteropByValue"; import cwraps from "./cwraps"; import { _lookup_js_owned_object, mono_wasm_get_js_handle, mono_wasm_get_jsobj_from_js_handle, mono_wasm_release_cs_owned_object, register_with_jsv_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles"; -import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; +import { Module, loaderHelpers, mono_assert } from "./globals"; import { ManagedObject, ManagedError, get_arg_gc_handle, get_arg_js_handle, get_arg_type, get_arg_i32, get_arg_f64, get_arg_i52, get_arg_i16, get_arg_u8, get_arg_f32, @@ -21,6 +21,7 @@ import { GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerTyp import { TypedArray } from "./types/emscripten"; import { get_marshaler_to_cs_by_type, jsinteropDoc, marshal_exception_to_cs } from "./marshal-to-cs"; import { localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; +import { call_delegate } from "./managed-exports"; export function initialize_marshalers_to_js(): void { if (cs_to_js_marshalers.size == 0) { @@ -199,7 +200,7 @@ function _marshal_delegate_to_js(arg: JSMarshalerArgument, _?: MarshalerType, re result = (arg1_js: any, arg2_js: any, arg3_js: any): any => { mono_assert(!WasmEnableThreads || !result.isDisposed, "Delegate is disposed and should not be invoked anymore."); // arg numbers are shifted by one, the real first is a gc handle of the callback - return runtimeHelpers.javaScriptExports.call_delegate(gc_handle, arg1_js, arg2_js, arg3_js, res_converter, arg1_converter, arg2_converter, arg3_converter); + return call_delegate(gc_handle, arg1_js, arg2_js, arg3_js, res_converter, arg1_converter, arg2_converter, arg3_converter); }; result.dispose = () => { if (!result.isDisposed) { diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts index 7af38977bff198..8aca7501b8653a 100644 --- a/src/mono/browser/runtime/marshal.ts +++ b/src/mono/browser/runtime/marshal.ts @@ -10,6 +10,7 @@ import { mono_wasm_new_external_root } from "./roots"; import { GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot, MarshalerType } from "./types/internal"; import { TypedArray, VoidPtr } from "./types/emscripten"; import { utf16ToString } from "./strings"; +import { get_managed_stack_trace } from "./managed-exports"; export const cs_to_js_marshalers = new Map(); export const js_to_cs_marshalers = new Map(); @@ -265,7 +266,7 @@ export function get_arg_js_handle(arg: JSMarshalerArgument): JSHandle { export function set_arg_proxy_context(arg: JSMarshalerArgument): void { if (!WasmEnableThreads) return; mono_assert(arg, "Null arg"); - setI32(arg + 16, runtimeHelpers.proxy_context_gc_handle); + setI32(arg + 16, runtimeHelpers.proxyGCHandle); } export function set_js_handle(arg: JSMarshalerArgument, jsHandle: JSHandle): void { @@ -353,10 +354,10 @@ export class ManagedError extends Error implements IDisposable { this.managed_stack = "... omitted managed stack trace.\n" + this.getSuperStack(); return this.managed_stack; } - if (!WasmEnableThreads || runtimeHelpers.proxy_context_gc_handle) { + if (!WasmEnableThreads || runtimeHelpers.proxyGCHandle) { const gc_handle = (this)[js_owned_gc_handle_symbol]; if (gc_handle !== GCHandleNull) { - const managed_stack = runtimeHelpers.javaScriptExports.get_managed_stack_trace(gc_handle); + const managed_stack = get_managed_stack_trace(gc_handle); if (managed_stack) { this.managed_stack = managed_stack + "\n" + this.getSuperStack(); return this.managed_stack; diff --git a/src/mono/browser/runtime/pthreads/browser/index.ts b/src/mono/browser/runtime/pthreads/browser/index.ts index e1ea4961a84634..84c58b694d13a1 100644 --- a/src/mono/browser/runtime/pthreads/browser/index.ts +++ b/src/mono/browser/runtime/pthreads/browser/index.ts @@ -3,31 +3,32 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; -import { MonoWorkerToMainMessage, PThreadInfo, pthreadPtr } from "../shared/types"; -import { MonoThreadMessage } from "../shared"; +import { MonoWorkerToMainMessage, PThreadInfo, PThreadPtr, PThreadPtrNull } from "../shared/types"; +import { MonoThreadMessage, mono_wasm_pthread_ptr, update_thread_info } from "../shared"; import { PThreadWorker, allocateUnusedWorker, getRunningWorkers, getUnusedWorkerPool, getWorker, loadWasmModuleToWorker } from "../shared/emscripten-internals"; import { createPromiseController, mono_assert, runtimeHelpers } from "../../globals"; import { MainToWorkerMessageType, PromiseAndController, PromiseController, WorkerToMainMessageType, monoMessageSymbol } from "../../types/internal"; import { mono_log_info } from "../../logging"; import { monoThreadInfo } from "../worker"; +import { mono_wasm_init_diagnostics } from "../../diagnostics"; -const threadPromises: Map[]> = new Map(); +const threadPromises: Map[]> = new Map(); export interface Thread { - readonly pthreadPtr: pthreadPtr; + readonly pthreadPtr: PThreadPtr; readonly port: MessagePort; postMessageToWorker(message: T): void; } class ThreadImpl implements Thread { - constructor(readonly pthreadPtr: pthreadPtr, readonly worker: Worker, readonly port: MessagePort) { } + constructor(readonly pthreadPtr: PThreadPtr, readonly worker: Worker, readonly port: MessagePort) { } postMessageToWorker(message: T): void { this.port.postMessage(message); } } /// wait until the thread with the given id has set up a message port to the runtime -export function waitForThread(pthreadPtr: pthreadPtr): Promise { +export function waitForThread(pthreadPtr: PThreadPtr): Promise { if (!WasmEnableThreads) return null as any; const worker = getWorker(pthreadPtr); if (worker?.thread) { @@ -43,7 +44,7 @@ export function waitForThread(pthreadPtr: pthreadPtr): Promise { return promiseAndController.promise; } -export function resolveThreadPromises(pthreadPtr: pthreadPtr, thread?: Thread): void { +export function resolveThreadPromises(pthreadPtr: PThreadPtr, thread?: Thread): void { if (!WasmEnableThreads) return; const arr = threadPromises.get(pthreadPtr); if (arr !== undefined) { @@ -61,13 +62,13 @@ export function resolveThreadPromises(pthreadPtr: pthreadPtr, thread?: Thread): // handler that runs in the main thread when a message is received from a pthread worker function monoWorkerMessageHandler(worker: PThreadWorker, ev: MessageEvent): void { if (!WasmEnableThreads) return; - let pthreadId: pthreadPtr; + let pthreadId: PThreadPtr; // this is emscripten message if (ev.data.cmd === "killThread") { pthreadId = ev.data["thread"]; mono_assert(pthreadId == worker.info.pthreadId, "expected pthreadId to match"); worker.info.isRunning = false; - worker.info.pthreadId = 0; + worker.info.pthreadId = PThreadPtrNull; return; } @@ -81,6 +82,7 @@ function monoWorkerMessageHandler(worker: PThreadWorker, ev: MessageEvent): let thread: Thread; pthreadId = message.info?.pthreadId ?? 0; + worker.info = Object.assign(worker.info, message.info, {}); switch (message.monoCmd) { case WorkerToMainMessageType.preload: // this one shot port from setupPreloadChannelToMainThread @@ -98,13 +100,13 @@ function monoWorkerMessageHandler(worker: PThreadWorker, ev: MessageEvent): worker.thread = thread; worker.info.isRunning = true; resolveThreadPromises(pthreadId, thread); - // fall through + break; case WorkerToMainMessageType.monoRegistered: case WorkerToMainMessageType.monoAttached: case WorkerToMainMessageType.enabledInterop: case WorkerToMainMessageType.monoUnRegistered: case WorkerToMainMessageType.updateInfo: - worker.info = Object.assign(worker.info!, message.info, {}); + // just worker.info updates above break; default: throw new Error(`Unhandled message from worker: ${message.monoCmd}`); @@ -138,6 +140,17 @@ export function thread_available(): Promise { return pendingWorkerLoad.promise; } +export async function mono_wasm_init_threads() { + if (!WasmEnableThreads) return; + monoThreadInfo.pthreadId = mono_wasm_pthread_ptr(); + monoThreadInfo.threadName = "UI Thread"; + monoThreadInfo.isUI = true; + monoThreadInfo.isRunning = true; + update_thread_info(); + await instantiateWasmPThreadWorkerPool(); + await mono_wasm_init_diagnostics(); +} + /// We call on the main thread this during startup to pre-allocate a pool of pthread workers. /// At this point asset resolution needs to be working (ie we loaded MonoConfig). /// This is used instead of the Emscripten PThread.initMainThread because we call it later. diff --git a/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts b/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts index 5516f1a0f813db..0529744570efda 100644 --- a/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts +++ b/src/mono/browser/runtime/pthreads/shared/emscripten-internals.ts @@ -3,10 +3,10 @@ import { Module } from "../../globals"; import { Thread } from "../browser"; -import { PThreadInfo, pthreadPtr } from "./types"; +import { PThreadInfo, PThreadPtr } from "./types"; /** @module emscripten-internals accessors to the functions in the emscripten PThreads library, including - * the low-level representations of {@linkcode pthreadPtr} thread info structs, etc. + * the low-level representations of {@linkcode PThreadPtr} thread info structs, etc. * Additionally, note that some of these functions are replaced by {@linkcode file://./emscripten-replacements.ts}. * These have a hard dependency on the version of Emscripten that we are using and may need to be kept in sync with * {@linkcode file://./../../../emsdk/upstream/emscripten/src/library_pthread.js} @@ -27,7 +27,7 @@ export interface PThreadLibrary { /// N.B. emscripten deletes the `pthread` property from the worker when it is not actively running a pthread export interface PThreadWorker extends Worker { - pthread_ptr: pthreadPtr; + pthread_ptr: PThreadPtr; loaded: boolean; // this info is updated via async messages from the worker, it could be stale info: PThreadInfo; @@ -35,12 +35,12 @@ export interface PThreadWorker extends Worker { } interface PThreadInfoMap { - [key: pthreadPtr]: PThreadWorker; + [key: number]: PThreadWorker; } -export function getWorker(pthreadPtr: pthreadPtr): PThreadWorker | undefined { - return getModulePThread().pthreads[pthreadPtr]; +export function getWorker(pthreadPtr: PThreadPtr): PThreadWorker | undefined { + return getModulePThread().pthreads[pthreadPtr as any]; } export function allocateUnusedWorker(): void { diff --git a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts index f0bbf80d6636b3..2f004c49369a62 100644 --- a/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts +++ b/src/mono/browser/runtime/pthreads/shared/emscripten-replacements.ts @@ -9,6 +9,7 @@ import { mono_wasm_pthread_on_pthread_created } from "../worker"; import { PThreadLibrary, PThreadWorker, getModulePThread, getUnusedWorkerPool } from "./emscripten-internals"; import { loaderHelpers, mono_assert } from "../../globals"; import { mono_log_warn } from "../../logging"; +import { PThreadPtrNull } from "./types"; /** @module emscripten-replacements Replacements for individual functions in the emscripten PThreads library. * These have a hard dependency on the version of Emscripten that we are using and may need to be kept in sync with @@ -46,7 +47,7 @@ export function replaceEmscriptenPThreadLibrary(modulePThread: PThreadLibrary): // we can't reuse the worker, because user code could leave the worker JS globals in a dirty state worker.info.isRunning = false; resolveThreadPromises(worker.pthread_ptr, undefined); - worker.info.pthreadId = 0; + worker.info.pthreadId = PThreadPtrNull; if (worker.thread?.port) { worker.thread.port.close(); } @@ -116,7 +117,7 @@ function allocateUnusedWorker(): PThreadWorker { getUnusedWorkerPool().push(worker); worker.loaded = false; worker.info = { - pthreadId: 0, + pthreadId: PThreadPtrNull, reuseCount: 0, updateCount: 0, threadPrefix: " - ", diff --git a/src/mono/browser/runtime/pthreads/shared/index.ts b/src/mono/browser/runtime/pthreads/shared/index.ts index d91efc5775173b..2e2b6a1dc71602 100644 --- a/src/mono/browser/runtime/pthreads/shared/index.ts +++ b/src/mono/browser/runtime/pthreads/shared/index.ts @@ -9,7 +9,7 @@ import { mono_log_debug, set_thread_prefix } from "../../logging"; import { bindings_init } from "../../startup"; import { forceDisposeProxies } from "../../gc-handles"; import { GCHandle, GCHandleNull, WorkerToMainMessageType, monoMessageSymbol } from "../../types/internal"; -import { MonoWorkerToMainMessage } from "./types"; +import { MonoWorkerToMainMessage, PThreadPtr, PThreadPtrNull } from "./types"; import { monoThreadInfo } from "../worker"; /// Messages sent on the dedicated mono channel between a pthread and the browser thread @@ -33,8 +33,8 @@ export function isMonoThreadMessage(x: unknown): x is MonoThreadMessage { export function mono_wasm_install_js_worker_interop(context_gc_handle: GCHandle): void { if (!WasmEnableThreads) return; bindings_init(); - if (!runtimeHelpers.proxy_context_gc_handle) { - runtimeHelpers.proxy_context_gc_handle = context_gc_handle; + if (!runtimeHelpers.proxyGCHandle) { + runtimeHelpers.proxyGCHandle = context_gc_handle; mono_log_debug("Installed JSSynchronizationContext"); } Module.runtimeKeepalivePush(); @@ -51,20 +51,20 @@ export function mono_wasm_install_js_worker_interop(context_gc_handle: GCHandle) export function mono_wasm_uninstall_js_worker_interop(): void { if (!WasmEnableThreads) return; mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "JS interop is not installed on this worker."); - mono_assert(runtimeHelpers.proxy_context_gc_handle, "JSSynchronizationContext is not installed on this worker."); + mono_assert(runtimeHelpers.proxyGCHandle, "JSSynchronizationContext is not installed on this worker."); forceDisposeProxies(true, runtimeHelpers.diagnosticTracing); Module.runtimeKeepalivePop(); - runtimeHelpers.proxy_context_gc_handle = GCHandleNull; + runtimeHelpers.proxyGCHandle = GCHandleNull; runtimeHelpers.mono_wasm_bindings_is_ready = false; update_thread_info(); } // this is just for Debug build of the runtime, making it easier to debug worker threads export function update_thread_info(): void { - const threadType = monoThreadInfo.isUI ? "main" - : !monoThreadInfo.isAttached ? "emsc" + const threadType = !monoThreadInfo.isRegistered ? "emsc" + : monoThreadInfo.isUI ? "-UI-" : monoThreadInfo.isTimer ? "timr" : monoThreadInfo.isLongRunning ? "long" : monoThreadInfo.isThreadPoolGate ? "gate" @@ -73,7 +73,9 @@ export function update_thread_info(): void { : monoThreadInfo.isExternalEventLoop ? "jsww" : monoThreadInfo.isBackground ? "back" : "norm"; - monoThreadInfo.threadPrefix = `0x${monoThreadInfo.pthreadId.toString(16).padStart(8, "0")}-${threadType}`; + const hexPtr = (monoThreadInfo.pthreadId as any).toString(16).padStart(8, "0"); + const hexPrefix = monoThreadInfo.isRegistered ? "0x" : "--"; + monoThreadInfo.threadPrefix = `${hexPrefix}${hexPtr}-${threadType}`; loaderHelpers.set_thread_prefix(monoThreadInfo.threadPrefix!); if (!loaderHelpers.config.forwardConsoleLogsToWS) { @@ -92,13 +94,13 @@ export function update_thread_info(): void { } } -export function mono_wasm_pthread_ptr(): number { - if (!WasmEnableThreads) return 0; +export function mono_wasm_pthread_ptr(): PThreadPtr { + if (!WasmEnableThreads) return PThreadPtrNull; return (Module)["_pthread_self"](); } -export function mono_wasm_main_thread_ptr(): number { - if (!WasmEnableThreads) return 0; +export function mono_wasm_main_thread_ptr(): PThreadPtr { + if (!WasmEnableThreads) return PThreadPtrNull; return (Module)["_emscripten_main_runtime_thread_id"](); } diff --git a/src/mono/browser/runtime/pthreads/shared/types.ts b/src/mono/browser/runtime/pthreads/shared/types.ts index 11716137a87e57..1ea8e0c65e5796 100644 --- a/src/mono/browser/runtime/pthreads/shared/types.ts +++ b/src/mono/browser/runtime/pthreads/shared/types.ts @@ -4,10 +4,13 @@ import type { WorkerToMainMessageType } from "../../types/internal"; /// pthread_t in C -export type pthreadPtr = number; +export type PThreadPtr = { + __brand: "PThreadPtr" +} +export const PThreadPtrNull: PThreadPtr = 0; export interface PThreadInfo { - pthreadId: pthreadPtr; + pthreadId: PThreadPtr; reuseCount: number, updateCount: number, diff --git a/src/mono/browser/runtime/pthreads/worker/index.ts b/src/mono/browser/runtime/pthreads/worker/index.ts index 0ea6e82de3d980..568bf510082928 100644 --- a/src/mono/browser/runtime/pthreads/worker/index.ts +++ b/src/mono/browser/runtime/pthreads/worker/index.ts @@ -7,7 +7,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { ENVIRONMENT_IS_PTHREAD, loaderHelpers, mono_assert } from "../../globals"; import { mono_wasm_pthread_ptr, postMessageToMain, update_thread_info } from "../shared"; -import { PThreadInfo } from "../shared/types"; +import { PThreadInfo, PThreadPtr, PThreadPtrNull } from "../shared/types"; import { WorkerToMainMessageType, is_nullish } from "../../types/internal"; import { MonoThreadMessage } from "../shared"; import { @@ -58,7 +58,7 @@ class WorkerSelf implements PThreadSelf { // in the worker, so this becomes non-null very early. export let pthread_self: PThreadSelf = null as any as PThreadSelf; export const monoThreadInfo: PThreadInfo = { - pthreadId: 0, + pthreadId: PThreadPtrNull, reuseCount: 0, updateCount: 0, threadPrefix: " - ", @@ -131,10 +131,12 @@ export function mono_wasm_pthread_on_pthread_created(): void { } /// Called in the worker thread (not main thread) from mono when a pthread becomes registered to the mono runtime. -export function mono_wasm_pthread_on_pthread_registered(pthread_id: number): void { +export function mono_wasm_pthread_on_pthread_registered(pthread_id: PThreadPtr): void { if (!WasmEnableThreads) return; try { mono_assert(monoThreadInfo !== null && monoThreadInfo.pthreadId == pthread_id, "expected monoThreadInfo to be set already when registering"); + monoThreadInfo.isRegistered = true; + update_thread_info(); postMessageToMain({ monoCmd: WorkerToMainMessageType.monoRegistered, info: monoThreadInfo, @@ -149,7 +151,7 @@ export function mono_wasm_pthread_on_pthread_registered(pthread_id: number): voi } /// Called in the worker thread (not main thread) from mono when a pthread becomes attached to the mono runtime. -export function mono_wasm_pthread_on_pthread_attached(pthread_id: number, thread_name: CharPtr, background_thread: number, threadpool_thread: number, external_eventloop: number, debugger_thread: number): void { +export function mono_wasm_pthread_on_pthread_attached(pthread_id: PThreadPtr, thread_name: CharPtr, background_thread: number, threadpool_thread: number, external_eventloop: number, debugger_thread: number): void { if (!WasmEnableThreads) return; try { mono_assert(monoThreadInfo !== null && monoThreadInfo.pthreadId == pthread_id, "expected monoThreadInfo to be set already when attaching"); @@ -192,12 +194,14 @@ export function mono_wasm_pthread_set_name(name: CharPtr): void { } /// Called in the worker thread (not main thread) from mono when a pthread becomes detached from the mono runtime. -export function mono_wasm_pthread_on_pthread_unregistered(pthread_id: number): void { +export function mono_wasm_pthread_on_pthread_unregistered(pthread_id: PThreadPtr): void { if (!WasmEnableThreads) return; try { mono_assert(pthread_id === monoThreadInfo.pthreadId, "expected pthread_id to match when un-registering"); postRunWorker(); monoThreadInfo.isAttached = false; + monoThreadInfo.isRegistered = false; + monoThreadInfo.threadName = "unregistered:(" + monoThreadInfo.threadName + ")"; update_thread_info(); postMessageToMain({ monoCmd: WorkerToMainMessageType.monoUnRegistered, diff --git a/src/mono/browser/runtime/run.ts b/src/mono/browser/runtime/run.ts index 0b8ab30725e25e..932d851973b752 100644 --- a/src/mono/browser/runtime/run.ts +++ b/src/mono/browser/runtime/run.ts @@ -11,6 +11,7 @@ import { mono_log_info } from "./logging"; import { assert_js_interop } from "./invoke-js"; import { assembly_load } from "./invoke-cs"; import { cancelThreads } from "./pthreads/browser"; +import { call_entry_point } from "./managed-exports"; /** * Possible signatures are described here https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/main-command-line @@ -63,7 +64,7 @@ export async function mono_run_main(main_assembly_name?: string, args?: string[] } const method = find_entry_point(main_assembly_name); - const res = await runtimeHelpers.javaScriptExports.call_entry_point(method, args); + const res = await call_entry_point(method, args); // one more timer loop before we return, so that any remaining queued calls could run await new Promise(resolve => globalThis.setTimeout(resolve, 0)); diff --git a/src/mono/browser/runtime/satelliteAssemblies.ts b/src/mono/browser/runtime/satelliteAssemblies.ts index 100af0696aae4d..4dcc17407b1e4e 100644 --- a/src/mono/browser/runtime/satelliteAssemblies.ts +++ b/src/mono/browser/runtime/satelliteAssemblies.ts @@ -1,7 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { loaderHelpers, runtimeHelpers } from "./globals"; +import { loaderHelpers } from "./globals"; +import { load_satellite_assembly } from "./managed-exports"; import { AssetEntry } from "./types"; export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise { @@ -30,6 +31,6 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise .reduce((previous, next) => previous.concat(next), new Array>()) .map(async bytesPromise => { const bytes = await bytesPromise; - runtimeHelpers.javaScriptExports.load_satellite_assembly(new Uint8Array(bytes)); + load_satellite_assembly(new Uint8Array(bytes)); })); } \ No newline at end of file diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index a1f6b95dd1814b..e8416dc30a4b4a 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -13,20 +13,19 @@ import { initialize_marshalers_to_cs } from "./marshal-to-cs"; import { initialize_marshalers_to_js } from "./marshal-to-js"; import { init_polyfills_async } from "./polyfills"; import { strings_init, utf8ToString } from "./strings"; -import { init_managed_exports } from "./managed-exports"; +import { init_managed_exports, install_main_synchronization_context } from "./managed-exports"; import { cwraps_internal } from "./exports-internal"; import { CharPtr, InstantiateWasmCallBack, InstantiateWasmSuccessCallback } from "./types/emscripten"; import { wait_for_all_assets } from "./assets"; -import { mono_wasm_init_diagnostics } from "./diagnostics"; import { replace_linker_placeholders } from "./exports-binding"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; import { interp_pgo_load_data, interp_pgo_save_data } from "./interp-pgo"; import { mono_log_debug, mono_log_error, mono_log_warn } from "./logging"; // threads -import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool } from "./pthreads/browser"; +import { preAllocatePThreadWorkerPool, mono_wasm_init_threads } from "./pthreads/browser"; import { currentWorkerThreadEvents, dotnetPthreadCreated, initWorkerThreadEvents, monoThreadInfo } from "./pthreads/worker"; -import { mono_wasm_main_thread_ptr, mono_wasm_pthread_ptr } from "./pthreads/shared"; +import { update_thread_info } from "./pthreads/shared"; import { jiterpreter_allocate_tables } from "./jiterpreter-support"; import { localHeapViewU8 } from "./memory"; import { assertNoProxies } from "./gc-handles"; @@ -240,22 +239,12 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { // signal this stage, this will allow pending assets to allocate memory runtimeHelpers.beforeOnRuntimeInitialized.promise_control.resolve(); - await wait_for_all_assets(); - + let threadsReady: Promise | undefined; if (WasmEnableThreads) { - await mono_wasm_init_threads(); - } - - // load runtime and apply environment settings (if necessary) - await start_runtime(); - - if (runtimeHelpers.config.interpreterPgo) { - await interp_pgo_load_data(); + threadsReady = mono_wasm_init_threads(); } - if (!ENVIRONMENT_IS_WORKER) { - Module.runtimeKeepalivePush(); - } + await wait_for_all_assets(); if (runtimeHelpers.config.virtualWorkingDirectory) { const FS = Module.FS; @@ -268,20 +257,33 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { FS.chdir(cwd); } - bindings_init(); - jiterpreter_allocate_tables(); + if (WasmEnableThreads && threadsReady) { + await threadsReady; + } + + if (runtimeHelpers.config.interpreterPgo) + setTimeout(maybeSaveInterpPgoTable, (runtimeHelpers.config.interpreterPgoSaveDelay || 15) * 1000); + + + Module.runtimeKeepalivePush(); + + // load runtime and apply environment settings (if necessary) + start_runtime(); + + if (runtimeHelpers.config.interpreterPgo) { + await interp_pgo_load_data(); + } + + if (!ENVIRONMENT_IS_WORKER) { + Module.runtimeKeepalivePush(); + } if (ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER) { Module.runtimeKeepalivePush(); } - runtimeHelpers.runtimeReady = true; runtimeList.registerRuntime(exportedRuntimeAPI); - if (WasmEnableThreads) { - runtimeHelpers.javaScriptExports.install_main_synchronization_context(); - } - if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready(); if (loaderHelpers.config.debugLevel !== 0 && loaderHelpers.config.cacheBootResources) { @@ -304,6 +306,7 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { await mono_wasm_after_user_runtime_initialized(); endMeasure(mark, MeasuredBlock.onRuntimeInitialized); } catch (err) { + Module.runtimeKeepalivePop(); mono_log_error("onRuntimeInitializedAsync() failed", err); loaderHelpers.mono_exit(1, err); throw err; @@ -344,9 +347,8 @@ export function postRunWorker() { if (!WasmEnableThreads) return; const mark = startMeasure(); try { - if (runtimeHelpers.proxy_context_gc_handle) { - const pthread_ptr = mono_wasm_pthread_ptr(); - mono_log_warn(`JSSynchronizationContext is still installed on worker 0x${pthread_ptr.toString(16)}.`); + if (runtimeHelpers.proxyGCHandle) { + mono_log_warn("JSSynchronizationContext is still installed on worker."); } else { assertNoProxies(); } @@ -362,19 +364,6 @@ export function postRunWorker() { } } -async function mono_wasm_init_threads() { - if (!WasmEnableThreads) return; - - const threadPrefix = `0x${mono_wasm_main_thread_ptr().toString(16)}-main`; - monoThreadInfo.threadPrefix = threadPrefix; - monoThreadInfo.threadName = "UI Thread"; - monoThreadInfo.isUI = true; - monoThreadInfo.isAttached = true; - loaderHelpers.set_thread_prefix(threadPrefix); - await instantiateWasmPThreadWorkerPool(); - await mono_wasm_init_diagnostics(); -} - function mono_wasm_pre_init_essential(isWorker: boolean): void { if (!isWorker) Module.addRunDependency("mono_wasm_pre_init_essential"); @@ -416,8 +405,6 @@ async function mono_wasm_pre_init_essential_async(): Promise { async function mono_wasm_after_user_runtime_initialized(): Promise { mono_log_debug("mono_wasm_after_user_runtime_initialized"); try { - mono_log_debug("Initializing mono runtime"); - if (Module.onDotnetReady) { try { await Module.onDotnetReady(); @@ -496,31 +483,50 @@ async function ensureUsedWasmFeatures() { } } -async function start_runtime() { - const mark = startMeasure(); +export function start_runtime() { + try { + const mark = startMeasure(); + mono_log_debug("Initializing mono runtime"); + for (const k in runtimeHelpers.config.environmentVariables) { + const v = runtimeHelpers.config.environmentVariables![k]; + if (typeof (v) === "string") + mono_wasm_setenv(k, v); + else + throw new Error(`Expected environment variable '${k}' to be a string but it was ${typeof v}: '${v}'`); + } + if (runtimeHelpers.config.runtimeOptions) + mono_wasm_set_runtime_options(runtimeHelpers.config.runtimeOptions); - for (const k in runtimeHelpers.config.environmentVariables) { - const v = runtimeHelpers.config.environmentVariables![k]; - if (typeof (v) === "string") - mono_wasm_setenv(k, v); - else - throw new Error(`Expected environment variable '${k}' to be a string but it was ${typeof v}: '${v}'`); - } - if (runtimeHelpers.config.runtimeOptions) - mono_wasm_set_runtime_options(runtimeHelpers.config.runtimeOptions); + if (runtimeHelpers.config.aotProfilerOptions) + mono_wasm_init_aot_profiler(runtimeHelpers.config.aotProfilerOptions); + + if (runtimeHelpers.config.browserProfilerOptions) + mono_wasm_init_browser_profiler(runtimeHelpers.config.browserProfilerOptions); + + mono_wasm_load_runtime(); - if (runtimeHelpers.config.aotProfilerOptions) - mono_wasm_init_aot_profiler(runtimeHelpers.config.aotProfilerOptions); + jiterpreter_allocate_tables(); + + bindings_init(); - if (runtimeHelpers.config.browserProfilerOptions) - mono_wasm_init_browser_profiler(runtimeHelpers.config.browserProfilerOptions); + runtimeHelpers.runtimeReady = true; - mono_wasm_load_runtime(); + if (WasmEnableThreads) { + monoThreadInfo.isAttached = true; + monoThreadInfo.isRegistered = true; + update_thread_info(); + runtimeHelpers.proxyGCHandle = install_main_synchronization_context(); + } - if (runtimeHelpers.config.interpreterPgo) - setTimeout(maybeSaveInterpPgoTable, (runtimeHelpers.config.interpreterPgoSaveDelay || 15) * 1000); + // get GCHandle of the ctx + runtimeHelpers.afterMonoStarted.promise_control.resolve(runtimeHelpers.proxyGCHandle); - endMeasure(mark, MeasuredBlock.startRuntime); + endMeasure(mark, MeasuredBlock.startRuntime); + } catch (err) { + mono_log_error("start_runtime() failed", err); + loaderHelpers.mono_exit(1, err); + throw err; + } } async function maybeSaveInterpPgoTable() { diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts index 0e382399d92d1e..e0f76ea3a546e8 100644 --- a/src/mono/browser/runtime/types/index.ts +++ b/src/mono/browser/runtime/types/index.ts @@ -316,7 +316,15 @@ export type SingleAssetBehaviors = /** * Typically blazor.boot.json */ - | "manifest"; + | "manifest" + /** + * The debugging symbols + */ + | "symbols" + /** + * Load segmentation rules file for Hybrid Globalization. + */ + | "segmentation-rules"; export type AssetBehaviors = SingleAssetBehaviors | /** @@ -347,14 +355,6 @@ export type AssetBehaviors = SingleAssetBehaviors | * The javascript module that came from nuget package . */ | "js-module-library-initializer" - /** - * The javascript module for threads. - */ - | "symbols" - /** - * Load segmentation rules file for Hybrid Globalization. - */ - | "segmentation-rules" export const enum GlobalizationMode { /** diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index a9f2f9aadcbfab..adcdf194a2f02b 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -56,6 +56,7 @@ export const MonoStringRefNull: MonoStringRef = 0; export const JSHandleDisposed: JSHandle = -1; export const JSHandleNull: JSHandle = 0; export const GCHandleNull: GCHandle = 0; +export const GCHandleInvalid: GCHandle = -1; export const VoidPtrNull: VoidPtr = 0; export const CharPtrNull: CharPtr = 0; export const NativePointerNull: NativePointer = 0; @@ -127,7 +128,6 @@ export type LoaderHelpers = { scriptUrl: string modulesUniqueQuery?: string preferredIcuAsset?: string | null, - invariantMode: boolean, actual_downloaded_assets_count: number, actual_instantiated_assets_count: number, @@ -194,13 +194,12 @@ export type RuntimeHelpers = { quit: Function, nativeExit: (code: number) => void, nativeAbort: (reason: any) => void, - javaScriptExports: JavaScriptExports, subtle: SubtleCrypto | null, updateMemoryViews: () => void getMemory(): WebAssembly.Memory, getWasmIndirectFunctionTable(): WebAssembly.Table, runtimeReady: boolean, - proxy_context_gc_handle: GCHandle, + proxyGCHandle: GCHandle | undefined, cspPolicy: boolean, allAssetsInMemory: PromiseAndController, @@ -210,6 +209,7 @@ export type RuntimeHelpers = { afterPreInit: PromiseAndController, afterPreRun: PromiseAndController, beforeOnRuntimeInitialized: PromiseAndController, + afterMonoStarted: PromiseAndController, afterOnRuntimeInitialized: PromiseAndController, afterPostRun: PromiseAndController, @@ -300,35 +300,6 @@ export function notThenable(x: T | PromiseLike): x is T { /// Primarily intended for debugging purposes. export type EventPipeSessionID = bigint; -// in all the exported internals methods, we use the same data structures for stack frame as normal full blow interop -// see src\libraries\System.Runtime.InteropServices.JavaScript\src\System\Runtime\InteropServices\JavaScript\Interop\JavaScriptExports.cs -export interface JavaScriptExports { - // the marshaled signature is: void ReleaseJSOwnedObjectByGCHandle(GCHandle gcHandle) - release_js_owned_object_by_gc_handle(gc_handle: GCHandle): void; - - // the marshaled signature is: void CompleteTask(GCHandle holder, Exception? exceptionResult, T? result) - complete_task(holder_gc_handle: GCHandle, isCanceling: boolean, error?: any, data?: any, res_converter?: MarshalerToCs): void; - - // the marshaled signature is: TRes? CallDelegate(GCHandle callback, T1? arg1, T2? arg2, T3? arg3) - call_delegate(callback_gc_handle: GCHandle, arg1_js: any, arg2_js: any, arg3_js: any, - res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs): any; - - // the marshaled signature is: Task? CallEntrypoint(MonoMethod* entrypointPtr, string[] args) - call_entry_point(entry_point: MonoMethod, args?: string[]): Promise; - - // the marshaled signature is: void InstallMainSynchronizationContext() - install_main_synchronization_context(): void; - - // the marshaled signature is: string GetManagedStackTrace(GCHandle exception) - get_managed_stack_trace(exception_gc_handle: GCHandle): string | null - - // the marshaled signature is: void LoadSatelliteAssembly(byte[] dll) - load_satellite_assembly(dll: Uint8Array): void; - - // the marshaled signature is: void LoadLazyAssembly(byte[] dll, byte[] pdb) - load_lazy_assembly(dll: Uint8Array, pdb: Uint8Array | null): void; -} - export type MarshalerToJs = (arg: JSMarshalerArgument, element_type?: MarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) => any; export type MarshalerToCs = (arg: JSMarshalerArgument, value: any, element_type?: MarshalerType, res_converter?: MarshalerToCs, arg1_converter?: MarshalerToJs, arg2_converter?: MarshalerToJs, arg3_converter?: MarshalerToJs) => void; export type BoundMarshalerToJs = (args: JSMarshalerArguments) => any;