-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
IOperation API extensions to expose low-level operations #21705
Conversation
…tions after the local rewritting phase.
Can you give a rundown on what these are? Thanks! |
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! |
Please take a look to this issue #21507.
This new GetOperation method overload only performs the local rewriting lowering phase by using the 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. |
There was a problem hiding this 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; |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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?
There was a problem hiding this 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// <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) |
There was a problem hiding this comment.
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; } |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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;') |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as before.
It looks like added tests do not cover all added/modified code paths. |
Done with review pass for C# side of changes (iteration 12). |
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.