-
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
List patterns: Recreate the decision dag for lowering #59019
Conversation
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs
Outdated
Show resolved
Hide resolved
@@ -60,13 +60,15 @@ internal sealed partial class DecisionDagBuilder | |||
private readonly Conversions _conversions; | |||
private readonly BindingDiagnosticBag _diagnostics; | |||
private readonly LabelSymbol _defaultLabel; | |||
private readonly bool _considerAlternativeIndexers; |
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.
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.
@@ -1430,7 +1479,7 @@ private static ImmutableArray<StateForCase> RemoveEvaluation(ImmutableArray<Stat | |||
continue; | |||
} | |||
|
|||
if (lengthValues.Any(BinaryOperatorKind.Equal, lengthValue)) | |||
if (lengthValues.Any(BinaryOperatorKind.Equal, lengthValue) && _considerAlternativeIndexers) |
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.
@@ -296,6 +296,9 @@ void addArg(RefKind refKind, BoundExpression expression) | |||
return _factory.AssignmentExpression(output, access); | |||
} | |||
|
|||
case BoundDagAssignmentEvaluation: |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I think I've addressed all the comments. ptal, thanks.
@@ -192,8 +199,6 @@ private void LowerOneTest(BoundDagTest test, bool invert = false) | |||
_factory.Syntax = test.Syntax; | |||
switch (test) | |||
{ | |||
case BoundDagAssignmentEvaluation: |
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.
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; |
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.
BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput( | ||
node.DecisionDag, inputExpression, resultBuilder, out _); | ||
|
||
BoundDecisionDag decisionDag = node.DecisionDag; |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❔ 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❔ The name result
threw me off.. it's a builder. Should probably rename to resultBuilder
here and above just like LocalRewriter_IsPatternOperator.cs
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 (commit 11)
I was out for a week and just got back to this. Looks good. Thanks! |
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 Thanks (iteration 11)
Fixes #57731? |
Yes, I believe so. (WorkItem marker was already added) |
An alternative approach to #57909
Relates to test plan #51289