Skip to content

Commit

Permalink
[browser][MT] JSType.OneWay fire and forget (#98567)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara authored Feb 18, 2024
1 parent 2f43856 commit d7acf9c
Show file tree
Hide file tree
Showing 41 changed files with 446 additions and 244 deletions.
8 changes: 4 additions & 4 deletions docs/workflow/debugging/mono/wasm-debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ $func166 @ dotnet.wasm:0xba0a
$func2810 @ dotnet.wasm:0xabacf
$func1615 @ dotnet.wasm:0x6f8eb
$func1619 @ dotnet.wasm:0x6ff58
$mono_wasm_invoke_method @ dotnet.wasm:0x96c9
Module._mono_wasm_invoke_method @ dotnet.6.0.1.hopd7ipo8x.js:1
$mono_wasm_invoke_jsexport @ dotnet.wasm:0x96c9
Module.mono_wasm_invoke_jsexport @ dotnet.6.0.1.hopd7ipo8x.js:1
managed__Microsoft_AspNetCore_Components_WebAssembly__Microsoft_AspNetCore_Components_WebAssembly_Services_DefaultWebAssemblyJSRuntime_BeginInvokeDotNet @ managed__Microsoft_AspNetCore_Components_WebAssembly__Microsoft_AspNetCore_Components_WebAssembly_Services_DefaultWebAssemblyJSRuntime_BeginInvokeDotNet:19
beginInvokeDotNetFromJS @ blazor.webassembly.js:1
b @ blazor.webassembly.js:1
Expand Down Expand Up @@ -244,8 +244,8 @@ $mono_jit_runtime_invoke @ dotnet.wasm:0x1dec32
$do_runtime_invoke @ dotnet.wasm:0x95fca
$mono_runtime_try_invoke @ dotnet.wasm:0x966fe
$mono_runtime_invoke @ dotnet.wasm:0x98982
$mono_wasm_invoke_method @ dotnet.wasm:0x227de2
Module._mono_wasm_invoke_method @ dotnet..y6ggkhlo8e.js:9927
$mono_wasm_invoke_jsexport @ dotnet.wasm:0x227de2
Module.mono_wasm_invoke_jsexport @ dotnet..y6ggkhlo8e.js:9927
managed__Microsoft_AspNetCore_Components_WebAssembly__Microsoft_AspNetCore_Components_WebAssembly_Services_DefaultWebAssemblyJSRuntime_BeginInvokeDotNet @ managed__Microsoft_AspNetCore_Components_WebAssembly__Microsoft_AspNetCore_Components_WebAssembly_Services_DefaultWebAssemblyJSRuntime_BeginInvokeDotNet:19
beginInvokeDotNetFromJS @ blazor.webassembly.js:1
b @ blazor.webassembly.js:1
Expand Down
8 changes: 4 additions & 4 deletions src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ internal static unsafe partial class Runtime
public static extern void UninstallWebWorkerInterop();

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void InvokeJSImportSync(nint data, nint signature);
public static extern void InvokeJSImportSync(nint signature, nint args);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void InvokeJSImportSyncSend(nint targetNativeTID, nint data, nint signature);
public static extern void InvokeJSImportSyncSend(nint targetNativeTID, nint signature, nint args);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void InvokeJSImportAsyncPost(nint targetNativeTID, nint data, nint signature);
public static extern void InvokeJSImportAsyncPost(nint targetNativeTID, nint signature, nint args);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void CancelPromise(nint taskHolderGCHandle);
[MethodImpl(MethodImplOptions.InternalCall)]
Expand All @@ -60,7 +60,7 @@ internal static unsafe partial class Runtime
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern unsafe void BindJSImport(void* signature, out int is_exception, out object result);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void InvokeJSImport(int importHandle, nint data);
public static extern void InvokeJSImportST(int importHandle, nint args);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void CancelPromise(nint gcHandle);
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ ResolvedGenerator fail(string failReason)
return ResolvedGenerator.NotSupported(new(info, context));

// void
case { TypeInfo: JSSimpleTypeInfo(KnownManagedType.Void), JSType: JSTypeFlags.OneWay }:
return ResolvedGenerator.Resolved(new VoidGenerator(MarshalerType.OneWay));
case { TypeInfo: JSSimpleTypeInfo(KnownManagedType.Void), JSType: JSTypeFlags.Discard }:
case { TypeInfo: JSSimpleTypeInfo(KnownManagedType.Void), JSType: JSTypeFlags.Void }:
case { TypeInfo: JSSimpleTypeInfo(KnownManagedType.Void), JSType: JSTypeFlags.None }:
Expand All @@ -52,6 +54,10 @@ ResolvedGenerator fail(string failReason)
case { JSType: JSTypeFlags.Discard }:
return fail(SR.DiscardOnlyVoid);

// oneway no void
case { JSType: JSTypeFlags.OneWay }:
return fail(SR.OneWayOnlyVoid);

// primitive
case { TypeInfo: JSSimpleTypeInfo simple }:
return Create(info, isToJs, simple.KnownType, Array.Empty<KnownManagedType>(), jsMarshalingInfo.JSType, Array.Empty<JSTypeFlags>(), fail);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal enum JSTypeFlags : int
MemoryView = 0x800,
Any = 0x1000,
Discard = 0x2000,
OneWay = 0x4000,
Missing = 0x4000_0000,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@
<data name="DiscardOnlyVoid" xml:space="preserve">
<value>'JSType.Discard' could be only used with void return argument.</value>
</data>
<data name="OneWayOnlyVoid" xml:space="preserve">
<value>'JSType.OneWay' could be only used with void returning method.</value>
</data>
<data name="FuncArgumentNotSupported" xml:space="preserve">
<value>Type {0} is not supported as argument of marshaled function.</value>
<comment>{0} is a type of the argument</comment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,16 @@
<Left>ref/net9.0/System.Runtime.InteropServices.JavaScript.dll</Left>
<Right>runtimes/browser/lib/net9.0/System.Runtime.InteropServices.JavaScript.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Runtime.InteropServices.JavaScript.JSType.OneWay</Target>
<Left>ref/net9.0/System.Runtime.InteropServices.JavaScript.dll</Left>
<Right>runtimes/browser/lib/net9.0/System.Runtime.InteropServices.JavaScript.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.Runtime.InteropServices.JavaScript.JSMarshalerType.get_OneWay</Target>
<Left>ref/net9.0/System.Runtime.InteropServices.JavaScript.dll</Left>
<Right>runtimes/browser/lib/net9.0/System.Runtime.InteropServices.JavaScript.dll</Right>
</Suppression>
</Suppressions>
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
Expand All @@ -14,12 +16,11 @@ namespace System.Runtime.InteropServices.JavaScript
// TODO: all the calls here should be running on deputy or TP in MT, not in UI thread
internal static unsafe partial class JavaScriptExports
{
// the marshaled signature is:
// Task<int>? CallEntrypoint(char* assemblyNamePtr, string[] args)
// the marshaled signature is: Task<int>? CallEntrypoint(char* assemblyNamePtr, string[] args)
public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
ref JSMarshalerArgument arg_result = ref arguments_buffer[1]; // initialized by caller in alloc_stack_frame()
ref JSMarshalerArgument arg_res = ref arguments_buffer[1]; // initialized by caller in alloc_stack_frame()
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; // initialized and set by caller
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // initialized and set by caller
ref JSMarshalerArgument arg_3 = ref arguments_buffer[4]; // initialized and set by caller
Expand All @@ -28,6 +29,7 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer)
#if FEATURE_WASM_MANAGED_THREADS
// when we arrive here, we are on the thread which owns the proxies
arg_exc.AssertCurrentThreadContext();
Debug.Assert(arg_res.slot.Type == MarshalerType.TaskPreCreated);
#endif

arg_1.ToManaged(out IntPtr assemblyNamePtr);
Expand All @@ -36,7 +38,7 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer)

Task<int>? result = JSHostImplementation.CallEntrypoint(assemblyNamePtr, args, waitForDebugger);

arg_result.ToJS(result, (ref JSMarshalerArgument arg, int value) =>
arg_res.ToJS(result, (ref JSMarshalerArgument arg, int value) =>
{
arg.ToJS(value);
});
Expand All @@ -47,6 +49,7 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer)
}
}

// the marshaled signature is: void LoadLazyAssembly(byte[] dll, byte[] pdb)
public static void LoadLazyAssembly(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0];
Expand All @@ -70,6 +73,7 @@ public static void LoadLazyAssembly(JSMarshalerArgument* arguments_buffer)
}
}

// the marshaled signature is: void LoadSatelliteAssembly(byte[] dll)
public static void LoadSatelliteAssembly(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0];
Expand All @@ -91,10 +95,8 @@ public static void LoadSatelliteAssembly(JSMarshalerArgument* arguments_buffer)
}
}

// The JS layer invokes this method when the JS wrapper for a JS owned object
// has been collected by the JS garbage collector
// the marshaled signature is:
// void ReleaseJSOwnedObjectByGCHandle(GCHandle gcHandle)
// The JS layer invokes this method when the JS wrapper for a JS owned object has been collected by the JS garbage collector
// the marshaled signature is: void ReleaseJSOwnedObjectByGCHandle(GCHandle gcHandle)
public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
Expand All @@ -112,8 +114,7 @@ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments
}
}

// the marshaled signature is:
// TRes? CallDelegate<T1,T2,T3TRes>(GCHandle callback, T1? arg1, T2? arg2, T3? arg3)
// the marshaled signature is: TRes? CallDelegate<T1,T2,T3TRes>(GCHandle callback, T1? arg1, T2? arg2, T3? arg3)
public static void CallDelegate(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by JS caller in alloc_stack_frame()
Expand Down Expand Up @@ -149,8 +150,7 @@ public static void CallDelegate(JSMarshalerArgument* arguments_buffer)
}
}

// the marshaled signature is:
// void CompleteTask<T>(GCHandle holder, Exception? exceptionResult, T? result)
// the marshaled signature is: void CompleteTask<T>(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()
Expand Down Expand Up @@ -209,12 +209,11 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer)
}
}

// the marshaled signature is:
// string GetManagedStackTrace(GCHandle exception)
// the marshaled signature is: string GetManagedStackTrace(GCHandle exception)
public static void GetManagedStackTrace(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
ref JSMarshalerArgument arg_res = ref arguments_buffer[1]; // used as return value
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
try
{
Expand All @@ -224,7 +223,7 @@ public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer)
GCHandle exception_gc_handle = (GCHandle)arg_1.slot.GCHandle;
if (exception_gc_handle.Target is Exception exception)
{
arg_return.ToJS(exception.StackTrace);
arg_res.ToJS(exception.StackTrace);
}
else
{
Expand All @@ -241,19 +240,18 @@ public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer)

// this is here temporarily, until JSWebWorker becomes public API
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")]
// the marshaled signature is:
// void InstallMainSynchronizationContext(nint jsNativeTID, out GCHandle contextHandle)
// the marshaled signature is: GCHandle InstallMainSynchronizationContext(nint jsNativeTID)
public static void InstallMainSynchronizationContext(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
ref JSMarshalerArgument arg_res = ref arguments_buffer[1];// initialized and set by caller
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3];// initialized and set by caller

try
{
var jsSynchronizationContext = JSSynchronizationContext.InstallWebWorkerInterop(true, CancellationToken.None);
jsSynchronizationContext.ProxyContext.JSNativeTID = arg_1.slot.IntPtrValue;
arg_2.slot.GCHandle = jsSynchronizationContext.ProxyContext.ContextHandle;
arg_res.slot.GCHandle = jsSynchronizationContext.ProxyContext.ContextHandle;
}
catch (Exception ex)
{
Expand All @@ -263,12 +261,11 @@ public static void InstallMainSynchronizationContext(JSMarshalerArgument* argume

#endif

// the marshaled signature is:
// Task BindAssemblyExports(string assemblyName)
// the marshaled signature is: Task BindAssemblyExports(string assemblyName)
public static void BindAssemblyExports(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
ref JSMarshalerArgument arg_result = ref arguments_buffer[1]; // used as return value
ref JSMarshalerArgument arg_res = ref arguments_buffer[1]; // used as return value
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
try
{
Expand All @@ -279,7 +276,7 @@ public static void BindAssemblyExports(JSMarshalerArgument* arguments_buffer)

var result = JSHostImplementation.BindAssemblyExports(assemblyName);

arg_result.ToJS(result);
arg_res.ToJS(result);
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ internal static unsafe partial class JavaScriptImports

#if DEBUG
[JSImport("globalThis.console.log")]
[return: JSMarshalAs<JSType.OneWay>]
public static partial void Log([JSMarshalAs<JSType.String>] string message);
#endif
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ internal JSFunctionBinding() { }
internal static volatile uint nextImportHandle = 1;
internal int ImportHandle;
internal bool IsAsync;
internal bool IsOneWay;
#if DEBUG
internal string? FunctionName;
#endif
Expand Down Expand Up @@ -285,6 +286,11 @@ internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span
arguments[1].slot.GCHandle = holder.GCHandle;
}

if (signature.IsOneWay)
{
arguments[1].slot.Type = MarshalerType.OneWay;
}

#if FEATURE_WASM_MANAGED_THREADS
// if we are on correct thread already or this is synchronous call, just call it
if (targetContext.IsCurrentThread())
Expand All @@ -299,15 +305,15 @@ internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span
#endif

}
else if (!signature.IsAsync)
else if (signature.IsAsync || signature.IsOneWay)
{
//sync
DispatchJSImportSyncSend(signature, targetContext, arguments);
//async
DispatchJSImportAsyncPost(signature, targetContext, arguments);
}
else
{
//async
DispatchJSImportAsyncPost(signature, targetContext, arguments);
//sync
DispatchJSImportSyncSend(signature, targetContext, arguments);
}
#else
InvokeJSImportCurrent(signature, arguments);
Expand All @@ -332,9 +338,9 @@ internal static unsafe void InvokeJSImportCurrent(JSFunctionBinding signature, S
fixed (JSMarshalerArgument* args = arguments)
{
#if FEATURE_WASM_MANAGED_THREADS
Interop.Runtime.InvokeJSImportSync((nint)args, (nint)signature.Header);
Interop.Runtime.InvokeJSImportSync((nint)signature.Header, (nint)args);
#else
Interop.Runtime.InvokeJSImport(signature.ImportHandle, (nint)args);
Interop.Runtime.InvokeJSImportST(signature.ImportHandle, (nint)args);
#endif
}

Expand All @@ -361,7 +367,7 @@ internal static unsafe void DispatchJSImportSyncSend(JSFunctionBinding signature
// we also don't throw PNSE here, because we know that the target has JS interop installed and that it could not block
// so it could take some time, while target is CPU busy, but not forever
// see also https://github.com/dotnet/runtime/issues/76958#issuecomment-1921418290
Interop.Runtime.InvokeJSImportSyncSend(targetContext.JSNativeTID, args, sig);
Interop.Runtime.InvokeJSImportSyncSend(targetContext.JSNativeTID, sig, args);

ref JSMarshalerArgument exceptionArg = ref arguments[0];
if (exceptionArg.slot.Type != MarshalerType.None)
Expand All @@ -375,7 +381,10 @@ internal static unsafe void DispatchJSImportSyncSend(JSFunctionBinding signature
#endif
internal static unsafe void DispatchJSImportAsyncPost(JSFunctionBinding signature, JSProxyContext targetContext, Span<JSMarshalerArgument> arguments)
{
// this copy is freed in mono_wasm_invoke_import_async
// meaning JS side needs to dispose it
ref JSMarshalerArgument exc = ref arguments[0];
exc.slot.ReceiverShouldFree = true;

var bytes = sizeof(JSMarshalerArgument) * arguments.Length;
void* cpy = (void*)Marshal.AllocHGlobal(bytes);
void* src = Unsafe.AsPointer(ref arguments[0]);
Expand All @@ -385,7 +394,7 @@ internal static unsafe void DispatchJSImportAsyncPost(JSFunctionBinding signatur
// we already know that we are not on the right thread
// this will return quickly after sending the message
// async
Interop.Runtime.InvokeJSImportAsyncPost(targetContext.JSNativeTID, (nint)cpy, sig);
Interop.Runtime.InvokeJSImportAsyncPost(targetContext.JSNativeTID, sig, (nint)cpy);

}

Expand Down Expand Up @@ -431,8 +440,8 @@ internal static unsafe void ResolveOrRejectPromise(JSProxyContext targetContext,
else
{
// meaning JS side needs to dispose it
ref JSMarshalerArgument res = ref arguments[1];
res.slot.BooleanValue = true;
ref JSMarshalerArgument exc = ref arguments[0];
exc.slot.ReceiverShouldFree = true;

// this copy is freed in mono_wasm_resolve_or_reject_promise
var bytes = sizeof(JSMarshalerArgument) * arguments.Length;
Expand Down
Loading

0 comments on commit d7acf9c

Please sign in to comment.