Skip to content
Draft
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
2 changes: 0 additions & 2 deletions src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
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 @@ -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.
/// </summary>
public static readonly IEqualityComparer<(MemberDeclarationSyntax Syntax, ImmutableArray<DiagnosticInfo> Diagnostics)> GeneratedSyntax = new CustomValueTupleElementComparer<MemberDeclarationSyntax, ImmutableArray<DiagnosticInfo>>(SyntaxEquivalentComparer.Instance, new ImmutableArraySequenceEqualComparer<DiagnosticInfo>(EqualityComparer<DiagnosticInfo>.Default));
public static readonly IEqualityComparer<(MemberDeclarationSyntax, StatementSyntax, AttributeListSyntax, ImmutableArray<DiagnosticInfo>)> GeneratedSyntax4 =
new CustomValueTupleElementComparer<MemberDeclarationSyntax, StatementSyntax, AttributeListSyntax, ImmutableArray<DiagnosticInfo>>(
SyntaxEquivalentComparer.Instance, SyntaxEquivalentComparer.Instance, SyntaxEquivalentComparer.Instance,
public static readonly IEqualityComparer<(MemberDeclarationSyntax, StatementSyntax, ImmutableArray<DiagnosticInfo>)> GeneratedSyntax3 =
new CustomValueTupleElementComparer<MemberDeclarationSyntax, StatementSyntax, ImmutableArray<DiagnosticInfo>>(
SyntaxEquivalentComparer.Instance, SyntaxEquivalentComparer.Instance,
new ImmutableArraySequenceEqualComparer<DiagnosticInfo>(EqualityComparer<DiagnosticInfo>.Default));
}

Expand Down Expand Up @@ -71,6 +71,32 @@ public int GetHashCode((T, U) obj)
throw new UnreachableException();
}
}
internal sealed class CustomValueTupleElementComparer<T, U, V> : IEqualityComparer<(T, U, V)>
{
private readonly IEqualityComparer<T> _item1Comparer;
private readonly IEqualityComparer<U> _item2Comparer;
private readonly IEqualityComparer<V> _item3Comparer;

public CustomValueTupleElementComparer(IEqualityComparer<T> item1Comparer, IEqualityComparer<U> item2Comparer, IEqualityComparer<V> 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<T, U, V, W> : IEqualityComparer<(T, U, V, W)>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
return ImmutableArray.Create(DiagnosticInfo.Create(GeneratorDiagnostics.JSExportRequiresAllowUnsafeBlocks, null));
}));

IncrementalValuesProvider<(MemberDeclarationSyntax, StatementSyntax, AttributeListSyntax, ImmutableArray<DiagnosticInfo>)> generateSingleStub = methodsToGenerate
IncrementalValuesProvider<(MemberDeclarationSyntax, StatementSyntax, ImmutableArray<DiagnosticInfo>)> generateSingleStub = methodsToGenerate
.Combine(stubEnvironment)
.Select(static (data, ct) => new
{
Expand All @@ -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<ImmutableArray<(StatementSyntax, AttributeListSyntax)>> regSyntax = generateSingleStub
IncrementalValueProvider<ImmutableArray<StatementSyntax>> regSyntax = generateSingleStub
.Select(
static (data, ct) => (data.Item2, data.Item3))
static (data, ct) => (data.Item2))
.Collect();

IncrementalValueProvider<string> registration = regSyntax
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -215,7 +215,7 @@ private static IncrementalStubGenerationContext CalculateStubInformation(
}

private static NamespaceDeclarationSyntax GenerateRegSource(
ImmutableArray<(StatementSyntax Registration, AttributeListSyntax Attribute)> methods, string assemblyName)
ImmutableArray<StatementSyntax> methods, string assemblyName)
{
const string generatedNamespace = "System.Runtime.InteropServices.JavaScript";
const string initializerClass = "__GeneratedInitializer";
Expand All @@ -230,8 +230,7 @@ private static NamespaceDeclarationSyntax GenerateRegSource(
var attributes = new List<AttributeListSyntax>();
foreach (var m in methods)
{
registerStatements.Add(m.Registration);
attributes.Add(m.Attribute);
registerStatements.Add(m);
}

FieldDeclarationSyntax field = FieldDeclaration(VariableDeclaration(PredefinedType(Token(SyntaxKind.BoolKeyword)))
Expand Down Expand Up @@ -319,7 +318,7 @@ private static StatementSyntax[] GenerateJSExportArchitectureCheck()
];
}

private static (MemberDeclarationSyntax, StatementSyntax, AttributeListSyntax, ImmutableArray<DiagnosticInfo>) GenerateSource(
private static (MemberDeclarationSyntax, StatementSyntax, ImmutableArray<DiagnosticInfo>) GenerateSource(
IncrementalStubGenerationContext incrementalContext)
{
var diagnostics = new GeneratorDiagnosticsBag(new DescriptorProvider(), incrementalContext.DiagnosticLocation, SR.ResourceManager, typeof(FxResources.Microsoft.Interop.JavaScript.JSImportGenerator.SR));
Expand Down Expand Up @@ -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<TypePositionInfo> signatureElements, string innerWrapperName)
{
List<ArgumentSyntax> arguments = [];
Expand Down Expand Up @@ -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<ArgumentSyntax>
{
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)
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ public sealed class JSFunctionBinding
public static void InvokeJS(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments) { throw null; }
public static JSFunctionBinding BindJSFunction(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures) { throw null; }
public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan<JSMarshalerType> signatures) { throw null; }
public static JSFunctionBinding BindManagedFunction(Action<IntPtr> wrapper, string methodName, int signatureHash, ReadOnlySpan<JSMarshalerType> signatures) { throw null; }
}

[Versioning.SupportedOSPlatformAttribute("browser")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal static unsafe partial class JavaScriptImports
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);
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")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,27 @@ public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, i
return JSHostImplementation.BindManagedFunction(fullyQualifiedName, signatureHash, signatures);
}

/// <summary>
/// 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.
/// </summary>
/// <param name="wrapper">Delegate of wrapper of exported method.</param>
/// <param name="methodName">The name of the exported method.</param>
/// <param name="signatureHash">The hash of the signature metadata.</param>
/// <param name="signatures">The metadata about the signature of the marshaled parameters.</param>
/// <returns>The method metadata.</returns>
/// <exception cref="PlatformNotSupportedException">The method is executed on architecture other than WebAssembly.</exception>
public static JSFunctionBinding BindManagedFunction(Action<IntPtr> wrapper, string methodName, int signatureHash, ReadOnlySpan<JSMarshalerType> 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(wrapper, methodName, signatureHash, signatures);
}

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