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

IOperation API extensions to expose low-level operations #21705

Conversation

edgardozoppi
Copy link
Member

@edgardozoppi edgardozoppi commented Aug 23, 2017

Since there are several requested changes I have decided to create a new PR #21810 with those changes.
Please take a look to the new PR #21810 instead.

These changes add a new overload to the SemanticModel.GetOperation method to generate an IOperation tree from the compiler's internal BoundTree after performing the local rewriting transformation phase. The new low-level IOperation tree reuses most of the original IOperation interfaces and extend them by adding a few newly created ones that can only appear after the local rewriting phase. This has been done to support both C# and VB.

Remarks
These changes are experimental so we still need to keep them behind the IOperation feature flag even after releasing the API in Dev 15.5.

More detailed information of the changes can be found in this issue #21507.

@CyrusNajmabadi
Copy link
Member

and extend them by adding a few newly created ones that can only appear after the local rewriting phase.

Can you give a rundown on what these are? Thanks!

@CyrusNajmabadi
Copy link
Member

after performing the local rewriting transformation phase.

Can you give a brief explanation of what phases of lowering this does? i.e. what happens to lambdas, iterators, async, other-code after the "local rewriting" phase.

Thanks!

@AlekseyTs AlekseyTs requested a review from a team August 24, 2017 16:53
@edgardozoppi
Copy link
Member Author

Can you give a rundown on what these are? Thanks!

Please take a look to this issue #21507.

Can you give a brief explanation of what phases of lowering this does? i.e. what happens to lambdas, iterators, async, other-code after the "local rewriting" phase.

This new GetOperation method overload only performs the local rewriting lowering phase by using the LocalRewriter classes (C# and VB versions respectively). Not sure exactly what are all the languages constructs that this phase transforms. I guess that is a question that someone from the compiler team can answer more precisely. You can take a look to the LocalRewriter implementation for C# here and for VB here.

There are other lowering phases to transform local functions, lambdas, iterators, async/await, etc. All those phases happen after the local rewriting when the compiler has to emit code. But for generating the low-level IOperation tree we are only using the first phase.

Copy link
Member

@agocke agocke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect some unit test coverage for these changes before merging.

var compilationState = new TypeCompilationState(method.ContainingType, _compilation, null);

var dynamicAnalysisSpans = ImmutableArray<CodeAnalysis.CodeGen.SourceSpan>.Empty;
//var synthesizedSubmissionFields = method.ContainingType.IsSubmissionClass ? new SynthesizedSubmissionFields(_compilation, method.ContainingType) : null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented-out code.

var loweredBody = LocalRewriter.Rewrite(
_compilation,
method,
-1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic number

node = SyntaxFactory.GetStandaloneNode(node);

var model = this.GetMemberModel(node);
if (model != null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return model?.GetOperationWorker(method, node, cancellationToken)

/// </summary>
ILabelSymbol Target { get; }
/// <summary>
/// Jump if the condition is true.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unclear. What does it do if this is false and the condition is true?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it won't jump in that case. I just copied that property from BoundConditionalGoto.JumpIfTrue property. I think it means if the conditional goto branch is behaving like JE (jumpe on equals) or JNE (jump on not equals). So, if the property JumpIfTrue is false, it will only jump when the condition is false.

@@ -858,6 +858,173 @@ public LazyBlockStatement(Lazy<ImmutableArray<IOperation>> statements, Immutable
}

/// <summary>
/// Represents a sequence of expressions.
/// </summary>
internal abstract partial class BaseSequenceExpression : Operation, ISequenceExpression
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see generated code here, but I don't see any changes to a generate base.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@agocke even though file is .generated.cs, it is a hand crafted file. #20204 tracks writing an operation generator similar to the syntax and bound node generators.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mavasani This is very confusing. Can we please rename it to not included generated in the file name?

Copy link
Contributor

@mavasani mavasani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, modulo addition of unit tests. @edgardozoppi Lets work today on adding unit tests for the new APIs.

@@ -465,11 +465,18 @@ private int CheckAndAdjustPositionForSpeculativeAttribute(int position)

#endregion Helpers for speculative binding

protected override IOperation GetOperationCore(SyntaxNode node, CancellationToken cancellationToken)
protected override IOperation GetOperationCore(IMethodSymbol method, SyntaxNode body, CancellationToken cancellationToken)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we consider changing the API to take only one of the symbol OR body parameters? Alternatively, please file a separate issue to track removal of one of these parameters.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -858,6 +858,173 @@ public LazyBlockStatement(Lazy<ImmutableArray<IOperation>> statements, Immutable
}

/// <summary>
/// Represents a sequence of expressions.
/// </summary>
internal abstract partial class BaseSequenceExpression : Operation, ISequenceExpression
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@agocke even though file is .generated.cs, it is a hand crafted file. #20204 tracks writing an operation generator similar to the syntax and bound node generators.

@mavasani mavasani requested review from a team August 25, 2017 16:41
/// <param name="method">The method symbol.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns></returns>
public IOperation GetOperation(IMethodSymbol method, CancellationToken cancellationToken = default)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public IOperation GetOperation(IMethodSymbol method, CancellationToken cancellationToken = default) [](start = 8, length = 99)

It is not clear why this API is on SemanticModel. SemanticModel is created for a particular syntax tree, this API doesn't depend on syntax at all. Consider moving it to Compilation.

/// <summary>
/// Expressions contained within the sequence.
/// </summary>
ImmutableArray<IOperation> Expressions { get; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expressions [](start = 35, length = 11)

SideEffects?

var csnode = (CSharpSyntaxNode)node;
CheckSyntaxNode(csnode);
return this.GetOperationWorker(csnode, GetOperationOptions.Lowest, cancellationToken);
var csmethod = (MethodSymbol)method;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(MethodSymbol)method [](start = 27, length = 20)

Please use EnsureCSharpSymbolOrNull helper.

CheckSyntaxNode(csnode);
return this.GetOperationWorker(csnode, GetOperationOptions.Lowest, cancellationToken);
var csmethod = (MethodSymbol)method;
CheckSymbol(csmethod);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckSymbol(csmethod); [](start = 12, length = 22)

It looks like this helper is using "symbol" as parameter name, I would expect "method" to be used for this call site instead.

var csmethod = (MethodSymbol)method;
CheckSymbol(csmethod);
var csbody = csmethod.GetBodySyntax();
CheckSyntaxNode(csbody);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckSyntaxNode(csbody); [](start = 12, length = 24)

I think consumer might be confused by an exception thrown by this helper because syntax is not an input for the GetOperation API.

private BoundStatement LowerMethodBody(MethodSymbol method, BoundStatement body)
{
const int methodOrdinal = -1;
var diagnostics = DiagnosticBag.GetInstance();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DiagnosticBag.GetInstance() [](start = 30, length = 27)

Are we leaking diagnostics bag from the pool?


internal static CSharpSyntaxNode GetBodySyntax(this MethodSymbol method)
{
var syntaxReference = method.DeclaringSyntaxReferences.FirstOrDefault();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var [](start = 12, length = 3)

Please spell out the type.

internal static CSharpSyntaxNode GetBodySyntax(this MethodSymbol method)
{
var syntaxReference = method.DeclaringSyntaxReferences.FirstOrDefault();
var node = syntaxReference.GetCSharpSyntax();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntaxReference [](start = 23, length = 15)

Could syntaxReference be null here? Would it be safe to call GetCSharpSyntax in this case?


internal static CSharpSyntaxNode GetBodySyntax(this MethodSymbol method)
{
var syntaxReference = method.DeclaringSyntaxReferences.FirstOrDefault();
Copy link
Contributor

@AlekseyTs AlekseyTs Aug 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FirstOrDefault [](start = 67, length = 14)

It is not clear why FirstOrDefault is the right thing to do. It is not clear why are we doing this through DeclaringSyntaxReferences at all. I think that we should only handle top-level methods explicitly declared in source (not lambdas, not local functions, etc.) It'll probably make sense to do what MethodCompiler.BindMethodBody does rather than inventing some new way of getting the bound body, perhaps share some code with that method.

// as the start of a tree to get operations for. This is guaranteed by the builder that populates the node map, as it will call
// UnboundLambda.BindForErrorRecovery() when it encounters an UnboundLambda node.
Debug.Assert(loweredBody.Kind != BoundKind.UnboundLambda);
result = _operationFactory.Create(loweredBody);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

result = _operationFactory.Create(loweredBody); [](start = 16, length = 47)

It feels wrong to tie lowered tree to any SemanticModel. SemanticModel deals with un-lowered bound trees, who knows what negative effects will this operation have, how will it affect the caches, etc. I think there should be no interaction between SemanticModel and the tree built of off lowered bound nodes.

private IBlockStatement CreateBoundStatementListOperation(BoundStatementList boundStatementList)
{
Lazy<ImmutableArray<IOperation>> statements =
new Lazy<ImmutableArray<IOperation>>(() => boundStatementList.Statements.Select(s => Create(s)).Where(s => s != null && s.Kind != OperationKind.None).ToImmutableArray());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s.Kind != OperationKind.None [](start = 136, length = 28)

Why is this the right condition? Isn't this potentially going to remove significant portion of the tree?

private ISequenceExpression CreateBoundSequenceOperation(BoundSequence boundSequence)
{
Lazy<ImmutableArray<IOperation>> expressions =
new Lazy<ImmutableArray<IOperation>>(() => boundSequence.SideEffects.Select(s => Create(s)).Where(s => s != null && s.Kind != OperationKind.None).ToImmutableArray());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s.Kind != OperationKind.None [](start = 132, length = 28)

Same question.

string expectedOperationTree = @"
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '{ ... }')
IBlockStatement (3 statements) (OperationKind.BlockStatement) (Syntax: 'if (p < 0) p = 0;')
IConditionalGotoStatement (JumpIfTrue: False) (OperationKind.ConditionalGotoStatement) (Syntax: 'p < 0')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IConditionalGotoStatement (JumpIfTrue: False) (OperationKind.ConditionalGotoStatement) (Syntax: 'p < 0') [](start = 4, length = 104)

From this output, how do I make sense where the jump is going to?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that the labels of IConditionalGotoStatement are automatically generated by the compiler when lowering the method body, so they have different names depending on the order in which the unit tests run.

Expression: ISimpleAssignmentExpression (OperationKind.SimpleAssignmentExpression, Type: System.Int32) (Syntax: 'p = 0')
Left: IParameterReferenceExpression: p (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'p')
Right: ILiteralExpression (Text: 0) (OperationKind.LiteralExpression, Type: System.Int32, Constant: 0) (Syntax: '0')
IBranchStatement (BranchKind.GoTo) (OperationKind.BranchStatement) (Syntax: 'if (p < 0) ... else p = 1;')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IBranchStatement (BranchKind.GoTo) (OperationKind.BranchStatement) (Syntax: 'if (p < 0) ... else p = 1;') [](start = 4, length = 106)

How can I determine where this branch jumps to?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as before.

@AlekseyTs
Copy link
Contributor

It looks like added tests do not cover all added/modified code paths.

@AlekseyTs
Copy link
Contributor

Done with review pass for C# side of changes (iteration 12).

@edgardozoppi
Copy link
Member Author

Since there are several requested changes I have decided to create a new PR #21810 with those changes. Also, because I did the changes in a new branch in my fork that has all the latest updates of the features/ioperation branch.

Please continue the review in the new PR #21810.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants