|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Microsoft.Interop.JavaScript
{
internal sealed class JSExportCodeGenerator : JSCodeGenerator
{
private readonly BoundGenerators _marshallers;
private readonly StubIdentifierContext _context;
private readonly JSExportData _jsExportData;
private readonly JSSignatureContext _signatureContext;
public JSExportCodeGenerator(
ImmutableArray<TypePositionInfo> argTypes,
JSExportData attributeData,
JSSignatureContext signatureContext,
GeneratorDiagnosticsBag diagnosticsBag,
IMarshallingGeneratorResolver generatorResolver)
{
_signatureContext = signatureContext;
_jsExportData = attributeData;
_marshallers = BoundGenerators.Create(argTypes, generatorResolver, StubCodeContext.DefaultNativeToManagedStub, new EmptyJSGenerator(), out var bindingFailures);
diagnosticsBag.ReportGeneratorDiagnostics(bindingFailures);
if (_marshallers.ManagedReturnMarshaller.UsesNativeIdentifier)
{
// If we need a different native return identifier, then recreate the context with the correct identifier before we generate any code.
_context = new DefaultIdentifierContext(ReturnIdentifier, ReturnNativeIdentifier, MarshalDirection.UnmanagedToManaged)
{
CodeEmitOptions = new(SkipInit: true),
};
}
else
{
_context = new DefaultIdentifierContext(ReturnIdentifier, ReturnIdentifier, MarshalDirection.UnmanagedToManaged)
{
CodeEmitOptions = new(SkipInit: true),
};
}
// validate task + span mix
if (_marshallers.ManagedReturnMarshaller.TypeInfo.MarshallingAttributeInfo is JSMarshallingInfo(_, JSTaskTypeInfo))
{
IBoundMarshallingGenerator spanArg = _marshallers.SignatureMarshallers.FirstOrDefault(m => m.TypeInfo.MarshallingAttributeInfo is JSMarshallingInfo(_, JSSpanTypeInfo));
if (spanArg != default)
{
diagnosticsBag.ReportGeneratorDiagnostic(new GeneratorDiagnostic.NotSupported(spanArg.TypeInfo)
{
NotSupportedDetails = SR.SpanAndTaskNotSupported
});
}
}
}
public BlockSyntax GenerateJSExportBody()
{
List<StatementSyntax> invoke = InvokeSyntax();
GeneratedStatements statements = GeneratedStatements.Create(_marshallers, _context);
bool shouldInitializeVariables = !statements.GuaranteedUnmarshal.IsEmpty || !statements.CleanupCallerAllocated.IsEmpty || !statements.CleanupCalleeAllocated.IsEmpty;
VariableDeclarations declarations = VariableDeclarations.GenerateDeclarationsForUnmanagedToManaged(_marshallers, _context, shouldInitializeVariables);
var setupStatements = new List<StatementSyntax>();
SetupSyntax(setupStatements);
if (!(statements.GuaranteedUnmarshal.IsEmpty && statements.CleanupCalleeAllocated.IsEmpty))
{
setupStatements.Add(SyntaxFactoryExtensions.Declare(PredefinedType(Token(SyntaxKind.BoolKeyword)), InvokeSucceededIdentifier, initializeToDefault: true));
}
setupStatements.AddRange(declarations.Initializations);
setupStatements.AddRange(declarations.Variables);
setupStatements.AddRange(statements.Setup);
var tryStatements = new List<StatementSyntax>();
tryStatements.AddRange(statements.Unmarshal);
tryStatements.AddRange(invoke);
if (!(statements.GuaranteedUnmarshal.IsEmpty && statements.CleanupCalleeAllocated.IsEmpty))
{
tryStatements.Add(ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
IdentifierName(InvokeSucceededIdentifier),
LiteralExpression(SyntaxKind.TrueLiteralExpression))));
}
tryStatements.AddRange(statements.NotifyForSuccessfulInvoke);
tryStatements.AddRange(statements.PinnedMarshal);
tryStatements.AddRange(statements.Marshal);
List<StatementSyntax> allStatements = setupStatements;
// Wrap unmarshall, invocation and return value marshalling in try-catch.
// In case of exception, marshal exception instead of return value.
var tryInvokeAndMarshal = TryStatement(SingletonList(CatchClause()
.WithDeclaration(CatchDeclaration(IdentifierName(Constants.ExceptionGlobal)).WithIdentifier(Identifier("ex")))
.WithBlock(Block(SingletonList<StatementSyntax>(
ExpressionStatement(InvocationExpression(
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(Constants.ArgumentException), IdentifierName(Constants.ToJSMethod)))
.WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(IdentifierName("ex")))))))))))
.WithBlock(Block(tryStatements));
List<StatementSyntax> finallyStatements = new List<StatementSyntax>();
if (!(statements.GuaranteedUnmarshal.IsEmpty && statements.CleanupCalleeAllocated.IsEmpty))
{
finallyStatements.Add(IfStatement(IdentifierName(InvokeSucceededIdentifier), Block(statements.GuaranteedUnmarshal.Concat(statements.CleanupCalleeAllocated))));
}
finallyStatements.AddRange(statements.CleanupCallerAllocated);
if (finallyStatements.Count > 0)
{
tryInvokeAndMarshal = TryStatement(Block(tryInvokeAndMarshal), default, FinallyClause(Block(finallyStatements)));
}
allStatements.Add(tryInvokeAndMarshal);
return Block(allStatements);
}
public static StatementSyntax[] GenerateJSExportArchitectureCheck()
{
return new StatementSyntax[]{
IfStatement(
BinaryExpression(SyntaxKind.LogicalOrExpression,
IdentifierName("initialized"),
BinaryExpression(SyntaxKind.NotEqualsExpression,
IdentifierName(Constants.OSArchitectureGlobal),
IdentifierName(Constants.ArchitectureWasmGlobal))),
ReturnStatement()),
ExpressionStatement(
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
IdentifierName("initialized"),
LiteralExpression(SyntaxKind.TrueLiteralExpression))),
};
}
public StatementSyntax GenerateJSExportRegistration()
{
var signatureArgs = new List<ArgumentSyntax>
{
Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(_signatureContext.QualifiedMethodName))),
Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(_signatureContext.TypesHash))),
CreateSignaturesSyntax()
};
return ExpressionStatement(InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(Constants.JSFunctionSignatureGlobal), IdentifierName(Constants.BindCSFunctionMethod)))
.WithArgumentList(ArgumentList(SeparatedList(signatureArgs))));
}
private ArgumentSyntax CreateSignaturesSyntax()
{
IEnumerable<ExpressionSyntax> types = _marshallers.ManagedReturnMarshaller is IJSMarshallingGenerator jsGen ? jsGen.GenerateBind() : [];
types = types
.Concat(_marshallers.NativeParameterMarshallers.OfType<IJSMarshallingGenerator>().SelectMany(p => p.GenerateBind()));
return Argument(ArrayCreationExpression(ArrayType(IdentifierName(Constants.JSMarshalerTypeGlobal))
.WithRankSpecifiers(SingletonList(ArrayRankSpecifier(SingletonSeparatedList<ExpressionSyntax>(OmittedArraySizeExpression())))))
.WithInitializer(InitializerExpression(SyntaxKind.ArrayInitializerExpression, SeparatedList(types))));
}
private void SetupSyntax(List<StatementSyntax> statementsToUpdate)
{
foreach (IBoundMarshallingGenerator marshaller in _marshallers.NativeParameterMarshallers)
{
statementsToUpdate.Add(LocalDeclarationStatement(VariableDeclaration(marshaller.TypeInfo.ManagedType.Syntax)
.WithVariables(SingletonSeparatedList(VariableDeclarator(marshaller.TypeInfo.InstanceIdentifier)))));
}
statementsToUpdate.Add(LocalDeclarationStatement(VariableDeclaration(RefType(IdentifierName(Constants.JSMarshalerArgumentGlobal)))
.WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier(Constants.ArgumentException))
.WithInitializer(EqualsValueClause(RefExpression(ElementAccessExpression(IdentifierName(Constants.ArgumentsBuffer))
.WithArgumentList(BracketedArgumentList(SingletonSeparatedList(
Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0)))))))))))));
statementsToUpdate.Add(LocalDeclarationStatement(VariableDeclaration(RefType(IdentifierName(Constants.JSMarshalerArgumentGlobal)))
.WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier(Constants.ArgumentReturn))
.WithInitializer(EqualsValueClause(RefExpression(ElementAccessExpression(IdentifierName(Constants.ArgumentsBuffer))
.WithArgumentList(BracketedArgumentList(SingletonSeparatedList(
Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(1)))))))))))));
}
private List<StatementSyntax> InvokeSyntax()
{
var statements = new List<StatementSyntax>();
var arguments = new List<ArgumentSyntax>();
// Generate code for each parameter for the current stage
foreach (IBoundMarshallingGenerator marshaller in _marshallers.NativeParameterMarshallers)
{
// convert arguments for invocation
statements.AddRange(marshaller.Generate(_context));
arguments.Add(Argument(IdentifierName(marshaller.TypeInfo.InstanceIdentifier)));
}
if (_marshallers.IsManagedVoidReturn)
{
statements.Add(ExpressionStatement(InvocationExpression(IdentifierName(_signatureContext.MethodName))
.WithArgumentList(ArgumentList(SeparatedList(arguments)))));
}
else
{
ExpressionSyntax invocation = InvocationExpression(IdentifierName(_signatureContext.MethodName))
.WithArgumentList(ArgumentList(SeparatedList(arguments)));
(string _, string nativeIdentifier) = _context.GetIdentifiers(_marshallers.ManagedReturnMarshaller.TypeInfo);
ExpressionStatementSyntax statement = ExpressionStatement(AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName(nativeIdentifier), invocation));
statements.Add(statement);
}
return statements;
}
public (ParameterListSyntax ParameterList, TypeSyntax ReturnType, AttributeListSyntax? ReturnTypeAttributes) GenerateTargetMethodSignatureData()
{
return _marshallers.GenerateTargetMethodSignatureData(_context);
}
}
}
|