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

Use manual recursion for binary patterns #75212

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

333fred
Copy link
Member

@333fred 333fred commented Sep 23, 2024

Much like binary operators, we can have deeply-nested binary patterns. An example of this is our own IsBuildOnlyDiagnostic, which has ~2500 binary patterns in a single arm, and growing every release. To avoid stack depth issues, I took the same approach we do for binary operators; rewrite recursion to use a manual stack for these cases. Fixes #73439.

Much like binary operators, we can have deeply-nested binary patterns. An example of this is our own IsBuildOnlyDiagnostic, which has ~2500 binary patterns in a single arm, and growing every release. To avoid stack depth issues, I took the same approach we do for binary operators; rewrite recursion to use a manual stack for these cases. Fixes dotnet#73439.
@333fred 333fred requested a review from a team as a code owner September 23, 2024 23:27
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Sep 23, 2024
@333fred
Copy link
Member Author

333fred commented Sep 23, 2024

@dotnet/roslyn-compiler for review. I cannot stress strongly enough, review with whitespace off. Otherwise all the extractions to local functions will be much harder to review.

@333fred
Copy link
Member Author

333fred commented Sep 23, 2024

Also note: A much simpler workaround would just be to break up IsBuildOnlyDiagnostic into a few different pattern arms. I didn't take that approach because that would just leave a maintenance task on us every so often to fix up the compilation when some builds randomly start failing; this seems much less painful long term, and I don't think the original code is unreasonable.

@333fred 333fred marked this pull request as draft September 24, 2024 00:08
@333fred
Copy link
Member Author

333fred commented Sep 24, 2024

Converting this back to draft while I find a few more places that binary operators are handled that we probably need to handle patterns as well.

@333fred 333fred marked this pull request as ready for review September 24, 2024 00:21
@333fred
Copy link
Member Author

333fred commented Sep 24, 2024

Alright. There are a couple of locations in the optimizer and emit where we do specially handle binary operators, but we these nodes don't survive the local rewriter, and there are existing assertions that we don't have any patterns at that point. This is ready for review.

@AlekseyTs
Copy link
Contributor

@333fred It looks like there are legitimate test failures

@333fred
Copy link
Member Author

333fred commented Sep 25, 2024

@AlekseyTs I believe they should be fixed now.

@AlekseyTs
Copy link
Contributor

@333fred It looks like EndToEnd tests are crashing, likely related to the added test


// Compute the common type. This algorithm is quadratic, but disjunctive patterns are unlikely to be huge
var narrowedTypeCandidates = ArrayBuilder<TypeSymbol>.GetInstance(2);
collectCandidates(preboundLeft, narrowedTypeCandidates);
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 26, 2024

Choose a reason for hiding this comment

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

collectCandidates(preboundLeft, narrowedTypeCandidates);

Perhaps the assumption from the comment above that "disjunctive patterns are unlikely to be huge" has been proven wrong. Therefore, it might make sense to keep the set of candidate types from the left as we bubble up, instead of revisiting the subtree over and over again. Also, it might be worth considering to collect only distinct types to decrease amount of memory used. #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'll take the former suggestion, but I'll avoid the latter suggestion for now. We can do it in a followup if it's necessary, but it's a bigger change and I don't think we need to look at it yet.


var rightPatternStack = ArrayBuilder<PatternSyntax>.GetInstance();

while (currentPattern is BinaryPatternSyntax binaryPattern)
Copy link
Contributor

Choose a reason for hiding this comment

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

while (currentPattern is BinaryPatternSyntax binaryPattern)

Consider using a loop with a post-condition. We know the condition is true on entry.

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 that results in less-clear code, like we have in VisitBinaryExpression above. I'd prefer to keep this as is.

@@ -111,8 +111,26 @@ public override BoundNode VisitNegatedPattern(BoundNegatedPattern node)

public override BoundNode VisitBinaryPattern(BoundBinaryPattern node)
{
Visit(node.Left);
Visit(node.Right);
// Users (such as ourselves) can have many, many nested binary patterns. To avoid crashing, do left recursion manually.
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 26, 2024

Choose a reason for hiding this comment

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

// Users (such as ourselves) can have many, many nested binary patterns. To avoid crashing, do left recursion manually.

Should we add a similar override to DebugVerifier? #Closed

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 5)

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

@333fred
Copy link
Member Author

333fred commented Sep 30, 2024

@dotnet/roslyn-compiler for a second review

static BoundPattern bindBinaryPattern(
BoundPattern preboundLeft,
Binder binder,
(BinaryPatternSyntax pat, bool permitDesignations) patternAndPermitDesignations,
Copy link
Member

Choose a reason for hiding this comment

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

(BinaryPatternSyntax pat, bool permitDesignations) patternAndPermitDesignations

Consider splitting the tuple into two separate 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.

It was originally separate parameters; I moved it to the tuple for simplicity of the calling code.

Copy link
Member

Choose a reason for hiding this comment

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

The method is not using the tuple as a tuple though. It feels like the caller should be responsible for deconstructing the tuple rather than the method.

@@ -184,22 +184,23 @@ public static Location GetTooLongOrComplexExpressionErrorLocation(BoundNode node
{
SyntaxNode syntax = node.Syntax;

if (!(syntax is ExpressionSyntax))
if (syntax is not (ExpressionSyntax or PatternSyntax))
Copy link
Member

Choose a reason for hiding this comment

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

(ExpressionSyntax or PatternSyntax)

Consider creating a static local function since this same check is used three times.

Copy link
Member Author

Choose a reason for hiding this comment

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

This doesn't really feel like the type of thing that needs to be extracted to a local function; it honestly feels like that would impede readability, not help. I'd prefer to leave this as is.

""";
const string append = $"""

or
Copy link
Member

Choose a reason for hiding this comment

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

or

Consider testing deep nesting of and patterns as well since there is some difference in Binder.BindBinaryPattern().

Console.Write(i is not 1 and not 2 and ...);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
3 participants