From 42ab28dd55a3d38c6d43edc991210bc2bafe1b94 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 19 Aug 2026 17:00:51 +0200 Subject: [PATCH 1/6] Fix suboptimal IL for small `is` patterns --- .../LocalRewriter_IsPatternOperator.cs | 53 +++- .../Test/Emit/CodeGen/CodeGenAsyncTests.cs | 92 +++++++ .../CSharp/Test/Emit/CodeGen/PatternTests.cs | 236 +++++++++++++----- .../Emit3/Semantics/PatternMatchingTests.cs | 212 ++++++++-------- .../Emit3/Semantics/PatternMatchingTests5.cs | 224 ++++++----------- 5 files changed, 494 insertions(+), 323 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs index 753a242ab44fc..c4a469fbb53de 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs @@ -12,6 +12,11 @@ namespace Microsoft.CodeAnalysis.CSharp { internal sealed partial class LocalRewriter { + /// + /// Benchmark results (see https://github.com/dotnet/runtime/pull/132452) show that small patterns are more efficient when emitted as comparisons rather than switch dispatch. + /// + private const int MaxTestsForInvertedLinearSequence = 4; + public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node) { BoundDecisionDag decisionDag = node.GetDecisionDagForLowering(_factory.Compilation); @@ -20,9 +25,7 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node if (canProduceLinearSequence(decisionDag.RootNode, whenTrueLabel: node.WhenTrueLabel, whenFalseLabel: node.WhenFalseLabel)) { // If we can build a linear test sequence `(e1 && e2 && e3)` for the dag, do so. - var isPatternRewriter = new IsPatternExpressionLinearLocalRewriter(node, this); - result = isPatternRewriter.LowerIsPatternAsLinearTestSequence(node, decisionDag, whenTrueLabel: node.WhenTrueLabel, whenFalseLabel: node.WhenFalseLabel); - isPatternRewriter.Free(); + result = LowerIsPatternAsLinearSequence(node, decisionDag, whenTrueLabel: node.WhenTrueLabel, whenFalseLabel: node.WhenFalseLabel); } else if (IsFailureNode(decisionDag.RootNode, node.WhenFalseLabel)) { @@ -31,9 +34,17 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node // Note that the positive case will be handled by canProduceLinearSequence above, however, we avoid to produce a full inverted linear sequence here // because we may be able to generate better code for a sequence of `or` patterns, using a switch dispatch, for example, which is done in the general rewriter. negated = !negated; - var isPatternRewriter = new IsPatternExpressionLinearLocalRewriter(node, this); - result = isPatternRewriter.LowerIsPatternAsLinearTestSequence(node, decisionDag, whenTrueLabel: node.WhenFalseLabel, whenFalseLabel: node.WhenTrueLabel); - isPatternRewriter.Free(); + result = LowerIsPatternAsLinearSequence(node, decisionDag, whenTrueLabel: node.WhenFalseLabel, whenFalseLabel: node.WhenTrueLabel); + } + else if (canProduceLinearSequence( + decisionDag.RootNode, + whenTrueLabel: node.WhenFalseLabel, + whenFalseLabel: node.WhenTrueLabel, + maxTests: MaxTestsForInvertedLinearSequence) && + !containsBindings(decisionDag)) + { + negated = !negated; + result = LowerIsPatternAsLinearSequence(node, decisionDag, whenTrueLabel: node.WhenFalseLabel, whenFalseLabel: node.WhenTrueLabel); } else { @@ -56,8 +67,10 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node static bool canProduceLinearSequence( BoundDecisionDagNode node, LabelSymbol whenTrueLabel, - LabelSymbol whenFalseLabel) + LabelSymbol whenFalseLabel, + int maxTests = int.MaxValue) { + int testCount = 0; while (true) { switch (node) @@ -72,6 +85,9 @@ static bool canProduceLinearSequence( node = e.Next; break; case BoundTestDecisionDagNode t: + if (++testCount > maxTests) + return false; + bool falseFail = IsFailureNode(t.WhenFalse, whenFalseLabel); if (falseFail == IsFailureNode(t.WhenTrue, whenFalseLabel)) return false; @@ -82,6 +98,29 @@ static bool canProduceLinearSequence( } } } + + static bool containsBindings(BoundDecisionDag decisionDag) + { + foreach (var node in decisionDag.TopologicallySortedNodes) + { + if (node is BoundWhenDecisionDagNode { Bindings.IsEmpty: false }) + return true; + } + + return false; + } + } + + private BoundExpression LowerIsPatternAsLinearSequence( + BoundIsPatternExpression node, + BoundDecisionDag decisionDag, + LabelSymbol whenTrueLabel, + LabelSymbol whenFalseLabel) + { + var rewriter = new IsPatternExpressionLinearLocalRewriter(node, this); + var result = rewriter.LowerIsPatternAsLinearTestSequence(node, decisionDag, whenTrueLabel, whenFalseLabel); + rewriter.Free(); + return result; } /// diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs index 43b7e210fc72b..467d43b7ea68b 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs @@ -6006,6 +6006,98 @@ .locals init (int V_0, //tests """); } + [ConditionalFact(typeof(CoreClrOnly)), WorkItem("https://github.com/dotnet/roslyn/issues/80052")] + public void IsPatternExpressionInAsyncMethod_DoesNotAffectHoistedLocals() + { + var source = """ + using System; + using System.Collections.Immutable; + using System.Threading.Tasks; + + await M(ImmutableArray.Create("", new('a', 10), new('a', 20), new('a', 30))); + Console.WriteLine("done"); + + static void Print(bool value) + { + Console.WriteLine(value); + } + + static async Task M(ImmutableArray values) + { + await Task.Yield(); + foreach (var value in values) + { + Print(value.Length is 10 or 20 or 30); + await Task.Delay(1).ConfigureAwait(false); + } + } + """; + + var verifier = CompileAndVerify( + source, + expectedOutput: """ + False + True + True + True + done + """, + options: TestOptions.ReleaseExe, + targetFramework: TargetFramework.NetCoreApp); + verifier.VerifyDiagnostics(); + } + + [ConditionalFact(typeof(CoreClrOnly)), WorkItem("https://github.com/dotnet/roslyn/issues/72753"), WorkItem("https://github.com/dotnet/roslyn/issues/80052")] + public void IsPatternExpressionInAwaitUsing() + { + var source = """ + using System; + using System.Threading.Tasks; + + await using (await GetResourceAsync()) + { + Exception exception = await GetExceptionAsync(); + Console.Write(exception is OperationCanceledException or CustomException { Message: "" }); + } + + static async Task GetResourceAsync() + { + await Task.Yield(); + return new Resource(); + } + + static async Task GetExceptionAsync() + { + await Task.Yield(); + return new CustomException(""); + } + + sealed class Resource : IAsyncDisposable + { + public ValueTask DisposeAsync() + { + Console.Write("Disposed"); + return default; + } + } + + sealed class CustomException : Exception + { + public CustomException(string message) + : base(message) + { + } + } + """; + + var verifier = CompileAndVerify( + source, + expectedOutput: "TrueDisposed", + options: TestOptions.ReleaseExe, + targetFramework: TargetFramework.NetCoreApp); + verifier.VerifyDiagnostics(); + } + [Fact] public void MyTask_16() { diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs index 4880c07ba2b7f..5ea1bef77e041 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs @@ -5505,23 +5505,18 @@ public static void Main() var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); compVerifier.VerifyIL("C.M1", """ { - // Code size 26 (0x1a) - .maxstack 1 - .locals init (bool V_0) + // Code size 21 (0x15) + .maxstack 2 IL_0000: ldarg.0 IL_0001: isinst "int" - IL_0006: brtrue.s IL_0012 + IL_0006: brtrue.s IL_0013 IL_0008: ldarg.0 IL_0009: isinst "long" - IL_000e: brtrue.s IL_0012 - IL_0010: br.s IL_0016 - IL_0012: ldc.i4.1 - IL_0013: stloc.0 - IL_0014: br.s IL_0018 - IL_0016: ldc.i4.0 - IL_0017: stloc.0 - IL_0018: ldloc.0 - IL_0019: ret + IL_000e: ldnull + IL_000f: cgt.un + IL_0011: br.s IL_0014 + IL_0013: ldc.i4.1 + IL_0014: ret } """); compVerifier.VerifyIL("C.M2", """ @@ -5546,22 +5541,18 @@ .maxstack 2 compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); compVerifier.VerifyIL("C.M1", """ { - // Code size 24 (0x18) - .maxstack 1 - .locals init (bool V_0) + // Code size 20 (0x14) + .maxstack 2 IL_0000: ldarg.0 IL_0001: isinst "int" - IL_0006: brtrue.s IL_0010 + IL_0006: brtrue.s IL_0012 IL_0008: ldarg.0 IL_0009: isinst "long" - IL_000e: brfalse.s IL_0014 - IL_0010: ldc.i4.1 - IL_0011: stloc.0 - IL_0012: br.s IL_0016 - IL_0014: ldc.i4.0 - IL_0015: stloc.0 - IL_0016: ldloc.0 - IL_0017: ret + IL_000e: ldnull + IL_000f: cgt.un + IL_0011: ret + IL_0012: ldc.i4.1 + IL_0013: ret } """); compVerifier.VerifyIL("C.M2", @" @@ -5605,27 +5596,18 @@ public static void Main() var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); compVerifier.VerifyIL("C.M1", """ { - // Code size 40 (0x28) + // Code size 29 (0x1d) .maxstack 1 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: isinst "int" - IL_0006: brtrue.s IL_0012 + IL_0006: brtrue.s IL_0017 IL_0008: ldarg.0 IL_0009: isinst "long" - IL_000e: brtrue.s IL_0012 - IL_0010: br.s IL_0016 - IL_0012: ldc.i4.1 - IL_0013: stloc.0 - IL_0014: br.s IL_0018 - IL_0016: ldc.i4.0 - IL_0017: stloc.0 - IL_0018: ldloc.0 - IL_0019: brtrue.s IL_0022 - IL_001b: ldstr "False" - IL_0020: br.s IL_0027 - IL_0022: ldstr "True" - IL_0027: ret + IL_000e: brtrue.s IL_0017 + IL_0010: ldstr "False" + IL_0015: br.s IL_001c + IL_0017: ldstr "True" + IL_001c: ret } """); compVerifier.VerifyIL("C.M2", """ @@ -5650,26 +5632,18 @@ .maxstack 1 compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); compVerifier.VerifyIL("C.M1", """ { - // Code size 37 (0x25) + // Code size 28 (0x1c) .maxstack 1 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: isinst "int" - IL_0006: brtrue.s IL_0010 + IL_0006: brtrue.s IL_0016 IL_0008: ldarg.0 IL_0009: isinst "long" - IL_000e: brfalse.s IL_0014 - IL_0010: ldc.i4.1 - IL_0011: stloc.0 - IL_0012: br.s IL_0016 - IL_0014: ldc.i4.0 - IL_0015: stloc.0 - IL_0016: ldloc.0 - IL_0017: brtrue.s IL_001f - IL_0019: ldstr "False" - IL_001e: ret - IL_001f: ldstr "True" - IL_0024: ret + IL_000e: brtrue.s IL_0016 + IL_0010: ldstr "False" + IL_0015: ret + IL_0016: ldstr "True" + IL_001b: ret } """); compVerifier.VerifyIL("C.M2", @" @@ -6157,6 +6131,156 @@ .maxstack 2 "); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80052")] + public void IsPatternDisjunct_OrPatternInAndExpression() + { + var source = """ + using System; + + class C + { + static bool M1(int x, char c) => x > 0 && c is '/' or '\\'; + static bool M2(int x, char c) => x > 0 && (c == '/' || c == '\\'); + + public static void Main() + { + Console.Write(M1(1, '/')); + Console.Write(M1(1, '\\')); + Console.Write(M1(1, 'a')); + Console.Write(M1(0, '/')); + } + } + """; + var verifier = CompileAndVerify(source, expectedOutput: "TrueTrueFalseFalse", options: TestOptions.ReleaseExe); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M1", """ +{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: ble.s IL_0011 + IL_0004: ldarg.1 + IL_0005: ldc.i4.s 47 + IL_0007: beq.s IL_000f + IL_0009: ldarg.1 + IL_000a: ldc.i4.s 92 + IL_000c: ceq + IL_000e: ret + IL_000f: ldc.i4.1 + IL_0010: ret + IL_0011: ldc.i4.0 + IL_0012: ret +} +"""); + verifier.VerifyIL("C.M2", """ +{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: ble.s IL_0011 + IL_0004: ldarg.1 + IL_0005: ldc.i4.s 47 + IL_0007: beq.s IL_000f + IL_0009: ldarg.1 + IL_000a: ldc.i4.s 92 + IL_000c: ceq + IL_000e: ret + IL_000f: ldc.i4.1 + IL_0010: ret + IL_0011: ldc.i4.0 + IL_0012: ret +} +"""); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80052")] + public void IsPatternDisjunct_InvertedLinearSequenceThreshold() + { + var source = """ + using System; + + class C + { + static bool Pattern2(byte value) => value is not ((byte)'E' or (byte)'e'); + static bool Comparison2(byte value) => value != 'E' && value != 'e'; + + static bool Pattern3(byte value) => value is not ((byte)'.' or (byte)'E' or (byte)'e'); + static bool Comparison3(byte value) => value != '.' && value != 'E' && value != 'e'; + + static bool Pattern4(byte value) => value is not (1 or 3 or 4 or 6); + static bool Comparison4(byte value) => value != 1 && value != 3 && value != 4 && value != 6; + + static bool Pattern5(byte value) => value is not (1 or 3 or 4 or 6 or 7); + + public static void Main() + { + foreach (byte value in new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, (byte)'.', (byte)'E', (byte)'e' }) + { + if (Pattern2(value) != Comparison2(value) || + Pattern3(value) != Comparison3(value) || + Pattern4(value) != Comparison4(value)) + { + throw new Exception(); + } + } + + Console.Write(Pattern5(0)); + Console.Write(Pattern5(3)); + } + } + """; + var verifier = CompileAndVerify(source, expectedOutput: "TrueFalse", options: TestOptions.ReleaseExe); + verifier.VerifyDiagnostics(); + + Assert.Equal(verifier.VisualizeIL("C.Comparison2"), verifier.VisualizeIL("C.Pattern2")); + Assert.Equal(verifier.VisualizeIL("C.Comparison3"), verifier.VisualizeIL("C.Pattern3")); + Assert.Equal(verifier.VisualizeIL("C.Comparison4"), verifier.VisualizeIL("C.Pattern4")); + Assert.Contains("switch", verifier.VisualizeIL("C.Pattern5")); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80052")] + public void IsPatternDisjunct_WithBinding() + { + var source = """ + using System; + + class A + { + public A(int value) => Value = value; + public int Value { get; } + } + + sealed class B : A + { + public B(int value) : base(value) + { + } + } + + sealed class C : A + { + public C(int value) : base(value) + { + } + } + + class Program + { + static int M(A value) => value is (B or C) and var match ? match.Value : -1; + + public static void Main() + { + Console.Write(M(new B(1))); + Console.Write(M(new C(2))); + Console.Write(M(new A(3))); + } + } + """; + CompileAndVerify(source, expectedOutput: "12-1", options: TestOptions.ReleaseExe).VerifyDiagnostics(); + } + [Fact, WorkItem(46536, "https://github.com/dotnet/roslyn/issues/46536")] public void MultiplePathsToNode_SwitchDispatch_01() { diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests.cs index 8d29aa71105df..ec0c9cdd57ab1 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests.cs @@ -8357,64 +8357,60 @@ static void Test(ReadOnlySpan chars) not: True") .VerifyIL("C.Test", """ { - // Code size 167 (0xa7) + // Code size 161 (0xa1) .maxstack 3 .locals init (bool V_0) IL_0000: nop - IL_0001: ldarg.0 - IL_0002: ldstr "string 1" - IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000c: call "bool System.MemoryExtensions.SequenceEqual(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0011: brtrue.s IL_0027 - IL_0013: ldarg.0 - IL_0014: ldstr "string 2" - IL_0019: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_001e: call "bool System.MemoryExtensions.SequenceEqual(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0023: brtrue.s IL_0027 - IL_0025: br.s IL_002b - IL_0027: ldc.i4.1 - IL_0028: stloc.0 - IL_0029: br.s IL_002d - IL_002b: ldc.i4.0 - IL_002c: stloc.0 - IL_002d: ldstr "or: " - IL_0032: ldloca.s V_0 - IL_0034: call "string bool.ToString()" - IL_0039: call "string string.Concat(string, string)" - IL_003e: call "void System.Console.WriteLine(string)" - IL_0043: nop - IL_0044: ldstr "and: " - IL_0049: ldarg.0 - IL_004a: ldstr "string 1" - IL_004f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0054: call "bool System.MemoryExtensions.SequenceEqual(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0059: brfalse.s IL_0067 - IL_005b: ldarga.s V_0 - IL_005d: call "int System.ReadOnlySpan.Length.get" - IL_0062: ldc.i4.7 - IL_0063: ceq - IL_0065: br.s IL_0068 - IL_0067: ldc.i4.0 - IL_0068: stloc.0 - IL_0069: ldloca.s V_0 - IL_006b: call "string bool.ToString()" - IL_0070: call "string string.Concat(string, string)" - IL_0075: call "void System.Console.WriteLine(string)" - IL_007a: nop - IL_007b: ldstr "not: " - IL_0080: ldarg.0 - IL_0081: ldstr "string 1" - IL_0086: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_008b: call "bool System.MemoryExtensions.SequenceEqual(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0090: ldc.i4.0 - IL_0091: ceq - IL_0093: stloc.0 - IL_0094: ldloca.s V_0 - IL_0096: call "string bool.ToString()" - IL_009b: call "string string.Concat(string, string)" - IL_00a0: call "void System.Console.WriteLine(string)" - IL_00a5: nop - IL_00a6: ret + IL_0001: ldstr "or: " + IL_0006: ldarg.0 + IL_0007: ldstr "string 1" + IL_000c: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0011: call "bool System.MemoryExtensions.SequenceEqual(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0016: brtrue.s IL_002a + IL_0018: ldarg.0 + IL_0019: ldstr "string 2" + IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0023: call "bool System.MemoryExtensions.SequenceEqual(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0028: br.s IL_002b + IL_002a: ldc.i4.1 + IL_002b: stloc.0 + IL_002c: ldloca.s V_0 + IL_002e: call "string bool.ToString()" + IL_0033: call "string string.Concat(string, string)" + IL_0038: call "void System.Console.WriteLine(string)" + IL_003d: nop + IL_003e: ldstr "and: " + IL_0043: ldarg.0 + IL_0044: ldstr "string 1" + IL_0049: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_004e: call "bool System.MemoryExtensions.SequenceEqual(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0053: brfalse.s IL_0061 + IL_0055: ldarga.s V_0 + IL_0057: call "int System.ReadOnlySpan.Length.get" + IL_005c: ldc.i4.7 + IL_005d: ceq + IL_005f: br.s IL_0062 + IL_0061: ldc.i4.0 + IL_0062: stloc.0 + IL_0063: ldloca.s V_0 + IL_0065: call "string bool.ToString()" + IL_006a: call "string string.Concat(string, string)" + IL_006f: call "void System.Console.WriteLine(string)" + IL_0074: nop + IL_0075: ldstr "not: " + IL_007a: ldarg.0 + IL_007b: ldstr "string 1" + IL_0080: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0085: call "bool System.MemoryExtensions.SequenceEqual(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_008a: ldc.i4.0 + IL_008b: ceq + IL_008d: stloc.0 + IL_008e: ldloca.s V_0 + IL_0090: call "string bool.ToString()" + IL_0095: call "string string.Concat(string, string)" + IL_009a: call "void System.Console.WriteLine(string)" + IL_009f: nop + IL_00a0: ret } """); } @@ -9955,64 +9951,60 @@ static void Test(Span chars) not: True") .VerifyIL("C.Test", """ { - // Code size 167 (0xa7) + // Code size 161 (0xa1) .maxstack 3 .locals init (bool V_0) IL_0000: nop - IL_0001: ldarg.0 - IL_0002: ldstr "string 1" - IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000c: call "bool System.MemoryExtensions.SequenceEqual(System.Span, System.ReadOnlySpan)" - IL_0011: brtrue.s IL_0027 - IL_0013: ldarg.0 - IL_0014: ldstr "string 2" - IL_0019: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_001e: call "bool System.MemoryExtensions.SequenceEqual(System.Span, System.ReadOnlySpan)" - IL_0023: brtrue.s IL_0027 - IL_0025: br.s IL_002b - IL_0027: ldc.i4.1 - IL_0028: stloc.0 - IL_0029: br.s IL_002d - IL_002b: ldc.i4.0 - IL_002c: stloc.0 - IL_002d: ldstr "or: " - IL_0032: ldloca.s V_0 - IL_0034: call "string bool.ToString()" - IL_0039: call "string string.Concat(string, string)" - IL_003e: call "void System.Console.WriteLine(string)" - IL_0043: nop - IL_0044: ldstr "and: " - IL_0049: ldarg.0 - IL_004a: ldstr "string 1" - IL_004f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0054: call "bool System.MemoryExtensions.SequenceEqual(System.Span, System.ReadOnlySpan)" - IL_0059: brfalse.s IL_0067 - IL_005b: ldarga.s V_0 - IL_005d: call "int System.Span.Length.get" - IL_0062: ldc.i4.7 - IL_0063: ceq - IL_0065: br.s IL_0068 - IL_0067: ldc.i4.0 - IL_0068: stloc.0 - IL_0069: ldloca.s V_0 - IL_006b: call "string bool.ToString()" - IL_0070: call "string string.Concat(string, string)" - IL_0075: call "void System.Console.WriteLine(string)" - IL_007a: nop - IL_007b: ldstr "not: " - IL_0080: ldarg.0 - IL_0081: ldstr "string 1" - IL_0086: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_008b: call "bool System.MemoryExtensions.SequenceEqual(System.Span, System.ReadOnlySpan)" - IL_0090: ldc.i4.0 - IL_0091: ceq - IL_0093: stloc.0 - IL_0094: ldloca.s V_0 - IL_0096: call "string bool.ToString()" - IL_009b: call "string string.Concat(string, string)" - IL_00a0: call "void System.Console.WriteLine(string)" - IL_00a5: nop - IL_00a6: ret + IL_0001: ldstr "or: " + IL_0006: ldarg.0 + IL_0007: ldstr "string 1" + IL_000c: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0011: call "bool System.MemoryExtensions.SequenceEqual(System.Span, System.ReadOnlySpan)" + IL_0016: brtrue.s IL_002a + IL_0018: ldarg.0 + IL_0019: ldstr "string 2" + IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0023: call "bool System.MemoryExtensions.SequenceEqual(System.Span, System.ReadOnlySpan)" + IL_0028: br.s IL_002b + IL_002a: ldc.i4.1 + IL_002b: stloc.0 + IL_002c: ldloca.s V_0 + IL_002e: call "string bool.ToString()" + IL_0033: call "string string.Concat(string, string)" + IL_0038: call "void System.Console.WriteLine(string)" + IL_003d: nop + IL_003e: ldstr "and: " + IL_0043: ldarg.0 + IL_0044: ldstr "string 1" + IL_0049: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_004e: call "bool System.MemoryExtensions.SequenceEqual(System.Span, System.ReadOnlySpan)" + IL_0053: brfalse.s IL_0061 + IL_0055: ldarga.s V_0 + IL_0057: call "int System.Span.Length.get" + IL_005c: ldc.i4.7 + IL_005d: ceq + IL_005f: br.s IL_0062 + IL_0061: ldc.i4.0 + IL_0062: stloc.0 + IL_0063: ldloca.s V_0 + IL_0065: call "string bool.ToString()" + IL_006a: call "string string.Concat(string, string)" + IL_006f: call "void System.Console.WriteLine(string)" + IL_0074: nop + IL_0075: ldstr "not: " + IL_007a: ldarg.0 + IL_007b: ldstr "string 1" + IL_0080: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0085: call "bool System.MemoryExtensions.SequenceEqual(System.Span, System.ReadOnlySpan)" + IL_008a: ldc.i4.0 + IL_008b: ceq + IL_008d: stloc.0 + IL_008e: ldloca.s V_0 + IL_0090: call "string bool.ToString()" + IL_0095: call "string string.Concat(string, string)" + IL_009a: call "void System.Console.WriteLine(string)" + IL_009f: nop + IL_00a0: ret } """); } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests5.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests5.cs index a6eec84018eac..35950774e703a 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests5.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests5.cs @@ -5280,22 +5280,17 @@ class C const string expectedInt32IL = """ { - // Code size 18 (0x12) + // Code size 13 (0xd) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.s 10 - IL_0003: beq.s IL_000a + IL_0003: beq.s IL_000b IL_0005: ldarg.0 IL_0006: ldc.i4.s 50 - IL_0008: bne.un.s IL_000e - IL_000a: ldc.i4.1 - IL_000b: stloc.0 - IL_000c: br.s IL_0010 - IL_000e: ldc.i4.0 - IL_000f: stloc.0 - IL_0010: ldloc.0 - IL_0011: ret + IL_0008: ceq + IL_000a: ret + IL_000b: ldc.i4.1 + IL_000c: ret } """; verifier.VerifyIL("C.SByte", expectedInt32IL); @@ -5307,125 +5302,87 @@ .locals init (bool V_0) verifier.VerifyIL("C.Enum", expectedInt32IL); verifier.VerifyIL("C.Int64", """ { - // Code size 20 (0x14) + // Code size 15 (0xf) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.s 10 IL_0003: conv.i8 - IL_0004: beq.s IL_000c + IL_0004: beq.s IL_000d IL_0006: ldarg.0 IL_0007: ldc.i4.s 50 IL_0009: conv.i8 - IL_000a: bne.un.s IL_0010 - IL_000c: ldc.i4.1 - IL_000d: stloc.0 - IL_000e: br.s IL_0012 - IL_0010: ldc.i4.0 - IL_0011: stloc.0 - IL_0012: ldloc.0 - IL_0013: ret + IL_000a: ceq + IL_000c: ret + IL_000d: ldc.i4.1 + IL_000e: ret } """); verifier.VerifyIL("C.UInt64", """ { - // Code size 20 (0x14) + // Code size 15 (0xf) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.s 10 IL_0003: conv.i8 - IL_0004: beq.s IL_000c + IL_0004: beq.s IL_000d IL_0006: ldarg.0 IL_0007: ldc.i4.s 50 IL_0009: conv.i8 - IL_000a: bne.un.s IL_0010 - IL_000c: ldc.i4.1 - IL_000d: stloc.0 - IL_000e: br.s IL_0012 - IL_0010: ldc.i4.0 - IL_0011: stloc.0 - IL_0012: ldloc.0 - IL_0013: ret + IL_000a: ceq + IL_000c: ret + IL_000d: ldc.i4.1 + IL_000e: ret } """); verifier.VerifyIL("C.Char", """ { - // Code size 18 (0x12) + // Code size 13 (0xd) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.s 97 - IL_0003: beq.s IL_000a + IL_0003: beq.s IL_000b IL_0005: ldarg.0 IL_0006: ldc.i4.s 122 - IL_0008: bne.un.s IL_000e - IL_000a: ldc.i4.1 - IL_000b: stloc.0 - IL_000c: br.s IL_0010 - IL_000e: ldc.i4.0 - IL_000f: stloc.0 - IL_0010: ldloc.0 - IL_0011: ret + IL_0008: ceq + IL_000a: ret + IL_000b: ldc.i4.1 + IL_000c: ret } """); verifier.VerifyIL("C.NInt", """ { - // Code size 23 (0x17) + // Code size 13 (0xd) .maxstack 2 - .locals init (bool V_0, - long V_1) IL_0000: ldarg.0 - IL_0001: conv.i8 - IL_0002: stloc.1 - IL_0003: ldloc.1 - IL_0004: ldc.i4.s 10 - IL_0006: conv.i8 - IL_0007: beq.s IL_000f - IL_0009: ldloc.1 - IL_000a: ldc.i4.s 50 - IL_000c: conv.i8 - IL_000d: bne.un.s IL_0013 - IL_000f: ldc.i4.1 - IL_0010: stloc.0 - IL_0011: br.s IL_0015 - IL_0013: ldc.i4.0 - IL_0014: stloc.0 - IL_0015: ldloc.0 - IL_0016: ret + IL_0001: ldc.i4.s 10 + IL_0003: beq.s IL_000b + IL_0005: ldarg.0 + IL_0006: ldc.i4.s 50 + IL_0008: ceq + IL_000a: ret + IL_000b: ldc.i4.1 + IL_000c: ret } """); verifier.VerifyIL("C.NUInt", """ { - // Code size 23 (0x17) + // Code size 13 (0xd) .maxstack 2 - .locals init (bool V_0, - ulong V_1) IL_0000: ldarg.0 - IL_0001: conv.u8 - IL_0002: stloc.1 - IL_0003: ldloc.1 - IL_0004: ldc.i4.s 10 - IL_0006: conv.i8 - IL_0007: beq.s IL_000f - IL_0009: ldloc.1 - IL_000a: ldc.i4.s 50 - IL_000c: conv.i8 - IL_000d: bne.un.s IL_0013 - IL_000f: ldc.i4.1 - IL_0010: stloc.0 - IL_0011: br.s IL_0015 - IL_0013: ldc.i4.0 - IL_0014: stloc.0 - IL_0015: ldloc.0 - IL_0016: ret + IL_0001: ldc.i4.s 10 + IL_0003: beq.s IL_000b + IL_0005: ldarg.0 + IL_0006: ldc.i4.s 50 + IL_0008: ceq + IL_000a: ret + IL_000b: ldc.i4.1 + IL_000c: ret } """); verifier.VerifyIL("C.Decimal", """ { - // Code size 58 (0x3a) + // Code size 51 (0x33) .maxstack 6 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.0 IL_0002: ldc.i4.0 @@ -5434,7 +5391,7 @@ .locals init (bool V_0) IL_0005: ldc.i4.s 28 IL_0007: newobj "decimal..ctor(int, int, int, bool, byte)" IL_000c: call "bool decimal.op_Equality(decimal, decimal)" - IL_0011: brtrue.s IL_0032 + IL_0011: brtrue.s IL_0031 IL_0013: ldarg.0 IL_0014: ldc.i4 0x10000000 IL_0019: ldc.i4 0x3e250261 @@ -5443,54 +5400,39 @@ .locals init (bool V_0) IL_0024: ldc.i4.s 28 IL_0026: newobj "decimal..ctor(int, int, int, bool, byte)" IL_002b: call "bool decimal.op_Equality(decimal, decimal)" - IL_0030: brfalse.s IL_0036 - IL_0032: ldc.i4.1 - IL_0033: stloc.0 - IL_0034: br.s IL_0038 - IL_0036: ldc.i4.0 - IL_0037: stloc.0 - IL_0038: ldloc.0 - IL_0039: ret + IL_0030: ret + IL_0031: ldc.i4.1 + IL_0032: ret } """); verifier.VerifyIL("C.Single", """ { - // Code size 24 (0x18) + // Code size 19 (0x13) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.r4 10 - IL_0006: beq.s IL_0010 + IL_0006: beq.s IL_0011 IL_0008: ldarg.0 IL_0009: ldc.r4 50 - IL_000e: bne.un.s IL_0014 - IL_0010: ldc.i4.1 - IL_0011: stloc.0 - IL_0012: br.s IL_0016 - IL_0014: ldc.i4.0 - IL_0015: stloc.0 - IL_0016: ldloc.0 - IL_0017: ret + IL_000e: ceq + IL_0010: ret + IL_0011: ldc.i4.1 + IL_0012: ret } """); verifier.VerifyIL("C.Double", """ { - // Code size 32 (0x20) + // Code size 27 (0x1b) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.r8 10 - IL_000a: beq.s IL_0018 + IL_000a: beq.s IL_0019 IL_000c: ldarg.0 IL_000d: ldc.r8 50 - IL_0016: bne.un.s IL_001c - IL_0018: ldc.i4.1 - IL_0019: stloc.0 - IL_001a: br.s IL_001e - IL_001c: ldc.i4.0 - IL_001d: stloc.0 - IL_001e: ldloc.0 - IL_001f: ret + IL_0016: ceq + IL_0018: ret + IL_0019: ldc.i4.1 + IL_001a: ret } """); } @@ -5526,72 +5468,54 @@ public static bool LargeReadOnlySpan(ReadOnlySpan value) => verifier.VerifyIL("C.String", """ { - // Code size 34 (0x22) + // Code size 27 (0x1b) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldstr "z" IL_0006: call "bool string.op_Equality(string, string)" - IL_000b: brtrue.s IL_001a + IL_000b: brtrue.s IL_0019 IL_000d: ldarg.0 IL_000e: ldstr "a" IL_0013: call "bool string.op_Equality(string, string)" - IL_0018: brfalse.s IL_001e - IL_001a: ldc.i4.1 - IL_001b: stloc.0 - IL_001c: br.s IL_0020 - IL_001e: ldc.i4.0 - IL_001f: stloc.0 - IL_0020: ldloc.0 - IL_0021: ret + IL_0018: ret + IL_0019: ldc.i4.1 + IL_001a: ret } """); verifier.VerifyIL("C.Span", """ { - // Code size 44 (0x2c) + // Code size 37 (0x25) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldstr "a" IL_0006: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" IL_000b: call "bool System.MemoryExtensions.SequenceEqual(System.Span, System.ReadOnlySpan)" - IL_0010: brtrue.s IL_0024 + IL_0010: brtrue.s IL_0023 IL_0012: ldarg.0 IL_0013: ldstr "z" IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" IL_001d: call "bool System.MemoryExtensions.SequenceEqual(System.Span, System.ReadOnlySpan)" - IL_0022: brfalse.s IL_0028 - IL_0024: ldc.i4.1 - IL_0025: stloc.0 - IL_0026: br.s IL_002a - IL_0028: ldc.i4.0 - IL_0029: stloc.0 - IL_002a: ldloc.0 - IL_002b: ret + IL_0022: ret + IL_0023: ldc.i4.1 + IL_0024: ret } """); verifier.VerifyIL("C.ReadOnlySpan", """ { - // Code size 44 (0x2c) + // Code size 37 (0x25) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldstr "a" IL_0006: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" IL_000b: call "bool System.MemoryExtensions.SequenceEqual(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0010: brtrue.s IL_0024 + IL_0010: brtrue.s IL_0023 IL_0012: ldarg.0 IL_0013: ldstr "z" IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" IL_001d: call "bool System.MemoryExtensions.SequenceEqual(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0022: brfalse.s IL_0028 - IL_0024: ldc.i4.1 - IL_0025: stloc.0 - IL_0026: br.s IL_002a - IL_0028: ldc.i4.0 - IL_0029: stloc.0 - IL_002a: ldloc.0 - IL_002b: ret + IL_0022: ret + IL_0023: ldc.i4.1 + IL_0024: ret } """); verifier.VerifyIL("C.LargeString", """ From 654249257ed2f6e9a83dde15f4422b70b3022dab Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 20 Aug 2026 14:49:31 +0200 Subject: [PATCH 2/6] Change threshold to 3 --- .../LocalRewriter/LocalRewriter_IsPatternOperator.cs | 5 +++-- src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs index c4a469fbb53de..188c19d8a0670 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs @@ -13,9 +13,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal sealed partial class LocalRewriter { /// - /// Benchmark results (see https://github.com/dotnet/runtime/pull/132452) show that small patterns are more efficient when emitted as comparisons rather than switch dispatch. + /// Benchmark results (see https://github.com/dotnet/roslyn/pull/84961) show that short patterns can be more + /// efficient when emitted as comparisons. Larger patterns can benefit from switch dispatch. /// - private const int MaxTestsForInvertedLinearSequence = 4; + private const int MaxTestsForInvertedLinearSequence = 3; public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node) { diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs index 5ea1bef77e041..9b37ccbd54343 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs @@ -6236,7 +6236,7 @@ public static void Main() Assert.Equal(verifier.VisualizeIL("C.Comparison2"), verifier.VisualizeIL("C.Pattern2")); Assert.Equal(verifier.VisualizeIL("C.Comparison3"), verifier.VisualizeIL("C.Pattern3")); - Assert.Equal(verifier.VisualizeIL("C.Comparison4"), verifier.VisualizeIL("C.Pattern4")); + Assert.Contains("switch", verifier.VisualizeIL("C.Pattern4")); Assert.Contains("switch", verifier.VisualizeIL("C.Pattern5")); } From dfdb0574a6135951ceb362dc6cda2d1cd740588e Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 20 Aug 2026 15:37:58 +0200 Subject: [PATCH 3/6] Fixup IL baselines --- .../CSharp/Test/CSharp15/UnionsTests.cs | 416 +++++++----------- 1 file changed, 162 insertions(+), 254 deletions(-) diff --git a/src/Compilers/CSharp/Test/CSharp15/UnionsTests.cs b/src/Compilers/CSharp/Test/CSharp15/UnionsTests.cs index eb79265e770fe..b5e1bb3ab7bc3 100644 --- a/src/Compilers/CSharp/Test/CSharp15/UnionsTests.cs +++ b/src/Compilers/CSharp/Test/CSharp15/UnionsTests.cs @@ -2511,19 +2511,17 @@ static bool Test2(C1 u) verifier.VerifyIL("Program.Test2", @" { // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + .maxstack 2 IL_0000: ldarg.0 - IL_0001: brfalse.s IL_000b + IL_0001: brfalse.s IL_000e IL_0003: ldarg.0 IL_0004: callvirt ""object C1.Value.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 + IL_0009: ldnull + IL_000a: cgt.un + IL_000c: br.s IL_000f + IL_000e: ldc.i4.0 IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 + IL_0010: ceq IL_0012: ret } "); @@ -2531,24 +2529,22 @@ .locals init (bool V_0) verifier.VerifyIL("Program.Test1", @" { // Code size 34 (0x22) - .maxstack 1 - .locals init (S1 V_0, - bool V_1) + .maxstack 2 + .locals init (S1 V_0) IL_0000: ldarga.s V_0 IL_0002: call ""bool S1?.HasValue.get"" - IL_0007: brfalse.s IL_001a + IL_0007: brfalse.s IL_001d IL_0009: ldarga.s V_0 IL_000b: call ""S1 S1?.GetValueOrDefault()"" IL_0010: stloc.0 IL_0011: ldloca.s V_0 IL_0013: call ""object S1.Value.get"" - IL_0018: brtrue.s IL_001e - IL_001a: ldc.i4.1 - IL_001b: stloc.1 - IL_001c: br.s IL_0020 + IL_0018: ldnull + IL_0019: cgt.un + IL_001b: br.s IL_001e + IL_001d: ldc.i4.0 IL_001e: ldc.i4.0 - IL_001f: stloc.1 - IL_0020: ldloc.1 + IL_001f: ceq IL_0021: ret } "); @@ -3186,19 +3182,17 @@ .locals init (bool V_0) @" { // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + .maxstack 2 IL_0000: ldarg.0 - IL_0001: brfalse.s IL_000b + IL_0001: brfalse.s IL_000e IL_0003: ldarg.0 IL_0004: callvirt ""object S1.Value.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 + IL_0009: ldnull + IL_000a: cgt.un + IL_000c: br.s IL_000f + IL_000e: ldc.i4.0 IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 + IL_0010: ceq IL_0012: ret } "); @@ -4722,73 +4716,59 @@ static bool Test6(C1 u) verifier.VerifyIL("Program.Test1", @" { - // Code size 37 (0x25) + // Code size 30 (0x1e) .maxstack 2 - .locals init (S1 V_0, - bool V_1) + .locals init (S1 V_0) IL_0000: ldarga.s V_0 IL_0002: call ""bool S1?.HasValue.get"" - IL_0007: brfalse.s IL_001a + IL_0007: brfalse.s IL_001c IL_0009: ldarga.s V_0 IL_000b: call ""S1 S1?.GetValueOrDefault()"" IL_0010: stloc.0 IL_0011: ldloca.s V_0 IL_0013: call ""object S1.Value.get"" - IL_0018: brtrue.s IL_001e - IL_001a: ldc.i4.1 - IL_001b: stloc.1 - IL_001c: br.s IL_0020 - IL_001e: ldc.i4.0 - IL_001f: stloc.1 - IL_0020: ldloc.1 - IL_0021: ldc.i4.0 - IL_0022: ceq - IL_0024: ret + IL_0018: ldnull + IL_0019: cgt.un + IL_001b: ret + IL_001c: ldc.i4.0 + IL_001d: ret } "); verifier.VerifyIL("Program.Test2", @" { - // Code size 22 (0x16) + // Code size 15 (0xf) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 - IL_0001: brfalse.s IL_000b + IL_0001: brfalse.s IL_000d IL_0003: ldarg.0 IL_0004: callvirt ""object C1.Value.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ldc.i4.0 - IL_0013: ceq - IL_0015: ret + IL_0009: ldnull + IL_000a: cgt.un + IL_000c: ret + IL_000d: ldc.i4.0 + IL_000e: ret } "); var test3 = @" { // Code size 34 (0x22) - .maxstack 1 - .locals init (S1 V_0, - bool V_1) + .maxstack 2 + .locals init (S1 V_0) IL_0000: ldarga.s V_0 IL_0002: call ""bool S1?.HasValue.get"" - IL_0007: brfalse.s IL_001a + IL_0007: brfalse.s IL_001d IL_0009: ldarga.s V_0 IL_000b: call ""S1 S1?.GetValueOrDefault()"" IL_0010: stloc.0 IL_0011: ldloca.s V_0 IL_0013: call ""object S1.Value.get"" - IL_0018: brtrue.s IL_001e - IL_001a: ldc.i4.1 - IL_001b: stloc.1 - IL_001c: br.s IL_0020 + IL_0018: ldnull + IL_0019: cgt.un + IL_001b: br.s IL_001e + IL_001d: ldc.i4.0 IL_001e: ldc.i4.0 - IL_001f: stloc.1 - IL_0020: ldloc.1 + IL_001f: ceq IL_0021: ret } "; @@ -4796,19 +4776,17 @@ .locals init (S1 V_0, var test4 = @" { // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + .maxstack 2 IL_0000: ldarg.0 - IL_0001: brfalse.s IL_000b + IL_0001: brfalse.s IL_000e IL_0003: ldarg.0 IL_0004: callvirt ""object C1.Value.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 + IL_0009: ldnull + IL_000a: cgt.un + IL_000c: br.s IL_000f + IL_000e: ldc.i4.0 IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 + IL_0010: ceq IL_0012: ret } "; @@ -7217,19 +7195,17 @@ static bool Test4(C t) verifier.VerifyIL("C.Test2(C)", @" { // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + .maxstack 2 IL_0000: ldarg.0 - IL_0001: brfalse.s IL_000b + IL_0001: brfalse.s IL_000e IL_0003: ldarg.0 IL_0004: callvirt ""object C.Value.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 + IL_0009: ldnull + IL_000a: cgt.un + IL_000c: br.s IL_000f + IL_000e: ldc.i4.0 IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 + IL_0010: ceq IL_0012: ret } "); @@ -7319,24 +7295,22 @@ static bool Test5(C t) verifier.VerifyIL("C.Test1(C?)", @" { // Code size 34 (0x22) - .maxstack 1 - .locals init (C V_0, - bool V_1) + .maxstack 2 + .locals init (C V_0) IL_0000: ldarga.s V_0 IL_0002: call ""bool C?.HasValue.get"" - IL_0007: brfalse.s IL_001a + IL_0007: brfalse.s IL_001d IL_0009: ldarga.s V_0 IL_000b: call ""C C?.GetValueOrDefault()"" IL_0010: stloc.0 IL_0011: ldloca.s V_0 IL_0013: call ""object C.Value.get"" - IL_0018: brtrue.s IL_001e - IL_001a: ldc.i4.1 - IL_001b: stloc.1 - IL_001c: br.s IL_0020 + IL_0018: ldnull + IL_0019: cgt.un + IL_001b: br.s IL_001e + IL_001d: ldc.i4.0 IL_001e: ldc.i4.0 - IL_001f: stloc.1 - IL_0020: ldloc.1 + IL_001f: ceq IL_0021: ret } "); @@ -7482,19 +7456,17 @@ static bool Test2(C t) verifier.VerifyIL("C.Test2(C)", @" { // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + .maxstack 2 IL_0000: ldarg.0 - IL_0001: brfalse.s IL_000b + IL_0001: brfalse.s IL_000e IL_0003: ldarg.0 IL_0004: callvirt ""object C.Value.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 + IL_0009: ldnull + IL_000a: cgt.un + IL_000c: br.s IL_000f + IL_000e: ldc.i4.0 IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 + IL_0010: ceq IL_0012: ret } "); @@ -25003,43 +24975,31 @@ static bool Test2(S1 u) verifier.VerifyIL("S1.Test1", @" { - // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + // Code size 16 (0x10) + .maxstack 2 IL_0000: ldarg.0 IL_0001: brfalse.s IL_000b IL_0003: ldarg.0 IL_0004: callvirt ""bool S1.HasValue.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ret + IL_0009: br.s IL_000c + IL_000b: ldc.i4.0 + IL_000c: ldc.i4.0 + IL_000d: ceq + IL_000f: ret } "); verifier.VerifyIL("S1.Test2", @" { - // Code size 22 (0x16) - .maxstack 2 - .locals init (bool V_0) + // Code size 12 (0xc) + .maxstack 1 IL_0000: ldarg.0 - IL_0001: brfalse.s IL_000b + IL_0001: brfalse.s IL_000a IL_0003: ldarg.0 IL_0004: callvirt ""bool S1.HasValue.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ldc.i4.0 - IL_0013: ceq - IL_0015: ret + IL_0009: ret + IL_000a: ldc.i4.0 + IL_000b: ret } "); } @@ -25085,42 +25045,34 @@ static bool Test2(S1 u) verifier.VerifyIL("S1.Test1", @" { // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + .maxstack 2 IL_0000: ldarg.0 - IL_0001: brfalse.s IL_000b + IL_0001: brfalse.s IL_000e IL_0003: ldarg.0 IL_0004: callvirt ""object S1.Value.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 + IL_0009: ldnull + IL_000a: cgt.un + IL_000c: br.s IL_000f + IL_000e: ldc.i4.0 IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 + IL_0010: ceq IL_0012: ret } "); verifier.VerifyIL("S1.Test2", @" { - // Code size 22 (0x16) + // Code size 15 (0xf) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 - IL_0001: brfalse.s IL_000b + IL_0001: brfalse.s IL_000d IL_0003: ldarg.0 IL_0004: callvirt ""object S1.Value.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ldc.i4.0 - IL_0013: ceq - IL_0015: ret + IL_0009: ldnull + IL_000a: cgt.un + IL_000c: ret + IL_000d: ldc.i4.0 + IL_000e: ret } "); } @@ -25361,21 +25313,17 @@ static bool Test1(C2 u) verifier.VerifyIL("Program.Test1", @" { - // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + // Code size 16 (0x10) + .maxstack 2 IL_0000: ldarg.0 IL_0001: brfalse.s IL_000b IL_0003: ldarg.0 IL_0004: callvirt ""bool C2.HasValue.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ret + IL_0009: br.s IL_000c + IL_000b: ldc.i4.0 + IL_000c: ldc.i4.0 + IL_000d: ceq + IL_000f: ret } "); } @@ -25457,61 +25405,49 @@ static bool Test3(C3 u) verifier.VerifyIL("Program.Test1", @" { - // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + // Code size 16 (0x10) + .maxstack 2 IL_0000: ldarg.0 IL_0001: brfalse.s IL_000b IL_0003: ldarg.0 IL_0004: callvirt ""bool C1.HasValue.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ret + IL_0009: br.s IL_000c + IL_000b: ldc.i4.0 + IL_000c: ldc.i4.0 + IL_000d: ceq + IL_000f: ret } "); verifier.VerifyIL("Program.Test2", @" { - // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + // Code size 16 (0x10) + .maxstack 2 IL_0000: ldarg.0 IL_0001: brfalse.s IL_000b IL_0003: ldarg.0 IL_0004: callvirt ""bool C1.HasValue.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ret + IL_0009: br.s IL_000c + IL_000b: ldc.i4.0 + IL_000c: ldc.i4.0 + IL_000d: ceq + IL_000f: ret } "); verifier.VerifyIL("Program.Test3", @" { - // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + // Code size 16 (0x10) + .maxstack 2 IL_0000: ldarg.0 IL_0001: brfalse.s IL_000b IL_0003: ldarg.0 IL_0004: callvirt ""bool C1.HasValue.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ret + IL_0009: br.s IL_000c + IL_000b: ldc.i4.0 + IL_000c: ldc.i4.0 + IL_000d: ceq + IL_000f: ret } "); } @@ -33947,43 +33883,31 @@ static bool Test2(S1 u) verifier.VerifyIL("S1.Test1", @" { - // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + // Code size 16 (0x10) + .maxstack 2 IL_0000: ldarg.0 IL_0001: brfalse.s IL_000b IL_0003: ldarg.0 IL_0004: callvirt ""bool S1.IUnionMembers.HasValue.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ret + IL_0009: br.s IL_000c + IL_000b: ldc.i4.0 + IL_000c: ldc.i4.0 + IL_000d: ceq + IL_000f: ret } "); verifier.VerifyIL("S1.Test2", @" { - // Code size 22 (0x16) - .maxstack 2 - .locals init (bool V_0) + // Code size 12 (0xc) + .maxstack 1 IL_0000: ldarg.0 - IL_0001: brfalse.s IL_000b + IL_0001: brfalse.s IL_000a IL_0003: ldarg.0 IL_0004: callvirt ""bool S1.IUnionMembers.HasValue.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ldc.i4.0 - IL_0013: ceq - IL_0015: ret + IL_0009: ret + IL_000a: ldc.i4.0 + IL_000b: ret } "); } @@ -34378,21 +34302,17 @@ static bool Test1(S1 u) var verifier = CompileAndVerify(comp, expectedOutput: "FalseTrueFalseFalse").VerifyDiagnostics(); verifier.VerifyIL("Program.Test1", @" { - // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + // Code size 16 (0x10) + .maxstack 2 IL_0000: ldarg.0 IL_0001: brfalse.s IL_000b IL_0003: ldarg.0 IL_0004: callvirt ""bool S1.HasValue.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ret + IL_0009: br.s IL_000c + IL_000b: ldc.i4.0 + IL_000c: ldc.i4.0 + IL_000d: ceq + IL_000f: ret } "); } @@ -34633,23 +34553,15 @@ static bool Test1(S1 u) var verifier = CompileAndVerify(comp, expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? "TrueFalseTrueTrue" : null, verify: Verification.FailsPEVerify).VerifyDiagnostics(); verifier.VerifyIL("Program.Test1", @" { - // Code size 22 (0x16) - .maxstack 2 - .locals init (bool V_0) + // Code size 12 (0xc) + .maxstack 1 IL_0000: ldarg.0 - IL_0001: brfalse.s IL_000b + IL_0001: brfalse.s IL_000a IL_0003: ldarg.0 IL_0004: callvirt ""bool S1.IUnionMembers.HasValue.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ldc.i4.0 - IL_0013: ceq - IL_0015: ret + IL_0009: ret + IL_000a: ldc.i4.0 + IL_000b: ret } "); } @@ -35444,21 +35356,17 @@ static bool Test1(S1 u) verifier.VerifyIL("Program.Test1", @" { - // Code size 19 (0x13) - .maxstack 1 - .locals init (bool V_0) + // Code size 16 (0x10) + .maxstack 2 IL_0000: ldarg.0 IL_0001: brfalse.s IL_000b IL_0003: ldarg.0 IL_0004: callvirt ""bool S1.IUnionMembersBase.HasValue.get"" - IL_0009: brtrue.s IL_000f - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: br.s IL_0011 - IL_000f: ldc.i4.0 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ret + IL_0009: br.s IL_000c + IL_000b: ldc.i4.0 + IL_000c: ldc.i4.0 + IL_000d: ceq + IL_000f: ret } "); } From 1525a03a69d3fa4712d4b49375bb4582a4e23e92 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 20 Aug 2026 15:59:46 +0200 Subject: [PATCH 4/6] Add a comment --- .../Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs index 188c19d8a0670..ddfa648b887a2 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs @@ -44,6 +44,7 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node maxTests: MaxTestsForInvertedLinearSequence) && !containsBindings(decisionDag)) { + // If we can build a short linear test sequence with swapped labels and no variable bindings, do so and negate the result. negated = !negated; result = LowerIsPatternAsLinearSequence(node, decisionDag, whenTrueLabel: node.WhenFalseLabel, whenFalseLabel: node.WhenTrueLabel); } From 4570a915bc2d02042ca1c585fb106664f3fad660 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Fri, 21 Aug 2026 13:17:44 +0200 Subject: [PATCH 5/6] Improve tests --- .../Test/Emit/CodeGen/CodeGenAsyncTests.cs | 56 ++++++-- .../CSharp/Test/Emit/CodeGen/PatternTests.cs | 128 +++++++++++++++++- 2 files changed, 167 insertions(+), 17 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs index 467d43b7ea68b..869943d7fd929 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs @@ -6006,15 +6006,14 @@ .locals init (int V_0, //tests """); } - [ConditionalFact(typeof(CoreClrOnly)), WorkItem("https://github.com/dotnet/roslyn/issues/80052")] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80052")] public void IsPatternExpressionInAsyncMethod_DoesNotAffectHoistedLocals() { var source = """ using System; - using System.Collections.Immutable; using System.Threading.Tasks; - await M(ImmutableArray.Create("", new('a', 10), new('a', 20), new('a', 30))); + await M(new StringCollection("", new('a', 10), new('a', 20), new('a', 30))); Console.WriteLine("done"); static void Print(bool value) @@ -6022,7 +6021,7 @@ static void Print(bool value) Console.WriteLine(value); } - static async Task M(ImmutableArray values) + static async Task M(StringCollection values) { await Task.Yield(); foreach (var value in values) @@ -6031,23 +6030,53 @@ static async Task M(ImmutableArray values) await Task.Delay(1).ConfigureAwait(false); } } + + // A mutable value-type enumerator that must be hoisted into the async state machine. + readonly struct StringCollection + { + private readonly string[] _values; + + public StringCollection(params string[] values) + { + _values = values; + } + + public Enumerator GetEnumerator() => new Enumerator(_values); + + public struct Enumerator + { + private readonly string[] _values; + private int _index; + + public Enumerator(string[] values) + { + _values = values; + _index = -1; + } + + public string Current => _values[_index]; + + public bool MoveNext() => ++_index < _values.Length; + } + } """; + var compilation = CreateCompilationWithTasksExtensions( + [source, IAsyncDisposableDefinition], + options: TestOptions.ReleaseExe); var verifier = CompileAndVerify( - source, + compilation, expectedOutput: """ False True True True done - """, - options: TestOptions.ReleaseExe, - targetFramework: TargetFramework.NetCoreApp); + """); verifier.VerifyDiagnostics(); } - [ConditionalFact(typeof(CoreClrOnly)), WorkItem("https://github.com/dotnet/roslyn/issues/72753"), WorkItem("https://github.com/dotnet/roslyn/issues/80052")] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72753"), WorkItem("https://github.com/dotnet/roslyn/issues/80052")] public void IsPatternExpressionInAwaitUsing() { var source = """ @@ -6090,11 +6119,12 @@ public CustomException(string message) } """; + var compilation = CreateCompilationWithTasksExtensions( + [source, IAsyncDisposableDefinition], + options: TestOptions.ReleaseExe); var verifier = CompileAndVerify( - source, - expectedOutput: "TrueDisposed", - options: TestOptions.ReleaseExe, - targetFramework: TargetFramework.NetCoreApp); + compilation, + expectedOutput: "TrueDisposed"); verifier.VerifyDiagnostics(); } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs index 9b37ccbd54343..c499116ddda5a 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs @@ -6234,10 +6234,130 @@ public static void Main() var verifier = CompileAndVerify(source, expectedOutput: "TrueFalse", options: TestOptions.ReleaseExe); verifier.VerifyDiagnostics(); - Assert.Equal(verifier.VisualizeIL("C.Comparison2"), verifier.VisualizeIL("C.Pattern2")); - Assert.Equal(verifier.VisualizeIL("C.Comparison3"), verifier.VisualizeIL("C.Pattern3")); - Assert.Contains("switch", verifier.VisualizeIL("C.Pattern4")); - Assert.Contains("switch", verifier.VisualizeIL("C.Pattern5")); + var expectedIL = """ + { + // Code size 16 (0x10) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.s 69 + IL_0003: beq.s IL_000e + IL_0005: ldarg.0 + IL_0006: ldc.i4.s 101 + IL_0008: ceq + IL_000a: ldc.i4.0 + IL_000b: ceq + IL_000d: ret + IL_000e: ldc.i4.0 + IL_000f: ret + } + """; + verifier.VerifyIL("C.Comparison2", expectedIL); + verifier.VerifyIL("C.Pattern2", expectedIL); + + expectedIL = """ + { + // Code size 21 (0x15) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.s 46 + IL_0003: beq.s IL_0013 + IL_0005: ldarg.0 + IL_0006: ldc.i4.s 69 + IL_0008: beq.s IL_0013 + IL_000a: ldarg.0 + IL_000b: ldc.i4.s 101 + IL_000d: ceq + IL_000f: ldc.i4.0 + IL_0010: ceq + IL_0012: ret + IL_0013: ldc.i4.0 + IL_0014: ret + } + """; + + verifier.VerifyIL("C.Comparison3", expectedIL); + verifier.VerifyIL("C.Pattern3", expectedIL); + + verifier.VerifyIL("C.Comparison4", """ + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: beq.s IL_0014 + IL_0004: ldarg.0 + IL_0005: ldc.i4.3 + IL_0006: beq.s IL_0014 + IL_0008: ldarg.0 + IL_0009: ldc.i4.4 + IL_000a: beq.s IL_0014 + IL_000c: ldarg.0 + IL_000d: ldc.i4.6 + IL_000e: ceq + IL_0010: ldc.i4.0 + IL_0011: ceq + IL_0013: ret + IL_0014: ldc.i4.0 + IL_0015: ret + } + """); + + verifier.VerifyIL("C.Pattern4", """ + { + // Code size 45 (0x2d) + .maxstack 2 + .locals init (bool V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: sub + IL_0003: switch ( + IL_0022, + IL_0026, + IL_0022, + IL_0022, + IL_0026, + IL_0022) + IL_0020: br.s IL_0026 + IL_0022: ldc.i4.1 + IL_0023: stloc.0 + IL_0024: br.s IL_0028 + IL_0026: ldc.i4.0 + IL_0027: stloc.0 + IL_0028: ldloc.0 + IL_0029: ldc.i4.0 + IL_002a: ceq + IL_002c: ret + } + """); + + verifier.VerifyIL("C.Pattern5", """ + { + // Code size 49 (0x31) + .maxstack 2 + .locals init (bool V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: sub + IL_0003: switch ( + IL_0026, + IL_002a, + IL_0026, + IL_0026, + IL_002a, + IL_0026, + IL_0026) + IL_0024: br.s IL_002a + IL_0026: ldc.i4.1 + IL_0027: stloc.0 + IL_0028: br.s IL_002c + IL_002a: ldc.i4.0 + IL_002b: stloc.0 + IL_002c: ldloc.0 + IL_002d: ldc.i4.0 + IL_002e: ceq + IL_0030: ret + } + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80052")] From 478bf1a1bc6cca9bdf9e6e1a6e4c5f00e494538e Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Mon, 24 Aug 2026 13:22:19 +0200 Subject: [PATCH 6/6] Verify runtime async --- .../Test/Emit/CodeGen/CodeGenAsyncTests.cs | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs index 869943d7fd929..ab4649ce4e2ae 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs @@ -6061,19 +6061,30 @@ public Enumerator(string[] values) } """; + var expectedOutput = """ + False + True + True + True + done + """; + var compilation = CreateCompilationWithTasksExtensions( [source, IAsyncDisposableDefinition], options: TestOptions.ReleaseExe); - var verifier = CompileAndVerify( + CompileAndVerify(compilation, expectedOutput: expectedOutput).VerifyDiagnostics(); + + compilation = CreateRuntimeAsyncCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify( compilation, - expectedOutput: """ - False - True - True - True - done - """); - verifier.VerifyDiagnostics(); + expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), + verify: Verification.Fails with + { + ILVerifyMessage = $""" + {ReturnValueMissing("
$", "0x4b")} + {ReturnValueMissing("<
$>g__M|0_1", "0x72")} + """, + }).VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72753"), WorkItem("https://github.com/dotnet/roslyn/issues/80052")] @@ -6119,13 +6130,25 @@ public CustomException(string message) } """; + var expectedOutput = "TrueDisposed"; + var compilation = CreateCompilationWithTasksExtensions( [source, IAsyncDisposableDefinition], options: TestOptions.ReleaseExe); - var verifier = CompileAndVerify( + CompileAndVerify(compilation, expectedOutput: expectedOutput).VerifyDiagnostics(); + + compilation = CreateRuntimeAsyncCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify( compilation, - expectedOutput: "TrueDisposed"); - verifier.VerifyDiagnostics(); + expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), + verify: Verification.Fails with + { + ILVerifyMessage = $$""" + {{ReturnValueMissing("
$", "0x71")}} + [<
$>g__GetResourceAsync|0_0]: Unexpected type on the stack. { Offset = 0x29, Found = ref 'Resource', Expected = ref '[System.Runtime]System.Threading.Tasks.Task`1' } + [<
$>g__GetExceptionAsync|0_1]: Unexpected type on the stack. { Offset = 0x2e, Found = ref 'CustomException', Expected = ref '[System.Runtime]System.Threading.Tasks.Task`1' } + """, + }).VerifyDiagnostics(); } [Fact]