Skip to content

Fix suboptimal IL for small is patterns - #84961

Merged
jjonescz merged 6 commits into
dotnet:mainfrom
jjonescz:80052-is-codegen
Aug 25, 2026
Merged

jjonescz merged 6 commits into
dotnet:mainfrom
jjonescz:80052-is-codegen

Conversation

@jjonescz

@jjonescz jjonescz commented Aug 19, 2026

Copy link
Copy Markdown
Member

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

@jjonescz
jjonescz requested a balanced review from Copilot August 19, 2026 15:01
@azure-pipelines

Copy link
Copy Markdown
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

@jjonescz
jjonescz marked this pull request as ready for review August 20, 2026 15:18
@jjonescz
jjonescz requested a review from a team as a code owner August 20, 2026 15:18
Copilot AI review requested due to automatic review settings August 20, 2026 15:18
@azure-pipelines

Copy link
Copy Markdown
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@RikkiGibson

Copy link
Copy Markdown
Member

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.

Comment thread src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs Outdated
Comment thread src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs Outdated
Comment thread src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs Outdated
@333fred

333fred commented Aug 20, 2026

Copy link
Copy Markdown
Member

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?

I was also thinking about this. While we should do something pragmatic right now, I'd much rather see a general fix.

@jjonescz

Copy link
Copy Markdown
Member Author

#68694 and #72273 already attempted a more general solution but both had to be reverted. I can file an issue to follow up on that if this PR is merged.

Copilot AI review requested due to automatic review settings August 21, 2026 11:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comment thread src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs
Copilot AI review requested due to automatic review settings August 24, 2026 11:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@jjonescz
jjonescz requested a review from 333fred August 24, 2026 13:06
@jjonescz
jjonescz requested a review from RikkiGibson August 24, 2026 16:59
internal sealed partial class LocalRewriter
{
/// <summary>
/// Benchmark results (see https://github.com/dotnet/roslyn/pull/84961) show that short patterns can be more

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be desirable to commit at least a subset of the benchmarks to src/Tools/Benchmarks/Benchmarks.csproj

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good idea. I think I will leave that to a follow up if that's okay.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

isPatternRewriter.Free();
result = LowerIsPatternAsLinearSequence(node, decisionDag, whenTrueLabel: node.WhenTrueLabel, whenFalseLabel: node.WhenFalseLabel);
}
else if (IsFailureNode(decisionDag.RootNode, node.WhenFalseLabel))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: It seems like this block could be deleted, I would expect the newly added block on line 40 to have the same effect

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This block also handles patterns with bindings which the block below does not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I didn't understand this part, are you saying that a case like x is var y is handled by this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

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.

Suboptimal codegen for is pattern compared to .NET 6/7

4 participants