Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ internal static unsafe partial class Runtime
[LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_CancelPromise")]
public static partial void CancelPromise(nint gcHandle);

[LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_BindAssemblyExports")]
public static partial void BindAssemblyExports(IntPtr assemblyNamePtr);
[LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_GetAssemblyExport")]
public static partial void GetAssemblyExport(IntPtr assemblyNamePtr, IntPtr namespacePtr, IntPtr classnamePtr, IntPtr methodNamePtr, int signatureHash, IntPtr* methodHandlePtr);

// TODO-WASM: delete once we switch to CoreCLR only
[LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_AssemblyGetEntryPoint")]
public static partial void AssemblyGetEntryPoint(IntPtr assemblyNamePtr, int auto_insert_breakpoint, void** monoMethodPtrPtr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,5 @@ internal static unsafe partial class Runtime
public static extern void AssemblyGetEntryPoint(IntPtr assemblyNamePtr, int auto_insert_breakpoint, void** monoMethodPtrPtr);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void BindAssemblyExports(IntPtr assemblyNamePtr);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void GetAssemblyExport(IntPtr assemblyNamePtr, IntPtr namespacePtr, IntPtr classnamePtr, IntPtr methodNamePtr, int signatureHash, IntPtr* monoMethodPtrPtr);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<Compile Include="System\Runtime\InteropServices\JavaScript\JSType.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSFunctionBinding.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSHostImplementation.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSHostImplementation.Exports.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSHostImplementation.Mono.cs" Condition="'$(RuntimeFlavor)' == 'Mono'" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSHostImplementation.CoreCLR.cs" Condition="'$(RuntimeFlavor)' == 'CoreCLR'" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSHostImplementation.Types.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public static void CallJSExport(int methodHandle, JSMarshalerArgument* arguments
{
ref JSMarshalerArgument argException = ref argumentsBuffer[0]; // initialized by caller in alloc_stack_frame()
var ctx = argException.AssertCurrentThreadContext();
if (!ctx.JSExportByHandle.TryGetValue(methodHandle, out var jsExport))
if (!ctx.TryGetJSExport(methodHandle, out var jsExport))
{
argException.ToJS(new InvalidOperationException("Unable to resolve JSExport by handle"));
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,19 @@ public static void BindAssemblyExports(JSMarshalerArgument* arguments_buffer)
}
}

// the arguments are passed positionally by mono_wasm_invoke_jsexport_by_handle, not as a marshaled frame
public static void CallJSExport(int methodHandle, JSMarshalerArgument* argumentsBuffer)
{
ref JSMarshalerArgument argException = ref argumentsBuffer[0]; // initialized by caller in alloc_stack_frame()
var ctx = argException.AssertCurrentThreadContext();
if (!ctx.TryGetJSExport(methodHandle, out var jsExport))
{
argException.ToJS(new InvalidOperationException("Unable to resolve JSExport by handle"));
return;
}
jsExport(new IntPtr(argumentsBuffer));
}

[MethodImpl(MethodImplOptions.NoInlining)] // profiler needs to find it executed under this name
public static void StopProfile()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,50 +33,5 @@ public static Task BindAssemblyExports(string? assemblyName)
registerMethod.Invoke(null, null);
return Task.CompletedTask;
}

// this reflection based approach is used by JSExport assemblies generated by Net10 or below
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's kept from trimming by DynamicDependencyAttribute in the generated code.")]
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "It's kept from trimming by DynamicDependencyAttribute in the generated code.")]
public static unsafe JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan<JSMarshalerType> signatures)
{
var ctx = JSProxyContext.CurrentThreadContext;
var (assemblyName, nameSpace, shortClassName, methodName) = ParseFQN(fullyQualifiedName);
var wrapperName = $"__Wrapper_{methodName}_{signatureHash}";
shortClassName = shortClassName.Replace('/', '+');

// get MethodInfo from the fully qualified name
var assembly = Assembly.Load(new AssemblyName(assemblyName));
var clazz = string.IsNullOrEmpty(nameSpace)
? assembly.GetType(shortClassName)
: assembly.GetType(nameSpace + "." + shortClassName);
if (clazz == null)
{
Environment.FailFast($"Can't find {nameSpace}{shortClassName} in {assemblyName} assembly");
}
var wrapperInfo = clazz.GetMethod(wrapperName, BindingFlags.Static | BindingFlags.NonPublic);
if (wrapperInfo == null)
{
Environment.FailFast($"Can't find method wrapper {wrapperName} in {nameSpace}.{shortClassName} in {assemblyName} assembly");
}

Action<IntPtr> wrapper = (IntPtr args) =>
{
object boxedLegacyArgs = Pointer.Box((void*)args, typeof(JSMarshalerArgument*));
// real signature is void (JSMarshalerArgument* args)
wrapperInfo.Invoke(null, new object?[] { boxedLegacyArgs });
};

int methodHandle = ctx.NextJSExportHandle++;
ctx.JSExportByHandle[methodHandle] = wrapper;

var signature = GetMethodSignature(signatures, null, null);

// this will hit JS side possibly on another thread, depending on JSProxyContext.CurrentThreadContext
JavaScriptImports.BindCSFunction(methodHandle, assemblyName, nameSpace, shortClassName, methodName, signatureHash, (IntPtr)signature.Header);

FreeMethodSignatureBuffer(signature);

return signature;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace System.Runtime.InteropServices.JavaScript
{
internal static partial class JSHostImplementation
{
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's kept from trimming by DynamicDependencyAttribute in the generated code.")]
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "It's kept from trimming by DynamicDependencyAttribute in the generated code.")]
public static unsafe JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan<JSMarshalerType> signatures)
{
var ctx = JSProxyContext.BindingContextOrMain();
var (assemblyName, nameSpace, shortClassName, methodName) = ParseFQN(fullyQualifiedName);
var wrapperName = $"__Wrapper_{methodName}_{signatureHash}";
// reflection wants '+' between nested types, but the JS side walks the export tree on '/', so keep shortClassName as parsed
var reflectionClassName = shortClassName.Replace('/', '+');

// get MethodInfo from the fully qualified name
var assembly = Assembly.Load(new AssemblyName(assemblyName));
var clazz = string.IsNullOrEmpty(nameSpace)
? assembly.GetType(reflectionClassName)
: assembly.GetType(nameSpace + "." + reflectionClassName);
if (clazz == null)
{
Environment.FailFast($"Can't find {nameSpace}{shortClassName} in {assemblyName} assembly");
}
var wrapperInfo = clazz.GetMethod(wrapperName, BindingFlags.Static | BindingFlags.NonPublic);
if (wrapperInfo == null)
{
Environment.FailFast($"Can't find method wrapper {wrapperName} in {nameSpace}.{shortClassName} in {assemblyName} assembly");
}

Action<IntPtr> wrapper = (IntPtr args) =>
{
object boxedLegacyArgs = Pointer.Box((void*)args, typeof(JSMarshalerArgument*));
// real signature is void (JSMarshalerArgument* args)
wrapperInfo.Invoke(null, new object?[] { boxedLegacyArgs });
Comment thread
pavelsavara marked this conversation as resolved.
};

int methodHandle = ctx.AllocJSExportHandle(wrapper);

var signature = GetMethodSignature(signatures, null, null);

// this will hit JS side possibly on another thread, depending on JSProxyContext.CurrentThreadContext
JavaScriptImports.BindCSFunction(methodHandle, assemblyName, nameSpace, shortClassName, methodName, signatureHash, (IntPtr)signature.Header);

FreeMethodSignatureBuffer(signature);

return signature;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,5 @@ public static Task BindAssemblyExports(string? assemblyName)
Interop.Runtime.BindAssemblyExports(Marshal.StringToCoTaskMemUTF8(assemblyName));
return Task.CompletedTask;
}

public static unsafe JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan<JSMarshalerType> signatures)
{
var (assemblyName, nameSpace, shortClassName, methodName) = ParseFQN(fullyQualifiedName);

IntPtr monoMethod;
Interop.Runtime.GetAssemblyExport(
// FIXME: Pass UTF-16 through directly so C can work with it, doing the conversion
// in C# pulls in a bunch of dependencies we don't need this early in startup.
// I tested removing the UTF8 conversion from this specific call, but other parts
// of startup I can't identify still pull in UTF16->UTF8 conversion, so it's not
// worth it to do that yet.
Marshal.StringToCoTaskMemUTF8(assemblyName),
Marshal.StringToCoTaskMemUTF8(nameSpace),
Marshal.StringToCoTaskMemUTF8(shortClassName),
Marshal.StringToCoTaskMemUTF8(methodName),
signatureHash,
&monoMethod);

if (monoMethod == IntPtr.Zero)
{
Environment.FailFast($"Can't find {nameSpace}{shortClassName}{methodName} in {assemblyName}.dll");
}

var signature = GetMethodSignature(signatures, null, null);

// this will hit JS side possibly on another thread, depending on JSProxyContext.CurrentThreadContext
JavaScriptImports.BindCSFunction(monoMethod, assemblyName, nameSpace, shortClassName, methodName, signatureHash, (IntPtr)signature.Header);

FreeMethodSignatureBuffer(signature);

return signature;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using static System.Runtime.InteropServices.JavaScript.JSHostImplementation;
Expand All @@ -23,8 +24,10 @@ internal sealed class JSProxyContext : IDisposable
// they have negative values, so that they don't collide with JSHandles.
private nint NextJSVHandle = -2;
private readonly List<nint> JSVHandleFreeList = new();
internal Dictionary<int, Action<IntPtr>> JSExportByHandle = new Dictionary<int, Action<IntPtr>>();
internal int NextJSExportHandle = 1;
// Guarded by lock (this) in the multi-threaded build: BindManagedFunction can register an export
// into another thread's context via BindingContextOrMain, concurrently with CallJSExport reading it.
private readonly Dictionary<int, Action<IntPtr>> JSExportByHandle = new Dictionary<int, Action<IntPtr>>();
private int NextJSExportHandle = 1;

public int PromiseHolderCount
{
Expand Down Expand Up @@ -274,6 +277,42 @@ public static JSProxyContext AssertIsInteropThread()
#endif
}

// Selects the context to bind a [JSExport] into. BindManagedFunction can be reached from an
// assembly module initializer running on a thread without JS interop (see the documented
// contract on JSFunctionBinding.BindManagedFunction), so fall back to the main/UI thread
// context rather than rejecting that supported path.
public static JSProxyContext BindingContextOrMain()
{
#if FEATURE_WASM_MANAGED_THREADS
return CurrentThreadContext ?? MainThreadContext;
#else
return MainThreadContext;
#endif
}

// Registration can run on a thread other than the one owning this context, see BindingContextOrMain.
public int AllocJSExportHandle(Action<IntPtr> wrapper)
{
#if FEATURE_WASM_MANAGED_THREADS
lock (this)
#endif
{
int methodHandle = NextJSExportHandle++;
JSExportByHandle[methodHandle] = wrapper;
return methodHandle;
}
}

public bool TryGetJSExport(int methodHandle, [MaybeNullWhen(false)] out Action<IntPtr> wrapper)
{
#if FEATURE_WASM_MANAGED_THREADS
lock (this)
#endif
{
return JSExportByHandle.TryGetValue(methodHandle, out wrapper);
}
}

#endregion

#region Handles
Expand Down
34 changes: 0 additions & 34 deletions src/mono/browser/runtime/corebindings.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ typedef int (*ds_job_cb)(void* data);

void SystemInteropJS_BindAssemblyExports (char *assembly_name);
void SystemInteropJS_AssemblyGetEntryPoint (char *assembly_name, int auto_insert_breakpoint, MonoMethod **method_out);
void SystemInteropJS_GetAssemblyExport (char *assembly_name, char *namespace, char *classname, char *methodname, int signature_hash, MonoMethod **method_out);

#ifndef DISABLE_THREADS
void SystemInteropJS_ReleaseCSOwnedObjectPost (pthread_t target_tid, int js_handle);
Expand Down Expand Up @@ -91,7 +90,6 @@ void bindings_initialize_internals (void)
mono_add_internal_call ("Interop/Runtime::CancelPromise", SystemInteropJS_CancelPromise);
mono_add_internal_call ("Interop/Runtime::AssemblyGetEntryPoint", SystemInteropJS_AssemblyGetEntryPoint);
mono_add_internal_call ("Interop/Runtime::BindAssemblyExports", SystemInteropJS_BindAssemblyExports);
mono_add_internal_call ("Interop/Runtime::GetAssemblyExport", SystemInteropJS_GetAssemblyExport);
mono_add_internal_call ("System.ConsolePal::Clear", SystemJS_ConsoleClear);

// JS-based globalization
Expand Down Expand Up @@ -215,38 +213,6 @@ void SystemInteropJS_BindAssemblyExports (char *assembly_name)
}
}

void SystemInteropJS_GetAssemblyExport (char *assembly_name, char *namespace, char *classname, char *methodname, int signature_hash, MonoMethod **method_out)
{
MonoError error;
MonoAssembly* assembly;
MonoImage *image;
MonoClass *klass;
MonoMethod *method=NULL;
char real_method_name_buffer[4096];
*method_out = NULL;

assert (assembly_name);
assembly = _mono_wasm_assembly_load (assembly_name);
assert (assembly);
image = mono_assembly_get_image (assembly);
assert (image);

klass = mono_class_from_name (image, namespace, classname);
assert (klass);

snprintf(real_method_name_buffer, 4096, "__Wrapper_%s_%d", methodname, signature_hash);

method = mono_class_get_method_from_name (klass, real_method_name_buffer, -1);
assert (method);

*method_out = method;
// This is freed by _mono_wasm_assembly_load for some reason
// free (assembly_name);
free (namespace);
free (classname);
free (methodname);
}

#ifndef DISABLE_THREADS

void* before_sync_js_import;
Expand Down
12 changes: 11 additions & 1 deletion src/mono/browser/runtime/cwraps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads";
import type {
MonoAssembly, MonoClass,
MonoMethod, MonoObject,
MonoType, MonoObjectRef, MonoStringRef, JSMarshalerArguments, PThreadPtr
MonoType, MonoObjectRef, MonoStringRef, JSMarshalerArguments, PThreadPtr, CSFnHandle
} from "./types/internal";
import type { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr, ManagedPointer } from "./types/emscripten";
import { Module, runtimeHelpers } from "./globals";
Expand All @@ -20,6 +20,9 @@ const threading_cwraps: SigLine[] = WasmEnableThreads ? [
[false, "mono_wasm_invoke_jsexport_async_post", "void", ["number", "number", "number"]],
[false, "mono_wasm_invoke_jsexport_sync_send", "void", ["number", "number", "number"]],
[false, "mono_wasm_invoke_jsexport_sync", "void", ["number", "number"]],
[false, "mono_wasm_invoke_jsexport_by_handle_async_post", "void", ["number", "number", "number"]],
[false, "mono_wasm_invoke_jsexport_by_handle_sync_send", "void", ["number", "number", "number"]],
[false, "mono_wasm_invoke_jsexport_by_handle_sync", "void", ["number", "number"]],
[true, "mono_wasm_create_deputy_thread", "number", []],
[true, "mono_wasm_create_io_thread", "number", []],
[true, "mono_wasm_register_ui_thread", "void", []],
Expand Down Expand Up @@ -65,6 +68,8 @@ const fn_signatures: SigLine[] = [
[() => !runtimeHelpers.emscriptenBuildOptions.enableLogProfiler, "mono_wasm_profiler_init_log", "void", ["string"]],
[false, "mono_wasm_exec_regression", "number", ["number", "string"]],
[false, "mono_wasm_invoke_jsexport", "void", ["number", "number"]],
[false, "mono_wasm_set_jsexport_dispatcher", "void", ["number"]],
[false, "mono_wasm_invoke_jsexport_by_handle", "void", ["number", "number"]],
[true, "mono_wasm_write_managed_pointer_unsafe", "void", ["number", "number"]],
[true, "mono_wasm_copy_managed_pointer", "void", ["number", "number"]],
[true, "mono_wasm_i52_to_f64", "number", ["number", "number"]],
Expand Down Expand Up @@ -143,6 +148,9 @@ export interface t_ThreadingCwraps {
mono_wasm_invoke_jsexport_async_post(targetTID: PThreadPtr, method: MonoMethod, args: VoidPtr): void;
mono_wasm_invoke_jsexport_sync_send(targetTID: PThreadPtr, method: MonoMethod, args: VoidPtr): void;
mono_wasm_invoke_jsexport_sync(method: MonoMethod, args: VoidPtr): void;
mono_wasm_invoke_jsexport_by_handle_async_post(targetTID: PThreadPtr, handle: CSFnHandle, args: VoidPtr): void;
mono_wasm_invoke_jsexport_by_handle_sync_send(targetTID: PThreadPtr, handle: CSFnHandle, args: VoidPtr): void;
mono_wasm_invoke_jsexport_by_handle_sync(handle: CSFnHandle, args: VoidPtr): void;
mono_wasm_create_deputy_thread(): PThreadPtr;
mono_wasm_create_io_thread(): PThreadPtr;
mono_wasm_register_ui_thread(): void;
Expand Down Expand Up @@ -189,6 +197,8 @@ export interface t_Cwraps {
mono_wasm_set_main_args(argc: number, argv: VoidPtr): void;
mono_wasm_exec_regression(verbose_level: number, image: string): number;
mono_wasm_invoke_jsexport(method: MonoMethod, args: JSMarshalerArguments): void;
mono_wasm_set_jsexport_dispatcher(method: MonoMethod): void;
mono_wasm_invoke_jsexport_by_handle(handle: CSFnHandle, args: JSMarshalerArguments): void;
mono_wasm_write_managed_pointer_unsafe(destination: VoidPtr | MonoObjectRef, pointer: ManagedPointer): void;
mono_wasm_copy_managed_pointer(destination: VoidPtr | MonoObjectRef, source: VoidPtr | MonoObjectRef): void;
mono_wasm_i52_to_f64(source: VoidPtr, error: Int32Ptr): number;
Expand Down
Loading
Loading