-
Notifications
You must be signed in to change notification settings - Fork 5.1k
[browser][MT] JSImport dispatch to target thread via JSSynchronizationContext #96319
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
Changes from all commits
1d49fea
152465e
d08feb0
1a0fbbe
33f4992
1ac49cd
615ce7a
7fbadb0
b9af932
69417f1
2bb9f68
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,9 @@ internal JSFunctionBinding() { } | |
internal static volatile uint nextImportHandle = 1; | ||
internal int ImportHandle; | ||
internal bool IsAsync; | ||
#if DEBUG | ||
internal string? FunctionName; | ||
#endif | ||
|
||
[StructLayout(LayoutKind.Sequential, Pack = 4)] | ||
internal struct JSBindingHeader | ||
|
@@ -197,15 +200,31 @@ public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, i | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static unsafe void InvokeJSFunction(JSObject jsFunction, Span<JSMarshalerArgument> arguments) | ||
{ | ||
ObjectDisposedException.ThrowIf(jsFunction.IsDisposed, jsFunction); | ||
jsFunction.AssertNotDisposed(); | ||
|
||
#if FEATURE_WASM_THREADS | ||
JSObject.AssertThreadAffinity(jsFunction); | ||
// if we are on correct thread already, just call it | ||
if (jsFunction.ProxyContext.IsCurrentThread()) | ||
{ | ||
InvokeJSFunctionCurrent(jsFunction, arguments); | ||
} | ||
else | ||
{ | ||
DispatchJSFunctionSync(jsFunction, arguments); | ||
} | ||
// async functions are not implemented | ||
#else | ||
InvokeJSFunctionCurrent(jsFunction, arguments); | ||
#endif | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static unsafe void InvokeJSFunctionCurrent(JSObject jsFunction, Span<JSMarshalerArgument> arguments) | ||
{ | ||
var functionHandle = (int)jsFunction.JSHandle; | ||
fixed (JSMarshalerArgument* ptr = arguments) | ||
{ | ||
Interop.Runtime.InvokeJSFunction(functionHandle, ptr); | ||
Interop.Runtime.InvokeJSFunction(functionHandle, (nint)ptr); | ||
ref JSMarshalerArgument exceptionArg = ref arguments[0]; | ||
if (exceptionArg.slot.Type != MarshalerType.None) | ||
{ | ||
|
@@ -214,12 +233,33 @@ internal static unsafe void InvokeJSFunction(JSObject jsFunction, Span<JSMarshal | |
} | ||
} | ||
|
||
|
||
#if FEATURE_WASM_THREADS | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static unsafe void DispatchJSFunctionSync(JSObject jsFunction, Span<JSMarshalerArgument> arguments) | ||
{ | ||
var args = (nint)Unsafe.AsPointer(ref arguments[0]); | ||
var functionHandle = jsFunction.JSHandle; | ||
|
||
jsFunction.ProxyContext.SynchronizationContext.Send(static o => | ||
{ | ||
var state = ((nint functionHandle, nint args))o!; | ||
Interop.Runtime.InvokeJSFunction(state.functionHandle, state.args); | ||
}, (functionHandle, args)); | ||
|
||
ref JSMarshalerArgument exceptionArg = ref arguments[0]; | ||
if (exceptionArg.slot.Type != MarshalerType.None) | ||
{ | ||
JSHostImplementation.ThrowException(ref exceptionArg); | ||
} | ||
} | ||
#endif | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments) | ||
{ | ||
#if FEATURE_WASM_THREADS | ||
var targetContext = JSProxyContext.SealJSImportCapturing(); | ||
JSProxyContext.AssertIsInteropThread(); | ||
arguments[0].slot.ContextHandle = targetContext.ContextHandle; | ||
arguments[1].slot.ContextHandle = targetContext.ContextHandle; | ||
#else | ||
|
@@ -229,20 +269,36 @@ internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span | |
if (signature.IsAsync) | ||
{ | ||
// pre-allocate the result handle and Task | ||
var holder = new JSHostImplementation.PromiseHolder(targetContext); | ||
var holder = targetContext.CreatePromiseHolder(); | ||
arguments[1].slot.Type = MarshalerType.TaskPreCreated; | ||
arguments[1].slot.GCHandle = holder.GCHandle; | ||
} | ||
|
||
fixed (JSMarshalerArgument* ptr = arguments) | ||
#if FEATURE_WASM_THREADS | ||
// if we are on correct thread already or this is synchronous call, just call it | ||
if (targetContext.IsCurrentThread()) | ||
{ | ||
Interop.Runtime.InvokeJSImport(signature.ImportHandle, ptr); | ||
ref JSMarshalerArgument exceptionArg = ref arguments[0]; | ||
if (exceptionArg.slot.Type != MarshalerType.None) | ||
InvokeJSImportCurrent(signature, arguments); | ||
|
||
#if DEBUG | ||
if (signature.IsAsync && arguments[1].slot.Type == MarshalerType.None) | ||
{ | ||
JSHostImplementation.ThrowException(ref exceptionArg); | ||
throw new InvalidOperationException("null Task/Promise return is not supported"); | ||
} | ||
#endif | ||
|
||
} | ||
else if (!signature.IsAsync) | ||
{ | ||
DispatchJSImportSync(signature, targetContext, arguments); | ||
} | ||
else | ||
{ | ||
DispatchJSImportAsync(signature, targetContext, arguments); | ||
} | ||
#else | ||
InvokeJSImportCurrent(signature, arguments); | ||
|
||
if (signature.IsAsync) | ||
{ | ||
// if js synchronously returned null | ||
|
@@ -252,22 +308,83 @@ internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span | |
holderHandle.Free(); | ||
} | ||
} | ||
#endif | ||
} | ||
|
||
internal static unsafe JSFunctionBinding BindJSImportImpl(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures) | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static unsafe void InvokeJSImportCurrent(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments) | ||
{ | ||
fixed (JSMarshalerArgument* args = arguments) | ||
{ | ||
#if FEATURE_WASM_THREADS | ||
JSProxyContext.AssertIsInteropThread(); | ||
Interop.Runtime.InvokeJSImportSync((nint)args, (nint)signature.Header); | ||
#else | ||
Interop.Runtime.InvokeJSImport(signature.ImportHandle, (nint)args); | ||
#endif | ||
} | ||
|
||
ref JSMarshalerArgument exceptionArg = ref arguments[0]; | ||
if (exceptionArg.slot.Type != MarshalerType.None) | ||
{ | ||
JSHostImplementation.ThrowException(ref exceptionArg); | ||
} | ||
} | ||
|
||
#if FEATURE_WASM_THREADS | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static unsafe void DispatchJSImportSync(JSFunctionBinding signature, JSProxyContext targetContext, Span<JSMarshalerArgument> arguments) | ||
{ | ||
var args = (nint)Unsafe.AsPointer(ref arguments[0]); | ||
var sig = (nint)signature.Header; | ||
|
||
targetContext.SynchronizationContext.Send(static o => | ||
{ | ||
var state = ((nint args, nint sig))o!; | ||
Interop.Runtime.InvokeJSImportSync(state.args, state.sig); | ||
}, (args, sig)); | ||
|
||
ref JSMarshalerArgument exceptionArg = ref arguments[0]; | ||
if (exceptionArg.slot.Type != MarshalerType.None) | ||
{ | ||
JSHostImplementation.ThrowException(ref exceptionArg); | ||
} | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static unsafe void DispatchJSImportAsync(JSFunctionBinding signature, JSProxyContext targetContext, Span<JSMarshalerArgument> arguments) | ||
{ | ||
// this copy is freed in mono_wasm_invoke_import_async | ||
var bytes = sizeof(JSMarshalerArgument) * arguments.Length; | ||
void* cpy = (void*)Marshal.AllocHGlobal(bytes); | ||
void* src = Unsafe.AsPointer(ref arguments[0]); | ||
Unsafe.CopyBlock(cpy, src, (uint)bytes); | ||
var sig = (nint)signature.Header; | ||
|
||
targetContext.SynchronizationContext.Post(static o => | ||
{ | ||
var state = ((nint args, nint sig))o!; | ||
Interop.Runtime.InvokeJSImportAsync(state.args, state.sig); | ||
}, ((nint)cpy, sig)); | ||
|
||
} | ||
|
||
#endif | ||
|
||
internal static unsafe JSFunctionBinding BindJSImportImpl(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures) | ||
{ | ||
var signature = JSHostImplementation.GetMethodSignature(signatures, functionName, moduleName); | ||
|
||
#if !FEATURE_WASM_THREADS | ||
|
||
Interop.Runtime.BindJSImport(signature.Header, out int isException, out object exceptionMessage); | ||
if (isException != 0) | ||
throw new JSException((string)exceptionMessage); | ||
|
||
JSHostImplementation.FreeMethodSignatureBuffer(signature); | ||
|
||
#endif | ||
|
||
return signature; | ||
} | ||
|
||
|
@@ -286,18 +403,41 @@ internal static unsafe JSFunctionBinding BindManagedFunctionImpl(string fullyQua | |
return signature; | ||
} | ||
|
||
#if !FEATURE_WASM_THREADS | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static unsafe void ResolveOrRejectPromise(Span<JSMarshalerArgument> arguments) | ||
{ | ||
fixed (JSMarshalerArgument* ptr = arguments) | ||
{ | ||
Interop.Runtime.ResolveOrRejectPromise(ptr); | ||
Interop.Runtime.ResolveOrRejectPromise((nint)ptr); | ||
ref JSMarshalerArgument exceptionArg = ref arguments[0]; | ||
if (exceptionArg.slot.Type != MarshalerType.None) | ||
{ | ||
JSHostImplementation.ThrowException(ref exceptionArg); | ||
} | ||
} | ||
} | ||
#else | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static unsafe void ResolveOrRejectPromise(JSProxyContext targetContext, Span<JSMarshalerArgument> arguments) | ||
{ | ||
// this copy is freed in mono_wasm_invoke_import_async | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this used to be always synchronous, and now becomes always-asynchronous. should we preserve the old synchronous execution when the target context is the current context? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that JS I want to switch this to emscripten dispatch later anyway. I will add comment and keep this open question for now. |
||
var bytes = sizeof(JSMarshalerArgument) * arguments.Length; | ||
void* cpy = (void*)Marshal.AllocHGlobal(bytes); | ||
void* src = Unsafe.AsPointer(ref arguments[0]); | ||
Unsafe.CopyBlock(cpy, src, (uint)bytes); | ||
|
||
// TODO: we could optimize away the work item allocation in JSSynchronizationContext if we synchronously dispatch this when we are already in the right thread. | ||
|
||
// async | ||
targetContext.SynchronizationContext.Post(static o => | ||
{ | ||
var args = (nint)o!; | ||
Interop.Runtime.ResolveOrRejectPromise(args); | ||
}, (nint)cpy); | ||
|
||
// this never throws directly | ||
} | ||
#endif | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.