Skip to content

Commit

Permalink
[browser] Move reflection from JS to C# (#98391)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara authored Feb 15, 2024
1 parent 988958f commit c34474e
Show file tree
Hide file tree
Showing 23 changed files with 471 additions and 491 deletions.
7 changes: 3 additions & 4 deletions src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
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;
using System.Reflection;
using System.Runtime.CompilerServices;

internal static partial class Interop
Expand All @@ -25,8 +26,6 @@ internal static unsafe partial class Runtime
public static extern void InvokeJSFunctionSend(nint targetNativeTID, nint functionHandle, nint data);
#endif

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern unsafe void BindCSFunction(in string fully_qualified_name, int signature_hash, void* signature, out int is_exception, out object result);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void ResolveOrRejectPromise(nint data);
#if FEATURE_WASM_MANAGED_THREADS
Expand Down Expand Up @@ -65,7 +64,7 @@ internal static unsafe partial class Runtime
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void CancelPromise(nint gcHandle);
#endif


[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern void SetEntryAssembly(Assembly assembly, int entryPointMetadataToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@
<data name="MissingManagedEntrypointHandle" xml:space="preserve">
<value>Managed entrypoint handle is not set.</value>
</data>
<data name="CannotResolveManagedEntrypointHandle" xml:space="preserve">
<value>Cannot resolve managed entrypoint handle.</value>
<data name="CannotResolveManagedEntrypoint" xml:space="preserve">
<value>Cannot resolve managed entrypoint {0} in assembly {1}.</value>
</data>
<data name="ReturnTypeNotSupportedForMain" xml:space="preserve">
<value>Return type '{0}' from main method in not supported.</value>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
// 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.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSHostImplementation;

namespace System.Runtime.InteropServices.JavaScript
{
Expand All @@ -18,87 +15,35 @@ namespace System.Runtime.InteropServices.JavaScript
internal static unsafe partial class JavaScriptExports
{
// the marshaled signature is:
// Task<int>? CallEntrypoint(MonoMethod* entrypointPtr, string[] args)
// Task<int>? CallEntrypoint(string mainAssemblyName, string[] args, bool waitForDebugger)
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_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
try
{
#if FEATURE_WASM_MANAGED_THREADS
// when we arrive here, we are on the thread which owns the proxies
arg_exc.AssertCurrentThreadContext();
#endif

arg_1.ToManaged(out IntPtr entrypointPtr);
if (entrypointPtr == IntPtr.Zero)
{
throw new MissingMethodException(SR.MissingManagedEntrypointHandle);
}
arg_1.ToManaged(out string? mainAssemblyName);
arg_2.ToManaged(out string?[]? args);
arg_3.ToManaged(out bool waitForDebugger);

RuntimeMethodHandle methodHandle = GetMethodHandleFromIntPtr(entrypointPtr);
// this would not work for generic types. But Main() could not be generic, so we are fine.
MethodInfo? method = MethodBase.GetMethodFromHandle(methodHandle) as MethodInfo;
if (method == null)
{
throw new InvalidOperationException(SR.CannotResolveManagedEntrypointHandle);
}
Task<int>? result = JSHostImplementation.CallEntrypoint(mainAssemblyName, args, waitForDebugger);

arg_2.ToManaged(out string?[]? args);
object[] argsToPass = System.Array.Empty<object>();
Task<int>? result = null;
var parameterInfos = method.GetParameters();
if (parameterInfos.Length > 0 && parameterInfos[0].ParameterType == typeof(string[]))
{
argsToPass = new object[] { args ?? System.Array.Empty<string>() };
}
if (method.ReturnType == typeof(void))
{
method.Invoke(null, argsToPass);
}
else if (method.ReturnType == typeof(int))
{
int intResult = (int)method.Invoke(null, argsToPass)!;
result = Task.FromResult(intResult);
}
else if (method.ReturnType == typeof(Task))
{
Task methodResult = (Task)method.Invoke(null, argsToPass)!;
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
result = tcs.Task;
methodResult.ContinueWith((t) =>
{
if (t.IsFaulted)
{
tcs.SetException(t.Exception!);
}
else
{
tcs.SetResult(0);
}
}, TaskScheduler.Default);
}
else if (method.ReturnType == typeof(Task<int>))
{
result = (Task<int>)method.Invoke(null, argsToPass)!;
}
else
{
throw new InvalidOperationException(SR.Format(SR.ReturnTypeNotSupportedForMain, method.ReturnType.FullName));
}
arg_result.ToJS(result, (ref JSMarshalerArgument arg, int value) =>
{
arg.ToJS(value);
});
}
catch (Exception ex)
{
if (ex is TargetInvocationException refEx && refEx.InnerException != null)
ex = refEx.InnerException;

arg_exc.ToJS(ex);
Environment.FailFast("CallEntrypoint: Unexpected synchronous failure. " + ex);
}
}

Expand Down Expand Up @@ -163,7 +108,7 @@ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments
}
catch (Exception ex)
{
arg_exc.ToJS(ex);
Environment.FailFast("ReleaseJSOwnedObjectByGCHandle: Unexpected synchronous failure. " + ex);
}
}

Expand All @@ -188,7 +133,7 @@ public static void CallDelegate(JSMarshalerArgument* arguments_buffer)
#endif

GCHandle callback_gc_handle = (GCHandle)arg_1.slot.GCHandle;
if (callback_gc_handle.Target is ToManagedCallback callback)
if (callback_gc_handle.Target is JSHostImplementation.ToManagedCallback callback)
{
// arg_2, arg_3, arg_4, arg_res are processed by the callback
callback(arguments_buffer);
Expand Down Expand Up @@ -219,7 +164,7 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer)
// when we arrive here, we are on the thread which owns the proxies
var ctx = arg_exc.AssertCurrentThreadContext();
var holder = ctx.GetPromiseHolder(arg_1.slot.GCHandle);
ToManagedCallback callback;
JSHostImplementation.ToManagedCallback callback;

#if FEATURE_WASM_MANAGED_THREADS
lock (ctx)
Expand Down Expand Up @@ -260,7 +205,7 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer)
}
catch (Exception ex)
{
arg_exc.ToJS(ex);
Environment.FailFast("CompleteTask: Unexpected synchronous failure. " + ex);
}
}

Expand Down Expand Up @@ -318,6 +263,30 @@ public static void InstallMainSynchronizationContext(JSMarshalerArgument* argume

#endif

// 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_1 = ref arguments_buffer[2];// initialized and set by caller
try
{
string? assemblyName;
// when we arrive here, we are on the thread which owns the proxies
arg_exc.AssertCurrentThreadContext();
arg_1.ToManaged(out assemblyName);

var result = JSHostImplementation.BindAssemblyExports(assemblyName);

arg_result.ToJS(result);
}
catch (Exception ex)
{
Environment.FailFast("BindAssemblyExports: Unexpected synchronous failure. " + ex);
}
}

[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 @@ -45,6 +45,10 @@ internal static unsafe partial class JavaScriptImports
[JSImport("INTERNAL.dynamic_import")]
// TODO: the continuation should be running on deputy or TP in MT
public static partial Task<JSObject> DynamicImport(string moduleName, string moduleUrl);

[JSImport("INTERNAL.mono_wasm_bind_cs_function")]
public static partial void BindCSFunction(IntPtr monoMethod, string assemblyName, string namespaceName, string shortClassName, string methodName, int signatureHash, IntPtr signature);

#if FEATURE_WASM_MANAGED_THREADS
[JSImport("INTERNAL.thread_available")]
// TODO: the continuation should be running on deputy or TP in MT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,11 @@ public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, i
{
if (RuntimeInformation.OSArchitecture != Architecture.Wasm)
throw new PlatformNotSupportedException();
#if FEATURE_WASM_MANAGED_THREADS
JSProxyContext.AssertIsInteropThread();
#endif
return BindManagedFunctionImpl(fullyQualifiedName, signatureHash, signatures);

// this could be called by assembly module initializer from Net7 code-gen
// on wrong thread, in which case we will bind it to UI thread

return JSHostImplementation.BindManagedFunction(fullyQualifiedName, signatureHash, signatures);
}

#if !DEBUG
Expand Down Expand Up @@ -407,21 +408,6 @@ internal static unsafe JSFunctionBinding BindJSImportImpl(string functionName, s
return signature;
}

internal static unsafe JSFunctionBinding BindManagedFunctionImpl(string fullyQualifiedName, int signatureHash, ReadOnlySpan<JSMarshalerType> signatures)
{
var signature = JSHostImplementation.GetMethodSignature(signatures, null, null);

Interop.Runtime.BindCSFunction(fullyQualifiedName, signatureHash, signature.Header, out int isException, out object exceptionMessage);
if (isException != 0)
{
throw new JSException((string)exceptionMessage);
}

JSHostImplementation.FreeMethodSignatureBuffer(signature);

return signature;
}

#if !DEBUG
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
Expand Down
Loading

0 comments on commit c34474e

Please sign in to comment.