diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index 01888e365ee0cb..000dd103d21890 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Reflection; using System.Runtime.CompilerServices; internal static partial class Interop @@ -25,8 +26,6 @@ internal static unsafe partial class Runtime public static extern void InvokeJSFunctionSend(nint targetNativeTID, nint functionHandle, nint data); #endif - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern unsafe void BindCSFunction(in string fully_qualified_name, int signature_hash, void* signature, out int is_exception, out object result); [MethodImpl(MethodImplOptions.InternalCall)] public static extern void ResolveOrRejectPromise(nint data); #if FEATURE_WASM_MANAGED_THREADS @@ -65,7 +64,7 @@ internal static unsafe partial class Runtime [MethodImpl(MethodImplOptions.InternalCall)] public static extern void CancelPromise(nint gcHandle); #endif - - + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void SetEntryAssembly(Assembly assembly, int entryPointMetadataToken); } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/Resources/Strings.resx b/src/libraries/System.Runtime.InteropServices.JavaScript/src/Resources/Strings.resx index 9cb71cc66223ad..e77e419997996d 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/Resources/Strings.resx +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/Resources/Strings.resx @@ -138,8 +138,8 @@ Managed entrypoint handle is not set. - - Cannot resolve managed entrypoint handle. + + Cannot resolve managed entrypoint {0} in assembly {1}. Return type '{0}' from main method in not supported. 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 f4a6efb3c333f3..9ec4a2400cd211 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 @@ -1,14 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using static System.Runtime.InteropServices.JavaScript.JSHostImplementation; namespace System.Runtime.InteropServices.JavaScript { @@ -18,13 +15,14 @@ namespace System.Runtime.InteropServices.JavaScript internal static unsafe partial class JavaScriptExports { // the marshaled signature is: - // Task? CallEntrypoint(MonoMethod* entrypointPtr, string[] args) + // Task? CallEntrypoint(string mainAssemblyName, string[] args, bool waitForDebugger) public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer) { ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() ref JSMarshalerArgument arg_result = ref arguments_buffer[1]; // 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 + ref JSMarshalerArgument arg_3 = ref arguments_buffer[4]; // initialized and set by caller try { #if FEATURE_WASM_MANAGED_THREADS @@ -32,62 +30,12 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer) arg_exc.AssertCurrentThreadContext(); #endif - arg_1.ToManaged(out IntPtr entrypointPtr); - if (entrypointPtr == IntPtr.Zero) - { - throw new MissingMethodException(SR.MissingManagedEntrypointHandle); - } + arg_1.ToManaged(out string? mainAssemblyName); + arg_2.ToManaged(out string?[]? args); + arg_3.ToManaged(out bool waitForDebugger); - RuntimeMethodHandle methodHandle = GetMethodHandleFromIntPtr(entrypointPtr); - // this would not work for generic types. But Main() could not be generic, so we are fine. - MethodInfo? method = MethodBase.GetMethodFromHandle(methodHandle) as MethodInfo; - if (method == null) - { - throw new InvalidOperationException(SR.CannotResolveManagedEntrypointHandle); - } + Task? result = JSHostImplementation.CallEntrypoint(mainAssemblyName, args, waitForDebugger); - arg_2.ToManaged(out string?[]? args); - object[] argsToPass = System.Array.Empty(); - Task? result = null; - var parameterInfos = method.GetParameters(); - if (parameterInfos.Length > 0 && parameterInfos[0].ParameterType == typeof(string[])) - { - argsToPass = new object[] { args ?? System.Array.Empty() }; - } - if (method.ReturnType == typeof(void)) - { - method.Invoke(null, argsToPass); - } - else if (method.ReturnType == typeof(int)) - { - int intResult = (int)method.Invoke(null, argsToPass)!; - result = Task.FromResult(intResult); - } - else if (method.ReturnType == typeof(Task)) - { - Task methodResult = (Task)method.Invoke(null, argsToPass)!; - TaskCompletionSource tcs = new TaskCompletionSource(); - result = tcs.Task; - methodResult.ContinueWith((t) => - { - if (t.IsFaulted) - { - tcs.SetException(t.Exception!); - } - else - { - tcs.SetResult(0); - } - }, TaskScheduler.Default); - } - else if (method.ReturnType == typeof(Task)) - { - result = (Task)method.Invoke(null, argsToPass)!; - } - else - { - throw new InvalidOperationException(SR.Format(SR.ReturnTypeNotSupportedForMain, method.ReturnType.FullName)); - } arg_result.ToJS(result, (ref JSMarshalerArgument arg, int value) => { arg.ToJS(value); @@ -95,10 +43,7 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer) } catch (Exception ex) { - if (ex is TargetInvocationException refEx && refEx.InnerException != null) - ex = refEx.InnerException; - - arg_exc.ToJS(ex); + Environment.FailFast("CallEntrypoint: Unexpected synchronous failure. " + ex); } } @@ -163,7 +108,7 @@ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments } catch (Exception ex) { - arg_exc.ToJS(ex); + Environment.FailFast("ReleaseJSOwnedObjectByGCHandle: Unexpected synchronous failure. " + ex); } } @@ -188,7 +133,7 @@ public static void CallDelegate(JSMarshalerArgument* arguments_buffer) #endif GCHandle callback_gc_handle = (GCHandle)arg_1.slot.GCHandle; - if (callback_gc_handle.Target is ToManagedCallback callback) + if (callback_gc_handle.Target is JSHostImplementation.ToManagedCallback callback) { // arg_2, arg_3, arg_4, arg_res are processed by the callback callback(arguments_buffer); @@ -219,7 +164,7 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer) // when we arrive here, we are on the thread which owns the proxies var ctx = arg_exc.AssertCurrentThreadContext(); var holder = ctx.GetPromiseHolder(arg_1.slot.GCHandle); - ToManagedCallback callback; + JSHostImplementation.ToManagedCallback callback; #if FEATURE_WASM_MANAGED_THREADS lock (ctx) @@ -260,7 +205,7 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer) } catch (Exception ex) { - arg_exc.ToJS(ex); + Environment.FailFast("CompleteTask: Unexpected synchronous failure. " + ex); } } @@ -318,6 +263,30 @@ public static void InstallMainSynchronizationContext(JSMarshalerArgument* argume #endif + // the marshaled signature is: + // Task BindAssemblyExports(string assemblyName) + public static void BindAssemblyExports(JSMarshalerArgument* arguments_buffer) + { + ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() + ref JSMarshalerArgument arg_result = ref arguments_buffer[1]; // used as return value + ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller + try + { + string? assemblyName; + // when we arrive here, we are on the thread which owns the proxies + arg_exc.AssertCurrentThreadContext(); + arg_1.ToManaged(out assemblyName); + + var result = JSHostImplementation.BindAssemblyExports(assemblyName); + + arg_result.ToJS(result); + } + catch (Exception ex) + { + Environment.FailFast("BindAssemblyExports: Unexpected synchronous failure. " + ex); + } + } + [MethodImpl(MethodImplOptions.NoInlining)] // profiler needs to find it executed under this name public static void StopProfile() { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs index 8c5c3782a2f379..2e41abd76c5c1e 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs @@ -45,6 +45,10 @@ internal static unsafe partial class JavaScriptImports [JSImport("INTERNAL.dynamic_import")] // TODO: the continuation should be running on deputy or TP in MT public static partial Task DynamicImport(string moduleName, string moduleUrl); + + [JSImport("INTERNAL.mono_wasm_bind_cs_function")] + public static partial void BindCSFunction(IntPtr monoMethod, string assemblyName, string namespaceName, string shortClassName, string methodName, int signatureHash, IntPtr signature); + #if FEATURE_WASM_MANAGED_THREADS [JSImport("INTERNAL.thread_available")] // TODO: the continuation should be running on deputy or TP in MT 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 acbd4a92ef9042..ae432cafc323cc 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 @@ -191,10 +191,11 @@ public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, i { if (RuntimeInformation.OSArchitecture != Architecture.Wasm) throw new PlatformNotSupportedException(); -#if FEATURE_WASM_MANAGED_THREADS - JSProxyContext.AssertIsInteropThread(); -#endif - return BindManagedFunctionImpl(fullyQualifiedName, signatureHash, signatures); + + // this could be called by assembly module initializer from Net7 code-gen + // on wrong thread, in which case we will bind it to UI thread + + return JSHostImplementation.BindManagedFunction(fullyQualifiedName, signatureHash, signatures); } #if !DEBUG @@ -407,21 +408,6 @@ internal static unsafe JSFunctionBinding BindJSImportImpl(string functionName, s return signature; } - internal static unsafe JSFunctionBinding BindManagedFunctionImpl(string fullyQualifiedName, int signatureHash, ReadOnlySpan signatures) - { - var signature = JSHostImplementation.GetMethodSignature(signatures, null, null); - - Interop.Runtime.BindCSFunction(fullyQualifiedName, signatureHash, signature.Header, out int isException, out object exceptionMessage); - if (isException != 0) - { - throw new JSException((string)exceptionMessage); - } - - JSHostImplementation.FreeMethodSignatureBuffer(signature); - - return signature; - } - #if !DEBUG [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs index 3035781eb730f1..7268951b68828b 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs @@ -33,14 +33,6 @@ public static bool GetTaskResultDynamic(Task task, out object? value) throw new InvalidOperationException(); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr) - { - var temp = new IntPtrAndHandle { ptr = ptr }; - return temp.methodHandle; - } - /// /// Gets the MethodInfo for the Task{T}.Result property getter. /// @@ -208,6 +200,176 @@ public static void LoadSatelliteAssembly(byte[] dllBytes) AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes)); } + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Dynamic access from JavaScript")] + [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Dynamic access from JavaScript")] + public static Task? CallEntrypoint(string? assemblyName, string?[]? args, bool waitForDebugger) + { + try + { + if (string.IsNullOrEmpty(assemblyName)) + { + throw new MissingMethodException(SR.MissingManagedEntrypointHandle); + } + if (!assemblyName.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase)) + { + assemblyName += ".dll"; + } + Assembly mainAssembly = Assembly.LoadFrom(assemblyName); + + MethodInfo? method = mainAssembly.EntryPoint; + if (method == null) + { + throw new InvalidOperationException(string.Format(SR.CannotResolveManagedEntrypoint, "Main", assemblyName)); + } + if (method.IsSpecialName) + { + // we are looking for the original async method, rather than for the compiler generated wrapper like
+ // because we need to yield to browser event loop + var type = method.DeclaringType!; + var name = method.Name; + var asyncName = name + "$"; + method = type.GetMethod(asyncName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (method == null) + { + asyncName = name.Substring(1, name.Length - 2); + method = type.GetMethod(asyncName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + } + if (method == null) + { + throw new InvalidOperationException(string.Format(SR.CannotResolveManagedEntrypoint, asyncName, assemblyName)); + } + } + + Interop.Runtime.SetEntryAssembly(mainAssembly, waitForDebugger ? method.MetadataToken : 0); + + object[] argsToPass = System.Array.Empty(); + Task? result = null; + var parameterInfos = method.GetParameters(); + if (parameterInfos.Length > 0 && parameterInfos[0].ParameterType == typeof(string[])) + { + argsToPass = new object[] { args ?? System.Array.Empty() }; + } + if (method.ReturnType == typeof(void)) + { + method.Invoke(null, argsToPass); + } + else if (method.ReturnType == typeof(int)) + { + int intResult = (int)method.Invoke(null, argsToPass)!; + result = Task.FromResult(intResult); + } + else if (method.ReturnType == typeof(Task)) + { + Task methodResult = (Task)method.Invoke(null, argsToPass)!; + TaskCompletionSource tcs = new TaskCompletionSource(); + result = tcs.Task; + methodResult.ContinueWith((t) => + { + if (t.IsFaulted) + { + tcs.SetException(t.Exception!); + } + else + { + tcs.SetResult(0); + } + }, TaskScheduler.Default); + } + else if (method.ReturnType == typeof(Task)) + { + result = (Task)method.Invoke(null, argsToPass)!; + } + else + { + throw new InvalidOperationException(SR.Format(SR.ReturnTypeNotSupportedForMain, method.ReturnType.FullName)); + } + return result; + } + catch (Exception ex) + { + if (ex is TargetInvocationException refEx && refEx.InnerException != null) + ex = refEx.InnerException; + return Task.FromException(ex); + } + } + + private static string GeneratedInitializerClassName = "System.Runtime.InteropServices.JavaScript.__GeneratedInitializer"; + private static string GeneratedInitializerMethodName = "__Register_"; + + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Dynamic access from JavaScript")] + [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Dynamic access from JavaScript")] + public static Task BindAssemblyExports(string? assemblyName) + { + try + { + if (string.IsNullOrEmpty(assemblyName)) + { + throw new MissingMethodException("Missing assembly name"); + } + if (!assemblyName.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase)) + { + assemblyName += ".dll"; + } + + Assembly assembly = Assembly.LoadFrom(assemblyName); + Type? type = assembly.GetType(GeneratedInitializerClassName); + if (type == null) + { + foreach (var module in assembly.Modules) + { + RuntimeHelpers.RunModuleConstructor(module.ModuleHandle); + } + } + else + { + MethodInfo? methodInfo = type.GetMethod(GeneratedInitializerMethodName, BindingFlags.NonPublic | BindingFlags.Static); + methodInfo?.Invoke(null, []); + } + + return Task.CompletedTask; + } + catch (Exception ex) + { + if (ex is TargetInvocationException refEx && refEx.InnerException != null) + ex = refEx.InnerException; + return Task.FromException(ex); + } + } + + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "TODO https://github.com/dotnet/runtime/issues/98366")] + [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "TODO https://github.com/dotnet/runtime/issues/98366")] + public static unsafe JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan signatures) + { + if (string.IsNullOrEmpty(fullyQualifiedName)) + { + throw new ArgumentNullException(nameof(fullyQualifiedName)); + } + + var signature = GetMethodSignature(signatures, null, null); + var (assemblyName, className, nameSpace, shortClassName, methodName) = ParseFQN(fullyQualifiedName); + + Assembly assembly = Assembly.LoadFrom(assemblyName + ".dll"); + Type? type = assembly.GetType(className); + if (type == null) + { + throw new InvalidOperationException("Class not found " + className); + } + var wrapper_name = $"__Wrapper_{methodName}_{signatureHash}"; + var methodInfo = type.GetMethod(wrapper_name, BindingFlags.NonPublic | BindingFlags.Static); + if (methodInfo == null) + { + throw new InvalidOperationException("Method not found " + wrapper_name); + } + + var monoMethod = GetIntPtrFromMethodHandle(methodInfo.MethodHandle); + + JavaScriptImports.BindCSFunction(monoMethod, assemblyName, nameSpace, shortClassName, methodName, signatureHash, (IntPtr)signature.Header); + + FreeMethodSignatureBuffer(signature); + + return signature; + } + #if FEATURE_WASM_MANAGED_THREADS [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "external_eventloop")] private static extern ref bool GetThreadExternalEventloop(Thread @this); @@ -218,5 +380,37 @@ public static void SetHasExternalEventLoop(Thread thread) } #endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr GetIntPtrFromMethodHandle(RuntimeMethodHandle methodHandle) + { + var temp = new IntPtrAndHandle { methodHandle = methodHandle }; + return temp.ptr; + } + + + public static (string assemblyName, string className, string nameSpace, string shortClassName, string methodName) ParseFQN(string fqn) + { + var assembly = fqn.Substring(fqn.IndexOf('[') + 1, fqn.IndexOf(']') - 1).Trim(); + fqn = fqn.Substring(fqn.IndexOf(']') + 1).Trim(); + var methodName = fqn.Substring(fqn.IndexOf(':') + 1); + var className = fqn.Substring(0, fqn.IndexOf(':')).Trim(); + + var nameSpace = ""; + var shortClassName = className; + var idx = fqn.LastIndexOf("."); + if (idx != -1) + { + nameSpace = fqn.Substring(0, idx); + shortClassName = className.Substring(idx + 1); + } + + if (string.IsNullOrEmpty(assembly)) + throw new InvalidOperationException("No assembly name specified " + fqn); + if (string.IsNullOrEmpty(className)) + throw new InvalidOperationException("No class name specified " + fqn); + if (string.IsNullOrEmpty(methodName)) + throw new InvalidOperationException("No method name specified " + fqn); + return (assembly, className, nameSpace, shortClassName, methodName); + } } } diff --git a/src/mono/browser/runtime/corebindings.c b/src/mono/browser/runtime/corebindings.c index 2814b0da915d06..68e8c6c7097660 100644 --- a/src/mono/browser/runtime/corebindings.c +++ b/src/mono/browser/runtime/corebindings.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "wasm-config.h" @@ -22,9 +23,12 @@ extern void mono_wasm_release_cs_owned_object (int js_handle); extern void mono_wasm_resolve_or_reject_promise (void *data); extern void mono_wasm_cancel_promise (int task_holder_gc_handle); extern void mono_wasm_console_clear (); +extern void mono_wasm_set_entrypoint_breakpoint (int entry_point_metadata_token); typedef void (*background_job_cb)(void); +void mono_wasm_set_entry_assembly (MonoReflectionAssembly* ref_assembly, int entry_point_metadata_token); + #ifndef DISABLE_THREADS void mono_wasm_release_cs_owned_object_post (pthread_t target_tid, int js_handle); void mono_wasm_resolve_or_reject_promise_post (pthread_t target_tid, void *data); @@ -32,7 +36,6 @@ void mono_wasm_cancel_promise_post (pthread_t target_tid, int task_holder_gc_han extern void mono_wasm_install_js_worker_interop (int context_gc_handle); extern void mono_wasm_uninstall_js_worker_interop (); -extern void mono_wasm_bind_cs_function (MonoString **fully_qualified_name, int signature_hash, void* signatures, int *is_exception, MonoObject **result); extern void mono_wasm_invoke_import_async (void* args, void* signature); void mono_wasm_invoke_import_async_post (pthread_t target_tid, void* args, void* signature); extern void mono_wasm_invoke_import_sync (void* args, void* signature); @@ -43,7 +46,6 @@ extern void mono_threads_wasm_async_run_in_target_thread_vi (pthread_t target_th extern void mono_threads_wasm_async_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2); extern void mono_threads_wasm_sync_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2); #else -extern void mono_wasm_bind_cs_function (MonoString **fully_qualified_name, int signature_hash, void* signatures, int *is_exception, MonoObject **result); extern void mono_wasm_bind_js_import (void *signature, int *is_exception, MonoObject **result); extern void mono_wasm_invoke_js_import (int function_handle, void *args); extern void mono_wasm_invoke_js_function (int function_js_handle, void *args); @@ -75,7 +77,6 @@ void bindings_initialize_internals (void) mono_add_internal_call ("Interop/Runtime::ResolveOrRejectPromisePost", mono_wasm_resolve_or_reject_promise_post); mono_add_internal_call ("Interop/Runtime::InstallWebWorkerInterop", mono_wasm_install_js_worker_interop); mono_add_internal_call ("Interop/Runtime::UninstallWebWorkerInterop", mono_wasm_uninstall_js_worker_interop); - mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function); mono_add_internal_call ("Interop/Runtime::InvokeJSImportSync", mono_wasm_invoke_import_sync); mono_add_internal_call ("Interop/Runtime::InvokeJSImportSyncSend", mono_wasm_invoke_import_sync_send); mono_add_internal_call ("Interop/Runtime::InvokeJSImportAsyncPost", mono_wasm_invoke_import_async_post); @@ -83,14 +84,15 @@ void bindings_initialize_internals (void) mono_add_internal_call ("Interop/Runtime::InvokeJSFunctionSend", mono_wasm_invoke_js_function_send); mono_add_internal_call ("Interop/Runtime::CancelPromise", mono_wasm_cancel_promise); mono_add_internal_call ("Interop/Runtime::CancelPromisePost", mono_wasm_cancel_promise_post); + mono_add_internal_call ("Interop/Runtime::SetEntryAssembly", mono_wasm_set_entry_assembly); #else mono_add_internal_call ("Interop/Runtime::ReleaseCSOwnedObject", mono_wasm_release_cs_owned_object); mono_add_internal_call ("Interop/Runtime::ResolveOrRejectPromise", mono_wasm_resolve_or_reject_promise); - mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function); mono_add_internal_call ("Interop/Runtime::BindJSImport", mono_wasm_bind_js_import); mono_add_internal_call ("Interop/Runtime::InvokeJSImport", mono_wasm_invoke_js_import); mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_js_function); mono_add_internal_call ("Interop/Runtime::CancelPromise", mono_wasm_cancel_promise); + mono_add_internal_call ("Interop/Runtime::SetEntryAssembly", mono_wasm_set_entry_assembly); #endif /* DISABLE_THREADS */ mono_add_internal_call ("Interop/JsGlobalization::ChangeCaseInvariant", mono_wasm_change_case_invariant); @@ -106,6 +108,16 @@ void bindings_initialize_internals (void) mono_add_internal_call ("System.ConsolePal::Clear", mono_wasm_console_clear); } +void mono_wasm_set_entry_assembly (MonoReflectionAssembly* ref_assembly, int entry_point_metadata_token) +{ + MonoAssembly *assembly = mono_reflection_assembly_get_assembly (ref_assembly); + mono_domain_ensure_entry_assembly (mono_get_root_domain (), assembly); + if (entry_point_metadata_token != 0) + { + mono_wasm_set_entrypoint_breakpoint (entry_point_metadata_token); + } +} + #ifndef DISABLE_THREADS void mono_wasm_release_cs_owned_object_post (pthread_t target_tid, int js_handle) diff --git a/src/mono/browser/runtime/cwraps.ts b/src/mono/browser/runtime/cwraps.ts index 7c38a34ee4e852..65e245585a671b 100644 --- a/src/mono/browser/runtime/cwraps.ts +++ b/src/mono/browser/runtime/cwraps.ts @@ -48,12 +48,9 @@ const fn_signatures: SigLine[] = [ [true, "mono_wasm_assembly_load", "number", ["string"]], [true, "mono_wasm_assembly_find_class", "number", ["number", "string", "string"]], - [true, "mono_wasm_runtime_run_module_cctor", "void", ["number"]], [true, "mono_wasm_assembly_find_method", "number", ["number", "string", "number"]], - [false, "mono_wasm_invoke_method_ref", "void", ["number", "number", "number", "number", "number"]], [true, "mono_wasm_string_from_utf16_ref", "void", ["number", "number", "number"]], [true, "mono_wasm_intern_string_ref", "void", ["number"]], - [true, "mono_wasm_assembly_get_entry_point", "number", ["number", "number"]], [false, "mono_wasm_exit", "void", ["number"]], [false, "mono_wasm_abort", "void", []], @@ -64,7 +61,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", "number", ["number", "number", "number"]], + [false, "mono_wasm_invoke_method", "void", ["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"]], @@ -171,9 +168,7 @@ export interface t_Cwraps { mono_wasm_assembly_load(name: string): MonoAssembly; mono_wasm_assembly_find_class(assembly: MonoAssembly, namespace: string, name: string): MonoClass; mono_wasm_assembly_find_method(klass: MonoClass, name: string, args: number): MonoMethod; - mono_wasm_invoke_method_ref(method: MonoMethod, this_arg: MonoObjectRef, params: VoidPtr, out_exc: MonoObjectRef, out_result: MonoObjectRef): void; mono_wasm_string_from_utf16_ref(str: CharPtr, len: number, result: MonoObjectRef): void; - mono_wasm_assembly_get_entry_point(assembly: MonoAssembly, idx: number): MonoMethod; mono_wasm_intern_string_ref(strRef: MonoStringRef): void; mono_wasm_exit(exit_code: number): void; @@ -181,14 +176,13 @@ 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(method: MonoMethod, args: JSMarshalerArguments, fail: MonoStringRef): number; + mono_wasm_invoke_method(method: MonoMethod, args: JSMarshalerArguments): void; 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; mono_wasm_u52_to_f64(source: VoidPtr, error: Int32Ptr): number; mono_wasm_f64_to_i52(destination: VoidPtr, value: number): I52Error; mono_wasm_f64_to_u52(destination: VoidPtr, value: number): I52Error; - mono_wasm_runtime_run_module_cctor(assembly: MonoAssembly): void; mono_wasm_method_get_name(method: MonoMethod): CharPtr; mono_wasm_method_get_full_name(method: MonoMethod): CharPtr; mono_wasm_gc_lock(): void; diff --git a/src/mono/browser/runtime/debug.ts b/src/mono/browser/runtime/debug.ts index 76562bc8f24930..acc467e4b6d3e1 100644 --- a/src/mono/browser/runtime/debug.ts +++ b/src/mono/browser/runtime/debug.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import BuildConfiguration from "consts:configuration"; -import { INTERNAL, Module, runtimeHelpers } from "./globals"; +import { INTERNAL, Module, loaderHelpers, runtimeHelpers } from "./globals"; import { toBase64StringImpl } from "./base64"; import cwraps from "./cwraps"; import { VoidPtr, CharPtr } from "./types/emscripten"; @@ -158,9 +158,9 @@ export function mono_wasm_debugger_attached(): void { cwraps.mono_wasm_set_is_debugger_attached(true); } -export function mono_wasm_set_entrypoint_breakpoint(assembly_name: CharPtr, entrypoint_method_token: number): void { +export function mono_wasm_set_entrypoint_breakpoint(entrypoint_method_token: number): void { //keep these assignments, these values are used by BrowserDebugProxy - _assembly_name_str = utf8ToString(assembly_name).concat(".dll"); + _assembly_name_str = loaderHelpers.config.mainAssemblyName + ".dll"; _entrypoint_method_token = entrypoint_method_token; //keep this console.assert, otherwise optimization will remove the assignments // eslint-disable-next-line no-console diff --git a/src/mono/browser/runtime/driver.c b/src/mono/browser/runtime/driver.c index 215eaca4dfe85e..3f47245a71efa6 100644 --- a/src/mono/browser/runtime/driver.c +++ b/src/mono/browser/runtime/driver.c @@ -227,92 +227,30 @@ mono_wasm_load_runtime (int debug_level) bindings_initialize_internals(); } -EMSCRIPTEN_KEEPALIVE int -mono_wasm_invoke_method (MonoMethod *method, void* args, MonoString **out_exc) +EMSCRIPTEN_KEEPALIVE void +mono_wasm_invoke_method (MonoMethod *method, void* args) { PVOLATILE(MonoObject) temp_exc = NULL; void *invoke_args[1] = { args }; - int is_err = 0; MONO_ENTER_GC_UNSAFE; 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` - if (temp_exc && out_exc) { + // so, if that happens, we should abort the runtime + if (temp_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) -{ - MonoImage *image; - MonoMethod *method; - - MONO_ENTER_GC_UNSAFE; - image = mono_assembly_get_image (assembly); - uint32_t entry = mono_image_get_entry_point (image); - if (!entry) - goto end; - - mono_domain_ensure_entry_assembly (root_domain, assembly); - method = mono_get_method (image, entry, NULL); - - /* - * If the entry point looks like a compiler generated wrapper around - * an async method in the form "" then try to look up the async methods - * "$" and "Name" it could be wrapping. We do this because the generated - * sync wrapper will call task.GetAwaiter().GetResult() when we actually want - * to yield to the host runtime. - */ - if (mono_method_get_flags (method, NULL) & 0x0800 /* METHOD_ATTRIBUTE_SPECIAL_NAME */) { - const char *name = mono_method_get_name (method); - int name_length = strlen (name); - - if ((*name != '<') || (name [name_length - 1] != '>')) - goto end; - - MonoClass *klass = mono_method_get_class (method); - assert(klass); - char *async_name = malloc (name_length + 2); - snprintf (async_name, name_length + 2, "%s$", name); - - // look for "$" - MonoMethodSignature *sig = mono_method_get_signature (method, image, mono_method_get_token (method)); - MonoMethod *async_method = mono_class_get_method_from_name (klass, async_name, mono_signature_get_param_count (sig)); - if (async_method != NULL) { - free (async_name); - method = async_method; - goto end; + store_volatile((MonoObject**)temp_exc, (MonoObject*)mono_object_to_string ((MonoObject*)temp_exc, (MonoObject **)&exc2)); + if (exc2) { + mono_wasm_trace_logger ("jsinterop", "critical", "mono_wasm_invoke_method unexpected double fault", 1, NULL); + } else { + mono_wasm_trace_logger ("jsinterop", "critical", mono_string_to_utf8((MonoString*)temp_exc), 1, NULL); } - - // look for "Name" by trimming the first and last character of "" - async_name [name_length - 1] = '\0'; - async_method = mono_class_get_method_from_name (klass, async_name + 1, mono_signature_get_param_count (sig)); - - free (async_name); - if (async_method != NULL) - method = async_method; + abort (); } - - end: MONO_EXIT_GC_UNSAFE; - if (auto_insert_breakpoint) - { - MonoAssemblyName *aname = mono_assembly_get_name (assembly); - const char *name = mono_assembly_name_get_name (aname); - if (name != NULL) - mono_wasm_set_entrypoint_breakpoint(name, mono_method_get_token (method)); - } - return method; } EMSCRIPTEN_KEEPALIVE void diff --git a/src/mono/browser/runtime/exports-binding.ts b/src/mono/browser/runtime/exports-binding.ts index cb286789879b67..17aadf3780e431 100644 --- a/src/mono/browser/runtime/exports-binding.ts +++ b/src/mono/browser/runtime/exports-binding.ts @@ -5,7 +5,6 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint, mono_wasm_fire_debugger_agent_message_with_data, mono_wasm_fire_debugger_agent_message_with_data_to_pause } from "./debug"; import { mono_wasm_release_cs_owned_object } from "./gc-handles"; -import { mono_wasm_bind_cs_function } from "./invoke-cs"; import { mono_wasm_bind_js_import, mono_wasm_invoke_js_function, mono_wasm_invoke_import_async, mono_wasm_invoke_import_sync, mono_wasm_invoke_js_import } from "./invoke-js"; import { mono_interp_tier_prepare_jiterpreter, mono_jiterp_free_method_data_js } from "./jiterpreter"; import { mono_interp_jit_wasm_entry_trampoline, mono_interp_record_interp_entry } from "./jiterpreter-interp-entry"; @@ -92,7 +91,6 @@ export const mono_wasm_imports = [ mono_wasm_bind_js_import, mono_wasm_invoke_js_function, mono_wasm_invoke_js_import, - mono_wasm_bind_cs_function, mono_wasm_resolve_or_reject_promise, mono_wasm_cancel_promise, mono_wasm_change_case_invariant, diff --git a/src/mono/browser/runtime/exports-internal.ts b/src/mono/browser/runtime/exports-internal.ts index f431298cb1f5af..5fe5773f975777 100644 --- a/src/mono/browser/runtime/exports-internal.ts +++ b/src/mono/browser/runtime/exports-internal.ts @@ -20,6 +20,7 @@ import { mono_wasm_get_func_id_to_name_mappings } from "./logging"; import { MonoObject, MonoObjectNull } from "./types/internal"; import { monoStringToStringUnsafe } from "./strings"; import { thread_available } from "./pthreads/browser"; +import { mono_wasm_bind_cs_function } from "./invoke-cs"; export function export_internal(): any { return { @@ -57,6 +58,7 @@ export function export_internal(): any { get_dotnet_instance: () => exportedRuntimeAPI, dynamic_import, thread_available, + mono_wasm_bind_cs_function, // BrowserWebSocket ws_wasm_create, diff --git a/src/mono/browser/runtime/gc-lock.ts b/src/mono/browser/runtime/gc-lock.ts index 876e6a2eb58693..ea373eecc0447d 100644 --- a/src/mono/browser/runtime/gc-lock.ts +++ b/src/mono/browser/runtime/gc-lock.ts @@ -1,11 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + import WasmEnableThreads from "consts:wasmEnableThreads"; import { ENVIRONMENT_IS_PTHREAD } from "./globals"; import cwraps from "./cwraps"; -let locked = false; +export let gc_locked = false; export function mono_wasm_gc_lock(): void { - if (locked) { + if (gc_locked) { throw new Error("GC is already locked"); } if (WasmEnableThreads) { @@ -14,11 +17,11 @@ export function mono_wasm_gc_lock(): void { } cwraps.mono_wasm_gc_lock(); } - locked = true; + gc_locked = true; } export function mono_wasm_gc_unlock(): void { - if (!locked) { + if (!gc_locked) { throw new Error("GC is not locked"); } if (WasmEnableThreads) { @@ -27,5 +30,5 @@ export function mono_wasm_gc_unlock(): void { } cwraps.mono_wasm_gc_unlock(); } - locked = false; + gc_locked = false; } diff --git a/src/mono/browser/runtime/invoke-cs.ts b/src/mono/browser/runtime/invoke-cs.ts index d45063ab30bbb6..1b0338180ebffe 100644 --- a/src/mono/browser/runtime/invoke-cs.ts +++ b/src/mono/browser/runtime/invoke-cs.ts @@ -6,131 +6,101 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { bind_arg_marshal_to_cs } from "./marshal-to-cs"; -import { marshal_exception_to_js, bind_arg_marshal_to_js, end_marshal_task_to_js } from "./marshal-to-js"; +import { bind_arg_marshal_to_js, end_marshal_task_to_js } from "./marshal-to-js"; import { - get_arg, get_sig, get_signature_argument_count, is_args_exception, - bound_cs_function_symbol, get_signature_version, alloc_stack_frame, get_signature_type, set_args_context, + get_sig, get_signature_argument_count, + bound_cs_function_symbol, get_signature_version, alloc_stack_frame, get_signature_type, } from "./marshal"; -import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots"; -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 { MonoMethod, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, MarshalerType, MonoAssembly } from "./types/internal"; import cwraps from "./cwraps"; -import { assert_js_interop, wrap_error_root, wrap_no_error_root } from "./invoke-js"; +import { assert_js_interop } from "./invoke-js"; import { startMeasure, MeasuredBlock, endMeasure } from "./profiler"; +import { bind_assembly_exports, invoke_sync_method } from "./managed-exports"; import { mono_log_debug } from "./logging"; const _assembly_cache_by_name = new Map(); -export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - assert_js_interop(); - const fqn_root = mono_wasm_new_external_root(fully_qualified_name), resultRoot = mono_wasm_new_external_root(result_address); +export function mono_wasm_bind_cs_function(method: MonoMethod, assemblyName: string, namespaceName: string, shortClassName: string, methodName: string, signatureHash: number, signature: JSFunctionSignature): void { + const fullyQualifiedName = `[${assemblyName}] ${namespaceName}.${shortClassName}:${methodName}`; const mark = startMeasure(); - try { - const version = get_signature_version(signature); - mono_assert(version === 2, () => `Signature version ${version} mismatch.`); - - const args_count = get_signature_argument_count(signature); - const js_fqn = monoStringToString(fqn_root)!; - mono_assert(js_fqn, "fully_qualified_name must be string"); - - mono_log_debug(`Binding [JSExport] ${js_fqn}`); - - const { assembly, namespace, classname, methodname } = parseFQN(js_fqn); - - const asm = assembly_load(assembly); - if (!asm) - throw new Error("Could not find assembly: " + assembly); - - const klass = cwraps.mono_wasm_assembly_find_class(asm, namespace, classname); - if (!klass) - throw new Error("Could not find class: " + namespace + ":" + classname + " in assembly " + assembly); - - const wrapper_name = `__Wrapper_${methodname}_${signature_hash}`; - const method = cwraps.mono_wasm_assembly_find_method(klass, wrapper_name, -1); - if (!method) - throw new Error(`Could not find method: ${wrapper_name} in ${klass} [${assembly}]`); - - const arg_marshalers: (BoundMarshalerToCs)[] = new Array(args_count); - for (let index = 0; index < args_count; index++) { - const sig = get_sig(signature, index + 2); - const marshaler_type = get_signature_type(sig); - const arg_marshaler = bind_arg_marshal_to_cs(sig, marshaler_type, index + 2); - mono_assert(arg_marshaler, "ERR43: argument marshaler must be resolved"); - arg_marshalers[index] = arg_marshaler; - } + mono_log_debug(`Binding [JSExport] ${assemblyName}.${shortClassName} from ${assemblyName} assembly`); + const version = get_signature_version(signature); + mono_assert(version === 2, () => `Signature version ${version} mismatch.`); - const res_sig = get_sig(signature, 1); - let res_marshaler_type = get_signature_type(res_sig); - const is_async = res_marshaler_type == MarshalerType.Task; - if (is_async) { - res_marshaler_type = MarshalerType.TaskPreCreated; - } - const res_converter = bind_arg_marshal_to_js(res_sig, res_marshaler_type, 1); - - const closure: BindingClosure = { - method, - fqn: js_fqn, - args_count, - arg_marshalers, - res_converter, - is_async, - isDisposed: false, - }; - let bound_fn: Function; - // void - if (args_count == 0 && !res_converter) { - bound_fn = bind_fn_0V(closure); - } - else if (args_count == 1 && !res_converter) { - bound_fn = bind_fn_1V(closure); - } - else if (is_async && args_count == 1 && res_converter) { - bound_fn = bind_fn_1RA(closure); - } - else if (is_async && args_count == 2 && res_converter) { - bound_fn = bind_fn_2RA(closure); - } - else if (args_count == 1 && res_converter) { - bound_fn = bind_fn_1R(closure); - } - else if (args_count == 2 && res_converter) { - bound_fn = bind_fn_2R(closure); - } - else { - bound_fn = bind_fn(closure); - } - // this is just to make debugging easier. - // It's not CSP compliant and possibly not performant, that's why it's only enabled in debug builds - // in Release configuration, it would be a trimmed by rollup - if (BuildConfiguration === "Debug" && !runtimeHelpers.cspPolicy) { - try { - bound_fn = new Function("fn", "return (function JSExport_" + methodname + "(){ return fn.apply(this, arguments)});")(bound_fn); - } - catch (ex) { - runtimeHelpers.cspPolicy = true; - } - } + const args_count = get_signature_argument_count(signature); - (bound_fn)[bound_cs_function_symbol] = closure; + const arg_marshalers: (BoundMarshalerToCs)[] = new Array(args_count); + for (let index = 0; index < args_count; index++) { + const sig = get_sig(signature, index + 2); + const marshaler_type = get_signature_type(sig); + const arg_marshaler = bind_arg_marshal_to_cs(sig, marshaler_type, index + 2); + mono_assert(arg_marshaler, "ERR43: argument marshaler must be resolved"); + arg_marshalers[index] = arg_marshaler; + } - _walk_exports_to_set_function(assembly, namespace, classname, methodname, signature_hash, bound_fn); - endMeasure(mark, MeasuredBlock.bindCsFunction, js_fqn); - wrap_no_error_root(is_exception, resultRoot); + const res_sig = get_sig(signature, 1); + let res_marshaler_type = get_signature_type(res_sig); + const is_async = res_marshaler_type == MarshalerType.Task; + if (is_async) { + res_marshaler_type = MarshalerType.TaskPreCreated; + } + const res_converter = bind_arg_marshal_to_js(res_sig, res_marshaler_type, 1); + + const closure: BindingClosure = { + method, + fullyQualifiedName, + args_count, + arg_marshalers, + res_converter, + is_async, + isDisposed: false, + }; + let bound_fn: Function; + // void + if (args_count == 0 && !res_converter) { + bound_fn = bind_fn_0V(closure); + } + else if (args_count == 1 && !res_converter) { + bound_fn = bind_fn_1V(closure); + } + else if (is_async && args_count == 1 && res_converter) { + bound_fn = bind_fn_1RA(closure); } - catch (ex: any) { - Module.err(ex.toString()); - wrap_error_root(is_exception, ex, resultRoot); - } finally { - resultRoot.release(); - fqn_root.release(); + else if (is_async && args_count == 2 && res_converter) { + bound_fn = bind_fn_2RA(closure); } + else if (args_count == 1 && res_converter) { + bound_fn = bind_fn_1R(closure); + } + else if (args_count == 2 && res_converter) { + bound_fn = bind_fn_2R(closure); + } + else { + bound_fn = bind_fn(closure); + } + + // this is just to make debugging easier. + // It's not CSP compliant and possibly not performant, that's why it's only enabled in debug builds + // in Release configuration, it would be a trimmed by rollup + if (BuildConfiguration === "Debug" && !runtimeHelpers.cspPolicy) { + try { + bound_fn = new Function("fn", "return (function JSExport_" + methodName + "(){ return fn.apply(this, arguments)});")(bound_fn); + } + catch (ex) { + runtimeHelpers.cspPolicy = true; + } + } + + (bound_fn)[bound_cs_function_symbol] = closure; + + _walk_exports_to_set_function(assemblyName, namespaceName, shortClassName, methodName, signatureHash, bound_fn); + endMeasure(mark, MeasuredBlock.bindCsFunction, fullyQualifiedName); } function bind_fn_0V(closure: BindingClosure) { const method = closure.method; - const fqn = closure.fqn; + const fqn = closure.fullyQualifiedName; if (!WasmEnableThreads) (closure) = null; return function bound_fn_0V() { const mark = startMeasure(); @@ -151,7 +121,7 @@ function bind_fn_0V(closure: BindingClosure) { function bind_fn_1V(closure: BindingClosure) { const method = closure.method; const marshaler1 = closure.arg_marshalers[0]!; - const fqn = closure.fqn; + const fqn = closure.fullyQualifiedName; if (!WasmEnableThreads) (closure) = null; return function bound_fn_1V(arg1: any) { const mark = startMeasure(); @@ -175,7 +145,7 @@ function bind_fn_1R(closure: BindingClosure) { const method = closure.method; const marshaler1 = closure.arg_marshalers[0]!; const res_converter = closure.res_converter!; - const fqn = closure.fqn; + const fqn = closure.fullyQualifiedName; if (!WasmEnableThreads) (closure) = null; return function bound_fn_1R(arg1: any) { const mark = startMeasure(); @@ -202,7 +172,7 @@ function bind_fn_1RA(closure: BindingClosure) { const method = closure.method; const marshaler1 = closure.arg_marshalers[0]!; const res_converter = closure.res_converter!; - const fqn = closure.fqn; + const fqn = closure.fullyQualifiedName; if (!WasmEnableThreads) (closure) = null; return function bound_fn_1R(arg1: any) { const mark = startMeasure(); @@ -235,7 +205,7 @@ function bind_fn_2R(closure: BindingClosure) { const marshaler1 = closure.arg_marshalers[0]!; const marshaler2 = closure.arg_marshalers[1]!; const res_converter = closure.res_converter!; - const fqn = closure.fqn; + const fqn = closure.fullyQualifiedName; if (!WasmEnableThreads) (closure) = null; return function bound_fn_2R(arg1: any, arg2: any) { const mark = startMeasure(); @@ -264,7 +234,7 @@ function bind_fn_2RA(closure: BindingClosure) { const marshaler1 = closure.arg_marshalers[0]!; const marshaler2 = closure.arg_marshalers[1]!; const res_converter = closure.res_converter!; - const fqn = closure.fqn; + const fqn = closure.fullyQualifiedName; if (!WasmEnableThreads) (closure) = null; return function bound_fn_2R(arg1: any, arg2: any) { const mark = startMeasure(); @@ -298,7 +268,7 @@ function bind_fn(closure: BindingClosure) { const arg_marshalers = closure.arg_marshalers; const res_converter = closure.res_converter; const method = closure.method; - const fqn = closure.fqn; + const fqn = closure.fullyQualifiedName; const is_async = closure.is_async; if (!WasmEnableThreads) (closure) = null; return function bound_fn(...js_args: any[]) { @@ -339,7 +309,7 @@ function bind_fn(closure: BindingClosure) { } type BindingClosure = { - fqn: string, + fullyQualifiedName: string, args_count: number, method: MonoMethod, arg_marshalers: (BoundMarshalerToCs)[], @@ -348,23 +318,6 @@ type BindingClosure = { isDisposed: boolean, } -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(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); - throw marshal_exception_to_js(exc); - } - } - 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("."); @@ -399,66 +352,12 @@ export async function mono_wasm_get_assembly_exports(assembly: string): Promise< assert_js_interop(); const result = exportsByAssembly.get(assembly); if (!result) { - const mark = startMeasure(); - const asm = assembly_load(assembly); - if (!asm) - throw new Error("Could not find assembly: " + assembly); - - const klass = cwraps.mono_wasm_assembly_find_class(asm, runtimeHelpers.runtime_interop_namespace, "__GeneratedInitializer"); - if (klass) { - const method = cwraps.mono_wasm_assembly_find_method(klass, "__Register_", -1); - if (method) { - const outException = mono_wasm_new_root(); - const outResult = mono_wasm_new_root(); - try { - cwraps.mono_wasm_invoke_method_ref(method, MonoObjectRefNull, VoidPtrNull, outException.address, outResult.address); - if (outException.value !== MonoObjectNull) { - const msg = monoStringToString(outResult)!; - throw new Error(msg); - } - } - finally { - outException.release(); - outResult.release(); - } - } - } else { - mono_assert(!WasmEnableThreads, () => `JSExport with multi-threading enabled is not supported with assembly ${assembly} as it was generated with the .NET 7 SDK`); - // this needs to stay here for compatibility with assemblies generated in Net7 - // it doesn't have the __GeneratedInitializer class - cwraps.mono_wasm_runtime_run_module_cctor(asm); - } - endMeasure(mark, MeasuredBlock.getAssemblyExports, assembly); + await bind_assembly_exports(assembly); } return exportsByAssembly.get(assembly) || {}; } -export function parseFQN(fqn: string) - : { assembly: string, namespace: string, classname: string, methodname: string } { - const assembly = fqn.substring(fqn.indexOf("[") + 1, fqn.indexOf("]")).trim(); - fqn = fqn.substring(fqn.indexOf("]") + 1).trim(); - - const methodname = fqn.substring(fqn.indexOf(":") + 1); - fqn = fqn.substring(0, fqn.indexOf(":")).trim(); - - let namespace = ""; - let classname = fqn; - if (fqn.indexOf(".") != -1) { - const idx = fqn.lastIndexOf("."); - namespace = fqn.substring(0, idx); - classname = fqn.substring(idx + 1); - } - - if (!assembly.trim()) - throw new Error("No assembly name specified " + fqn); - if (!classname.trim()) - throw new Error("No class name specified " + fqn); - if (!methodname.trim()) - throw new Error("No method name specified " + fqn); - return { assembly, namespace, classname, methodname }; -} - export function assembly_load(name: string): MonoAssembly { if (_assembly_cache_by_name.has(name)) return _assembly_cache_by_name.get(name); diff --git a/src/mono/browser/runtime/invoke-js.ts b/src/mono/browser/runtime/invoke-js.ts index ccd7e294a3f883..5323407eefa0fe 100644 --- a/src/mono/browser/runtime/invoke-js.ts +++ b/src/mono/browser/runtime/invoke-js.ts @@ -449,6 +449,8 @@ export function wrap_error_root(is_exception: Int32Ptr | null, ex: any, result: } // to set out parameters of icalls +// TODO replace it with replace it with UTF8 char*, no GC root needed +// https://github.com/dotnet/runtime/issues/98365 export function wrap_no_error_root(is_exception: Int32Ptr | null, result?: WasmRoot): void { if (is_exception) { receiveWorkerHeapViews(); diff --git a/src/mono/browser/runtime/jiterpreter-tables.ts b/src/mono/browser/runtime/jiterpreter-tables.ts index 71a3eb86c9f014..0ab6ce675267be 100644 --- a/src/mono/browser/runtime/jiterpreter-tables.ts +++ b/src/mono/browser/runtime/jiterpreter-tables.ts @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + import { WasmOpcode, WasmSimdOpcode, JiterpSpecialOpcode } from "./jiterpreter-opcodes"; diff --git a/src/mono/browser/runtime/managed-exports.ts b/src/mono/browser/runtime/managed-exports.ts index 4a95d0ca362a55..9a140ac0f6c70a 100644 --- a/src/mono/browser/runtime/managed-exports.ts +++ b/src/mono/browser/runtime/managed-exports.ts @@ -3,15 +3,14 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; -import { GCHandle, GCHandleNull, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod } from "./types/internal"; +import { GCHandle, GCHandleNull, JSMarshalerArguments, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod } from "./types/internal"; import cwraps from "./cwraps"; import { runtimeHelpers, Module, loaderHelpers, mono_assert } from "./globals"; 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_array_to_cs, marshal_array_to_cs_impl, marshal_bool_to_cs, marshal_exception_to_cs, marshal_string_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, marshal_exception_to_js } from "./marshal-to-js"; import { do_not_force_dispose } from "./gc-handles"; -import { assert_c_interop } from "./invoke-js"; +import { assert_c_interop, assert_js_interop } from "./invoke-js"; import { mono_wasm_main_thread_ptr } from "./pthreads/shared"; import { _zero_region } from "./memory"; @@ -19,18 +18,21 @@ const managedExports: ManagedExports = {} as any; export function init_managed_exports(): void { const exports_fqn_asm = "System.Runtime.InteropServices.JavaScript"; + // TODO https://github.com/dotnet/runtime/issues/98366 runtimeHelpers.runtime_interop_module = cwraps.mono_wasm_assembly_load(exports_fqn_asm); if (!runtimeHelpers.runtime_interop_module) throw "Can't find bindings module assembly: " + exports_fqn_asm; - runtimeHelpers.runtime_interop_namespace = "System.Runtime.InteropServices.JavaScript"; + runtimeHelpers.runtime_interop_namespace = exports_fqn_asm; runtimeHelpers.runtime_interop_exports_classname = "JavaScriptExports"; + // TODO https://github.com/dotnet/runtime/issues/98366 runtimeHelpers.runtime_interop_exports_class = cwraps.mono_wasm_assembly_find_class(runtimeHelpers.runtime_interop_module, runtimeHelpers.runtime_interop_namespace, runtimeHelpers.runtime_interop_exports_classname); if (!runtimeHelpers.runtime_interop_exports_class) throw "Can't find " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + " class"; managedExports.InstallMainSynchronizationContext = WasmEnableThreads ? get_method("InstallMainSynchronizationContext") : undefined; managedExports.CallEntrypoint = get_method("CallEntrypoint"); + managedExports.BindAssemblyExports = get_method("BindAssemblyExports"); managedExports.ReleaseJSOwnedObjectByGCHandle = get_method("ReleaseJSOwnedObjectByGCHandle"); managedExports.CompleteTask = get_method("CompleteTask"); managedExports.CallDelegate = get_method("CallDelegate"); @@ -39,26 +41,23 @@ export function init_managed_exports(): void { managedExports.LoadLazyAssembly = get_method("LoadLazyAssembly"); } -// the marshaled signature is: Task? CallEntrypoint(MonoMethod* entrypointPtr, string[] args) -export async function call_entry_point(entry_point: MonoMethod, program_args?: string[]): Promise { +// the marshaled signature is: Task? CallEntrypoint(string mainAssemblyName, string[] args) +export function call_entry_point(main_assembly_name: string, program_args: string[] | undefined, waitForDebugger: boolean): Promise { loaderHelpers.assert_runtime_running(); const sp = Module.stackSave(); try { - Module.runtimeKeepalivePush(); - const args = alloc_stack_frame(4); + const args = alloc_stack_frame(5); 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); + const arg3 = get_arg(args, 4); + marshal_string_to_cs(arg1, main_assembly_name); + marshal_array_to_cs_impl(arg2, program_args && !program_args.length ? undefined : program_args, MarshalerType.String); + marshal_bool_to_cs(arg3, waitForDebugger); // because this is async, we could pre-allocate the promise let promise = begin_marshal_task_to_js(res, MarshalerType.TaskPreCreated, marshal_int32_to_js); - // 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); // in case the C# side returned synchronously @@ -68,10 +67,10 @@ export async function call_entry_point(entry_point: MonoMethod, program_args?: s promise = Promise.resolve(0); } (promise as any)[do_not_force_dispose] = true; // prevent disposing the task in forceDisposeProxies() - return await promise; + + return promise; } finally { - Module.runtimeKeepalivePop();// after await promise ! - Module.stackRestore(sp); + Module.stackRestore(sp); // synchronously } } @@ -219,8 +218,7 @@ export function install_main_synchronization_context(): GCHandle { 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"); + cwraps.mono_wasm_invoke_method(managedExports.InstallMainSynchronizationContext!, args); if (is_args_exception(args)) { const exc = get_arg(args, 0); throw marshal_exception_to_js(exc); @@ -231,7 +229,45 @@ export function install_main_synchronization_context(): GCHandle { } } +export function invoke_sync_method(method: MonoMethod, args: JSMarshalerArguments): void { + assert_js_interop(); + cwraps.mono_wasm_invoke_method(method, args as any); + if (is_args_exception(args)) { + const exc = get_arg(args, 0); + throw marshal_exception_to_js(exc); + } +} + +// the marshaled signature is: Task BindAssemblyExports(string assemblyName) +export function bind_assembly_exports(assemblyName: string): Promise { + loaderHelpers.assert_runtime_running(); + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(3); + const res = get_arg(args, 1); + const arg1 = get_arg(args, 2); + marshal_string_to_cs(arg1, assemblyName); + + // because this is async, we could pre-allocate the promise + let promise = begin_marshal_task_to_js(res, MarshalerType.TaskPreCreated); + + invoke_sync_method(managedExports.BindAssemblyExports, args); + + // 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(); + } + return promise; + } finally { + Module.stackRestore(sp); // synchronously + } +} + + function get_method(method_name: string): MonoMethod { + // TODO https://github.com/dotnet/runtime/issues/98366 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; @@ -242,6 +278,7 @@ type ManagedExports = { InstallMainSynchronizationContext: MonoMethod | undefined, entry_point: MonoMethod, CallEntrypoint: MonoMethod, + BindAssemblyExports: MonoMethod, ReleaseJSOwnedObjectByGCHandle: MonoMethod, CompleteTask: MonoMethod, CallDelegate: MonoMethod, diff --git a/src/mono/browser/runtime/marshal-to-cs.ts b/src/mono/browser/runtime/marshal-to-cs.ts index 63657a122195e7..c1b663044c8b84 100644 --- a/src/mono/browser/runtime/marshal-to-cs.ts +++ b/src/mono/browser/runtime/marshal-to-cs.ts @@ -25,6 +25,7 @@ import { TypedArray } from "./types/emscripten"; import { addUnsettledPromise, settleUnsettledPromise } from "./pthreads/shared/eventloop"; import { mono_log_debug } from "./logging"; import { complete_task } from "./managed-exports"; +import { gc_locked } from "./gc-lock"; export const jsinteropDoc = "For more information see https://aka.ms/dotnet-wasm-jsinterop"; @@ -33,7 +34,7 @@ export function initialize_marshalers_to_cs(): void { js_to_cs_marshalers.set(MarshalerType.Array, marshal_array_to_cs); js_to_cs_marshalers.set(MarshalerType.Span, _marshal_span_to_cs); js_to_cs_marshalers.set(MarshalerType.ArraySegment, _marshal_array_segment_to_cs); - js_to_cs_marshalers.set(MarshalerType.Boolean, _marshal_bool_to_cs); + js_to_cs_marshalers.set(MarshalerType.Boolean, marshal_bool_to_cs); js_to_cs_marshalers.set(MarshalerType.Byte, _marshal_byte_to_cs); js_to_cs_marshalers.set(MarshalerType.Char, _marshal_char_to_cs); js_to_cs_marshalers.set(MarshalerType.Int16, _marshal_int16_to_cs); @@ -45,7 +46,7 @@ export function initialize_marshalers_to_cs(): void { js_to_cs_marshalers.set(MarshalerType.IntPtr, marshal_intptr_to_cs); js_to_cs_marshalers.set(MarshalerType.DateTime, _marshal_date_time_to_cs); js_to_cs_marshalers.set(MarshalerType.DateTimeOffset, _marshal_date_time_offset_to_cs); - js_to_cs_marshalers.set(MarshalerType.String, _marshal_string_to_cs); + js_to_cs_marshalers.set(MarshalerType.String, marshal_string_to_cs); js_to_cs_marshalers.set(MarshalerType.Exception, marshal_exception_to_cs); js_to_cs_marshalers.set(MarshalerType.JSException, marshal_exception_to_cs); js_to_cs_marshalers.set(MarshalerType.JSObject, marshal_js_object_to_cs); @@ -97,7 +98,7 @@ export function get_marshaler_to_cs_by_type(marshaler_type: MarshalerType): Mars return converter; } -function _marshal_bool_to_cs(arg: JSMarshalerArgument, value: any): void { +export function marshal_bool_to_cs(arg: JSMarshalerArgument, value: any): void { if (value === null || value === undefined) { set_arg_type(arg, MarshalerType.None); } @@ -219,7 +220,7 @@ function _marshal_date_time_offset_to_cs(arg: JSMarshalerArgument, value: Date): } } -function _marshal_string_to_cs(arg: JSMarshalerArgument, value: string) { +export function marshal_string_to_cs(arg: JSMarshalerArgument, value: string) { if (value === null || value === undefined) { set_arg_type(arg, MarshalerType.None); } @@ -556,17 +557,19 @@ export function marshal_array_to_cs_impl(arg: JSMarshalerArgument, value: Array< mono_check(Array.isArray(value), "Value is not an Array"); _zero_region(buffer_ptr, buffer_length); if (!WasmEnableJsInteropByValue) { + mono_assert(!WasmEnableThreads || !gc_locked, "GC must not be locked when creating a GC root"); cwraps.mono_wasm_register_root(buffer_ptr, buffer_length, "marshal_array_to_cs"); } for (let index = 0; index < length; index++) { const element_arg = get_arg(buffer_ptr, index); - _marshal_string_to_cs(element_arg, value[index]); + marshal_string_to_cs(element_arg, value[index]); } } else if (element_type == MarshalerType.Object) { mono_check(Array.isArray(value), "Value is not an Array"); _zero_region(buffer_ptr, buffer_length); if (!WasmEnableJsInteropByValue) { + mono_assert(!WasmEnableThreads || !gc_locked, "GC must not be locked when creating a GC root"); cwraps.mono_wasm_register_root(buffer_ptr, buffer_length, "marshal_array_to_cs"); } for (let index = 0; index < length; index++) { diff --git a/src/mono/browser/runtime/marshal-to-js.ts b/src/mono/browser/runtime/marshal-to-js.ts index e40c83899bc07f..d3987586b01d4d 100644 --- a/src/mono/browser/runtime/marshal-to-js.ts +++ b/src/mono/browser/runtime/marshal-to-js.ts @@ -22,6 +22,7 @@ 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"; +import { gc_locked } from "./gc-lock"; export function initialize_marshalers_to_js(): void { if (cs_to_js_marshalers.size == 0) { @@ -494,6 +495,7 @@ function _marshal_array_to_js_impl(arg: JSMarshalerArgument, element_type: Marsh result[index] = marshal_string_to_js(element_arg); } if (!WasmEnableJsInteropByValue) { + mono_assert(!WasmEnableThreads || !gc_locked, "GC must not be locked when disposing a GC root"); cwraps.mono_wasm_deregister_root(buffer_ptr); } } @@ -504,6 +506,7 @@ function _marshal_array_to_js_impl(arg: JSMarshalerArgument, element_type: Marsh result[index] = _marshal_cs_object_to_js(element_arg); } if (!WasmEnableJsInteropByValue) { + mono_assert(!WasmEnableThreads || !gc_locked, "GC must not be locked when disposing a GC root"); cwraps.mono_wasm_deregister_root(buffer_ptr); } } diff --git a/src/mono/browser/runtime/roots.ts b/src/mono/browser/runtime/roots.ts index 21f9f18077b7c7..24958a22e08458 100644 --- a/src/mono/browser/runtime/roots.ts +++ b/src/mono/browser/runtime/roots.ts @@ -1,11 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import WasmEnableThreads from "consts:wasmEnableThreads"; + import cwraps from "./cwraps"; -import { Module } from "./globals"; +import { Module, mono_assert } from "./globals"; import { VoidPtr, ManagedPointer, NativePointer } from "./types/emscripten"; import { MonoObjectRef, MonoObjectRefNull, MonoObject, is_nullish, WasmRoot, WasmRootBuffer } from "./types/internal"; import { _zero_region, localHeapViewU32 } from "./memory"; +import { gc_locked } from "./gc-lock"; const maxScratchRoots = 8192; let _scratch_root_buffer: WasmRootBuffer | null = null; @@ -36,25 +39,6 @@ export function mono_wasm_new_root_buffer(capacity: number, name?: string): Wasm return new WasmRootBufferImpl(offset, capacity, true, name); } -/** - * Creates a root buffer object representing an existing allocation in the native heap and registers - * the allocation with the GC. The caller is responsible for managing the lifetime of the allocation. - */ -export function mono_wasm_new_root_buffer_from_pointer(offset: VoidPtr, capacity: number, name?: string): WasmRootBuffer { - if (capacity <= 0) - throw new Error("capacity >= 1"); - - capacity = capacity | 0; - - const capacityBytes = capacity * 4; - if ((offset % 4) !== 0) - throw new Error("Unaligned offset"); - - _zero_region(offset, capacityBytes); - - return new WasmRootBufferImpl(offset, capacity, false, name); -} - /** * Allocates a WasmRoot pointing to a root provided and controlled by external code. Typicaly on managed stack. * Releasing this root will not de-allocate the root space. You still need to call .release(). @@ -188,6 +172,7 @@ export class WasmRootBufferImpl implements WasmRootBuffer { this.__offset32 = offset >>> 2; this.__count = capacity; this.length = capacity; + mono_assert(!WasmEnableThreads || !gc_locked, "GC must not be locked when creating a GC root"); this.__handle = cwraps.mono_wasm_register_root(offset, capacityBytes, name || "noname"); this.__ownsAllocation = ownsAllocation; } @@ -247,6 +232,7 @@ export class WasmRootBufferImpl implements WasmRootBuffer { release(): void { if (this.__offset && this.__ownsAllocation) { + mono_assert(!WasmEnableThreads || !gc_locked, "GC must not be locked when disposing a GC root"); cwraps.mono_wasm_deregister_root(this.__offset); _zero_region(this.__offset, this.__count * 4); Module._free(this.__offset); diff --git a/src/mono/browser/runtime/run.ts b/src/mono/browser/runtime/run.ts index 932d851973b752..8760aab6514cad 100644 --- a/src/mono/browser/runtime/run.ts +++ b/src/mono/browser/runtime/run.ts @@ -3,13 +3,11 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; -import { ENVIRONMENT_IS_NODE, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; +import { ENVIRONMENT_IS_NODE, Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { mono_wasm_wait_for_debugger } from "./debug"; import { mono_wasm_set_main_args } from "./startup"; import cwraps from "./cwraps"; 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"; @@ -58,36 +56,26 @@ export async function mono_run_main(main_assembly_name?: string, args?: string[] } mono_wasm_set_main_args(main_assembly_name, args); + loaderHelpers.config.mainAssemblyName = main_assembly_name; + if (runtimeHelpers.waitForDebugger == -1) { mono_log_info("waiting for debugger..."); await mono_wasm_wait_for_debugger(); } - const method = find_entry_point(main_assembly_name); - const res = await call_entry_point(method, args); + try { + Module.runtimeKeepalivePush(); - // one more timer loop before we return, so that any remaining queued calls could run - await new Promise(resolve => globalThis.setTimeout(resolve, 0)); + // one more timer loop before we return, so that any remaining queued calls could run + await new Promise(resolve => globalThis.setTimeout(resolve, 0)); - return res; + return await call_entry_point(main_assembly_name, args, runtimeHelpers.waitForDebugger == 1); + } finally { + Module.runtimeKeepalivePop();// after await promise ! + } } -export function find_entry_point(assembly: string) { - loaderHelpers.assert_runtime_running(); - assert_js_interop(); - const asm = assembly_load(assembly); - if (!asm) - throw new Error("Could not find assembly: " + assembly); - let auto_set_breakpoint = 0; - if (runtimeHelpers.waitForDebugger == 1) - auto_set_breakpoint = 1; - - const method = cwraps.mono_wasm_assembly_get_entry_point(asm, auto_set_breakpoint); - if (!method) - throw new Error("Could not find entry point for assembly: " + assembly); - return method; -} export function nativeExit(code: number) { if (WasmEnableThreads) { diff --git a/src/mono/browser/runtime/runtime.c b/src/mono/browser/runtime/runtime.c index 73e64f0a8a3fea..9030c4a52ee556 100644 --- a/src/mono/browser/runtime/runtime.c +++ b/src/mono/browser/runtime/runtime.c @@ -318,6 +318,7 @@ mono_wasm_load_runtime_common (int debug_level, MonoLogCallback log_callback, co return domain; } +// TODO https://github.com/dotnet/runtime/issues/98366 EMSCRIPTEN_KEEPALIVE MonoAssembly* mono_wasm_assembly_load (const char *name) { @@ -331,6 +332,7 @@ mono_wasm_assembly_load (const char *name) return res; } +// TODO https://github.com/dotnet/runtime/issues/98366 EMSCRIPTEN_KEEPALIVE MonoClass* mono_wasm_assembly_find_class (MonoAssembly *assembly, const char *namespace, const char *name) { @@ -342,21 +344,7 @@ mono_wasm_assembly_find_class (MonoAssembly *assembly, const char *namespace, co return result; } -extern int mono_runtime_run_module_cctor (MonoImage *image, MonoError *error); - -EMSCRIPTEN_KEEPALIVE void -mono_wasm_runtime_run_module_cctor (MonoAssembly *assembly) -{ - assert (assembly); - MonoError error; - MONO_ENTER_GC_UNSAFE; - MonoImage *image = mono_assembly_get_image (assembly); - if (!mono_runtime_run_module_cctor(image, &error)) { - //g_print ("Failed to run module constructor due to %s\n", mono_error_get_message (error)); - } - MONO_EXIT_GC_UNSAFE; -} - +// TODO https://github.com/dotnet/runtime/issues/98366 EMSCRIPTEN_KEEPALIVE MonoMethod* mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int arguments) { @@ -367,31 +355,3 @@ mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int argument MONO_EXIT_GC_UNSAFE; return result; } - -EMSCRIPTEN_KEEPALIVE void -mono_wasm_invoke_method_ref (MonoMethod *method, MonoObject **this_arg_in, void *params[], MonoObject **_out_exc, MonoObject **out_result) -{ - PPVOLATILE(MonoObject) out_exc = _out_exc; - PVOLATILE(MonoObject) temp_exc = NULL; - if (out_exc) - *out_exc = NULL; - else - out_exc = &temp_exc; - - MONO_ENTER_GC_UNSAFE; - if (out_result) { - *out_result = NULL; - PVOLATILE(MonoObject) invoke_result = mono_runtime_invoke (method, this_arg_in ? *this_arg_in : NULL, params, (MonoObject **)out_exc); - store_volatile(out_result, invoke_result); - } else { - mono_runtime_invoke (method, this_arg_in ? *this_arg_in : NULL, params, (MonoObject **)out_exc); - } - - if (*out_exc && out_result) { - PVOLATILE(MonoObject) exc2 = NULL; - store_volatile(out_result, (MonoObject*)mono_object_to_string (*out_exc, (MonoObject **)&exc2)); - if (exc2) - store_volatile(out_result, (MonoObject*)mono_string_new (mono_get_root_domain (), "Exception Double Fault")); - } - MONO_EXIT_GC_UNSAFE; -} diff --git a/src/mono/browser/runtime/strings.ts b/src/mono/browser/runtime/strings.ts index 9058f29d0bf2a9..ae73fe57ff50d0 100644 --- a/src/mono/browser/runtime/strings.ts +++ b/src/mono/browser/runtime/strings.ts @@ -31,6 +31,8 @@ export function strings_init(): void { } mono_wasm_string_decoder_buffer = Module._malloc(12); } + if (!mono_wasm_string_root) + mono_wasm_string_root = mono_wasm_new_root(); } export function stringToUTF8(str: string): Uint8Array { @@ -257,8 +259,6 @@ let mono_wasm_string_root: any; export function monoStringToStringUnsafe(mono_string: MonoString): string | null { if (mono_string === MonoStringNull) return null; - if (!mono_wasm_string_root) - mono_wasm_string_root = mono_wasm_new_root(); mono_wasm_string_root.value = mono_string; const result = monoStringToString(mono_wasm_string_root);