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] Move reflection from JS to C# #98391

Merged
merged 13 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
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,76 +15,27 @@ 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);
Expand Down Expand Up @@ -188,7 +136,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 +167,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 @@ -318,6 +266,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)
{
arg_exc.ToJS(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
Loading