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 699e61d792d0a..66788de91dd0a 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 @@ -1,4 +1,8 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34004.107 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{ED86AB26-1CFB-457D-BF87-B7A0D8FAF272}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Collections", "..\System.Collections\ref\System.Collections.csproj", "{8B1D80E9-AE0D-4E3C-9F91-E6862B49AEF3}" @@ -43,11 +47,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{569E6837-077 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{67F3A00A-AE6C-434C-927D-E5D38DE2DA2C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "tools\gen", "{39B30F44-B141-44E9-B7A7-B1A9EDB1A61C}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{39B30F44-B141-44E9-B7A7-B1A9EDB1A61C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "tools\src", "{D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{32CDDDCD-5319-494C-AB41-42B87C467F04}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{32CDDDCD-5319-494C-AB41-42B87C467F04}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{F92020A9-28BE-4398-86FF-5CFE44C94882}" EndProject @@ -135,28 +139,32 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {ED86AB26-1CFB-457D-BF87-B7A0D8FAF272} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} + {8B1D80E9-AE0D-4E3C-9F91-E6862B49AEF3} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF} + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1} = {569E6837-0771-4C08-BB09-460281030538} + {28278E01-BF5C-4AB6-AA7A-8DD4E6C04DB1} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF} + {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45} = {67F3A00A-AE6C-434C-927D-E5D38DE2DA2C} {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} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF} - {28278E01-BF5C-4AB6-AA7A-8DD4E6C04DB1} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF} - {44BAE6F1-94C2-415B-9A16-3B8EC429B09B} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF} - {CE5E53C1-F9B5-41EE-8D00-837913EC57D1} = {569E6837-0771-4C08-BB09-460281030538} {FB12C247-AFEF-4772-BB0C-983969B6CF32} = {569E6837-0771-4C08-BB09-460281030538} {09AA6758-0BD3-4312-9C07-AE9F1D50A3AD} = {569E6837-0771-4C08-BB09-460281030538} {B4E3E774-2C16-4CBF-87EF-88C547529B94} = {569E6837-0771-4C08-BB09-460281030538} - {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45} = {67F3A00A-AE6C-434C-927D-E5D38DE2DA2C} + {EC3ADEFA-1FF3-482C-8CCB-FE4C77292532} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} + {44BAE6F1-94C2-415B-9A16-3B8EC429B09B} = {1DFF019B-6B73-4E5A-A6DA-5EBEF4AA7EBF} {4D8B7538-D933-4F3A-818D-4E19ABA7E182} = {39B30F44-B141-44E9-B7A7-B1A9EDB1A61C} {6C60944F-4FE1-450F-884B-D523EDFCFAB3} = {39B30F44-B141-44E9-B7A7-B1A9EDB1A61C} - {39B30F44-B141-44E9-B7A7-B1A9EDB1A61C} = {F92020A9-28BE-4398-86FF-5CFE44C94882} {B8F2E56D-6571-466D-9EF2-9FCAD3FC6E5B} = {D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A} {42F9A600-BEC3-4F87-97EE-38E0DCAABC5A} = {D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A} - {D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A} = {F92020A9-28BE-4398-86FF-5CFE44C94882} {008873D5-9028-4FF3-8354-71F713748625} = {32CDDDCD-5319-494C-AB41-42B87C467F04} + {39B30F44-B141-44E9-B7A7-B1A9EDB1A61C} = {F92020A9-28BE-4398-86FF-5CFE44C94882} + {D819DEB6-6F6B-484B-9F0F-BDBCEDC83A1A} = {F92020A9-28BE-4398-86FF-5CFE44C94882} {32CDDDCD-5319-494C-AB41-42B87C467F04} = {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/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs index 3f3573f5f26c1..bc34b5f9fc27a 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 gcvHandle); + private static partial void _CancelPromise(IntPtr gcHandle); public static void CancelPromise(Task promise) { @@ -26,7 +26,7 @@ public static void CancelPromise(Task promise) holder.SynchronizationContext!.Send(static (JSHostImplementation.PromiseHolder holder) => { #endif - _CancelPromise(holder.GCVHandle); + _CancelPromise(holder.GCHandle); #if FEATURE_WASM_THREADS }, holder); #endif @@ -47,7 +47,7 @@ public static void CancelPromise(Task promise, Action callback, T state) holder.SynchronizationContext!.Send((JSHostImplementation.PromiseHolder holder) => { #endif - _CancelPromise(holder.GCVHandle); + _CancelPromise(holder.GCHandle); 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 85683f3f4f75b..747ebdbcb7d10 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 @@ -143,15 +143,27 @@ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments try { var gcHandle = arg_1.slot.GCHandle; - if (IsGCVHandle(gcHandle) && ThreadJsOwnedHolders.Remove(gcHandle, out PromiseHolder? holder)) + if (IsGCVHandle(gcHandle)) { - holder.GCVHandle = IntPtr.Zero; - holder.Callback!(null); + if (ThreadJsOwnedHolders.Remove(gcHandle, out PromiseHolder? holder)) + { + holder.GCHandle = IntPtr.Zero; + holder.Callback!(null); + } } else { GCHandle handle = (GCHandle)gcHandle; - ThreadJsOwnedObjects.Remove(handle.Target!); + var target = handle.Target!; + if (target is PromiseHolder holder) + { + holder.GCHandle = IntPtr.Zero; + holder.Callback!(null); + } + else + { + ThreadJsOwnedObjects.Remove(target); + } handle.Free(); } } @@ -191,7 +203,7 @@ public static void CallDelegate(JSMarshalerArgument* arguments_buffer) } // the marshaled signature is: - // void CompleteTask(GCVHandle holder, Exception? exceptionResult, T? result) + // void CompleteTask(GCHandle 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() @@ -200,17 +212,31 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer) // arg_3 set by caller when this is SetResult call try { - var callback_gcv_handle = arg_1.slot.GCHandle; - if (ThreadJsOwnedHolders.Remove(callback_gcv_handle, out PromiseHolder? promiseHolder) && promiseHolder.Callback != null) + var holderGCHandle = arg_1.slot.GCHandle; + if (IsGCVHandle(holderGCHandle)) { - promiseHolder.GCVHandle = IntPtr.Zero; - - // arg_2, arg_3 are processed by the callback - promiseHolder.Callback(arguments_buffer); + if (ThreadJsOwnedHolders.Remove(holderGCHandle, out PromiseHolder? holder)) + { + holder.GCHandle = IntPtr.Zero; + // arg_2, arg_3 are processed by the callback + holder.Callback!(arguments_buffer); + } } else { - throw new InvalidOperationException(SR.NullPromiseHolder); + GCHandle handle = (GCHandle)holderGCHandle; + var target = handle.Target!; + if (target is PromiseHolder holder) + { + holder.GCHandle = IntPtr.Zero; + // arg_2, arg_3 are processed by the callback + holder.Callback!(arguments_buffer); + } + else + { + ThreadJsOwnedObjects.Remove(target); + } + handle.Free(); } } catch (Exception ex) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs index f06834b8b37cc..e623ef1923822 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs @@ -47,7 +47,8 @@ public override string? StackTrace } #if FEATURE_WASM_THREADS - if (jsException.OwnerThreadId != Thread.CurrentThread.ManagedThreadId) + var currentTID = JSSynchronizationContext.CurrentJSSynchronizationContext?.TargetTID; + if (jsException.OwnerTID != currentTID) { return bs; } 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 0b61c4cea08b0..3f8234bbfc950 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 @@ -198,7 +198,7 @@ public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, i } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe void InvokeJSImpl(JSObject jsFunction, Span arguments) + internal static unsafe void InvokeJSFunction(JSObject jsFunction, Span arguments) { ObjectDisposedException.ThrowIf(jsFunction.IsDisposed, jsFunction); #if FEATURE_WASM_THREADS @@ -220,6 +220,19 @@ internal static unsafe void InvokeJSImpl(JSObject jsFunction, Span arguments) { + if (signature.IsAsync) + { + // pre-allocate the result handle and Task +#if FEATURE_WASM_THREADS + JSSynchronizationContext.AssertWebWorkerContext(); + var holder = new JSHostImplementation.PromiseHolder(JSSynchronizationContext.CurrentJSSynchronizationContext!); +#else + var holder = new JSHostImplementation.PromiseHolder(); +#endif + arguments[1].slot.Type = MarshalerType.Task; + arguments[1].slot.GCHandle = holder.GCHandle; + } + fixed (JSMarshalerArgument* ptr = arguments) { Interop.Runtime.InvokeJSImport(signature.ImportHandle, ptr); @@ -229,6 +242,15 @@ internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span JSHostImplementation.ThrowException(ref exceptionArg); } } + if (signature.IsAsync) + { + // if js synchronously returned null + if (arguments[1].slot.Type == MarshalerType.None) + { + var holderHandle = (GCHandle)arguments[1].slot.GCHandle; + holderHandle.Free(); + } + } } internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName, string moduleName, ReadOnlySpan signatures) 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 1cf4646569ed3..532cbcbe47bf7 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 @@ -12,20 +12,32 @@ internal static partial class JSHostImplementation public sealed class PromiseHolder { - public nint GCVHandle; + public nint GCHandle; 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; + internal JSSynchronizationContext SynchronizationContext; +#endif + +#if FEATURE_WASM_THREADS + public PromiseHolder(JSSynchronizationContext targetContext) + { + GCHandle = (IntPtr)InteropServices.GCHandle.Alloc(this, GCHandleType.Normal); + SynchronizationContext = targetContext; + } +#else + public PromiseHolder() + { + GCHandle = (IntPtr)InteropServices.GCHandle.Alloc(this, GCHandleType.Normal); + } #endif public PromiseHolder(nint gcvHandle) { - this.GCVHandle = gcvHandle; + GCHandle = gcvHandle; #if FEATURE_WASM_THREADS - this.OwnerThreadId = Thread.CurrentThread.ManagedThreadId; - this.SynchronizationContext = SynchronizationContext.Current ?? new SynchronizationContext(); + JSSynchronizationContext.AssertWebWorkerContext(); + SynchronizationContext = JSSynchronizationContext.CurrentJSSynchronizationContext!; #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 1c77e429b196a..b2c0504e4d4f3 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 @@ -84,6 +84,12 @@ public static List JSVHandleFreeList public static nint AllocJSVHandle() { +#if FEATURE_WASM_THREADS + // TODO, when Task is passed to JSImport as parameter, it could be sent from another thread (in the future) + // and so we need to use JSVHandleFreeList of the target thread + JSSynchronizationContext.AssertWebWorkerContext(); +#endif + if (JSVHandleFreeList.Count > 0) { var jsvHandle = JSVHandleFreeList[JSVHandleFreeList.Count]; 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 97b6fcae46e42..82fe397b7ec47 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 @@ -30,7 +30,7 @@ public SynchronizationContext SynchronizationContext #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 nint OwnerTID; #endif #if !DISABLE_LEGACY_JS_INTEROP internal GCHandle? InFlight; @@ -42,12 +42,13 @@ internal JSObject(IntPtr jsHandle) { JSHandle = jsHandle; #if FEATURE_WASM_THREADS - OwnerThreadId = Thread.CurrentThread.ManagedThreadId; - m_SynchronizationContext = JSSynchronizationContext.CurrentJSSynchronizationContext; - if (m_SynchronizationContext == null) + var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext; + if (ctx == null) { - throw new InvalidOperationException(); // should not happen + Environment.FailFast("Missing CurrentJSSynchronizationContext"); } + m_SynchronizationContext = ctx; + OwnerTID = ctx!.TargetTID; #endif } @@ -93,16 +94,19 @@ internal static void AssertThreadAffinity(object value) { return; } - else if (value is JSObject jsObject) + JSSynchronizationContext.AssertWebWorkerContext(); + var currentTID = JSSynchronizationContext.CurrentJSSynchronizationContext!.TargetTID; + + if (value is JSObject jsObject) { - if (jsObject.OwnerThreadId != Thread.CurrentThread.ManagedThreadId) + if (jsObject.OwnerTID != currentTID) { throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created."); } } else if (value is JSException jsException) { - if (jsException.jsException != null && jsException.jsException.OwnerThreadId != Thread.CurrentThread.ManagedThreadId) + if (jsException.jsException != null && jsException.jsException.OwnerTID != currentTID) { throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created."); } @@ -131,8 +135,21 @@ private void DisposeThis() if (!_isDisposed) { #if FEATURE_WASM_THREADS - SynchronizationContext.Send(static (JSObject self) => + if (SynchronizationContext == SynchronizationContext.Current) + { + lock (_thisLock) + { + JSHostImplementation.ReleaseCSOwnedObject(JSHandle); + _isDisposed = true; + JSHandle = IntPtr.Zero; + m_SynchronizationContext = null; + } //lock + return; + } + + SynchronizationContext.Post(static (object? s) => { + var self = (JSObject)s!; lock (self._thisLock) { JSHostImplementation.ReleaseCSOwnedObject(self.JSHandle); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs index be2f29b7a6a05..ca2c0bab7bdff 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs @@ -29,7 +29,7 @@ public void InvokeJS() args_exception.Initialize(); args_return.Initialize(); - JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + JSFunctionBinding.InvokeJSFunction(JSObject, arguments); } } @@ -60,7 +60,7 @@ public void InvokeJS(T arg1) args_return.Initialize(); Arg1Marshaler(ref args_arg1, arg1); - JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + JSFunctionBinding.InvokeJSFunction(JSObject, arguments); } } @@ -94,7 +94,7 @@ public void InvokeJS(T1 arg1, T2 arg2) Arg1Marshaler(ref args_arg1, arg1); Arg2Marshaler(ref args_arg2, arg2); - JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + JSFunctionBinding.InvokeJSFunction(JSObject, arguments); } } @@ -132,7 +132,7 @@ public void InvokeJS(T1 arg1, T2 arg2, T3 arg3) Arg2Marshaler(ref args_arg2, arg2); Arg3Marshaler(ref args_arg3, arg3); - JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + JSFunctionBinding.InvokeJSFunction(JSObject, arguments); } } @@ -238,7 +238,7 @@ public TResult InvokeJS() args_exception.Initialize(); args_return.Initialize(); - JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + JSFunctionBinding.InvokeJSFunction(JSObject, arguments); ResMarshaler(ref args_return, out TResult res); return res; @@ -274,7 +274,7 @@ public TResult InvokeJS(T arg1) args_return.Initialize(); Arg1Marshaler(ref args_arg1, arg1); - JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + JSFunctionBinding.InvokeJSFunction(JSObject, arguments); ResMarshaler(ref args_return, out TResult res); return res; @@ -313,7 +313,7 @@ public TResult InvokeJS(T1 arg1, T2 arg2) Arg1Marshaler(ref args_arg1, arg1); Arg2Marshaler(ref args_arg2, arg2); - JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + JSFunctionBinding.InvokeJSFunction(JSObject, arguments); ResMarshaler(ref args_return, out TResult res); return res; @@ -356,7 +356,7 @@ public TResult InvokeJS(T1 arg1, T2 arg2, T3 arg3) Arg2Marshaler(ref args_arg2, arg2); Arg3Marshaler(ref args_arg3, arg3); - JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + JSFunctionBinding.InvokeJSFunction(JSObject, arguments); ResMarshaler(ref args_return, out TResult res); return res; diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs index 5717e8d2cee87..a7220c8934a6d 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs @@ -43,7 +43,6 @@ public void ToJS(JSObject? value) ObjectDisposedException.ThrowIf(value.IsDisposed, value); slot.Type = MarshalerType.JSObject; slot.JSHandle = value.JSHandle; - ObjectDisposedException.ThrowIf(slot.JSHandle == IntPtr.Zero, value); } } 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 6b3f7a578d87d..e7a52ee56d37e 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 @@ -43,7 +43,7 @@ public unsafe void ToManaged(out Task? value) value = null; return; } - PromiseHolder holder = CreateJSOwnedHolder(slot.GCHandle); + PromiseHolder holder = GetPromiseHolder(slot.GCHandle); TaskCompletionSource tcs = new TaskCompletionSource(holder); ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => { @@ -84,7 +84,7 @@ public unsafe void ToManaged(out Task? value, ArgumentToManagedCallback value = null; return; } - PromiseHolder holder = CreateJSOwnedHolder(slot.GCHandle); + PromiseHolder holder = GetPromiseHolder(slot.GCHandle); TaskCompletionSource tcs = new TaskCompletionSource(holder); ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => { @@ -114,13 +114,20 @@ public unsafe void ToManaged(out Task? value, ArgumentToManagedCallback } // TODO unregister and collect pending PromiseHolder also when no C# is awaiting ? - private static PromiseHolder CreateJSOwnedHolder(nint gcvHandle) + private static PromiseHolder GetPromiseHolder(nint gcHandle) { -#if FEATURE_WASM_THREADS - JSSynchronizationContext.AssertWebWorkerContext(); -#endif - var holder = new PromiseHolder(gcvHandle); - ThreadJsOwnedHolders.Add(gcvHandle, holder); + PromiseHolder holder; + if (IsGCVHandle(gcHandle)) + { + // this path should only happen when the Promise is passed as argument of JSExport + holder = new PromiseHolder(gcHandle); + // TODO for MT this must hit the ThreadJsOwnedHolders in the correct thread + ThreadJsOwnedHolders.Add(gcHandle, holder); + } + else + { + holder = (PromiseHolder)((GCHandle)gcHandle).Target!; + } return holder; } @@ -159,9 +166,20 @@ internal void ToJSDynamic(Task? value) return; } } - slot.Type = MarshalerType.Task; - slot.JSHandle = AllocJSVHandle(); + if (slot.Type == MarshalerType.None) + { + // this path should only happen when the Task is passed as argument of JSImport + slot.JSHandle = AllocJSVHandle(); + slot.Type = MarshalerType.Task; + } + else + { + // this path should hit for return values from JSExport/call_entry_point + // promise and handle is pre-allocated in slot.JSHandle + } + + var taskHolder = new JSObject(slot.JSHandle); #if FEATURE_WASM_THREADS @@ -210,7 +228,6 @@ public void ToJS(Task? value) slot.Type = MarshalerType.None; return; } - if (task.IsCompleted) { if (task.Exception != null) @@ -228,9 +245,19 @@ public void ToJS(Task? value) return; } } - slot.Type = MarshalerType.Task; - slot.JSHandle = AllocJSVHandle(); + if (slot.Type == MarshalerType.None) + { + // this path should only happen when the Task is passed as argument of JSImport + slot.JSHandle = AllocJSVHandle(); + slot.Type = MarshalerType.Task; + } + else + { + // this path should hit for return values from JSExport/call_entry_point + // promise and handle is pre-allocated in slot.JSHandle + } + var taskHolder = new JSObject(slot.JSHandle); #if FEATURE_WASM_THREADS @@ -289,8 +316,19 @@ public void ToJS(Task? value, ArgumentToJSCallback marshaler) return; } } - slot.Type = MarshalerType.Task; - slot.JSHandle = AllocJSVHandle(); + + if (slot.Type == MarshalerType.None) + { + // this path should only happen when the Task is passed as argument of JSImport + slot.JSHandle = AllocJSVHandle(); + slot.Type = MarshalerType.Task; + } + else + { + // this path should hit for return values from JSExport/call_entry_point + // promise and handle is pre-allocated in slot.JSHandle + } + var taskHolder = new JSObject(slot.JSHandle); #if FEATURE_WASM_THREADS diff --git a/src/mono/sample/wasm/browser-advanced/Program.cs b/src/mono/sample/wasm/browser-advanced/Program.cs index 53cbdbb2e3e0a..093d8c4040419 100644 --- a/src/mono/sample/wasm/browser-advanced/Program.cs +++ b/src/mono/sample/wasm/browser-advanced/Program.cs @@ -5,22 +5,24 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices.JavaScript; using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace Sample { public partial class Test { - public static int Main(string[] args) + public static async Task Main(string[] args) { - Console.WriteLine ("Hello, World!"); + Console.WriteLine("Hello, World!"); var rand = new Random(); - Console.WriteLine ("Today's lucky number is " + rand.Next(100) + " and " + Guid.NewGuid()); + Console.WriteLine("Today's lucky number is " + rand.Next(100) + " and " + Guid.NewGuid()); var start = DateTime.UtcNow; var timezonesCount = TimeZoneInfo.GetSystemTimeZones().Count; + await Delay(100); var end = DateTime.UtcNow; - Console.WriteLine($"Found {timezonesCount} timezones in the TZ database in {end-start}"); + Console.WriteLine($"Found {timezonesCount} timezones in the TZ database in {end - start}"); TimeZoneInfo utc = TimeZoneInfo.FindSystemTimeZoneById("UTC"); Console.WriteLine($"{utc.DisplayName} BaseUtcOffset is {utc.BaseUtcOffset}"); @@ -45,6 +47,16 @@ public static int Main(string[] args) [JSImport("Sample.Test.add", "main.js")] internal static partial int Add(int a, int b); + [JSImport("Sample.Test.delay", "main.js")] + [return: JSMarshalAs>] + internal static partial Task Delay([JSMarshalAs] int ms); + + [JSExport] + internal static async Task PrintMeaning(Task meaningPromise) + { + Console.WriteLine("Meaning of life is " + await meaningPromise); + } + [JSExport] internal static int TestMeaning() { @@ -62,12 +74,12 @@ internal static bool IsPrime(int number) if (number % 2 == 0) return false; var boundary = (int)Math.Floor(Math.Sqrt(number)); - + for (int i = 3; i <= boundary; i += 2) if (number % i == 0) return false; - - return true; - } + + return true; + } } } diff --git a/src/mono/sample/wasm/browser-advanced/main.js b/src/mono/sample/wasm/browser-advanced/main.js index b5c414322fefd..613c30cf48b22 100644 --- a/src/mono/sample/wasm/browser-advanced/main.js +++ b/src/mono/sample/wasm/browser-advanced/main.js @@ -7,6 +7,10 @@ function add(a, b) { return a + b; } +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + let testAbort = true; let testError = true; @@ -71,7 +75,8 @@ try { setModuleImports("main.js", { Sample: { Test: { - add + add, + delay, } } }); @@ -90,6 +95,9 @@ try { document.getElementById("out").innerHTML = `${meaning} as computed on dotnet ver ${runtimeBuildInfo.productVersion}`; } + const deepMeaning = new Promise(resolve => setTimeout(() => resolve(meaning), 100)); + exports.Sample.Test.PrintMeaning(deepMeaning); + let exit_code = await runMain(config.mainAssemblyName, []); exit(exit_code); } diff --git a/src/mono/wasm/runtime/cancelable-promise.ts b/src/mono/wasm/runtime/cancelable-promise.ts index 3ae287bf99e4d..bf6b0c4774020 100644 --- a/src/mono/wasm/runtime/cancelable-promise.ts +++ b/src/mono/wasm/runtime/cancelable-promise.ts @@ -22,9 +22,9 @@ export function wrap_as_cancelable_promise(fn: () => Promise): Controllabl return promise; } -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}`); +export function mono_wasm_cancel_promise(task_holder_gc_handle: GCHandle): void { + const holder = _lookup_js_owned_object(task_holder_gc_handle) as PromiseHolder; + mono_assert(!!holder, () => `Expected Promise for GCHandle ${task_holder_gc_handle}`); const promise = holder.promise; loaderHelpers.assertIsControllablePromise(promise); diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c index 0496b65fd5c72..00fed13f2d0a5 100644 --- a/src/mono/wasm/runtime/corebindings.c +++ b/src/mono/wasm/runtime/corebindings.c @@ -19,7 +19,7 @@ //JS funcs extern void mono_wasm_release_cs_owned_object (int js_handle); extern void mono_wasm_bind_js_import(void *signature, int *is_exception, MonoObject **result); -extern void mono_wasm_invoke_bound_function(int function_js_handle, void *args); +extern void mono_wasm_invoke_js_function(int function_js_handle, void *args); extern void mono_wasm_invoke_js_import(int function_handle, void *args); 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_resolve_or_reject_promise(void *data); @@ -62,7 +62,7 @@ void bindings_initialize_internals (void) { mono_add_internal_call ("Interop/Runtime::ReleaseCSOwnedObject", mono_wasm_release_cs_owned_object); mono_add_internal_call ("Interop/Runtime::BindJSImport", mono_wasm_bind_js_import); - mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_bound_function); + mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_js_function); mono_add_internal_call ("Interop/Runtime::InvokeJSImport", mono_wasm_invoke_js_import); mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function); mono_add_internal_call ("Interop/Runtime::ResolveOrRejectPromise", mono_wasm_resolve_or_reject_promise); diff --git a/src/mono/wasm/runtime/exports-binding.ts b/src/mono/wasm/runtime/exports-binding.ts index ae9e01cb949f2..e4f2b1fd0ea14 100644 --- a/src/mono/wasm/runtime/exports-binding.ts +++ b/src/mono/wasm/runtime/exports-binding.ts @@ -7,7 +7,7 @@ import WasmEnableLegacyJsInterop from "consts:wasmEnableLegacyJsInterop"; 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_bound_function, mono_wasm_invoke_js_import } from "./invoke-js"; +import { mono_wasm_bind_js_import, mono_wasm_invoke_js_function, 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"; import { mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue } from "./jiterpreter-jit-call"; @@ -105,7 +105,7 @@ export const mono_wasm_imports = [ // corebindings.c mono_wasm_release_cs_owned_object, mono_wasm_bind_js_import, - mono_wasm_invoke_bound_function, + mono_wasm_invoke_js_function, mono_wasm_invoke_js_import, mono_wasm_bind_cs_function, mono_wasm_resolve_or_reject_promise, diff --git a/src/mono/wasm/runtime/gc-handles.ts b/src/mono/wasm/runtime/gc-handles.ts index 4eabca080f08c..feacccce2a5f0 100644 --- a/src/mono/wasm/runtime/gc-handles.ts +++ b/src/mono/wasm/runtime/gc-handles.ts @@ -105,7 +105,7 @@ export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void { _cs_owned_objects_by_js_handle[js_handle] = undefined; _js_handle_free_list.push(js_handle); } - if (is_jsv_handle(js_handle)) { + else 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; } diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 65a0f160d7e3c..6e6ef99832fad 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -6,7 +6,7 @@ import BuildConfiguration from "consts:configuration"; import MonoWasmThreads from "consts:monoWasmThreads"; 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 } from "./marshal-to-js"; +import { marshal_exception_to_js, 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, @@ -64,9 +64,11 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, } const res_sig = get_sig(signature, 1); - const res_marshaler_type = get_signature_type(res_sig); - if (res_marshaler_type == MarshalerType.Task) { + let res_marshaler_type = get_signature_type(res_sig); + const is_async = res_marshaler_type == MarshalerType.Task; + if (is_async) { assert_synchronization_context(); + res_marshaler_type = MarshalerType.TaskPreCreated; } const res_converter = bind_arg_marshal_to_js(res_sig, res_marshaler_type, 1); @@ -76,15 +78,23 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, 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); } @@ -192,6 +202,38 @@ function bind_fn_1R(closure: BindingClosure) { }; } +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; + if (!MonoWasmThreads) (closure) = null; + return function bound_fn_1R(arg1: any) { + const mark = startMeasure(); + loaderHelpers.assert_runtime_running(); + mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed"); + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(3); + marshaler1(args, arg1); + + // pre-allocate the promise + let promise = res_converter(args); + + // call C# side + invoke_method_and_handle_exception(method, args); + + // in case the C# side returned synchronously + promise = end_marshal_task_to_js(args, undefined, promise); + + return promise; + } finally { + Module.stackRestore(sp); + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); + } + }; +} + function bind_fn_2R(closure: BindingClosure) { const method = closure.method; const marshaler1 = closure.arg_marshalers[0]!; @@ -221,12 +263,47 @@ function bind_fn_2R(closure: BindingClosure) { }; } +function bind_fn_2RA(closure: BindingClosure) { + const method = closure.method; + const marshaler1 = closure.arg_marshalers[0]!; + const marshaler2 = closure.arg_marshalers[1]!; + const res_converter = closure.res_converter!; + const fqn = closure.fqn; + if (!MonoWasmThreads) (closure) = null; + return function bound_fn_2R(arg1: any, arg2: any) { + const mark = startMeasure(); + loaderHelpers.assert_runtime_running(); + mono_assert(!MonoWasmThreads || !closure.isDisposed, "The function was already disposed"); + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(4); + marshaler1(args, arg1); + marshaler2(args, arg2); + + // pre-allocate the promise + let promise = res_converter(args); + + // call C# side + invoke_method_and_handle_exception(method, args); + + // in case the C# side returned synchronously + promise = end_marshal_task_to_js(args, undefined, promise); + + return promise; + } finally { + Module.stackRestore(sp); + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); + } + }; +} + function bind_fn(closure: BindingClosure) { const args_count = closure.args_count; const arg_marshalers = closure.arg_marshalers; const res_converter = closure.res_converter; const method = closure.method; const fqn = closure.fqn; + const is_async = closure.is_async; if (!MonoWasmThreads) (closure) = null; return function bound_fn(...js_args: any[]) { const mark = startMeasure(); @@ -242,14 +319,22 @@ function bind_fn(closure: BindingClosure) { marshaler(args, js_arg); } } + let js_result = undefined; + if (is_async && res_converter) { + // pre-allocate the promise + js_result = res_converter(args); + } // call C# side invoke_method_and_handle_exception(method, args); - - if (res_converter) { - const js_result = res_converter(args); - return js_result; + if (is_async) { + // in case the C# side returned synchronously + js_result = end_marshal_task_to_js(args, undefined, js_result); + } + else if (res_converter) { + js_result = res_converter(args); } + return js_result; } finally { Module.stackRestore(sp); endMeasure(mark, MeasuredBlock.callCsFunction, fqn); @@ -263,6 +348,7 @@ type BindingClosure = { method: MonoMethod, arg_marshalers: (BoundMarshalerToCs)[], res_converter: BoundMarshalerToJs | undefined, + is_async: boolean, isDisposed: boolean, } diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index 4b3c4156dda01..d87e1b3d2f5cd 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -269,7 +269,7 @@ type BindingClosure = { arg_cleanup: (Function | undefined)[] } -export function mono_wasm_invoke_bound_function(bound_function_js_handle: JSHandle, args: JSMarshalerArguments): void { +export function mono_wasm_invoke_js_function(bound_function_js_handle: JSHandle, args: JSMarshalerArguments): void { const bound_fn = mono_wasm_get_jsobj_from_js_handle(bound_function_js_handle); mono_assert(bound_fn && typeof (bound_fn) === "function" && bound_fn[bound_js_function_symbol], () => `Bound function handle expected ${bound_function_js_handle}`); bound_fn(args); diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index fee9ed61fecca..7e1347617aab7 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -9,7 +9,7 @@ import { runtimeHelpers, Module, loaderHelpers, mono_assert } from "./globals"; import { alloc_stack_frame, get_arg, set_arg_type, set_gc_handle } from "./marshal"; import { invoke_method_and_handle_exception } 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"; +import { marshal_int32_to_js, end_marshal_task_to_js, marshal_string_to_js, begin_marshal_task_to_js } from "./marshal-to-js"; import { do_not_force_dispose } from "./gc-handles"; export function init_managed_exports(): void { @@ -55,8 +55,15 @@ export function init_managed_exports(): void { program_args = undefined; } marshal_array_to_cs_impl(arg2, program_args, MarshalerType.String); + + // because this is async, we could pre-allocate the promise + let promise = begin_marshal_task_to_js(res, MarshalerType.TaskPreCreated, marshal_int32_to_js); + invoke_method_and_handle_exception(call_entry_point, args); - let promise = marshal_task_to_js(res, undefined, marshal_int32_to_js); + + // in case the C# side returned synchronously + promise = end_marshal_task_to_js(args, marshal_int32_to_js, promise); + if (promise === null || promise === undefined) { promise = Promise.resolve(0); } @@ -108,14 +115,14 @@ export function init_managed_exports(): void { Module.stackRestore(sp); } }; - runtimeHelpers.javaScriptExports.complete_task = (holder_gcv_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs) => { + runtimeHelpers.javaScriptExports.complete_task = (holder_gc_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_gcv_handle); + set_gc_handle(arg1, holder_gc_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 f741351be33f6..8d78356d5de70 100644 --- a/src/mono/wasm/runtime/marshal-to-cs.ts +++ b/src/mono/wasm/runtime/marshal-to-cs.ts @@ -15,7 +15,7 @@ import { 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, - set_arg_element_type, ManagedObject, JavaScriptMarshalerArgSize, proxy_debug_symbol + set_arg_element_type, ManagedObject, JavaScriptMarshalerArgSize, proxy_debug_symbol, get_arg_gc_handle, get_arg_type } from "./marshal"; import { get_marshaler_to_js_by_type } from "./marshal-to-js"; import { _zero_region, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; @@ -315,13 +315,16 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: } mono_check(isThenable(value), "Value is not a Promise"); - const gc_handle = alloc_gcv_handle(); - set_gc_handle(arg, gc_handle); - set_arg_type(arg, MarshalerType.Task); + const handleIsPreallocated = get_arg_type(arg) == MarshalerType.Task; + const gc_handle = handleIsPreallocated ? get_arg_gc_handle(arg) : alloc_gcv_handle(); + if (!handleIsPreallocated) { + set_gc_handle(arg, gc_handle); + set_arg_type(arg, MarshalerType.Task); + } const holder = new PromiseHolder(value); setup_managed_proxy(holder, gc_handle); if (BuildConfiguration === "Debug") { - (holder as any)[proxy_debug_symbol] = `PromiseHolder with GCVHandle ${gc_handle}`; + (holder as any)[proxy_debug_symbol] = `PromiseHolder with GCHandle ${gc_handle}`; } if (MonoWasmThreads) diff --git a/src/mono/wasm/runtime/marshal-to-js.ts b/src/mono/wasm/runtime/marshal-to-js.ts index dba386715c2e1..1697dc964161b 100644 --- a/src/mono/wasm/runtime/marshal-to-js.ts +++ b/src/mono/wasm/runtime/marshal-to-js.ts @@ -6,7 +6,7 @@ import BuildConfiguration from "consts:configuration"; import WasmEnableJsInteropByValue from "consts:wasmEnableJsInteropByValue"; import cwraps from "./cwraps"; -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 { _lookup_js_owned_object, mono_wasm_get_js_handle, mono_wasm_get_jsobj_from_js_handle, mono_wasm_release_cs_owned_object, register_with_jsv_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles"; import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { ManagedObject, ManagedError, @@ -14,10 +14,10 @@ import { 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 + ArraySegment, Span, MemoryViewType, get_signature_arg3_type, get_arg_i64_big, get_arg_intptr, get_arg_element_type, JavaScriptMarshalerArgSize, proxy_debug_symbol, set_js_handle } from "./marshal"; import { monoStringToString, utf16ToString } from "./strings"; -import { GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs, MarshalerType } from "./types/internal"; +import { GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs, MarshalerType, JSHandle } from "./types/internal"; 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"; @@ -47,6 +47,7 @@ export function initialize_marshalers_to_js(): void { 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.TaskPreCreated, begin_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); @@ -216,11 +217,58 @@ function _marshal_delegate_to_js(arg: JSMarshalerArgument, _?: MarshalerType, re } export class TaskHolder { - constructor(public resolve_or_reject: (type: MarshalerType, argInner: JSMarshalerArgument) => void) { + constructor(public promise: Promise, public resolve_or_reject: (type: MarshalerType, js_handle: JSHandle, argInner: JSMarshalerArgument) => void) { } } export function marshal_task_to_js(arg: JSMarshalerArgument, _?: MarshalerType, res_converter?: MarshalerToJs): Promise | null { + // this path is used only when Task is passed as argument to JSImport and virtual JSHandle would be used + + // if there is synchronous result, return it + const promise = sync_marshal_task_to_js(arg, res_converter); + if (promise !== false) { + return promise; + } + + const jsv_handle = get_arg_js_handle(arg); + const holder = create_task_holder(res_converter); + register_with_jsv_handle(holder, jsv_handle); + if (BuildConfiguration === "Debug") { + (holder as any)[proxy_debug_symbol] = `TaskHolder with JSVHandle ${jsv_handle}`; + } + + return holder.promise; +} + +export function begin_marshal_task_to_js(arg: JSMarshalerArgument, _?: MarshalerType, res_converter?: MarshalerToJs): Promise | null { + // this path is used when Task is returned from JSExport/call_entry_point + const holder = create_task_holder(res_converter); + const js_handle = mono_wasm_get_js_handle(holder); + if (BuildConfiguration === "Debug") { + (holder as any)[proxy_debug_symbol] = `TaskHolder with JSHandle ${js_handle}`; + } + set_js_handle(arg, js_handle); + set_arg_type(arg, MarshalerType.Task); + return holder.promise; +} + +export function end_marshal_task_to_js(args: JSMarshalerArguments, res_converter: MarshalerToJs | undefined, eagerPromise: Promise | null) { + // this path is used when Task is returned from JSExport/call_entry_point + // it could return synchronously (when on the same thread) + const res = get_arg(args, 1); + + // if there is synchronous result, return it + const promise = sync_marshal_task_to_js(res, res_converter); + if (promise !== false) { + const js_handle = get_arg_js_handle(res); + mono_wasm_release_cs_owned_object(js_handle); + return promise; + } + + return eagerPromise; +} + +function sync_marshal_task_to_js(arg: JSMarshalerArgument, res_converter?: MarshalerToJs) { const type = get_arg_type(arg); if (type === MarshalerType.None) { return null; @@ -244,11 +292,12 @@ export function marshal_task_to_js(arg: JSMarshalerArgument, _?: MarshalerType, const val = res_converter(arg); return Promise.resolve(val); } + return false; +} - const jsv_handle = get_arg_js_handle(arg); +function create_task_holder(res_converter?: MarshalerToJs) { const { promise, promise_control } = loaderHelpers.createPromiseController(); - - const holder = new TaskHolder((type, argInner) => { + const holder = new TaskHolder(promise, (type, js_handle, argInner) => { if (type === MarshalerType.TaskRejected) { const reason = marshal_exception_to_js(argInner); promise_control.reject(reason); @@ -270,15 +319,9 @@ export function marshal_task_to_js(arg: JSMarshalerArgument, _?: MarshalerType, else { mono_assert(false, () => `Unexpected type ${MarshalerType[type]}`); } - mono_wasm_release_cs_owned_object(jsv_handle); + mono_wasm_release_cs_owned_object(js_handle); }); - if (BuildConfiguration === "Debug") { - (holder as any)[proxy_debug_symbol] = `TaskHolder with JSVHandle ${jsv_handle}`; - } - - register_with_jsv_handle(holder, jsv_handle); - - return promise; + return holder; } export function mono_wasm_resolve_or_reject_promise(args: JSMarshalerArguments): void { @@ -291,12 +334,12 @@ export function mono_wasm_resolve_or_reject_promise(args: JSMarshalerArguments): const arg_value = get_arg(args, 3); const type = get_arg_type(arg_handle); - const jsv_handle = get_arg_js_handle(arg_handle); + const js_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}`); + const holder = mono_wasm_get_jsobj_from_js_handle(js_handle) as TaskHolder; + mono_assert(holder, () => `Cannot find Promise for JSHandle ${js_handle}`); - holder.resolve_or_reject(type, arg_value); + holder.resolve_or_reject(type, js_handle, arg_value); set_arg_type(res, MarshalerType.Void); set_arg_type(exc, MarshalerType.None); @@ -364,6 +407,7 @@ function _marshal_js_object_to_js(arg: JSMarshalerArgument): any { } const js_handle = get_arg_js_handle(arg); const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); + mono_assert(js_obj, "Expected JS object instance"); return js_obj; } diff --git a/src/mono/wasm/runtime/marshal.ts b/src/mono/wasm/runtime/marshal.ts index 89f63a596c404..a788512e9d567 100644 --- a/src/mono/wasm/runtime/marshal.ts +++ b/src/mono/wasm/runtime/marshal.ts @@ -5,7 +5,7 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles"; 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 { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8, _zero_region } 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 { TypedArray, VoidPtr } from "./types/emscripten"; @@ -23,12 +23,9 @@ export const JSMarshalerTypeSize = 32; export const JSMarshalerSignatureHeaderSize = 4 * 8; // without Exception and Result export function alloc_stack_frame(size: number): JSMarshalerArguments { - const args = Module.stackAlloc(JavaScriptMarshalerArgSize * size) as any; - mono_assert(args && (args) % 8 == 0, "Arg alignment"); - const exc = get_arg(args, 0); - set_arg_type(exc, MarshalerType.None); - const res = get_arg(args, 1); - set_arg_type(res, MarshalerType.None); + const bytes = JavaScriptMarshalerArgSize * size; + const args = Module.stackAlloc(bytes) as any; + _zero_region(args, bytes); return args; } diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 53fb458d6a432..ecd40de075a49 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -339,7 +339,7 @@ export interface JavaScriptExports { release_js_owned_object_by_gc_handle(gc_handle: GCHandle): void; // the marshaled signature is: void CompleteTask(GCHandle holder, Exception? exceptionResult, T? result) - complete_task(holder_gcv_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs): void; + complete_task(holder_gc_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, @@ -399,6 +399,7 @@ export enum MarshalerType { JSException, TaskResolved, TaskRejected, + TaskPreCreated, } export interface JSMarshalerArguments extends NativePointer {