List patterns: Recreate the decision dag for lowering#59019
List patterns: Recreate the decision dag for lowering#59019jcouv merged 11 commits intodotnet:mainfrom
Conversation
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs
Outdated
Show resolved
Hide resolved
| private readonly Conversions _conversions; | ||
| private readonly BindingDiagnosticBag _diagnostics; | ||
| private readonly LabelSymbol _defaultLabel; | ||
| private readonly bool _considerAlternativeIndexers; |
There was a problem hiding this comment.
I noticed the name is not entirely true - we do consider alternative indexers, we just avoid synthesizing tests. Will try something more on-point like _forLowering for an easier explanation.
| } | ||
|
|
||
| if (lengthValues.Any(BinaryOperatorKind.Equal, lengthValue)) | ||
| if (lengthValues.Any(BinaryOperatorKind.Equal, lengthValue) && _considerAlternativeIndexers) |
| return _factory.AssignmentExpression(output, access); | ||
| } | ||
|
|
||
| case BoundDagAssignmentEvaluation: |
There was a problem hiding this comment.
I'd rather mention every evaluation node for an exhaustive match. The type check itself is not reachable anyways.
(I think this also address your next comment)
There was a problem hiding this comment.
I'd rather mention every evaluation node for an exhaustive match. The type check itself is not reachable anyways.
(I think this also address your next comment)
How does this help to diagnose a potential failure? It feels like throw ExceptionUtilities.UnexpectedValue(evaluation) is going to be more informative by comparison to throw ExceptionUtilities.Unreachable.
There was a problem hiding this comment.
I think UnexpectedValue is useful when new nodes are added and need to be covered, while this particular node already exists and with this change it must be unreachable here.
We do have some other instances of this pattern in the codebase.
There was a problem hiding this comment.
I can change to throw ExceptionUtilities.UnexpectedValue(evaluation.Kind); Turned out that's more common than throwing unreachable for this propose.
Or just case BoundDagAssignmentEvaluation: default: I think that's is more aligned with your thinking.
There was a problem hiding this comment.
I think UnexpectedValue is useful when new nodes are added and need to be covered, while this particular node already exists and with this change it must be unreachable here.
We do have some other instances of this pattern in the codebase.
I think ExceptionUtilities.UnexpectedValue(evaluation) is more informative and, while investigating a failure, I wouldn't really care much about whether a new node caused a failure. I definitely wouldn't try to deduce this information from the kind of exception thrown. We use ExceptionUtilities.UnexpectedValue(evaluation) for default case here and, in my opinion, there is no good reason to diverge for case BoundDagAssignmentEvaluation. We could still keep the case for it if you prefer.
There was a problem hiding this comment.
Done. I think I've addressed all the comments. ptal, thanks.
| _factory.Syntax = test.Syntax; | ||
| switch (test) | ||
| { | ||
| case BoundDagAssignmentEvaluation: |
There was a problem hiding this comment.
LowerEvaluation is the very next thing we call with an explicit unreachable case mentioned above.
It is not obvious why it is safe to use the original Dag here. I think the implementation will be more robust if the code that is added to Refers to: src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs:20 in 54f5259. [](commit_id = 54f5259, deletion_comment = False) |
| BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput( | ||
| node.DecisionDag, loweredSwitchGoverningExpression, result, out BoundExpression savedInputExpression); | ||
|
|
||
| LabelSymbol? defaultLabel = node.DefaultLabel; |
| BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput( | ||
| node.DecisionDag, inputExpression, resultBuilder, out _); | ||
|
|
||
| BoundDecisionDag decisionDag = node.DecisionDag; |
There was a problem hiding this comment.
Only a single decision dag is kept in the bound tree. The other is created on the fly if needed.
(Plus DecisionDagNotForCodeGeneration is not true. Normally, we do use it for codegen.)
There was a problem hiding this comment.
Only a single decision dag is kept in the bound tree. The other is created on the fly if needed.
(Plus DecisionDagNotForCodeGeneration is not true. Normally, we do use it for codegen.)
The goal is to prevent people from using this Dag for codegen without special considerations first. It does not matter much if we end up reusing the Dag at the end, the main part is that we are supposed to check if it is reusable first. An "alarm" in the name helps to force a developer (and code reviewers as well) to think about the usage from that perspective. Feel free to use a "better" name.
There was a problem hiding this comment.
The goal is to prevent people from using this Dag for codegen without special considerations first. It does not matter much if we end up reusing the Dag at the end, the main part is that we are supposed to check if it is reusable first
I think I get it. How do you feel about an instance method GetDecisionDagForLowering that does this check? We can also rename the field to ReachabilityDecisionDag for explicit signal on plain usages.
There was a problem hiding this comment.
How do you feel about an instance method GetDecisionDagForLowering that does this check? We can also rename the field to ReachabilityDecisionDag for explicit signal on plain usages.
I think that will address my concern. There is still a question in what type GetDecisionDagForLowering will be defined, but having a dedicated helper feels like a good idea.
| "; | ||
| var verifier = CompileAndVerify(new[] { source, TestSources.Index, TestSources.Range }, parseOptions: TestOptions.RegularWithListPatterns, options: TestOptions.ReleaseDll).VerifyDiagnostics(); | ||
| AssertEx.Multiple( | ||
| () => verifier.VerifyIL("X.Test1", @" |
There was a problem hiding this comment.
Decompiled code from ListPattern_Codegen
private static int Test1(int[] x)
{
if (x != null)
{
int num = x.Length;
if (num >= 1 && x[num - 1] == 1 && x[0] == 1)
{
return 0;
}
}
return 1;
}
private static int Test2(int[] x)
{
if (x != null)
{
int num = x.Length;
if (num >= 1 && x[0] == 2 && x[num - 1] == 1)
{
return 0;
}
}
return 3;
}
private static int Test3(int[] x)
{
if (x != null)
{
int num = x.Length;
if (num >= 1)
{
if (x[0] == 2)
{
return 4;
}
if (x[num - 1] == 1)
{
return 5;
}
}
}
return 3;
}
private static int Test4(int[] x)
{
if (x != null)
{
int num = x.Length;
if (num >= 1)
{
int num2 = x[0];
if (num2 == 2)
{
return 4;
}
int num3 = x[num - 1];
if (num3 == 1)
{
return 5;
}
if (num >= 2 && num2 == 6 && num3 == 7)
{
return 8;
}
}
}
}|
Done with review pass (commit 7) |
|
Done with review pass (commit 8) |
| internal partial class BoundSwitchStatement : IBoundSwitchStatement | ||
| { | ||
| BoundNode IBoundSwitchStatement.Value => this.Expression; | ||
| ImmutableArray<BoundStatementList> IBoundSwitchStatement.Cases => StaticCast<BoundStatementList>.From(this.SwitchSections); |
There was a problem hiding this comment.
❔ Not related to this PR: this interface is unused.
| BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput( | ||
| node.DecisionDag, loweredSwitchGoverningExpression, result, out BoundExpression savedInputExpression); | ||
| node.GetDecisionDagForLowering(_factory.Compilation, out LabelSymbol? defaultLabel), | ||
| loweredSwitchGoverningExpression, result, out BoundExpression savedInputExpression); |
There was a problem hiding this comment.
❔ The name result threw me off.. it's a builder. Should probably rename to resultBuilder here and above just like LocalRewriter_IsPatternOperator.cs
|
I was out for a week and just got back to this. Looks good. Thanks! |
|
Fixes #57731? |
Yes, I believe so. (WorkItem marker was already added) |
An alternative approach to #57909
Relates to test plan #51289