From f816f5ee554913dca91fd6490c90a689a80422f1 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 27 Nov 2025 18:33:57 +0100 Subject: [PATCH 1/2] wip --- .../src/Interop/Browser/Interop.Runtime.cs | 2 - .../gen/JSImportGenerator/Comparers.cs | 32 +++++- .../gen/JSImportGenerator/Constants.cs | 2 + .../JSImportGenerator/JSExportGenerator.cs | 56 ++++++----- .../JSImportGenerator/JSSignatureContext.cs | 15 +-- ...stem.Runtime.InteropServices.JavaScript.cs | 2 +- .../JavaScript/Interop/JavaScriptExports.cs | 21 ++++ .../Interop/JavaScriptImports.Generated.cs | 2 +- .../JavaScript/JSFunctionBinding.cs | 7 +- .../JavaScript/JSHostImplementation.cs | 97 ++----------------- .../JavaScript/JSProxyContext.cs | 4 +- .../JSImportGenerator.UnitTest/Compiles.cs | 3 +- .../JavaScript/JavaScriptTestHelper.cs | 8 +- ...iCompatBaseline.NetCoreAppLatestStable.xml | 6 ++ src/mono/browser/runtime/corebindings.c | 34 ------- src/mono/browser/runtime/invoke-cs.ts | 26 ++--- src/mono/browser/runtime/managed-exports.ts | 16 ++- src/mono/browser/runtime/types/internal.ts | 3 + 18 files changed, 150 insertions(+), 186 deletions(-) diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index aa35d57828ee3a..38dabd79ec081e 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -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); } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/Comparers.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/Comparers.cs index f27af1ccd55645..1a8b906bb8cbc8 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/Comparers.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/Comparers.cs @@ -16,9 +16,9 @@ internal static class Comparers /// Comparer for an individual generated stub source as a syntax tree and the generated diagnostics for the stub. /// public static readonly IEqualityComparer<(MemberDeclarationSyntax Syntax, ImmutableArray Diagnostics)> GeneratedSyntax = new CustomValueTupleElementComparer>(SyntaxEquivalentComparer.Instance, new ImmutableArraySequenceEqualComparer(EqualityComparer.Default)); - public static readonly IEqualityComparer<(MemberDeclarationSyntax, StatementSyntax, AttributeListSyntax, ImmutableArray)> GeneratedSyntax4 = - new CustomValueTupleElementComparer>( - SyntaxEquivalentComparer.Instance, SyntaxEquivalentComparer.Instance, SyntaxEquivalentComparer.Instance, + public static readonly IEqualityComparer<(MemberDeclarationSyntax, StatementSyntax, ImmutableArray)> GeneratedSyntax3 = + new CustomValueTupleElementComparer>( + SyntaxEquivalentComparer.Instance, SyntaxEquivalentComparer.Instance, new ImmutableArraySequenceEqualComparer(EqualityComparer.Default)); } @@ -71,6 +71,32 @@ public int GetHashCode((T, U) obj) throw new UnreachableException(); } } + internal sealed class CustomValueTupleElementComparer : IEqualityComparer<(T, U, V)> + { + private readonly IEqualityComparer _item1Comparer; + private readonly IEqualityComparer _item2Comparer; + private readonly IEqualityComparer _item3Comparer; + + public CustomValueTupleElementComparer(IEqualityComparer item1Comparer, IEqualityComparer item2Comparer, IEqualityComparer item3Comparer) + { + _item1Comparer = item1Comparer; + _item2Comparer = item2Comparer; + _item3Comparer = item3Comparer; + } + + public bool Equals((T, U, V) x, (T, U, V) y) + { + return _item1Comparer.Equals(x.Item1, y.Item1) + && _item2Comparer.Equals(x.Item2, y.Item2) + && _item3Comparer.Equals(x.Item3, y.Item3) + ; + } + + public int GetHashCode((T, U, V) obj) + { + throw new UnreachableException(); + } + } internal sealed class CustomValueTupleElementComparer : IEqualityComparer<(T, U, V, W)> { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/Constants.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/Constants.cs index 5dd5fa98ceb1b4..c003e120243c7a 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/Constants.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/Constants.cs @@ -24,11 +24,13 @@ internal static class Constants public const string SpanGlobal = "global::System.Span"; public const string ArraySegmentGlobal = "global::System.ArraySegment"; public const string FuncGlobal = "global::System.Func"; + public const string IntPtrGlobal = "global::System.IntPtr"; public const string ActionGlobal = "global::System.Action"; public const string ExceptionGlobal = "global::System.Exception"; public const string OSArchitectureGlobal = "global::System.Runtime.InteropServices.RuntimeInformation.OSArchitecture"; public const string ArchitectureWasmGlobal = "global::System.Runtime.InteropServices.Architecture.Wasm"; public const string ArgumentsBuffer = "__arguments_buffer"; + public const string ArgumentsPtr = "__arguments_ptr"; public const string ArgumentException = "__arg_exception"; public const string ArgumentReturn = "__arg_return"; public const string ToJSMethod = "ToJS"; diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs index a97b2439b29528..8ee536f6db6075 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs @@ -72,7 +72,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return ImmutableArray.Create(DiagnosticInfo.Create(GeneratorDiagnostics.JSExportRequiresAllowUnsafeBlocks, null)); })); - IncrementalValuesProvider<(MemberDeclarationSyntax, StatementSyntax, AttributeListSyntax, ImmutableArray)> generateSingleStub = methodsToGenerate + IncrementalValuesProvider<(MemberDeclarationSyntax, StatementSyntax, ImmutableArray)> generateSingleStub = methodsToGenerate .Combine(stubEnvironment) .Select(static (data, ct) => new { @@ -87,14 +87,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Select( static (data, ct) => GenerateSource(data) ) - .WithComparer(Comparers.GeneratedSyntax4) + .WithComparer(Comparers.GeneratedSyntax3) .WithTrackingName(StepNames.GenerateSingleStub); - context.RegisterDiagnostics(generateSingleStub.SelectMany((stubInfo, ct) => stubInfo.Item4)); + context.RegisterDiagnostics(generateSingleStub.SelectMany((stubInfo, ct) => stubInfo.Item3)); - IncrementalValueProvider> regSyntax = generateSingleStub + IncrementalValueProvider> regSyntax = generateSingleStub .Select( - static (data, ct) => (data.Item2, data.Item3)) + static (data, ct) => (data.Item2)) .Collect(); IncrementalValueProvider registration = regSyntax @@ -142,11 +142,11 @@ private static MemberDeclarationSyntax PrintGeneratedSource( { MemberDeclarationSyntax wrappperMethod = MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), Identifier(wrapperName)) - .WithModifiers(TokenList(new[] { Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.UnsafeKeyword) })) + .WithModifiers(TokenList(new[] { Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.UnsafeKeyword) })) .WithAttributeLists(SingletonList(AttributeList(SingletonSeparatedList( Attribute(IdentifierName(Constants.DebuggerNonUserCodeAttribute)))))) .WithParameterList(ParameterList(SingletonSeparatedList( - Parameter(Identifier(Constants.ArgumentsBuffer)).WithType(PointerType(ParseTypeName(Constants.JSMarshalerArgumentGlobal)))))) + Parameter(Identifier(Constants.ArgumentsPtr)).WithType(ParseTypeName(Constants.IntPtrGlobal))))) .WithBody(wrapperStatements); MemberDeclarationSyntax toPrint = containingSyntaxContext.WrapMembersInContainingSyntaxWithUnsafeModifier(wrappperMethod); @@ -215,7 +215,7 @@ private static IncrementalStubGenerationContext CalculateStubInformation( } private static NamespaceDeclarationSyntax GenerateRegSource( - ImmutableArray<(StatementSyntax Registration, AttributeListSyntax Attribute)> methods, string assemblyName) + ImmutableArray methods, string assemblyName) { const string generatedNamespace = "System.Runtime.InteropServices.JavaScript"; const string initializerClass = "__GeneratedInitializer"; @@ -230,8 +230,7 @@ private static NamespaceDeclarationSyntax GenerateRegSource( var attributes = new List(); foreach (var m in methods) { - registerStatements.Add(m.Registration); - attributes.Add(m.Attribute); + registerStatements.Add(m); } FieldDeclarationSyntax field = FieldDeclaration(VariableDeclaration(PredefinedType(Token(SyntaxKind.BoolKeyword))) @@ -319,7 +318,7 @@ private static StatementSyntax[] GenerateJSExportArchitectureCheck() ]; } - private static (MemberDeclarationSyntax, StatementSyntax, AttributeListSyntax, ImmutableArray) GenerateSource( + private static (MemberDeclarationSyntax, StatementSyntax, ImmutableArray) GenerateSource( IncrementalStubGenerationContext incrementalContext) { var diagnostics = new GeneratorDiagnosticsBag(new DescriptorProvider(), incrementalContext.DiagnosticLocation, SR.ResourceManager, typeof(FxResources.Microsoft.Interop.JavaScript.JSImportGenerator.SR)); @@ -368,23 +367,35 @@ private static (MemberDeclarationSyntax, StatementSyntax, AttributeListSyntax, I const string innerWrapperName = "__Stub"; BlockSyntax wrapperToInnerStubBlock = Block( + CreateArgsConversion(), CreateWrapperToInnerStubCall(signatureElements, innerWrapperName), GenerateInnerLocalFunction(incrementalContext, innerWrapperName, stubGenerator)); - StatementSyntax registration = GenerateJSExportRegistration(incrementalContext.SignatureContext); - AttributeListSyntax registrationAttribute = AttributeList(SingletonSeparatedList(Attribute(IdentifierName(Constants.DynamicDependencyAttributeGlobal)) - .WithArgumentList(AttributeArgumentList(SeparatedList(new[]{ - AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(wrapperName))), - AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(incrementalContext.SignatureContext.StubTypeFullName))), - AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(incrementalContext.SignatureContext.AssemblyName))), - } - ))))); + StatementSyntax registration = GenerateJSExportRegistration(incrementalContext.SignatureContext, wrapperName); return (PrintGeneratedSource(incrementalContext.ContainingSyntaxContext, wrapperToInnerStubBlock, wrapperName), - registration, registrationAttribute, + registration, incrementalContext.Diagnostics.Array.AddRange(diagnostics.Diagnostics)); } + private static LocalDeclarationStatementSyntax CreateArgsConversion() + { + // var __arguments_buffer = (global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument*) __arguments_ptr; + return LocalDeclarationStatement( + VariableDeclaration( + PointerType( + IdentifierName(Constants.JSMarshalerArgumentGlobal))) + .WithVariables( + SingletonSeparatedList( + VariableDeclarator(Identifier(Constants.ArgumentsBuffer)) + .WithInitializer( + EqualsValueClause( + CastExpression( + PointerType( + IdentifierName(Constants.JSMarshalerArgumentGlobal)), + IdentifierName(Constants.ArgumentsPtr))))))); + } + private static ExpressionStatementSyntax CreateWrapperToInnerStubCall(ImmutableArray signatureElements, string innerWrapperName) { List arguments = []; @@ -436,11 +447,12 @@ private static LocalFunctionStatementSyntax GenerateInnerLocalFunction(Increment Attribute(IdentifierName(Constants.DebuggerNonUserCodeAttribute)))))); } - private static ExpressionStatementSyntax GenerateJSExportRegistration(JSSignatureContext context) + private static ExpressionStatementSyntax GenerateJSExportRegistration(JSSignatureContext context, string wrapperName) { var signatureArgs = new List { - Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(context.QualifiedMethodName))), + Argument(IdentifierName(context.StubTypeFullName + "." + wrapperName)), + Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(context.MethodShortName))), Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(context.TypesHash))), SignatureBindingHelpers.CreateSignaturesArgument(context.SignatureContext.ElementTypeInformation, StubCodeContext.DefaultNativeToManagedStub) }; diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs index f59bb25bca0f51..0af2249ec86bfd 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs @@ -62,35 +62,24 @@ public static JSSignatureContext Create( }; var fullName = $"{method.ContainingType.ToDisplayString()}.{method.Name}"; - string qualifiedName = GetFullyQualifiedMethodName(env, method); return new JSSignatureContext() { SignatureContext = sigContext, TypesHash = typesHash, StubTypeFullName = stubTypeFullName, + MethodShortName = method.Name, MethodName = fullName, - QualifiedMethodName = qualifiedName, BindingName = "__signature_" + method.Name + "_" + typesHash, AssemblyName = env.Compilation.AssemblyName, }; } - private static string GetFullyQualifiedMethodName(StubEnvironment env, IMethodSymbol method) - { - // Mono style nested class name format. - string typeName = method.ContainingType.ToDisplayString(TypeAndContainingTypesStyle).Replace(".", "/"); - - if (!method.ContainingType.ContainingNamespace.IsGlobalNamespace) - typeName = $"{method.ContainingType.ContainingNamespace.ToDisplayString()}.{typeName}"; - - return $"[{env.Compilation.AssemblyName}]{typeName}:{method.Name}"; - } public string? StubTypeFullName { get; init; } public int TypesHash { get; init; } + public string MethodShortName { get; init; } public string MethodName { get; init; } - public string QualifiedMethodName { get; init; } public string BindingName { get; init; } public string AssemblyName { get; init; } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs index d13c24cf0b9311..9a5555d1d57f92 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs @@ -155,7 +155,7 @@ public sealed class JSFunctionBinding internal JSFunctionBinding() { throw null; } public static void InvokeJS(JSFunctionBinding signature, Span arguments) { throw null; } public static JSFunctionBinding BindJSFunction(string functionName, string moduleName, ReadOnlySpan signatures) { throw null; } - public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan signatures) { throw null; } + public static JSFunctionBinding BindManagedFunction(Action wrapper, string methodName, int signatureHash, ReadOnlySpan signatures) { throw null; } } [Versioning.SupportedOSPlatformAttribute("browser")] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index 559a7921352a3b..ebbd169b64fb0f 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -365,6 +365,27 @@ public static void BindAssemblyExports(JSMarshalerArgument* arguments_buffer) } } + public static void CallJSExport(JSMarshalerArgument* arguments_buffer) + { + ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; + ref JSMarshalerArgument arg_res = ref arguments_buffer[1]; + var ctx = arg_exc.AssertCurrentThreadContext(); + if (!ctx.s_JSExportByHandle.TryGetValue(arg_res.slot.Int32Value, out var jsExport)) + { + arg_exc.ToJS(new InvalidOperationException("Unable to resolve JSExport by handle")); + return; + } + arg_res.slot.Int32Value = 0; + try + { + jsExport(new IntPtr(arguments_buffer)); + } + catch (Exception ex) + { + arg_exc.ToJS(ex); + } + } + [MethodImpl(MethodImplOptions.NoInlining)] // profiler needs to find it executed under this name public static void StopProfile() { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs index e7ba1f9aadd65c..4f9563e98b5865 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs @@ -46,7 +46,7 @@ internal static unsafe partial class JavaScriptImports public static partial Task 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); + public static partial void BindCSFunction(int methodHandle, string assemblyName, string namespaceName, string shortClassName, string methodName, int signatureHash, IntPtr signature); #if DEBUG [JSImport("globalThis.console.log")] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs index 9375b83b013401..9ad0eae6238178 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs @@ -182,12 +182,13 @@ public static JSFunctionBinding BindJSFunction(string functionName, string modul /// Binds a specific managed function wrapper so that it can later be invoked by JavaScript callers. /// This API supports JSImport infrastructure and is not intended to be used directly from your code. /// - /// The fully qualified name of the exported method. + /// Delegate of wrapper of exported method. + /// The name of the exported method. /// The hash of the signature metadata. /// The metadata about the signature of the marshaled parameters. /// The method metadata. /// The method is executed on architecture other than WebAssembly. - public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan signatures) + public static JSFunctionBinding BindManagedFunction(Action wrapper, string methodName, int signatureHash, ReadOnlySpan signatures) { if (RuntimeInformation.OSArchitecture != Architecture.Wasm) throw new PlatformNotSupportedException(); @@ -195,7 +196,7 @@ public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, i // 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); + return JSHostImplementation.BindManagedFunction(wrapper, methodName, signatureHash, signatures); } #if !DEBUG diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs index 26a133fbbc6021..8752d343248df3 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs @@ -279,33 +279,21 @@ public static Task BindAssemblyExports(string? assemblyName) return Task.CompletedTask; } - public static unsafe JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan signatures) + public static unsafe JSFunctionBinding BindManagedFunction(Action wrapper, string methodName, int signatureHash, ReadOnlySpan 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 ctx = JSProxyContext.CurrentThreadContext; + var clazz = wrapper.Method.DeclaringType!; + var assemblyName = clazz.Assembly.GetName().Name ?? string.Empty; + var nameSpace = clazz.Namespace ?? string.Empty; + var shortClassName = clazz.Name; + + int methodHandle = ctx.s_JSExportByHandle.Count; + ctx.s_JSExportByHandle[methodHandle] = wrapper; 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); + JavaScriptImports.BindCSFunction(methodHandle, assemblyName, nameSpace, shortClassName, methodName, signatureHash, (IntPtr)signature.Header); FreeMethodSignatureBuffer(signature); @@ -330,70 +318,5 @@ public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr) var temp = new IntPtrAndHandle { ptr = ptr }; return temp.methodHandle; } - - // The BCL implementations of IndexOf/LastIndexOf/Trim are vectorized & fast, - // but they pull in a bunch of code that is otherwise not necessarily - // useful during early app startup, so we use simple scalar implementations - private static int SmallIndexOf (string s, char ch, int direction = 1) { - if (s.Length < 1) - return -1; - int start_index = (direction > 0) ? 0 : s.Length - 1, - end_index = (direction > 0) ? s.Length - 1 : 0; - for (int i = start_index; i != end_index; i += direction) { - if (s[i] == ch) - return i; - } - return -1; - } - - private static string SmallTrim (string s) { - if (s.Length < 1) - return s; - int head = 0, tail = s.Length - 1; - while (head < s.Length) { - if (s[head] == ' ') - head++; - else - break; - } - while (tail >= 0) { - if (s[tail] == ' ') - tail--; - else - break; - } - if ((head > 0) || (tail < s.Length - 1)) - return s.Substring(head, tail - head + 1); - else - return s; - } - - public static (string assemblyName, string nameSpace, string shortClassName, string methodName) ParseFQN(string fqn) - { - var assembly = fqn.Substring(SmallIndexOf(fqn, '[') + 1, SmallIndexOf(fqn, ']') - 1); - fqn = SmallTrim(fqn); - fqn = fqn.Substring(SmallIndexOf(fqn, ']') + 1); - fqn = SmallTrim(fqn); - var methodName = fqn.Substring(SmallIndexOf(fqn, ':') + 1); - var className = fqn.Substring(0, SmallIndexOf(fqn, ':')); - className = SmallTrim(className); - - var nameSpace = ""; - var shortClassName = className; - var idx = SmallIndexOf(fqn, '.', -1); - if (idx != -1) - { - nameSpace = fqn.Substring(0, idx); - shortClassName = className.Substring(idx + 1); - } - - if (string.IsNullOrEmpty(assembly)) - throw new InvalidOperationException("No assembly name specified " + fqn); - if (string.IsNullOrEmpty(className)) - throw new InvalidOperationException("No class name specified " + fqn); - if (string.IsNullOrEmpty(methodName)) - throw new InvalidOperationException("No method name specified " + fqn); - return (assembly, nameSpace, shortClassName, methodName); - } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs index cd1509ba8309a9..efa46af83e33a6 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs @@ -23,6 +23,7 @@ internal sealed class JSProxyContext : IDisposable // they have negative values, so that they don't collide with JSHandles. private nint NextJSVHandle = -2; private readonly List JSVHandleFreeList = new(); + internal Dictionary> s_JSExportByHandle = new Dictionary>(); #if !FEATURE_WASM_MANAGED_THREADS private JSProxyContext() @@ -491,7 +492,8 @@ public static void ReleaseCSOwnedObject(JSObject jso, bool skipJS) if (!ctx.ThreadCsOwnedObjects.Remove(jsHandle)) { Environment.FailFast($"ReleaseCSOwnedObject expected to find registration for JSHandle: {jsHandle}, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}"); - }; + } + ; if (!skipJS) { #if FEATURE_WASM_MANAGED_THREADS diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs index b48cba10bdc3d8..e630d6646cd098 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs @@ -192,8 +192,9 @@ static void __Register_() unsafe partial class Basic { [global::System.Diagnostics.DebuggerNonUserCode] - internal static unsafe void __Wrapper_AnnotatedExport_1583225186(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* __arguments_buffer) + internal static unsafe void __Wrapper_AnnotatedExport_1583225186(global::System.IntPtr __arguments_ptr) { + var __arguments_buffer = (global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument*) __arguments_ptr; __Stub(__arguments_buffer[2], __arguments_buffer[3], __arguments_buffer[4], __arguments_buffer[5], __arguments_buffer[6], __arguments_buffer[7], __arguments_buffer[8], __arguments_buffer[9], __arguments_buffer[10], __arguments_buffer[11], __arguments_buffer[12], __arguments_buffer[13], __arguments_buffer[14], __arguments_buffer[15], __arguments_buffer[16], __arguments_buffer); [global::System.Diagnostics.DebuggerNonUserCode] void __Stub(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a1_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a2_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a3_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a4_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a5_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a6_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a7_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a8_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a9_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a10_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a11_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a12_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a13_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a14_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __a15_native, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* ____arg_exception_native__param) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index 26b81d5bf52273..6f8f4d86ae4876 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -441,7 +441,7 @@ public static Exception EchoException([JSMarshalAs] Exception arg1 [JSImport("invoke1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfInt([JSMarshalAs>] Task value, [JSMarshalAs] string name); - + [JSImport("returnResolvedPromise", "JavaScriptTestHelper")] internal static partial Task ReturnResolvedPromise(); @@ -1031,7 +1031,7 @@ public static JSObject EchoIJSObject([JSMarshalAs] JSObject arg1) { return arg1; } - + [JSImport("beforeYield", "JavaScriptTestHelper")] public static partial void BeforeYield(); @@ -1093,12 +1093,12 @@ public static string EchoString(string message) return message + "11"; } - private partial class NestedClass + internal partial class NestedClass { [System.Runtime.InteropServices.JavaScript.JSExport] public static string EchoString(string message) => message + "12"; - private partial class DoubleNestedClass + internal partial class DoubleNestedClass { [System.Runtime.InteropServices.JavaScript.JSExport] public static string EchoString(string message) => message + "13"; diff --git a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml index a0d092eccf48b3..a1dcb4b230d3c9 100644 --- a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml +++ b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml @@ -1,6 +1,12 @@  + + CP0002 + M:System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindManagedFunction(System.String,System.Int32,System.ReadOnlySpan{System.Runtime.InteropServices.JavaScript.JSMarshalerType}) + net9.0/System.Runtime.InteropServices.JavaScript.dll + net10.0/System.Runtime.InteropServices.JavaScript.dll + CP0002 M:System.Runtime.Intrinsics.Arm.Sve.LoadVectorByteNonFaultingZeroExtendToInt16(System.Byte*) diff --git a/src/mono/browser/runtime/corebindings.c b/src/mono/browser/runtime/corebindings.c index b12426bf0d59b4..688b651f739a75 100644 --- a/src/mono/browser/runtime/corebindings.c +++ b/src/mono/browser/runtime/corebindings.c @@ -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); @@ -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 @@ -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; diff --git a/src/mono/browser/runtime/invoke-cs.ts b/src/mono/browser/runtime/invoke-cs.ts index 3e78c48301774a..fd8d7a340583b6 100644 --- a/src/mono/browser/runtime/invoke-cs.ts +++ b/src/mono/browser/runtime/invoke-cs.ts @@ -11,13 +11,13 @@ import { get_sig, get_signature_argument_count, bound_cs_function_symbol, get_signature_version, alloc_stack_frame, get_signature_type, } from "./marshal"; -import { MonoMethod, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, MarshalerType } from "./types/internal"; +import { JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, MarshalerType, CSFnHandle } from "./types/internal"; import { assert_js_interop } from "./invoke-js"; import { startMeasure, MeasuredBlock, endMeasure } from "./profiler"; -import { bind_assembly_exports, invoke_async_jsexport, invoke_sync_jsexport } from "./managed-exports"; +import { bind_assembly_exports, invoke_async_jsexport2, invoke_sync_jsexport2 } from "./managed-exports"; import { mono_log_debug } from "./logging"; -export function mono_wasm_bind_cs_function (method: MonoMethod, assemblyName: string, namespaceName: string, shortClassName: string, methodName: string, signatureHash: number, signature: JSFunctionSignature): void { +export function mono_wasm_bind_cs_function (method: CSFnHandle, assemblyName: string, namespaceName: string, shortClassName: string, methodName: string, signatureHash: number, signature: JSFunctionSignature): void { const fullyQualifiedName = `[${assemblyName}] ${namespaceName}.${shortClassName}:${methodName}`; const mark = startMeasure(); mono_log_debug(() => `Binding [JSExport] ${namespaceName}.${shortClassName}:${methodName} from ${assemblyName} assembly`); @@ -121,7 +121,7 @@ function bind_fn_0V (closure: BindingClosure) { const size = 2; const args = alloc_stack_frame(size); // call C# side - invoke_sync_jsexport(method, args); + invoke_sync_jsexport2(method, args); } finally { if (loaderHelpers.is_runtime_running()) Module.stackRestore(sp); @@ -146,7 +146,7 @@ function bind_fn_1V (closure: BindingClosure) { marshaler1(args, arg1); // call C# side - invoke_sync_jsexport(method, args); + invoke_sync_jsexport2(method, args); } finally { if (loaderHelpers.is_runtime_running()) Module.stackRestore(sp); @@ -172,7 +172,7 @@ function bind_fn_1R (closure: BindingClosure) { marshaler1(args, arg1); // call C# side - invoke_sync_jsexport(method, args); + invoke_sync_jsexport2(method, args); const js_result = res_converter(args); return js_result; @@ -204,7 +204,7 @@ function bind_fn_1RA (closure: BindingClosure) { let promise = res_converter(args); // call C# side - invoke_async_jsexport(runtimeHelpers.managedThreadTID, method, args, size); + invoke_async_jsexport2(runtimeHelpers.managedThreadTID, method, args, size); // in case the C# side returned synchronously promise = end_marshal_task_to_js(args, undefined, promise); @@ -237,7 +237,7 @@ function bind_fn_2R (closure: BindingClosure) { marshaler2(args, arg2); // call C# side - invoke_sync_jsexport(method, args); + invoke_sync_jsexport2(method, args); const js_result = res_converter(args); return js_result; @@ -271,7 +271,7 @@ function bind_fn_2RA (closure: BindingClosure) { let promise = res_converter(args); // call C# side - invoke_async_jsexport(runtimeHelpers.managedThreadTID, method, args, size); + invoke_async_jsexport2(runtimeHelpers.managedThreadTID, method, args, size); // in case the C# side returned synchronously promise = end_marshal_task_to_js(args, undefined, promise); @@ -317,14 +317,14 @@ function bind_fn (closure: BindingClosure) { // call C# side if (is_async) { - invoke_async_jsexport(runtimeHelpers.managedThreadTID, method, args, size); + invoke_async_jsexport2(runtimeHelpers.managedThreadTID, method, args, size); // in case the C# side returned synchronously js_result = end_marshal_task_to_js(args, undefined, js_result); } else if (is_discard_no_wait) { // call C# side, fire and forget - invoke_async_jsexport(runtimeHelpers.managedThreadTID, method, args, size); + invoke_async_jsexport2(runtimeHelpers.managedThreadTID, method, args, size); } else { - invoke_sync_jsexport(method, args); + invoke_sync_jsexport2(method, args); if (res_converter) { js_result = res_converter(args); } @@ -341,7 +341,7 @@ function bind_fn (closure: BindingClosure) { type BindingClosure = { fullyQualifiedName: string, args_count: number, - method: MonoMethod, + method: CSFnHandle, arg_marshalers: (BoundMarshalerToCs)[], res_converter: BoundMarshalerToJs | undefined, is_async: boolean, diff --git a/src/mono/browser/runtime/managed-exports.ts b/src/mono/browser/runtime/managed-exports.ts index 159b250d58f1e0..bad11fad2283d8 100644 --- a/src/mono/browser/runtime/managed-exports.ts +++ b/src/mono/browser/runtime/managed-exports.ts @@ -3,7 +3,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; -import { GCHandle, GCHandleNull, JSMarshalerArguments, JSThreadBlockingMode, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod, PThreadPtr } from "./types/internal"; +import { CSFnHandle, GCHandle, GCHandleNull, JSMarshalerArguments, JSThreadBlockingMode, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod, PThreadPtr } from "./types/internal"; import cwraps, { threads_c_functions as twraps } from "./cwraps"; import { runtimeHelpers, Module, loaderHelpers, mono_assert } from "./globals"; import { JavaScriptMarshalerArgSize, alloc_stack_frame, get_arg, get_arg_gc_handle, is_args_exception, set_arg_i32, set_arg_intptr, set_arg_type, set_gc_handle, set_receiver_should_free } from "./marshal"; @@ -41,6 +41,7 @@ export function init_managed_exports (): void { managedExports.GetManagedStackTrace = get_method("GetManagedStackTrace"); managedExports.LoadSatelliteAssembly = get_method("LoadSatelliteAssembly"); managedExports.LoadLazyAssembly = get_method("LoadLazyAssembly"); + managedExports.CallJSExport = get_method("CallJSExport"); } // the marshaled signature is: Task? CallEntrypoint(char* mainAssemblyName, string[] args) @@ -324,6 +325,18 @@ export function invoke_sync_jsexport (method: MonoMethod, args: JSMarshalerArgum } } +export function invoke_async_jsexport2 (managedTID: PThreadPtr, method: CSFnHandle, args: JSMarshalerArguments, size: number): void { + const res = get_arg(args, 1); + set_arg_i32(res, method as any); + invoke_async_jsexport(managedTID, managedExports.CallJSExport, args, size); +} + +export function invoke_sync_jsexport2 (method: CSFnHandle, args: JSMarshalerArguments): void { + const res = get_arg(args, 1); + set_arg_i32(res, method as any); + invoke_sync_jsexport(managedExports.CallJSExport, args); +} + // the marshaled signature is: Task BindAssemblyExports(string assemblyName) export function bind_assembly_exports (assemblyName: string): Promise { loaderHelpers.assert_runtime_running(); @@ -373,4 +386,5 @@ type ManagedExports = { GetManagedStackTrace: MonoMethod, LoadSatelliteAssembly: MonoMethod, LoadLazyAssembly: MonoMethod, + CallJSExport: MonoMethod, } diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index 21952f0c01f187..4fca9e12ef2b21 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -13,6 +13,9 @@ export type JSHandle = { export type JSFnHandle = { __brand: "JSFnHandle" } +export type CSFnHandle = { + __brand: "CSFnHandle" +} export type PThreadPtr = { __brand: "PThreadPtr" // like pthread_t in C } From 79b4e056c7cc2be4b07f45f38bd3412826c16be7 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Fri, 28 Nov 2025 16:57:08 +0100 Subject: [PATCH 2/2] - add new API and keep existing API - implement leagcy reflection in managed code --- ...stem.Runtime.InteropServices.JavaScript.cs | 1 + .../JavaScript/JSFunctionBinding.cs | 20 +++ .../JavaScript/JSHostImplementation.cs | 131 +++++++++++++++++- .../JavaScript/JSProxyContext.cs | 1 - .../JSImportGenerator.UnitTest/Compiles.cs | 11 +- ...iCompatBaseline.NetCoreAppLatestStable.xml | 6 - 6 files changed, 156 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs index 9a5555d1d57f92..3990b1a7fac843 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/ref/System.Runtime.InteropServices.JavaScript.cs @@ -155,6 +155,7 @@ public sealed class JSFunctionBinding internal JSFunctionBinding() { throw null; } public static void InvokeJS(JSFunctionBinding signature, Span arguments) { throw null; } public static JSFunctionBinding BindJSFunction(string functionName, string moduleName, ReadOnlySpan signatures) { throw null; } + public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan signatures) { throw null; } public static JSFunctionBinding BindManagedFunction(Action wrapper, string methodName, int signatureHash, ReadOnlySpan signatures) { throw null; } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs index 9ad0eae6238178..567f143cb03282 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs @@ -178,6 +178,26 @@ public static JSFunctionBinding BindJSFunction(string functionName, string modul return BindJSImportImpl(functionName, moduleName, signatures); } + /// + /// Binds a specific managed function wrapper so that it can later be invoked by JavaScript callers. + /// This API supports JSImport infrastructure and is not intended to be used directly from your code. + /// + /// The fully qualified name of the exported method. + /// The hash of the signature metadata. + /// The metadata about the signature of the marshaled parameters. + /// The method metadata. + /// The method is executed on architecture other than WebAssembly. + public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan signatures) + { + if (RuntimeInformation.OSArchitecture != Architecture.Wasm) + throw new PlatformNotSupportedException(); + + // 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); + } + /// /// Binds a specific managed function wrapper so that it can later be invoked by JavaScript callers. /// This API supports JSImport infrastructure and is not intended to be used directly from your code. diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs index 8752d343248df3..fba0ec1f99282c 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs @@ -279,13 +279,72 @@ public static Task BindAssemblyExports(string? assemblyName) 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 signatures) + { + var ctx = JSProxyContext.CurrentThreadContext; + var (assemblyName, nameSpace, shortClassName, methodName) = ParseFQN(fullyQualifiedName); + var wrapperName = $"__Wrapper_{methodName}_{signatureHash}"; + + + // 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}.dll"); + } + 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}.dll"); + } + + Action 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.s_JSExportByHandle.Count; + ctx.s_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; + } + public static unsafe JSFunctionBinding BindManagedFunction(Action wrapper, string methodName, int signatureHash, ReadOnlySpan signatures) { var ctx = JSProxyContext.CurrentThreadContext; var clazz = wrapper.Method.DeclaringType!; var assemblyName = clazz.Assembly.GetName().Name ?? string.Empty; - var nameSpace = clazz.Namespace ?? string.Empty; var shortClassName = clazz.Name; + var nameSpace = clazz.Namespace ?? string.Empty; + var containing = string.Empty; + while (clazz.DeclaringType != null) + { + clazz = clazz.DeclaringType; + containing = string.IsNullOrEmpty(containing) + ? clazz.Name + : clazz.Name + "." + containing; + } + if (!string.IsNullOrEmpty(containing)) + { + nameSpace = string.IsNullOrEmpty(nameSpace) + ? containing + : nameSpace + "." + containing; + } int methodHandle = ctx.s_JSExportByHandle.Count; ctx.s_JSExportByHandle[methodHandle] = wrapper; @@ -318,5 +377,75 @@ public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr) var temp = new IntPtrAndHandle { ptr = ptr }; return temp.methodHandle; } + + // The BCL implementations of IndexOf/LastIndexOf/Trim are vectorized & fast, + // but they pull in a bunch of code that is otherwise not necessarily + // useful during early app startup, so we use simple scalar implementations + private static int SmallIndexOf(string s, char ch, int direction = 1) + { + if (s.Length < 1) + return -1; + int start_index = (direction > 0) ? 0 : s.Length - 1, + end_index = (direction > 0) ? s.Length - 1 : 0; + for (int i = start_index; i != end_index; i += direction) + { + if (s[i] == ch) + return i; + } + return -1; + } + + private static string SmallTrim(string s) + { + if (s.Length < 1) + return s; + int head = 0, tail = s.Length - 1; + while (head < s.Length) + { + if (s[head] == ' ') + head++; + else + break; + } + while (tail >= 0) + { + if (s[tail] == ' ') + tail--; + else + break; + } + if ((head > 0) || (tail < s.Length - 1)) + return s.Substring(head, tail - head + 1); + else + return s; + } + + private static (string assemblyName, string nameSpace, string shortClassName, string methodName) ParseFQN(string fqn) + { + var assembly = fqn.Substring(SmallIndexOf(fqn, '[') + 1, SmallIndexOf(fqn, ']') - 1); + fqn = SmallTrim(fqn); + fqn = fqn.Substring(SmallIndexOf(fqn, ']') + 1); + fqn = SmallTrim(fqn); + var methodName = fqn.Substring(SmallIndexOf(fqn, ':') + 1); + var className = fqn.Substring(0, SmallIndexOf(fqn, ':')); + className = SmallTrim(className); + + var nameSpace = ""; + var shortClassName = className; + var idx = SmallIndexOf(fqn, '.', -1); + if (idx != -1) + { + nameSpace = fqn.Substring(0, idx); + shortClassName = className.Substring(idx + 1); + } + + if (string.IsNullOrEmpty(assembly)) + throw new InvalidOperationException("No assembly name specified " + fqn); + if (string.IsNullOrEmpty(className)) + throw new InvalidOperationException("No class name specified " + fqn); + if (string.IsNullOrEmpty(methodName)) + throw new InvalidOperationException("No method name specified " + fqn); + return (assembly, nameSpace, shortClassName, methodName); + } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs index efa46af83e33a6..e50cd71aefb90d 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs @@ -493,7 +493,6 @@ public static void ReleaseCSOwnedObject(JSObject jso, bool skipJS) { Environment.FailFast($"ReleaseCSOwnedObject expected to find registration for JSHandle: {jsHandle}, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}"); } - ; if (!skipJS) { #if FEATURE_WASM_MANAGED_THREADS diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs index e630d6646cd098..5bd7f44341e4fb 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs @@ -179,20 +179,19 @@ static internal void __TrimmingPreserve_() { } - [global::System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute("__Wrapper_AnnotatedExport_1583225186", "Basic", "TestProject")] static void __Register_() { if (initialized || global::System.Runtime.InteropServices.RuntimeInformation.OSArchitecture != global::System.Runtime.InteropServices.Architecture.Wasm) return; initialized = true; - global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindManagedFunction("[TestProject]Basic:AnnotatedExport", 1583225186, [global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Discard, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Action(), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Function(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Span(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.ArraySegment(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Array(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTimeOffset, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTimeOffset), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64)]); + global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindManagedFunction(Basic.__Wrapper_AnnotatedExport_1583225186 ,"AnnotatedExport", 1583225186, [global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Discard, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Action(), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Function(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Span(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.ArraySegment(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Byte), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Array(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Object), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTimeOffset, global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTime), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.DateTimeOffset), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int52), global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.BigInt64)]); } } } unsafe partial class Basic { [global::System.Diagnostics.DebuggerNonUserCode] - internal static unsafe void __Wrapper_AnnotatedExport_1583225186(global::System.IntPtr __arguments_ptr) + public static unsafe void __Wrapper_AnnotatedExport_1583225186(global::System.IntPtr __arguments_ptr) { var __arguments_buffer = (global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument*) __arguments_ptr; __Stub(__arguments_buffer[2], __arguments_buffer[3], __arguments_buffer[4], __arguments_buffer[5], __arguments_buffer[6], __arguments_buffer[7], __arguments_buffer[8], __arguments_buffer[9], __arguments_buffer[10], __arguments_buffer[11], __arguments_buffer[12], __arguments_buffer[13], __arguments_buffer[14], __arguments_buffer[15], __arguments_buffer[16], __arguments_buffer); @@ -345,21 +344,21 @@ static internal void __TrimmingPreserve_() { } - [global::System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute("__Wrapper_Export1_622134597", "Basic", "TestProject")] static void __Register_() { if (initialized || global::System.Runtime.InteropServices.RuntimeInformation.OSArchitecture != global::System.Runtime.InteropServices.Architecture.Wasm) return; initialized = true; - global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindManagedFunction("[TestProject]Basic:Export1", 622134597, [global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32)]); + global::System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindManagedFunction(Basic.__Wrapper_Export1_622134597 ,"Export1", 622134597, [global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Task(global::System.Runtime.InteropServices.JavaScript.JSMarshalerType.Int32)]); } } } unsafe partial class Basic { [global::System.Diagnostics.DebuggerNonUserCode] - internal static unsafe void __Wrapper_Export1_622134597(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* __arguments_buffer) + public static unsafe void __Wrapper_Export1_622134597(global::System.IntPtr __arguments_ptr) { + var __arguments_buffer = (global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument*) __arguments_ptr; __Stub(__arguments_buffer, __arguments_buffer + 1); [global::System.Diagnostics.DebuggerNonUserCode] void __Stub(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* ____arg_exception_native__param, global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* __invokeRetValUnmanaged__param) diff --git a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml index a1dcb4b230d3c9..a0d092eccf48b3 100644 --- a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml +++ b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml @@ -1,12 +1,6 @@  - - CP0002 - M:System.Runtime.InteropServices.JavaScript.JSFunctionBinding.BindManagedFunction(System.String,System.Int32,System.ReadOnlySpan{System.Runtime.InteropServices.JavaScript.JSMarshalerType}) - net9.0/System.Runtime.InteropServices.JavaScript.dll - net10.0/System.Runtime.InteropServices.JavaScript.dll - CP0002 M:System.Runtime.Intrinsics.Arm.Sve.LoadVectorByteNonFaultingZeroExtendToInt16(System.Byte*)