From d652302207bf003f32ad974f9d6d21728b5c6644 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 18 Oct 2023 11:42:43 +0200 Subject: [PATCH] second attempt https://github.com/dotnet/runtime/pull/93010 --- .../src/Interop/Browser/Interop.Runtime.cs | 2 +- ...tem.Runtime.InteropServices.JavaScript.sln | 26 +- .../src/Resources/Strings.resx | 8 +- .../JavaScript/CancelablePromise.cs | 14 +- .../JavaScript/Interop/JavaScriptExports.cs | 57 ++-- .../JavaScript/Interop/JavaScriptImports.cs | 4 +- .../JavaScript/Interop/LegacyExports.cs | 6 +- .../JavaScript/JSFunctionBinding.cs | 15 + .../JavaScript/JSHostImplementation.Types.cs | 14 +- .../JavaScript/JSHostImplementation.cs | 112 +++++-- .../JavaScript/JSMarshalerArgument.cs | 6 +- .../JavaScript/JSObject.References.cs | 18 +- .../JavaScript/MarshalerType.cs | 4 +- .../Marshaling/JSMarshalerArgument.Object.cs | 3 +- .../Marshaling/JSMarshalerArgument.Task.cs | 274 ++++++------------ .../JavaScript/JavaScriptTestHelper.cs | 2 - src/mono/wasm/runtime/cancelable-promise.ts | 9 +- src/mono/wasm/runtime/corebindings.c | 4 +- src/mono/wasm/runtime/exports-binding.ts | 4 +- src/mono/wasm/runtime/gc-handles.ts | 125 +++++--- src/mono/wasm/runtime/invoke-cs.ts | 2 +- src/mono/wasm/runtime/invoke-js.ts | 2 +- .../wasm/runtime/loader/promise-controller.ts | 2 +- src/mono/wasm/runtime/managed-exports.ts | 20 +- src/mono/wasm/runtime/marshal-to-cs.ts | 41 +-- src/mono/wasm/runtime/marshal-to-js.ts | 156 +++++----- src/mono/wasm/runtime/marshal.ts | 66 ++--- .../wasm/runtime/net6-legacy/method-calls.ts | 4 +- src/mono/wasm/runtime/types/internal.ts | 11 +- src/mono/wasm/runtime/weak-ref.ts | 4 +- 30 files changed, 498 insertions(+), 517 deletions(-) diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index edf075ab2867d..03c19b3d03e0e 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -22,7 +22,7 @@ internal static unsafe partial class Runtime [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 MarshalPromise(void* data); + public static extern void ResolveOrRejectPromise(void* data); [MethodImpl(MethodImplOptions.InternalCall)] public static extern IntPtr RegisterGCRoot(IntPtr start, int bytesSize, IntPtr name); [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln b/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln index 0f5bce54f6882..b0c228723bac4 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln @@ -43,11 +43,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{F2C2C78A-CED EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "tools\gen", "{569E6837-0771-4C08-BB09-460281030538}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{569E6837-0771-4C08-BB09-460281030538}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "tools\src", "{67F3A00A-AE6C-434C-927D-E5D38DE2DA2C}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{67F3A00A-AE6C-434C-927D-E5D38DE2DA2C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{F92020A9-28BE-4398-86FF-5CFE44C94882}" EndProject @@ -135,28 +135,32 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {ED86AB26-1CFB-457D-BF87-B7A0D8FAF272} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} + {8B1D80E9-AE0D-4E3C-9F91-E6862B49AEF3} = {C22C479B-769A-4859-B974-E9B9D65918DE} + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} + {28278E01-BF5C-4AB6-AA7A-8DD4E6C04DB1} = {C22C479B-769A-4859-B974-E9B9D65918DE} + {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45} = {7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3} {BFED925C-18F2-4C98-833E-66F205234598} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} {ABA5A92B-CAD8-47E8-A7CE-D28A67FB69C0} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} {765B4AA5-723A-44FF-BC4E-EB0F03103F6D} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} - {EC3ADEFA-1FF3-482C-8CCB-FE4C77292532} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} - {8B1D80E9-AE0D-4E3C-9F91-E6862B49AEF3} = {C22C479B-769A-4859-B974-E9B9D65918DE} - {28278E01-BF5C-4AB6-AA7A-8DD4E6C04DB1} = {C22C479B-769A-4859-B974-E9B9D65918DE} - {44BAE6F1-94C2-415B-9A16-3B8EC429B09B} = {C22C479B-769A-4859-B974-E9B9D65918DE} - {CE5E53C1-F9B5-41EE-8D00-837913EC57D1} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} {FB12C247-AFEF-4772-BB0C-983969B6CF32} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} {09AA6758-0BD3-4312-9C07-AE9F1D50A3AD} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} {B4E3E774-2C16-4CBF-87EF-88C547529B94} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} - {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45} = {7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3} + {EC3ADEFA-1FF3-482C-8CCB-FE4C77292532} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} + {44BAE6F1-94C2-415B-9A16-3B8EC429B09B} = {C22C479B-769A-4859-B974-E9B9D65918DE} {4D8B7538-D933-4F3A-818D-4E19ABA7E182} = {569E6837-0771-4C08-BB09-460281030538} {6C60944F-4FE1-450F-884B-D523EDFCFAB3} = {569E6837-0771-4C08-BB09-460281030538} - {569E6837-0771-4C08-BB09-460281030538} = {F92020A9-28BE-4398-86FF-5CFE44C94882} {B8F2E56D-6571-466D-9EF2-9FCAD3FC6E5B} = {67F3A00A-AE6C-434C-927D-E5D38DE2DA2C} {42F9A600-BEC3-4F87-97EE-38E0DCAABC5A} = {67F3A00A-AE6C-434C-927D-E5D38DE2DA2C} - {67F3A00A-AE6C-434C-927D-E5D38DE2DA2C} = {F92020A9-28BE-4398-86FF-5CFE44C94882} {008873D5-9028-4FF3-8354-71F713748625} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF} + {569E6837-0771-4C08-BB09-460281030538} = {F92020A9-28BE-4398-86FF-5CFE44C94882} + {67F3A00A-AE6C-434C-927D-E5D38DE2DA2C} = {F92020A9-28BE-4398-86FF-5CFE44C94882} {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF} = {F92020A9-28BE-4398-86FF-5CFE44C94882} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3FE64246-4AFA-424A-AE5D-7007E20451B5} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{42f9a600-bec3-4f87-97ee-38e0dcaabc5a}*SharedItemsImports = 5 + ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{6c60944f-4fe1-450f-884b-d523edfcfab3}*SharedItemsImports = 5 + EndGlobalSection EndGlobal 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 dc490f9911b2e..9cb71cc66223a 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/Resources/Strings.resx +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/Resources/Strings.resx @@ -147,8 +147,8 @@ ToManagedCallback is null. - - TaskCallback is null. + + PromiseHolder is null. Empty profile data. @@ -162,8 +162,8 @@ Failed to marshal exception. - - Failed to marshal Task callback. + + Failed to marshal Promise callback. Invalid InFlightCounter for JSObject {0}, expected: {1}, actual: {2}. 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 7a8a0aa1f2d9f..3f3573f5f26c1 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 @@ -9,7 +9,7 @@ namespace System.Runtime.InteropServices.JavaScript public static partial class CancelablePromise { [JSImport("INTERNAL.mono_wasm_cancel_promise")] - private static partial void _CancelPromise(IntPtr promiseGCHandle); + private static partial void _CancelPromise(IntPtr gcvHandle); public static void CancelPromise(Task promise) { @@ -18,15 +18,15 @@ public static void CancelPromise(Task promise) { return; } - JSHostImplementation.TaskCallback? holder = promise.AsyncState as JSHostImplementation.TaskCallback; + JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder; if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise"); #if FEATURE_WASM_THREADS - holder.SynchronizationContext!.Send(static (JSHostImplementation.TaskCallback holder) => + holder.SynchronizationContext!.Send(static (JSHostImplementation.PromiseHolder holder) => { #endif - _CancelPromise(holder.GCHandle); + _CancelPromise(holder.GCVHandle); #if FEATURE_WASM_THREADS }, holder); #endif @@ -39,15 +39,15 @@ public static void CancelPromise(Task promise, Action callback, T state) { return; } - JSHostImplementation.TaskCallback? holder = promise.AsyncState as JSHostImplementation.TaskCallback; + JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder; if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise"); #if FEATURE_WASM_THREADS - holder.SynchronizationContext!.Send((JSHostImplementation.TaskCallback holder) => + holder.SynchronizationContext!.Send((JSHostImplementation.PromiseHolder holder) => { #endif - _CancelPromise(holder.GCHandle); + _CancelPromise(holder.GCVHandle); callback.Invoke(state); #if FEATURE_WASM_THREADS }, holder); 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 20c0279d9ff1e..7c0a18dba1648 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 @@ -8,6 +8,7 @@ using System.Threading.Tasks; using System.Diagnostics.CodeAnalysis; using System.Threading; +using static System.Runtime.InteropServices.JavaScript.JSHostImplementation; namespace System.Runtime.InteropServices.JavaScript { @@ -31,7 +32,7 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer) throw new MissingMethodException(SR.MissingManagedEntrypointHandle); } - RuntimeMethodHandle methodHandle = JSHostImplementation.GetMethodHandleFromIntPtr(entrypointPtr); + 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) @@ -141,32 +142,18 @@ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; // initialized and set by caller try { - GCHandle handle = (GCHandle)arg_1.slot.GCHandle; - - JSHostImplementation.ThreadJsOwnedObjects.Remove(handle.Target!); - handle.Free(); - } - catch (Exception ex) - { - arg_exc.ToJS(ex); - } - } - - // the marshaled signature is: - // GCHandle CreateTaskCallback() - public static void CreateTaskCallback(JSMarshalerArgument* arguments_buffer) - { - ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() - ref JSMarshalerArgument arg_return = ref arguments_buffer[1]; // used as return value - try - { - JSHostImplementation.TaskCallback holder = new JSHostImplementation.TaskCallback(); -#if FEATURE_WASM_THREADS - holder.OwnerThreadId = Thread.CurrentThread.ManagedThreadId; - holder.SynchronizationContext = SynchronizationContext.Current ?? new SynchronizationContext(); -#endif - arg_return.slot.Type = MarshalerType.Object; - arg_return.slot.GCHandle = holder.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(holder); + var gcHandle = arg_1.slot.GCHandle; + if (IsGCVHandle(gcHandle) && ThreadJsOwnedHolders.Remove(gcHandle, out PromiseHolder? holder)) + { + holder.GCVHandle = IntPtr.Zero; + holder.Callback!(null); + } + else + { + GCHandle handle = (GCHandle)gcHandle; + ThreadJsOwnedObjects.Remove(handle.Target!); + handle.Free(); + } } catch (Exception ex) { @@ -187,7 +174,7 @@ public static void CallDelegate(JSMarshalerArgument* arguments_buffer) try { GCHandle callback_gc_handle = (GCHandle)arg_1.slot.GCHandle; - if (callback_gc_handle.Target is JSHostImplementation.ToManagedCallback callback) + if (callback_gc_handle.Target is ToManagedCallback callback) { // arg_2, arg_3, arg_4, arg_res are processed by the callback callback(arguments_buffer); @@ -204,7 +191,7 @@ public static void CallDelegate(JSMarshalerArgument* arguments_buffer) } // the marshaled signature is: - // void CompleteTask(GCHandle holder, Exception? exceptionResult, T? result) + // void CompleteTask(GCVHandle holder, Exception? exceptionResult, T? result) public static void CompleteTask(JSMarshalerArgument* arguments_buffer) { ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() @@ -213,15 +200,17 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer) // arg_3 set by caller when this is SetResult call try { - GCHandle callback_gc_handle = (GCHandle)arg_1.slot.GCHandle; - if (callback_gc_handle.Target is JSHostImplementation.TaskCallback holder && holder.Callback is not null) + var callback_gcv_handle = arg_1.slot.GCHandle; + if (ThreadJsOwnedHolders.Remove(callback_gcv_handle, out PromiseHolder? promiseHolder) && promiseHolder.Callback != null) { + promiseHolder.GCVHandle = IntPtr.Zero; + // arg_2, arg_3 are processed by the callback - holder.Callback(arguments_buffer); + promiseHolder.Callback(arguments_buffer); } else { - throw new InvalidOperationException(SR.NullTaskCallback); + throw new InvalidOperationException(SR.NullPromiseHolder); } } catch (Exception ex) @@ -264,7 +253,7 @@ public static void InstallSynchronizationContext (JSMarshalerArgument* arguments ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() try { - JSHostImplementation.InstallWebWorkerInterop(true, true); + InstallWebWorkerInterop(true, true); } catch (Exception ex) { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs index 79805b1b2df02..4ebb8a772e236 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs @@ -7,11 +7,11 @@ namespace System.Runtime.InteropServices.JavaScript { internal static unsafe partial class JavaScriptImports { - public static void MarshalPromise(Span arguments) + public static void ResolveOrRejectPromise(Span arguments) { fixed (JSMarshalerArgument* ptr = arguments) { - Interop.Runtime.MarshalPromise(ptr); + Interop.Runtime.ResolveOrRejectPromise(ptr); ref JSMarshalerArgument exceptionArg = ref arguments[0]; if (exceptionArg.slot.Type != MarshalerType.None) { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/LegacyExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/LegacyExports.cs index c15b81826b0fb..bb4b017ef743f 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/LegacyExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/LegacyExports.cs @@ -32,7 +32,7 @@ internal static void PreventTrimming() public static void GetCSOwnedObjectByJSHandleRef(nint jsHandle, int shouldAddInflight, out JSObject? result) { - if (JSHostImplementation.ThreadCsOwnedObjects.TryGetValue((int)jsHandle, out WeakReference? reference)) + if (JSHostImplementation.ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference? reference)) { reference.TryGetTarget(out JSObject? jsObject); if (shouldAddInflight != 0) @@ -74,7 +74,7 @@ public static void CreateCSOwnedProxyRef(nint jsHandle, LegacyHostImplementation JSObject? res = null; - if (!JSHostImplementation.ThreadCsOwnedObjects.TryGetValue((int)jsHandle, out WeakReference? reference) || + if (!JSHostImplementation.ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference? reference) || !reference.TryGetTarget(out res) || res.IsDisposed) { @@ -90,7 +90,7 @@ public static void CreateCSOwnedProxyRef(nint jsHandle, LegacyHostImplementation _ => throw new ArgumentOutOfRangeException(nameof(mappedType)) }; #pragma warning restore CS0612 // Type or member is obsolete - JSHostImplementation.ThreadCsOwnedObjects[(int)jsHandle] = new WeakReference(res, trackResurrection: true); + JSHostImplementation.ThreadCsOwnedObjects[jsHandle] = new WeakReference(res, trackResurrection: true); } if (shouldAddInflight != 0) { 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 d5b0036369775..3e4fe41acc053 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 @@ -42,13 +42,28 @@ internal struct JSBindingHeader internal struct JSBindingType { internal MarshalerType Type; + internal MarshalerType __ReservedB1; + internal MarshalerType __ReservedB2; + internal MarshalerType __ReservedB3; internal IntPtr __Reserved; internal IntPtr JSCustomMarshallerCode; internal int JSCustomMarshallerCodeLength; internal MarshalerType ResultMarshalerType; + internal MarshalerType __ReservedB4; + internal MarshalerType __ReservedB5; + internal MarshalerType __ReservedB6; internal MarshalerType Arg1MarshalerType; + internal MarshalerType __ReservedB7; + internal MarshalerType __ReservedB8; + internal MarshalerType __ReservedB9; internal MarshalerType Arg2MarshalerType; + internal MarshalerType __ReservedB10; + internal MarshalerType __ReservedB11; + internal MarshalerType __ReservedB12; internal MarshalerType Arg3MarshalerType; + internal MarshalerType __ReservedB13; + internal MarshalerType __ReservedB14; + internal MarshalerType __ReservedB15; } internal unsafe int ArgumentCount diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs index 0158d24499dba..1cf4646569ed3 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Threading; +using System.Threading.Tasks; namespace System.Runtime.InteropServices.JavaScript { @@ -9,15 +10,24 @@ internal static partial class JSHostImplementation { internal unsafe delegate void ToManagedCallback(JSMarshalerArgument* arguments_buffer); - public sealed class TaskCallback + public sealed class PromiseHolder { - public nint GCHandle; + public nint GCVHandle; public ToManagedCallback? Callback; #if FEATURE_WASM_THREADS // the JavaScript object could only exist on the single web worker and can't migrate to other workers internal int OwnerThreadId; internal SynchronizationContext? SynchronizationContext; #endif + + public PromiseHolder(nint gcvHandle) + { + this.GCVHandle = gcvHandle; +#if FEATURE_WASM_THREADS + this.OwnerThreadId = Thread.CurrentThread.ManagedThreadId; + this.SynchronizationContext = SynchronizationContext.Current ?? new SynchronizationContext(); +#endif + } } [StructLayout(LayoutKind.Explicit)] 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 eaaf04b31d28e..cfc4f7930d580 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 @@ -20,9 +20,9 @@ internal static partial class JSHostImplementation #if FEATURE_WASM_THREADS [ThreadStatic] #endif - private static Dictionary>? s_csOwnedObjects; + private static Dictionary>? s_csOwnedObjects; - public static Dictionary> ThreadCsOwnedObjects + public static Dictionary> ThreadCsOwnedObjects { get { @@ -35,17 +35,78 @@ public static Dictionary> ThreadCsOwnedObjects #if FEATURE_WASM_THREADS [ThreadStatic] #endif - private static Dictionary? s_jsOwnedObjects; + private static Dictionary? s_jsOwnedObjects; - public static Dictionary ThreadJsOwnedObjects + public static Dictionary ThreadJsOwnedObjects { get { - s_jsOwnedObjects ??= new Dictionary(ReferenceEqualityComparer.Instance); + s_jsOwnedObjects ??= new Dictionary(ReferenceEqualityComparer.Instance); return s_jsOwnedObjects; } } + // this is similar to GCHandle, but the GCVHandle is allocated on JS side and this keeps the C# proxy alive +#if FEATURE_WASM_THREADS + [ThreadStatic] +#endif + private static Dictionary? s_jsOwnedHolders; + + public static Dictionary ThreadJsOwnedHolders + { + get + { + s_jsOwnedHolders ??= new Dictionary(); + return s_jsOwnedHolders; + } + } + + // JSVHandle is like JSHandle, but it's not tracked and allocated by the JS side + // It's used when we need to create JSHandle-like identity ahead of time, before calling JS. + // they have negative values, so that they don't collide with JSHandles. +#if FEATURE_WASM_THREADS + [ThreadStatic] +#endif + public static nint NextJSVHandle; + +#if FEATURE_WASM_THREADS + [ThreadStatic] +#endif + private static List? s_JSVHandleFreeList; + public static List JSVHandleFreeList + { + get + { + s_JSVHandleFreeList ??= new(); + return s_JSVHandleFreeList; + } + } + + public static nint AllocJSVHandle() + { + if (JSVHandleFreeList.Count > 0) + { + var jsvHandle = JSVHandleFreeList[JSVHandleFreeList.Count]; + JSVHandleFreeList.RemoveAt(JSVHandleFreeList.Count - 1); + return jsvHandle; + } + if (NextJSVHandle == IntPtr.Zero) + { + NextJSVHandle = -2; + } + return NextJSVHandle--; + } + + public static void FreeJSVHandle(nint jsvHandle) + { + JSVHandleFreeList.Add(jsvHandle); + } + + public static bool IsGCVHandle(nint gcHandle) + { + return gcHandle < -1; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReleaseCSOwnedObject(nint jsHandle) { @@ -54,17 +115,24 @@ public static void ReleaseCSOwnedObject(nint jsHandle) #if FEATURE_WASM_THREADS JSSynchronizationContext.AssertWebWorkerContext(); #endif - ThreadCsOwnedObjects.Remove((int)jsHandle); + ThreadCsOwnedObjects.Remove(jsHandle); Interop.Runtime.ReleaseCSOwnedObject(jsHandle); } } - public static object? GetTaskResultDynamic(Task task) + public static bool GetTaskResultDynamic(Task task, out object? value) { - MethodInfo method = GetTaskResultMethodInfo(task.GetType()); + var type = task.GetType(); + if (type == typeof(Task)) + { + value = null; + return false; + } + MethodInfo method = GetTaskResultMethodInfo(type); if (method != null) { - return method.Invoke(task, null); + value = method.Invoke(task, null); + return true; } throw new InvalidOperationException(); } @@ -182,7 +250,7 @@ public static unsafe JSFunctionBinding GetMethodSignature(ReadOnlySpan? reference) || + if (!ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference? reference) || !reference.TryGetTarget(out res) || res.IsDisposed) { res = new JSObject(jsHandle); - ThreadCsOwnedObjects[(int)jsHandle] = new WeakReference(res, trackResurrection: true); + ThreadCsOwnedObjects[jsHandle] = new WeakReference(res, trackResurrection: true); } return res; } @@ -279,7 +347,7 @@ public static void UninstallWebWorkerInterop() JSSynchronizationContext.CurrentJSSynchronizationContext = null; ctx.isDisposed = true; } - catch(Exception ex) + catch (Exception ex) { Environment.FailFast($"Unexpected error in UninstallWebWorkerInterop, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}. " + ex); } @@ -305,19 +373,17 @@ public static void UninstallWebWorkerInterop() foreach (var gch in ThreadJsOwnedObjects.Values) { GCHandle gcHandle = (GCHandle)gch; - - // if this is pending promise we reject it - if (gcHandle.Target is TaskCallback holder) + gcHandle.Free(); + } + foreach (var holder in ThreadJsOwnedHolders.Values) + { + unsafe { - unsafe - { - holder.Callback!.Invoke(null); - } + holder.Callback!.Invoke(null); } - gcHandle.Free(); } } - catch(Exception ex) + catch (Exception ex) { Environment.FailFast($"Unexpected error in UninstallWebWorkerInterop, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}. " + ex); } @@ -325,6 +391,8 @@ public static void UninstallWebWorkerInterop() ThreadCsOwnedObjects.Clear(); ThreadJsOwnedObjects.Clear(); + JSVHandleFreeList.Clear(); + NextJSVHandle = IntPtr.Zero; } [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "external_eventloop")] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs index 19fb096aebae3..38bc3e23ffc10 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs @@ -44,17 +44,17 @@ internal struct JSMarshalerArgumentImpl internal IntPtr JSHandle; [FieldOffset(4)] internal IntPtr GCHandle; - [FieldOffset(4)] - internal MarshalerType ElementType; [FieldOffset(8)] internal int Length; /// - /// Discriminator + /// Discriminators /// [FieldOffset(12)] internal MarshalerType Type; + [FieldOffset(13)] + internal MarshalerType ElementType; } /// diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs index 69ec3d7701ba9..97b6fcae46e42 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs @@ -93,27 +93,20 @@ internal static void AssertThreadAffinity(object value) { return; } - if (value is JSObject jsObject) + else if (value is JSObject jsObject) { if (jsObject.OwnerThreadId != Thread.CurrentThread.ManagedThreadId) { throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created."); } } - if (value is JSException jsException) + else if (value is JSException jsException) { if (jsException.jsException != null && jsException.jsException.OwnerThreadId != Thread.CurrentThread.ManagedThreadId) { throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created."); } } - if (value is JSHostImplementation.TaskCallback holder) - { - if (holder.OwnerThreadId != Thread.CurrentThread.ManagedThreadId) - { - throw new InvalidOperationException("The JavaScript promise can be used only on the thread where it was created."); - } - } } #endif @@ -126,6 +119,13 @@ internal static void AssertThreadAffinity(object value) /// public override string ToString() => $"(js-obj js '{JSHandle}')"; + internal void DisposeLocal() + { + JSHostImplementation.ThreadCsOwnedObjects.Remove(JSHandle); + _isDisposed = true; + JSHandle = IntPtr.Zero; + } + private void DisposeThis() { if (!_isDisposed) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs index 4e245866c0e64..7a2359b74073a 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs @@ -5,7 +5,7 @@ namespace System.Runtime.InteropServices.JavaScript { // maps to names of System.Runtime.InteropServices.JavaScript.JSMarshalerType // please sync with src\mono\wasm\runtime\marshal.ts - internal enum MarshalerType : int + internal enum MarshalerType : byte { None = 0, Void = 1, @@ -38,6 +38,8 @@ internal enum MarshalerType : int #if !JSIMPORTGENERATOR // only on runtime JSException, + TaskResolved, + TaskRejected, #endif } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs index cb32c2da0450d..fc40f481a098f 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs @@ -91,7 +91,7 @@ public unsafe void ToManaged(out object? value) throw new NotSupportedException(SR.Format(SR.ToManagedNotImplemented, slot.ElementType+ "[]")); } } - else if (slot.Type == MarshalerType.Task) + else if (slot.Type == MarshalerType.Task || slot.Type == MarshalerType.TaskResolved || slot.Type == MarshalerType.TaskRejected) { ToManaged(out Task? val, static (ref JSMarshalerArgument arg, out object? value) => { @@ -277,7 +277,6 @@ public void ToJS(object? value) { byte[] val = (byte[])value; ToJS(val); - slot.ElementType = MarshalerType.Byte; } else if (typeof(int[]) == type) { 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 e5fa56a11df87..78f40a84a1019 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 @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +using static System.Runtime.InteropServices.JavaScript.JSHostImplementation; namespace System.Runtime.InteropServices.JavaScript { @@ -36,18 +37,15 @@ public partial struct JSMarshalerArgument /// The value to be marshaled. public unsafe void ToManaged(out Task? value) { + // there is no nice way in JS how to check that JS promise is already resolved, to send MarshalerType.TaskRejected, MarshalerType.TaskResolved if (slot.Type == MarshalerType.None) { value = null; return; } - - GCHandle gcHandle = (GCHandle)slot.GCHandle; - JSHostImplementation.TaskCallback? holder = (JSHostImplementation.TaskCallback?)gcHandle.Target; - if (holder == null) throw new InvalidOperationException(SR.FailedToMarshalTaskCallback); - + PromiseHolder holder = CreateJSOwnedHolder(slot.GCHandle); TaskCompletionSource tcs = new TaskCompletionSource(holder); - JSHostImplementation.ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => + ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => { if (arguments_buffer == null) { @@ -80,18 +78,15 @@ public unsafe void ToManaged(out Task? value) /// Type of marshaled result of the . public unsafe void ToManaged(out Task? value, ArgumentToManagedCallback marshaler) { + // there is no nice way in JS how to check that JS promise is already resolved, to send MarshalerType.TaskRejected, MarshalerType.TaskResolved if (slot.Type == MarshalerType.None) { value = null; return; } - - GCHandle gcHandle = (GCHandle)slot.GCHandle; - JSHostImplementation.TaskCallback? holder = (JSHostImplementation.TaskCallback?)gcHandle.Target; - if (holder == null) throw new InvalidOperationException(SR.FailedToMarshalTaskCallback); - + PromiseHolder holder = CreateJSOwnedHolder(slot.GCHandle); TaskCompletionSource tcs = new TaskCompletionSource(holder); - JSHostImplementation.ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => + ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => { if (arguments_buffer == null) { @@ -118,6 +113,18 @@ public unsafe void ToManaged(out Task? value, ArgumentToManagedCallback value = tcs.Task; } + // TODO unregister and collect pending PromiseHolder also when no C# is awaiting ? + private static PromiseHolder CreateJSOwnedHolder(nint gcvHandle) + { +#if FEATURE_WASM_THREADS + JSSynchronizationContext.AssertWebWorkerContext(); +#endif + var holder = new PromiseHolder(gcvHandle); + ThreadJsOwnedHolders.Add(gcvHandle, holder); + return holder; + } + + internal void ToJSDynamic(Task? value) { Task? task = value; @@ -127,28 +134,37 @@ internal void ToJSDynamic(Task? value) slot.Type = MarshalerType.None; return; } - slot.Type = MarshalerType.Task; if (task.IsCompleted) { if (task.Exception != null) { Exception ex = task.Exception; - slot.JSHandle = CreateFailedPromise(ex); + ToJS(ex); + slot.ElementType = slot.Type; + slot.Type = MarshalerType.TaskRejected; return; } else { - object? result = JSHostImplementation.GetTaskResultDynamic(task); - slot.JSHandle = CreateResolvedPromise(result, MarshalResult); + if (GetTaskResultDynamic(task, out object? result)) + { + ToJS(result); + slot.ElementType = slot.Type; + } + else + { + slot.ElementType = MarshalerType.Void; + } + slot.Type = MarshalerType.TaskResolved; return; } } + slot.Type = MarshalerType.Task; + slot.JSHandle = AllocJSVHandle(); + var taskHolder = new JSObject(slot.JSHandle); - IntPtr jsHandle = CreatePendingPromise(); - slot.JSHandle = jsHandle; - JSObject promise = JSHostImplementation.CreateCSOwnedProxy(jsHandle); #if FEATURE_WASM_THREADS task.ContinueWith(_ => Complete(), TaskScheduler.FromCurrentSynchronizationContext()); @@ -158,36 +174,21 @@ internal void ToJSDynamic(Task? value) void Complete() { -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(promise); -#endif - - // When this task was never resolved/rejected - // promise (held by this lambda) would be collected by GC after the Task is collected - // and would also allow the JS promise to be collected - - try + if (task.Exception != null) + { + RejectPromise(taskHolder, task.Exception); + } + else { - if (task.Exception != null) + if (GetTaskResultDynamic(task, out object? result)) { - FailPromise(promise, task.Exception); + ResolvePromise(taskHolder, result, MarshalResult); } else { - object? result = JSHostImplementation.GetTaskResultDynamic(task); - - ResolvePromise(promise, result, MarshalResult); + ResolveVoidPromise(taskHolder); } } - catch (Exception ex) - { - throw new InvalidOperationException(ex.Message, ex); - } - finally - { - // this should never happen after the task was GC'd - promise.Dispose(); - } } static void MarshalResult(ref JSMarshalerArgument arg, object? taskResult) @@ -210,26 +211,28 @@ public void ToJS(Task? value) slot.Type = MarshalerType.None; return; } - slot.Type = MarshalerType.Task; if (task.IsCompleted) { if (task.Exception != null) { Exception ex = task.Exception; - slot.JSHandle = CreateFailedPromise(ex); + ToJS(ex); + slot.ElementType = slot.Type; + slot.Type = MarshalerType.TaskRejected; return; } else { - slot.JSHandle = IntPtr.Zero; + slot.ElementType = slot.Type; + slot.Type = MarshalerType.TaskResolved; return; } } + slot.Type = MarshalerType.Task; - IntPtr jsHandle = CreatePendingPromise(); - slot.JSHandle = jsHandle; - JSObject promise = JSHostImplementation.CreateCSOwnedProxy(jsHandle); + slot.JSHandle = AllocJSVHandle(); + var taskHolder = new JSObject(slot.JSHandle); #if FEATURE_WASM_THREADS task.ContinueWith(_ => Complete(), TaskScheduler.FromCurrentSynchronizationContext()); @@ -239,33 +242,13 @@ public void ToJS(Task? value) void Complete() { -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(promise); -#endif - - // When this task was never resolved/rejected - // promise (held by this lambda) would be collected by GC after the Task is collected - // and would also allow the JS promise to be collected - - try - { - if (task.Exception != null) - { - FailPromise(promise, task.Exception); - } - else - { - ResolveVoidPromise(promise); - } - } - catch (Exception ex) + if (task.Exception != null) { - throw new InvalidOperationException(ex.Message, ex); + RejectPromise(taskHolder, task.Exception); } - finally + else { - // this should never happen after the task was GC'd - promise.Dispose(); + ResolveVoidPromise(taskHolder); } } } @@ -286,28 +269,29 @@ public void ToJS(Task? value, ArgumentToJSCallback marshaler) slot.Type = MarshalerType.None; return; } - slot.Type = MarshalerType.Task; if (task.IsCompleted) { if (task.Exception != null) { Exception ex = task.Exception; - slot.JSHandle = CreateFailedPromise(ex); + ToJS(ex); + slot.ElementType = slot.Type; + slot.Type = MarshalerType.TaskRejected; return; } else { T result = task.Result; - slot.JSHandle = CreateResolvedPromise(result, marshaler); + ToJS(result); + slot.ElementType = slot.Type; + slot.Type = MarshalerType.TaskResolved; return; } } - - - IntPtr jsHandle = CreatePendingPromise(); - slot.JSHandle = jsHandle; - JSObject promise = JSHostImplementation.CreateCSOwnedProxy(jsHandle); + slot.Type = MarshalerType.Task; + slot.JSHandle = AllocJSVHandle(); + var taskHolder = new JSObject(slot.JSHandle); #if FEATURE_WASM_THREADS task.ContinueWith(_ => Complete(), TaskScheduler.FromCurrentSynchronizationContext()); @@ -317,80 +301,21 @@ public void ToJS(Task? value, ArgumentToJSCallback marshaler) void Complete() { -#if FEATURE_WASM_THREADS - JSObject.AssertThreadAffinity(promise); -#endif - // When this task was never resolved/rejected - // promise (held by this lambda) would be collected by GC after the Task is collected - // and would also allow the JS promise to be collected - - try - { - if (task.Exception != null) - { - FailPromise(promise, task.Exception); - } - else - { - T result = task.Result; - ResolvePromise(promise, result, marshaler); - } - } - catch (Exception ex) + if (task.Exception != null) { - throw new InvalidOperationException(ex.Message, ex); + RejectPromise(taskHolder, task.Exception); } - finally + else { - // this should never happen after the task was GC'd - promise.Dispose(); + T result = task.Result; + ResolvePromise(taskHolder, result, marshaler); } } } - private static IntPtr CreatePendingPromise() + private static void RejectPromise(JSObject holder, Exception ex) { - Span args = stackalloc JSMarshalerArgument[4]; - ref JSMarshalerArgument exc = ref args[0]; - ref JSMarshalerArgument res = ref args[1]; - ref JSMarshalerArgument arg_handle = ref args[2]; - ref JSMarshalerArgument arg_value = ref args[3]; - - exc.Initialize(); - res.Initialize(); - arg_value.Initialize(); - - // should create new promise - arg_handle.slot.Type = MarshalerType.Task; - arg_handle.slot.JSHandle = IntPtr.Zero; - arg_value.slot.Type = MarshalerType.Task; - - JavaScriptImports.MarshalPromise(args); - return res.slot.JSHandle; - } - - private static IntPtr CreateFailedPromise(Exception ex) - { - Span args = stackalloc JSMarshalerArgument[4]; - ref JSMarshalerArgument exc = ref args[0]; - ref JSMarshalerArgument res = ref args[1]; - ref JSMarshalerArgument arg_handle = ref args[2]; - ref JSMarshalerArgument arg_value = ref args[3]; - res.Initialize(); - arg_value.Initialize(); - - // should create new promise - arg_handle.slot.Type = MarshalerType.Task; - arg_handle.slot.JSHandle = IntPtr.Zero; - // should fail it with exception - exc.ToJS(ex); - JavaScriptImports.MarshalPromise(args); - return res.slot.JSHandle; - } - - private static void FailPromise(JSObject promise, Exception ex) - { - ObjectDisposedException.ThrowIf(promise.IsDisposed, promise); + holder.AssertNotDisposed(); Span args = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument exc = ref args[0]; @@ -400,43 +325,22 @@ private static void FailPromise(JSObject promise, Exception ex) exc.Initialize(); res.Initialize(); - arg_value.Initialize(); // should update existing promise - arg_handle.slot.Type = MarshalerType.None; - arg_handle.slot.JSHandle = promise.JSHandle; + arg_handle.slot.Type = MarshalerType.TaskRejected; + arg_handle.slot.JSHandle = holder.JSHandle; // should fail it with exception - exc.ToJS(ex); + arg_value.ToJS(ex); - JavaScriptImports.MarshalPromise(args); - } + JavaScriptImports.ResolveOrRejectPromise(args); - private static IntPtr CreateResolvedPromise(T value, ArgumentToJSCallback marshaler) - { - Span args = stackalloc JSMarshalerArgument[4]; - ref JSMarshalerArgument exc = ref args[0]; - ref JSMarshalerArgument res = ref args[1]; - ref JSMarshalerArgument arg_handle = ref args[2]; - ref JSMarshalerArgument arg_value = ref args[3]; - - exc.Initialize(); - res.Initialize(); - - // should create new promise - arg_handle.slot.Type = MarshalerType.Task; - arg_handle.slot.JSHandle = IntPtr.Zero; - - // and resolve it with value - marshaler(ref arg_value, value); - - JavaScriptImports.MarshalPromise(args); - return res.slot.JSHandle; + holder.DisposeLocal(); } - private static void ResolveVoidPromise(JSObject promise) + private static void ResolveVoidPromise(JSObject holder) { - ObjectDisposedException.ThrowIf(promise.IsDisposed, promise); + holder.AssertNotDisposed(); Span args = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument exc = ref args[0]; @@ -448,17 +352,19 @@ private static void ResolveVoidPromise(JSObject promise) res.Initialize(); // should update existing promise - arg_handle.slot.Type = MarshalerType.None; - arg_handle.slot.JSHandle = promise.JSHandle; + arg_handle.slot.Type = MarshalerType.TaskResolved; + arg_handle.slot.JSHandle = holder.JSHandle; + + arg_value.slot.Type = MarshalerType.Void; - arg_value.slot.Type = MarshalerType.None; + JavaScriptImports.ResolveOrRejectPromise(args); - JavaScriptImports.MarshalPromise(args); + holder.DisposeLocal(); } - private static void ResolvePromise(JSObject promise, T value, ArgumentToJSCallback marshaler) + private static void ResolvePromise(JSObject holder, T value, ArgumentToJSCallback marshaler) { - ObjectDisposedException.ThrowIf(promise.IsDisposed, promise); + holder.AssertNotDisposed(); Span args = stackalloc JSMarshalerArgument[4]; ref JSMarshalerArgument exc = ref args[0]; @@ -470,13 +376,15 @@ private static void ResolvePromise(JSObject promise, T value, ArgumentToJSCal res.Initialize(); // should update existing promise - arg_handle.slot.Type = MarshalerType.None; - arg_handle.slot.JSHandle = promise.JSHandle; + arg_handle.slot.Type = MarshalerType.TaskResolved; + arg_handle.slot.JSHandle = holder.JSHandle; // and resolve it with value marshaler(ref arg_value, value); - JavaScriptImports.MarshalPromise(args); + JavaScriptImports.ResolveOrRejectPromise(args); + + holder.DisposeLocal(); } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index f61ab281e5cb1..18b53e61d920c 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -1010,8 +1010,6 @@ public static Task DisposeAsync() { _module.Dispose(); _module = null; - // you can set verbose: true to see which proxies are left to the GC to collect - ForceDisposeProxies(false, verbose: false); return Task.CompletedTask; } } diff --git a/src/mono/wasm/runtime/cancelable-promise.ts b/src/mono/wasm/runtime/cancelable-promise.ts index 2fbb648c249ff..3ae287bf99e4d 100644 --- a/src/mono/wasm/runtime/cancelable-promise.ts +++ b/src/mono/wasm/runtime/cancelable-promise.ts @@ -3,7 +3,7 @@ import { _lookup_js_owned_object } from "./gc-handles"; import { createPromiseController, loaderHelpers, mono_assert } from "./globals"; -import { TaskCallbackHolder } from "./marshal-to-cs"; +import { PromiseHolder } from "./marshal-to-cs"; import { ControllablePromise, GCHandle } from "./types/internal"; export const _are_promises_supported = ((typeof Promise === "object") || (typeof Promise === "function")) && (typeof Promise.resolve === "function"); @@ -22,12 +22,11 @@ export function wrap_as_cancelable_promise(fn: () => Promise): Controllabl return promise; } -export function mono_wasm_cancel_promise(task_holder_gc_handle: GCHandle): void { - const holder = _lookup_js_owned_object(task_holder_gc_handle) as TaskCallbackHolder; - if (!holder) return; // probably already GC collected +export function mono_wasm_cancel_promise(task_holder_gcv_handle: GCHandle): void { + const holder = _lookup_js_owned_object(task_holder_gcv_handle) as PromiseHolder; + mono_assert(!!holder, () => `Expected Promise for GCVHandle ${task_holder_gcv_handle}`); const promise = holder.promise; - mono_assert(!!promise, () => `Expected Promise for GCHandle ${task_holder_gc_handle}`); loaderHelpers.assertIsControllablePromise(promise); const promise_control = loaderHelpers.getPromiseController(promise); promise_control.reject(new Error("OperationCanceledException")); diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c index e12a0adc379e9..dd15493724e93 100644 --- a/src/mono/wasm/runtime/corebindings.c +++ b/src/mono/wasm/runtime/corebindings.c @@ -22,7 +22,7 @@ extern void mono_wasm_bind_js_function(MonoString **function_name, MonoString ** extern void mono_wasm_invoke_bound_function(int function_js_handle, void *data); extern void mono_wasm_invoke_import(int fn_handle, void *data); 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_marshal_promise(void *data); +extern void mono_wasm_resolve_or_reject_promise(void *data); typedef void (*background_job_cb)(void); void mono_main_thread_schedule_background_job (background_job_cb cb); @@ -67,7 +67,7 @@ void bindings_initialize_internals (void) mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_bound_function); mono_add_internal_call ("Interop/Runtime::InvokeImport", mono_wasm_invoke_import); mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function); - mono_add_internal_call ("Interop/Runtime::MarshalPromise", mono_wasm_marshal_promise); + mono_add_internal_call ("Interop/Runtime::ResolveOrRejectPromise", mono_wasm_resolve_or_reject_promise); mono_add_internal_call ("Interop/Runtime::RegisterGCRoot", mono_wasm_register_root); mono_add_internal_call ("Interop/Runtime::DeregisterGCRoot", mono_wasm_deregister_root); diff --git a/src/mono/wasm/runtime/exports-binding.ts b/src/mono/wasm/runtime/exports-binding.ts index d7ef9ad945d14..5fbfc421a9a57 100644 --- a/src/mono/wasm/runtime/exports-binding.ts +++ b/src/mono/wasm/runtime/exports-binding.ts @@ -11,7 +11,7 @@ import { mono_wasm_bind_js_function, mono_wasm_invoke_bound_function, mono_wasm_ 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"; import { mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue } from "./jiterpreter-jit-call"; -import { mono_wasm_marshal_promise } from "./marshal-to-js"; +import { mono_wasm_resolve_or_reject_promise } from "./marshal-to-js"; import { mono_wasm_eventloop_has_unsettled_interop_promises } from "./pthreads/shared/eventloop"; import { mono_wasm_pthread_on_pthread_attached, mono_wasm_pthread_on_pthread_detached } from "./pthreads/worker"; import { mono_wasm_schedule_timer, schedule_background_exec } from "./scheduling"; @@ -108,7 +108,7 @@ export const mono_wasm_imports = [ mono_wasm_invoke_bound_function, mono_wasm_invoke_import, mono_wasm_bind_cs_function, - mono_wasm_marshal_promise, + mono_wasm_resolve_or_reject_promise, mono_wasm_change_case_invariant, mono_wasm_change_case, mono_wasm_compare_string, diff --git a/src/mono/wasm/runtime/gc-handles.ts b/src/mono/wasm/runtime/gc-handles.ts index 857b73782992b..b1d152dc57bdc 100644 --- a/src/mono/wasm/runtime/gc-handles.ts +++ b/src/mono/wasm/runtime/gc-handles.ts @@ -8,7 +8,7 @@ import { loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { fn_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, JSHandleDisposed, JSHandleNull } from "./types/internal"; +import { GCHandle, GCHandleNull, JSHandle, WeakRefInternal } from "./types/internal"; import { _use_weak_ref, create_weak_ref } from "./weak-ref"; import { exportsByAssembly } from "./invoke-cs"; @@ -18,10 +18,38 @@ let _js_owned_object_registry: FinalizationRegistry; // this is array, not map. We maintain list of gaps in _js_handle_free_list so that it could be as compact as possible // 0th element is always null, because JSHandle == 0 is invalid handle. const _cs_owned_objects_by_js_handle: any[] = [null]; +const _cs_owned_objects_by_jsv_handle: any[] = [null]; const _js_handle_free_list: JSHandle[] = []; let _next_js_handle = 1; -export const _js_owned_object_table = new Map(); +export const _js_owned_object_table = new Map>(); + +const _gcv_handle_free_list: GCHandle[] = []; +let _next_gcv_handle = -2; + +// GCVHandle is like GCHandle, but it's not tracked and allocated by the mono GC, but just by JS. +// It's used when we need to create GCHandle-like identity ahead of time, before calling Mono. +// they have negative values, so that they don't collide with GCHandles. +export function alloc_gcv_handle(): GCHandle { + const gcv_handle = _gcv_handle_free_list.length ? _gcv_handle_free_list.pop() : _next_gcv_handle--; + return gcv_handle as any; +} + +export function free_gcv_handle(gcv_handle: GCHandle): void { + _gcv_handle_free_list.push(gcv_handle); +} + +export function is_jsv_handle(js_handle: JSHandle): boolean { + return (js_handle as any) < -1; +} + +export function is_js_handle(js_handle: JSHandle): boolean { + return (js_handle as any) > 0; +} + +export function is_gcv_handle(gc_handle: GCHandle): boolean { + return (gc_handle as any) < -1; +} // NOTE: FinalizationRegistry and WeakRef are missing on Safari below 14.1 if (_use_finalization_registry) { @@ -34,14 +62,10 @@ export const do_not_force_dispose = Symbol.for("wasm do_not_force_dispose"); export function mono_wasm_get_jsobj_from_js_handle(js_handle: JSHandle): any { - if (js_handle !== JSHandleNull && js_handle !== JSHandleDisposed) + if (is_js_handle(js_handle)) return _cs_owned_objects_by_js_handle[js_handle]; - return null; -} - -export function get_js_obj(js_handle: JSHandle): any { - if (js_handle !== JSHandleNull && js_handle !== JSHandleDisposed) - return mono_wasm_get_jsobj_from_js_handle(js_handle); + if (is_jsv_handle(js_handle)) + return _cs_owned_objects_by_jsv_handle[0 - js_handle]; return null; } @@ -50,8 +74,9 @@ export function mono_wasm_get_js_handle(js_obj: any): JSHandle { return js_obj[cs_owned_js_handle_symbol]; } const js_handle = _js_handle_free_list.length ? _js_handle_free_list.pop() : _next_js_handle++; + // note _cs_owned_objects_by_js_handle is list, not Map. That's why we maintain _js_handle_free_list. - _cs_owned_objects_by_js_handle[js_handle!] = js_obj; + _cs_owned_objects_by_js_handle[js_handle] = js_obj; if (Object.isExtensible(js_obj)) { js_obj[cs_owned_js_handle_symbol] = js_handle; @@ -64,50 +89,67 @@ export function mono_wasm_get_js_handle(js_obj: any): JSHandle { return js_handle as JSHandle; } -export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void { - const obj = _cs_owned_objects_by_js_handle[js_handle]; - if (typeof obj !== "undefined" && obj !== null) { - if (typeof obj[cs_owned_js_handle_symbol] !== "undefined") { - obj[cs_owned_js_handle_symbol] = undefined; - } +export function register_with_jsv_handle(js_obj: any, jsv_handle: JSHandle) { + // note _cs_owned_objects_by_js_handle is list, not Map. That's why we maintain _js_handle_free_list. + _cs_owned_objects_by_jsv_handle[0 - jsv_handle] = js_obj; + + if (Object.isExtensible(js_obj)) { + js_obj[cs_owned_js_handle_symbol] = jsv_handle; + } +} +export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void { + let obj: any; + if (is_js_handle(js_handle)) { + obj = _cs_owned_objects_by_js_handle[js_handle]; _cs_owned_objects_by_js_handle[js_handle] = undefined; _js_handle_free_list.push(js_handle); } + if (is_jsv_handle(js_handle)) { + obj = _cs_owned_objects_by_jsv_handle[0 - js_handle]; + _cs_owned_objects_by_jsv_handle[0 - js_handle] = undefined; + } + mono_assert(obj !== undefined && obj !== null, "ObjectDisposedException"); + if (typeof obj[cs_owned_js_handle_symbol] !== "undefined") { + obj[cs_owned_js_handle_symbol] = undefined; + } } -export function setup_managed_proxy(result: any, gc_handle: GCHandle): void { +export function setup_managed_proxy(owner: any, gc_handle: GCHandle): void { // keep the gc_handle so that we could easily convert it back to original C# object for roundtrip - result[js_owned_gc_handle_symbol] = gc_handle; + owner[js_owned_gc_handle_symbol] = gc_handle; // NOTE: this would be leaking C# objects when the browser doesn't support FinalizationRegistry/WeakRef if (_use_finalization_registry) { // register for GC of the C# object after the JS side is done with the object - _js_owned_object_registry.register(result, gc_handle, result); + _js_owned_object_registry.register(owner, gc_handle, owner); } // register for instance reuse // NOTE: this would be leaking C# objects when the browser doesn't support FinalizationRegistry/WeakRef - const wr = create_weak_ref(result); + const wr = create_weak_ref(owner); _js_owned_object_table.set(gc_handle, wr); } -export function teardown_managed_proxy(result: any, gc_handle: GCHandle): void { +export function teardown_managed_proxy(owner: any, gc_handle: GCHandle, skipManaged?: boolean): void { // The JS object associated with this gc_handle has been collected by the JS GC. // As such, it's not possible for this gc_handle to be invoked by JS anymore, so // we can release the tracking weakref (it's null now, by definition), // and tell the C# side to stop holding a reference to the managed object. // "The FinalizationRegistry callback is called potentially multiple times" - if (result) { - gc_handle = result[js_owned_gc_handle_symbol]; - result[js_owned_gc_handle_symbol] = GCHandleNull; + if (owner) { + gc_handle = owner[js_owned_gc_handle_symbol]; + owner[js_owned_gc_handle_symbol] = GCHandleNull; if (_use_finalization_registry) { - _js_owned_object_registry.unregister(result); + _js_owned_object_registry.unregister(owner); } } - if (gc_handle !== GCHandleNull && _js_owned_object_table.delete(gc_handle)) { + if (gc_handle !== GCHandleNull && _js_owned_object_table.delete(gc_handle) && !skipManaged) { runtimeHelpers.javaScriptExports.release_js_owned_object_by_gc_handle(gc_handle); } + if (is_gcv_handle(gc_handle)) { + free_gcv_handle(gc_handle); + } } export function assert_not_disposed(result: any): GCHandle { @@ -129,9 +171,9 @@ export function _lookup_js_owned_object(gc_handle: GCHandle): any { return null; const wr = _js_owned_object_table.get(gc_handle); if (wr) { - return wr.deref(); - // TODO: could this be null before _js_owned_object_finalized was called ? + // this could be null even before _js_owned_object_finalized was called // TODO: are there race condition consequences ? + return wr.deref(); } return null; } @@ -140,12 +182,13 @@ export function assertNoProxies(): void { if (!MonoWasmThreads) return; mono_assert(_js_owned_object_table.size === 0, "There should be no proxies on this thread."); mono_assert(_cs_owned_objects_by_js_handle.length === 1, "There should be no proxies on this thread."); + mono_assert(_cs_owned_objects_by_jsv_handle.length === 1, "There should be no proxies on this thread."); mono_assert(exportsByAssembly.size === 0, "There should be no exports on this thread."); mono_assert(fn_wrapper_by_fn_handle.length === 1, "There should be no imports on this thread."); } -// when we arrive here, the C# side is already done with the object. -// We don't have to call back to release them. +// when we arrive here from UninstallWebWorkerInterop, the C# will unregister the handles too. +// when called from elsewhere, C# side could be unbalanced!! export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): void { let keepSomeCsAlive = false; let keepSomeJsAlive = false; @@ -158,7 +201,7 @@ export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): const gc_handles = [..._js_owned_object_table.keys()]; for (const gc_handle of gc_handles) { const wr = _js_owned_object_table.get(gc_handle); - const obj = wr.deref(); + const obj = wr && wr.deref(); if (_use_finalization_registry && obj) { _js_owned_object_registry.unregister(obj); } @@ -184,7 +227,7 @@ export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): if (obj[js_owned_gc_handle_symbol] === gc_handle) { obj[js_owned_gc_handle_symbol] = GCHandleNull; } - if (!_use_weak_ref && wr) wr.dispose(); + if (!_use_weak_ref && wr) wr.dispose!(); doneGCHandles++; } else { keepSomeCsAlive = true; @@ -197,13 +240,11 @@ export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): _js_owned_object_registry = new globalThis.FinalizationRegistry(_js_owned_object_finalized); } } - - // dispose all proxies to JS objects - for (let js_handle = 0; js_handle < _cs_owned_objects_by_js_handle.length; js_handle++) { - const obj = _cs_owned_objects_by_js_handle[js_handle]; + const free_js_handle = (js_handle: number, list: any[]): void => { + const obj = list[js_handle]; const keepAlive = obj && typeof obj[do_not_force_dispose] === "boolean" && obj[do_not_force_dispose]; if (!keepAlive) { - _cs_owned_objects_by_js_handle[js_handle] = undefined; + list[js_handle] = undefined; } if (obj) { if (verbose) { @@ -230,12 +271,22 @@ export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): keepSomeJsAlive = true; } } + }; + // dispose all proxies to JS objects + for (let js_handle = 0; js_handle < _cs_owned_objects_by_js_handle.length; js_handle++) { + free_js_handle(js_handle, _cs_owned_objects_by_js_handle); + } + for (let jsv_handle = 0; jsv_handle < _cs_owned_objects_by_jsv_handle.length; jsv_handle++) { + free_js_handle(jsv_handle, _cs_owned_objects_by_jsv_handle); } if (!keepSomeJsAlive) { _cs_owned_objects_by_js_handle.length = 1; + _cs_owned_objects_by_jsv_handle.length = 1; _next_js_handle = 1; _js_handle_free_list.length = 0; } + _gcv_handle_free_list.length = 0; + _next_gcv_handle = -2; if (disposeMethods) { // dispose all [JSImport] diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index b38393a6fe6f4..65a0f160d7e3c 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -28,7 +28,7 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, const mark = startMeasure(); try { const version = get_signature_version(signature); - mono_assert(version === 1, () => `Signature version ${version} mismatch.`); + mono_assert(version === 2, () => `Signature version ${version} mismatch.`); const args_count = get_signature_argument_count(signature); const js_fqn = monoStringToString(fqn_root)!; diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index a17ef89be3f06..844c0f4827a47 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -28,7 +28,7 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ resultRoot = mono_wasm_new_external_root(result_address); try { const version = get_signature_version(signature); - mono_assert(version === 1, () => `Signature version ${version} mismatch.`); + mono_assert(version === 2, () => `Signature version ${version} mismatch.`); const js_function_name = monoStringToString(function_name_root)!; const mark = startMeasure(); diff --git a/src/mono/wasm/runtime/loader/promise-controller.ts b/src/mono/wasm/runtime/loader/promise-controller.ts index 2a0f0a7a4144f..b27c8216f4fc3 100644 --- a/src/mono/wasm/runtime/loader/promise-controller.ts +++ b/src/mono/wasm/runtime/loader/promise-controller.ts @@ -51,5 +51,5 @@ export function isControllablePromise(promise: Promise): promise is Contro } export function assertIsControllablePromise(promise: Promise): asserts promise is ControllablePromise { - mono_assert(isControllablePromise(promise), "Promise is not controllable"); + mono_assert(promise && isControllablePromise(promise), "Promise is not controllable"); } diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index 49b16f7ae3477..fee9ed61fecca 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -6,7 +6,7 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import { GCHandle, 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, get_arg_gc_handle, set_arg_type, set_gc_handle } from "./marshal"; +import { alloc_stack_frame, get_arg, set_arg_type, set_gc_handle } from "./marshal"; import { invoke_method_and_handle_exception } 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, marshal_string_to_js, marshal_task_to_js } from "./marshal-to-js"; @@ -30,8 +30,6 @@ export function init_managed_exports(): void { 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 create_task_callback_method = get_method("CreateTaskCallback"); - mono_assert(create_task_callback_method, "Can't find CreateTaskCallback 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"); @@ -110,26 +108,14 @@ export function init_managed_exports(): void { Module.stackRestore(sp); } }; - runtimeHelpers.javaScriptExports.create_task_callback = () => { - const sp = Module.stackSave(); - loaderHelpers.assert_runtime_running(); - try { - const args = alloc_stack_frame(2); - invoke_method_and_handle_exception(create_task_callback_method, args); - const res = get_arg(args, 1); - return get_arg_gc_handle(res); - } finally { - Module.stackRestore(sp); - } - }; - runtimeHelpers.javaScriptExports.complete_task = (holder_gc_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs) => { + runtimeHelpers.javaScriptExports.complete_task = (holder_gcv_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs) => { loaderHelpers.assert_runtime_running(); const sp = Module.stackSave(); try { const args = alloc_stack_frame(5); const arg1 = get_arg(args, 2); set_arg_type(arg1, MarshalerType.Object); - set_gc_handle(arg1, holder_gc_handle); + set_gc_handle(arg1, holder_gcv_handle); const arg2 = get_arg(args, 3); if (error) { marshal_exception_to_cs(arg2, error); diff --git a/src/mono/wasm/runtime/marshal-to-cs.ts b/src/mono/wasm/runtime/marshal-to-cs.ts index 14d6c7e3dd747..493ff67c8f02b 100644 --- a/src/mono/wasm/runtime/marshal-to-cs.ts +++ b/src/mono/wasm/runtime/marshal-to-cs.ts @@ -6,23 +6,22 @@ import BuildConfiguration from "consts:configuration"; import { isThenable } from "./cancelable-promise"; import cwraps from "./cwraps"; -import { 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 { 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 { 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, set_arg_length, get_arg, get_signature_arg1_type, get_signature_arg2_type, js_to_cs_marshalers, get_signature_res_type, bound_js_function_symbol, set_arg_u16, array_element_size, - get_string_root, Span, ArraySegment, MemoryViewType, get_signature_arg3_type, set_arg_i64_big, set_arg_intptr, IDisposable, + get_string_root, Span, ArraySegment, MemoryViewType, get_signature_arg3_type, set_arg_i64_big, set_arg_intptr, set_arg_element_type, ManagedObject, JavaScriptMarshalerArgSize, proxy_debug_symbol } from "./marshal"; import { get_marshaler_to_js_by_type } from "./marshal-to-js"; import { _zero_region, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; import { stringToMonoStringRoot } from "./strings"; -import { GCHandle, GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToCs, MarshalerType } from "./types/internal"; +import { JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToCs, MarshalerType } from "./types/internal"; import { TypedArray } from "./types/emscripten"; import { addUnsettledPromise, settleUnsettledPromise } from "./pthreads/shared/eventloop"; -import { mono_log_warn } from "./logging"; export const jsinteropDoc = "For more information see https://aka.ms/dotnet-wasm-jsinterop"; @@ -49,6 +48,8 @@ export function initialize_marshalers_to_cs(): void { js_to_cs_marshalers.set(MarshalerType.JSObject, marshal_js_object_to_cs); js_to_cs_marshalers.set(MarshalerType.Object, _marshal_cs_object_to_cs); js_to_cs_marshalers.set(MarshalerType.Task, _marshal_task_to_cs); + js_to_cs_marshalers.set(MarshalerType.TaskResolved, _marshal_task_to_cs); + js_to_cs_marshalers.set(MarshalerType.TaskRejected, _marshal_task_to_cs); js_to_cs_marshalers.set(MarshalerType.Action, _marshal_function_to_cs); js_to_cs_marshalers.set(MarshalerType.Function, _marshal_function_to_cs); js_to_cs_marshalers.set(MarshalerType.None, _marshal_null_to_cs);// also void @@ -291,19 +292,9 @@ function _marshal_function_to_cs(arg: JSMarshalerArgument, value: Function, _?: set_arg_type(arg, MarshalerType.Function);//TODO or action ? } -export class TaskCallbackHolder implements IDisposable { - public promise: Promise; - - public constructor(promise: Promise) { - this.promise = promise; - } - - dispose(): void { - teardown_managed_proxy(this, GCHandleNull); - } - - get isDisposed(): boolean { - return (this)[js_owned_gc_handle_symbol] === GCHandleNull; +export class PromiseHolder extends ManagedObject { + public constructor(public promise: Promise) { + super(); } } @@ -314,13 +305,13 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: } mono_check(isThenable(value), "Value is not a Promise"); - const gc_handle: GCHandle = runtimeHelpers.javaScriptExports.create_task_callback(); + const gc_handle = alloc_gcv_handle(); set_gc_handle(arg, gc_handle); set_arg_type(arg, MarshalerType.Task); - const holder = new TaskCallbackHolder(value); + const holder = new PromiseHolder(value); setup_managed_proxy(holder, gc_handle); if (BuildConfiguration === "Debug") { - (holder as any)[proxy_debug_symbol] = `C# Task with GCHandle ${gc_handle}`; + (holder as any)[proxy_debug_symbol] = `PromiseHolder with GCVHandle ${gc_handle}`; } if (MonoWasmThreads) @@ -333,10 +324,10 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: if (MonoWasmThreads) settleUnsettledPromise(); runtimeHelpers.javaScriptExports.complete_task(gc_handle, null, data, res_converter || _marshal_cs_object_to_cs); - teardown_managed_proxy(holder, gc_handle); // this holds holder alive for finalizer, until the promise is freed, (holding promise instead would not work) + teardown_managed_proxy(holder, gc_handle, true); // this holds holder alive for finalizer, until the promise is freed, (holding promise instead would not work) } catch (ex) { - mono_log_warn("Exception marshalling result of JS promise to CS: ", ex); + runtimeHelpers.abort(ex); } }).catch(reason => { try { @@ -345,12 +336,10 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: if (MonoWasmThreads) settleUnsettledPromise(); runtimeHelpers.javaScriptExports.complete_task(gc_handle, reason, null, undefined); - teardown_managed_proxy(holder, gc_handle); // this holds holder alive for finalizer, until the promise is freed + teardown_managed_proxy(holder, gc_handle, true); // this holds holder alive for finalizer, until the promise is freed } catch (ex) { - if (!loaderHelpers.is_exited()) { - mono_log_warn("Exception marshalling error of JS promise to CS: ", ex); - } + runtimeHelpers.abort(ex); } }); } diff --git a/src/mono/wasm/runtime/marshal-to-js.ts b/src/mono/wasm/runtime/marshal-to-js.ts index 1ad71dbc4ae78..21af5460110ee 100644 --- a/src/mono/wasm/runtime/marshal-to-js.ts +++ b/src/mono/wasm/runtime/marshal-to-js.ts @@ -5,20 +5,20 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import BuildConfiguration from "consts:configuration"; import cwraps from "./cwraps"; -import { _lookup_js_owned_object, mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles"; -import { Module, createPromiseController, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; +import { _lookup_js_owned_object, 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 { 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, - get_arg_b8, get_arg_date, get_arg_length, set_js_handle, get_arg, set_arg_type, + get_arg_b8, get_arg_date, get_arg_length, get_arg, set_arg_type, get_signature_arg2_type, get_signature_arg1_type, cs_to_js_marshalers, get_signature_res_type, get_arg_u16, array_element_size, get_string_root, ArraySegment, Span, MemoryViewType, get_signature_arg3_type, get_arg_i64_big, get_arg_intptr, get_arg_element_type, JavaScriptMarshalerArgSize, proxy_debug_symbol } from "./marshal"; import { monoStringToString } from "./strings"; -import { JSHandleNull, GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs, MarshalerType } from "./types/internal"; +import { GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs, MarshalerType } from "./types/internal"; import { TypedArray } from "./types/emscripten"; -import { get_marshaler_to_cs_by_type, jsinteropDoc } from "./marshal-to-cs"; +import { get_marshaler_to_cs_by_type, jsinteropDoc, marshal_exception_to_cs } from "./marshal-to-cs"; import { localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; export function initialize_marshalers_to_js(): void { @@ -44,6 +44,8 @@ export function initialize_marshalers_to_js(): void { cs_to_js_marshalers.set(MarshalerType.DateTime, _marshal_datetime_to_js); cs_to_js_marshalers.set(MarshalerType.DateTimeOffset, _marshal_datetime_to_js); cs_to_js_marshalers.set(MarshalerType.Task, marshal_task_to_js); + cs_to_js_marshalers.set(MarshalerType.TaskRejected, marshal_task_to_js); + cs_to_js_marshalers.set(MarshalerType.TaskResolved, marshal_task_to_js); cs_to_js_marshalers.set(MarshalerType.Action, _marshal_delegate_to_js); cs_to_js_marshalers.set(MarshalerType.Function, _marshal_delegate_to_js); cs_to_js_marshalers.set(MarshalerType.None, _marshal_null_to_js); @@ -212,107 +214,95 @@ function _marshal_delegate_to_js(arg: JSMarshalerArgument, _?: MarshalerType, re return result; } +export class TaskHolder { + constructor(public resolve_or_reject: (type: MarshalerType, argInner: JSMarshalerArgument) => void) { + } +} + export function marshal_task_to_js(arg: JSMarshalerArgument, _?: MarshalerType, res_converter?: MarshalerToJs): Promise | null { const type = get_arg_type(arg); if (type === MarshalerType.None) { return null; } - - if (type !== MarshalerType.Task) { - + if (type === MarshalerType.TaskRejected) { + return Promise.reject(marshal_exception_to_js(arg)); + } + if (type === MarshalerType.TaskResolved) { + const element_type = get_arg_element_type(arg); + if (element_type === MarshalerType.Void) { + return Promise.resolve(); + } + // this will change the type to the actual type of the result + set_arg_type(arg, element_type); if (!res_converter) { // when we arrived here from _marshal_cs_object_to_js - res_converter = cs_to_js_marshalers.get(type); + res_converter = cs_to_js_marshalers.get(element_type); } - mono_assert(res_converter, () => `Unknown sub_converter for type ${MarshalerType[type]}. ${jsinteropDoc}`); + mono_assert(res_converter, () => `Unknown sub_converter for type ${MarshalerType[element_type]}. ${jsinteropDoc}`); - // this is already resolved const val = res_converter(arg); - return new Promise((resolve) => resolve(val)); + return Promise.resolve(val); } - const js_handle = get_arg_js_handle(arg); - if (js_handle == JSHandleNull) { - // this is already resolved void - return new Promise((resolve) => resolve(undefined)); - } - const promise = mono_wasm_get_jsobj_from_js_handle(js_handle); - mono_assert(!!promise, () => `ERR28: promise not found for js_handle: ${js_handle} `); - if (BuildConfiguration === "Debug") { - (promise as any)[proxy_debug_symbol] = `JS Promise with JSHandle ${js_handle}`; - } - loaderHelpers.assertIsControllablePromise(promise); - const promise_control = loaderHelpers.getPromiseController(promise); + const jsv_handle = get_arg_js_handle(arg); + const { promise, promise_control } = loaderHelpers.createPromiseController(); - const orig_resolve = promise_control.resolve; - promise_control.resolve = (argInner: JSMarshalerArgument) => { - const type = get_arg_type(argInner); - if (type === MarshalerType.None) { - orig_resolve(null); - return; + const holder = new TaskHolder((type, argInner) => { + if (type === MarshalerType.TaskRejected) { + const reason = marshal_exception_to_js(argInner); + promise_control.reject(reason); + } else if (type === MarshalerType.TaskResolved) { + const type = get_arg_type(argInner); + if (type === MarshalerType.Void) { + promise_control.resolve(undefined); + } else { + if (!res_converter) { + // when we arrived here from _marshal_cs_object_to_js + res_converter = cs_to_js_marshalers.get(type); + } + mono_assert(res_converter, () => `Unknown sub_converter for type ${MarshalerType[type]}. ${jsinteropDoc}`); + + const js_value = res_converter!(argInner); + promise_control.resolve(js_value); + } } - - if (!res_converter) { - // when we arrived here from _marshal_cs_object_to_js - res_converter = cs_to_js_marshalers.get(type); + else { + mono_assert(false, () => `Unexpected type ${MarshalerType[type]}`); } - mono_assert(res_converter, () => `Unknown sub_converter for type ${MarshalerType[type]}. ${jsinteropDoc}`); + mono_wasm_release_cs_owned_object(jsv_handle); + }); + if (BuildConfiguration === "Debug") { + (holder as any)[proxy_debug_symbol] = `TaskHolder with JSVHandle ${jsv_handle}`; + } - const js_value = res_converter!(argInner); - orig_resolve(js_value); - }; + register_with_jsv_handle(holder, jsv_handle); return promise; } -export function mono_wasm_marshal_promise(args: JSMarshalerArguments): void { +export function mono_wasm_resolve_or_reject_promise(args: JSMarshalerArguments): void { const exc = get_arg(args, 0); - const res = get_arg(args, 1); - const arg_handle = get_arg(args, 2); - const arg_value = get_arg(args, 3); - - const exc_type = get_arg_type(exc); - const value_type = get_arg_type(arg_value); - const js_handle = get_arg_js_handle(arg_handle); - - if (js_handle === JSHandleNull) { - const { promise, promise_control } = createPromiseController(); - const js_handle = mono_wasm_get_js_handle(promise)!; - if (BuildConfiguration === "Debug" && Object.isExtensible(promise)) { - (promise as any)[proxy_debug_symbol] = `JS Promise with JSHandle ${js_handle}`; - } - set_js_handle(res, js_handle); + try { + loaderHelpers.assert_runtime_running(); - if (exc_type !== MarshalerType.None) { - // this is already failed task - const reason = marshal_exception_to_js(exc); - promise_control.reject(reason); - } - else if (value_type !== MarshalerType.Task) { - // this is already resolved task - const sub_converter = cs_to_js_marshalers.get(value_type); - mono_assert(sub_converter, () => `Unknown sub_converter for type ${MarshalerType[value_type]}. ${jsinteropDoc}`); - const data = sub_converter(arg_value); - promise_control.resolve(data); - } - } else { - // resolve existing promise - const promise = mono_wasm_get_jsobj_from_js_handle(js_handle); - mono_assert(!!promise, () => `ERR25: promise not found for js_handle: ${js_handle} `); - loaderHelpers.assertIsControllablePromise(promise); - const promise_control = loaderHelpers.getPromiseController(promise); - - if (exc_type !== MarshalerType.None) { - const reason = marshal_exception_to_js(exc); - promise_control.reject(reason); - } - else if (value_type !== MarshalerType.Task) { - // here we assume that resolve was wrapped with sub_converter inside _marshal_task_to_js - promise_control.resolve(arg_value); - } + const res = get_arg(args, 1); + const arg_handle = get_arg(args, 2); + const arg_value = get_arg(args, 3); + + const type = get_arg_type(arg_handle); + const jsv_handle = get_arg_js_handle(arg_handle); + + const holder = mono_wasm_get_jsobj_from_js_handle(jsv_handle) as TaskHolder; + mono_assert(holder, () => `Cannot find Promise for JSVHandle ${jsv_handle}`); + + holder.resolve_or_reject(type, arg_value); + + set_arg_type(res, MarshalerType.Void); + set_arg_type(exc, MarshalerType.None); + + } catch (ex: any) { + marshal_exception_to_cs(exc, ex); } - set_arg_type(res, MarshalerType.Task); - set_arg_type(exc, MarshalerType.None); } export function marshal_string_to_js(arg: JSMarshalerArgument): string | null { diff --git a/src/mono/wasm/runtime/marshal.ts b/src/mono/wasm/runtime/marshal.ts index 262051dab6fb3..07afc1b2f67e4 100644 --- a/src/mono/wasm/runtime/marshal.ts +++ b/src/mono/wasm/runtime/marshal.ts @@ -8,7 +8,7 @@ import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; 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 { CharPtr, TypedArray, VoidPtr } from "./types/emscripten"; +import { TypedArray, VoidPtr } from "./types/emscripten"; export const cs_to_js_marshalers = new Map(); export const js_to_cs_marshalers = new Map(); @@ -17,28 +17,6 @@ export const bound_js_function_symbol = Symbol.for("wasm bound_js_function"); export const imported_js_function_symbol = Symbol.for("wasm imported_js_function"); export const proxy_debug_symbol = Symbol.for("wasm proxy_debug"); -/** - * JSFunctionSignature is pointer to [ - * Version: number, - * ArgumentCount: number, - * exc: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} - * res: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} - * arg1: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} - * arg2: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} - * ... - * ] - * - * Layout of the call stack frame buffers is array of JSMarshalerArgument - * JSMarshalerArguments is pointer to [ - * exc: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, - * res: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, - * arg1: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, - * arg2: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, - * ... - * ] - */ - - export const JavaScriptMarshalerArgSize = 16; export const JSMarshalerTypeSize = 32; export const JSMarshalerSignatureHeaderSize = 4 + 4; // without Exception and Result @@ -71,37 +49,27 @@ export function get_sig(signature: JSFunctionSignature, index: number): JSMarsha export function get_signature_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null sig"); - return getU32(sig); + return getU8(sig); } export function get_signature_res_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null sig"); - return getU32(sig + 16); -} - -export function get_signature_custom_code(sig: JSMarshalerType): CharPtr { - mono_assert(sig, "Null sig"); - return getU32(sig + 8); -} - -export function get_signature_custom_code_len(sig: JSMarshalerType): number { - mono_assert(sig, "Null sig"); - return getU32(sig + 12); + return getU8(sig + 16); } export function get_signature_arg1_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null sig"); - return getU32(sig + 20); + return getU8(sig + 20); } export function get_signature_arg2_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null sig"); - return getU32(sig + 24); + return getU8(sig + 24); } export function get_signature_arg3_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null sig"); - return getU32(sig + 28); + return getU8(sig + 28); } export function get_signature_argument_count(signature: JSFunctionSignature): number { @@ -116,29 +84,29 @@ export function get_signature_version(signature: JSFunctionSignature): number { export function get_sig_type(sig: JSMarshalerType): MarshalerType { mono_assert(sig, "Null signatures"); - return getU32(sig); + return getU8(sig); } export function get_arg_type(arg: JSMarshalerArgument): MarshalerType { mono_assert(arg, "Null arg"); - const type = getU32(arg + 12); + const type = getU8(arg + 12); return type; } export function get_arg_element_type(arg: JSMarshalerArgument): MarshalerType { mono_assert(arg, "Null arg"); - const type = getU32(arg + 4); + const type = getU8(arg + 13); return type; } export function set_arg_type(arg: JSMarshalerArgument, type: MarshalerType): void { mono_assert(arg, "Null arg"); - setU32(arg + 12, type); + setU8(arg + 12, type); } export function set_arg_element_type(arg: JSMarshalerArgument, type: MarshalerType): void { mono_assert(arg, "Null arg"); - setU32(arg + 4, type); + setU8(arg + 13, type); } export function get_arg_b8(arg: JSMarshalerArgument): boolean { @@ -168,7 +136,7 @@ export function get_arg_i32(arg: JSMarshalerArgument): number { export function get_arg_intptr(arg: JSMarshalerArgument): number { mono_assert(arg, "Null arg"); - return getU32(arg); + return getI32(arg); } export function get_arg_i52(arg: JSMarshalerArgument): number { @@ -227,7 +195,7 @@ export function set_arg_i32(arg: JSMarshalerArgument, value: number): void { export function set_arg_intptr(arg: JSMarshalerArgument, value: VoidPtr): void { mono_assert(arg, "Null arg"); - setU32(arg, value); + setI32(arg, value); } export function set_arg_i52(arg: JSMarshalerArgument, value: number): void { @@ -261,22 +229,22 @@ export function set_arg_f32(arg: JSMarshalerArgument, value: number): void { export function get_arg_js_handle(arg: JSMarshalerArgument): JSHandle { mono_assert(arg, "Null arg"); - return getU32(arg + 4); + return getI32(arg + 4); } export function set_js_handle(arg: JSMarshalerArgument, jsHandle: JSHandle): void { mono_assert(arg, "Null arg"); - setU32(arg + 4, jsHandle); + setI32(arg + 4, jsHandle); } export function get_arg_gc_handle(arg: JSMarshalerArgument): GCHandle { mono_assert(arg, "Null arg"); - return getU32(arg + 4); + return getI32(arg + 4); } export function set_gc_handle(arg: JSMarshalerArgument, gcHandle: GCHandle): void { mono_assert(arg, "Null arg"); - setU32(arg + 4, gcHandle); + setI32(arg + 4, gcHandle); } export function get_string_root(arg: JSMarshalerArgument): WasmRoot { diff --git a/src/mono/wasm/runtime/net6-legacy/method-calls.ts b/src/mono/wasm/runtime/net6-legacy/method-calls.ts index 825e5e7d4107f..a2033b256f000 100644 --- a/src/mono/wasm/runtime/net6-legacy/method-calls.ts +++ b/src/mono/wasm/runtime/net6-legacy/method-calls.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { get_js_obj, mono_wasm_get_jsobj_from_js_handle } from "../gc-handles"; +import { mono_wasm_get_jsobj_from_js_handle } from "../gc-handles"; import { Module, INTERNAL, loaderHelpers } from "../globals"; import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; import { _release_temp_frame } from "../memory"; @@ -103,7 +103,7 @@ export function mono_wasm_invoke_js_with_args_ref(js_handle: JSHandle, method_na return; } - const obj = get_js_obj(js_handle); + const obj = mono_wasm_get_jsobj_from_js_handle(js_handle); if (is_nullish(obj)) { wrap_error_root(is_exception, "ERR13: Invalid JS object handle '" + js_handle + "' while invoking '" + js_name + "'", resultRoot); return; diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 8167bb328b824..840edbabe308e 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -338,11 +338,8 @@ 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: GCHandle CreateTaskCallback() - create_task_callback(): GCHandle; - // the marshaled signature is: void CompleteTask(GCHandle holder, Exception? exceptionResult, T? result) - complete_task(holder_gc_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs): void; + complete_task(holder_gcv_handle: GCHandle, 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, @@ -400,6 +397,8 @@ export enum MarshalerType { // only on runtime JSException, + TaskResolved, + TaskRejected, } export interface JSMarshalerArguments extends NativePointer { @@ -513,3 +512,7 @@ export type RuntimeModuleExportsInternal = { export type NativeModuleExportsInternal = { default: (unificator: Function) => EmscriptenModuleInternal } + +export type WeakRefInternal = WeakRef & { + dispose?: () => void +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/weak-ref.ts b/src/mono/wasm/runtime/weak-ref.ts index f5c6187f12d82..ebb4ab7f8e08b 100644 --- a/src/mono/wasm/runtime/weak-ref.ts +++ b/src/mono/wasm/runtime/weak-ref.ts @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { WeakRefInternal } from "./types/internal"; + export const _use_weak_ref = typeof globalThis.WeakRef === "function"; -export function create_weak_ref(js_obj: T): WeakRef { +export function create_weak_ref(js_obj: T): WeakRefInternal { if (_use_weak_ref) { return new WeakRef(js_obj); }