Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[browser][MT] JSType.OneWay fire and forget #98567

Merged
merged 5 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
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 @@ public sealed class 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
Loading