Fix suboptimal IL for small is patterns - #84961
Conversation
|
Azure Pipelines: Successfully started running 1 pipeline(s). 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Optimizes C# lowering for small disjunctive is patterns to avoid temporary booleans and inefficient branches.
Changes:
- Uses inverted linear lowering for binding-free patterns with up to four tests.
- Updates affected IL baselines.
- Adds regression, threshold, binding, and async tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
LocalRewriter_IsPatternOperator.cs |
Adds the optimized lowering path. |
PatternTests.cs |
Adds regression and boundary tests. |
CodeGenAsyncTests.cs |
Covers async lowering scenarios. |
PatternMatchingTests.cs |
Updates Span pattern IL baselines. |
PatternMatchingTests5.cs |
Updates numeric, string, and Span IL baselines. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
I was wondering whether the increased overhead of the DAG codegen, is inherent to it or not? Basically, is there is some adjustment we can instead make to the DAG codegen, which would allow it to generate code generally as good or better than the "naive" sequence of linear tests? This could either be an optimization of the lowered DAG code itself, or, some shape change to the lowered code which allows a subsequent optimization pass (e.g. temp reuse) to be able to optimize some code it couldn't before. Note that I'm not deeply opposed to the current approach or to being pragmatic about making sure we can solve the key scenario for .NET 11. Just wondering if it's possible for us to pursue a better general solution. |
I was also thinking about this. While we should do something pragmatic right now, I'd much rather see a general fix. |
| internal sealed partial class LocalRewriter | ||
| { | ||
| /// <summary> | ||
| /// Benchmark results (see https://github.com/dotnet/roslyn/pull/84961) show that short patterns can be more |
There was a problem hiding this comment.
It would be desirable to commit at least a subset of the benchmarks to src/Tools/Benchmarks/Benchmarks.csproj
There was a problem hiding this comment.
Good idea. I think I will leave that to a follow up if that's okay.
| isPatternRewriter.Free(); | ||
| result = LowerIsPatternAsLinearSequence(node, decisionDag, whenTrueLabel: node.WhenTrueLabel, whenFalseLabel: node.WhenFalseLabel); | ||
| } | ||
| else if (IsFailureNode(decisionDag.RootNode, node.WhenFalseLabel)) |
There was a problem hiding this comment.
nit: It seems like this block could be deleted, I would expect the newly added block on line 40 to have the same effect
There was a problem hiding this comment.
This block also handles patterns with bindings which the block below does not.
There was a problem hiding this comment.
I didn't understand this part, are you saying that a case like x is var y is handled by this?
There was a problem hiding this comment.
x is var y would be handled by the first if. This branch handles a very specific case where the input is constant and so we can determine it is false at compile time (but there can still be a binding), e.g., const int x = 2; x is 3 and int y.
| whenTrueLabel: node.WhenFalseLabel, | ||
| whenFalseLabel: node.WhenTrueLabel, | ||
| maxTests: MaxTestsForInvertedLinearSequence) && | ||
| !containsBindings(decisionDag)) |
There was a problem hiding this comment.
What's the reason for needing to bailout when bindings are used, but not in the positive case? I guess when we start inverting things, then, keeping track of when the binding is actually assigned gets more complicated?
There was a problem hiding this comment.
The inverted pattern could leave the bindings unassigned.
For example, consider a pattern value is (B or C) and var match. Swapping true/false labels produces value is not B && value is not C. Then we negate it, so the final lowered form is !(value is not B && value is not C). It's equivalent to the original except it is missing the binding; we would need some special logic to preserve it.
Fixes #80052.
This PR is a refined version of #82429 (which got stale).
Benchmark: https://gist.github.com/jjonescz/4c35cef07ad198a8da9b17d480da5a99. It also tests sparse vs dense values (so it could be more efficient to consider that too when deciding the lowering strategy), but for starters I think we can simply choose between linear vs DAG lowering on a simple threshold for the number of tests.
See also dotnet/runtime#132452 and dotnet/runtime#132504.
Microsoft Reviewers: Open in CodeFlow