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

List patterns: Recreate the decision dag for lowering #59019

Merged
merged 11 commits into from
Feb 4, 2022
Merged

Conversation

alrz
Copy link
Member

@alrz alrz commented Jan 22, 2022

An alternative approach to #57909
Relates to test plan #51289

@alrz alrz marked this pull request as ready for review January 22, 2022 18:23
@alrz alrz requested a review from a team as a code owner January 22, 2022 18:23
@alrz alrz requested review from jcouv and AlekseyTs January 22, 2022 18:23
@@ -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;
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 25, 2022

Choose a reason for hiding this comment

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

_considerAlternativeIndexers

Consider adding a doc comment. #Closed

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 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)
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 25, 2022

Choose a reason for hiding this comment

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

_considerAlternativeIndexers

Consider checking this first. #Closed

@@ -296,6 +296,9 @@ void addArg(RefKind refKind, BoundExpression expression)
return _factory.AssignmentExpression(output, access);
}

case BoundDagAssignmentEvaluation:
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 25, 2022

Choose a reason for hiding this comment

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

case BoundDagAssignmentEvaluation:

Is default: branch not good enough? #Closed

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'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)

Copy link
Contributor

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.

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 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.

Copy link
Member Author

@alrz alrz Jan 26, 2022

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.

Copy link
Contributor

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.

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. 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:
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 25, 2022

Choose a reason for hiding this comment

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

case BoundDagAssignmentEvaluation:

Would it make sense to keep this case with throw? #Closed

Copy link
Member Author

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.

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jan 25, 2022

        if (canProduceLinearSequence(node.DecisionDag.RootNode, whenTrueLabel: node.WhenTrueLabel, whenFalseLabel: node.WhenFalseLabel))

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 LowerGeneralIsPattern is added before this if instead. #Closed


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;
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 25, 2022

Choose a reason for hiding this comment

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

defaultLabel

It looks like this declaration can be moved inside the if. #Closed

BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput(
node.DecisionDag, inputExpression, resultBuilder, out _);

BoundDecisionDag decisionDag = node.DecisionDag;
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 25, 2022

Choose a reason for hiding this comment

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

DecisionDag

It might be useful to rename properties like this in bound nodes to something like "DecisionDagNotForCodeGeneration" #Closed

Copy link
Member Author

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.)

Copy link
Contributor

@AlekseyTs AlekseyTs Jan 26, 2022

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.

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 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.

Copy link
Contributor

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", @"
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 25, 2022

Choose a reason for hiding this comment

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

VerifyIL

Could you share result of decompiling IL to C#? #Closed

Copy link
Member Author

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;
            }
        }
    }
}

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 7)

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jan 26, 2022

            BoundDecisionDag decisionDag = isPatternExpression.DecisionDag;

A plain usage of this property during lowering is unexpected. #Closed


Refers to: src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs:225 in 54f5259. [](commit_id = 54f5259, deletion_comment = False)

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 8)

Comment on lines 10 to 13
internal partial class BoundSwitchStatement : IBoundSwitchStatement
{
BoundNode IBoundSwitchStatement.Value => this.Expression;
ImmutableArray<BoundStatementList> IBoundSwitchStatement.Cases => StaticCast<BoundStatementList>.From(this.SwitchSections);
Copy link
Member Author

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);
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 name result threw me off.. it's a builder. Should probably rename to resultBuilder here and above just like LocalRewriter_IsPatternOperator.cs

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

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

LGTM (commit 11)

@AlekseyTs
Copy link
Contributor

@jcouv, @333fred, @dotnet/roslyn-compiler For the second review.

@jcouv jcouv self-assigned this Feb 4, 2022
@jcouv
Copy link
Member

jcouv commented Feb 4, 2022

I was out for a week and just got back to this. Looks good. Thanks!
PS: in general, don't hesitate to ping on PRs. It's a helpful reminder :-)

Copy link
Member

@jcouv jcouv left a 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)

@jcouv jcouv merged commit c229bc4 into dotnet:main Feb 4, 2022
@ghost ghost added this to the Next milestone Feb 4, 2022
@jcouv
Copy link
Member

jcouv commented Feb 4, 2022

Fixes #57731?

@alrz
Copy link
Member Author

alrz commented Feb 4, 2022

Fixes #57731?

Yes, I believe so. (WorkItem marker was already added)

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.

4 participants