From 040ab41c69329ce675bfbb01d58e1d6de432b6ab Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 17 Jul 2021 15:01:12 +0200 Subject: [PATCH 01/15] Initial implementation of the pattern matching transform. --- .../ICSharpCode.Decompiler.Tests.csproj | 1 + .../PrettyTestRunner.cs | 6 ++ .../TestCases/Pretty/OutVariables.cs | 2 +- .../TestCases/Pretty/PatternMatching.cs | 36 +++++++++ .../CSharp/CSharpDecompiler.cs | 1 + .../CSharp/ExpressionBuilder.cs | 5 ++ .../ICSharpCode.Decompiler.csproj | 1 + .../IL/Instructions/MatchInstruction.cs | 16 ++-- .../IL/Transforms/PatternMatchingTransform.cs | 73 +++++++++++++++++++ .../IL/Transforms/StatementTransform.cs | 2 + 10 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs create mode 100644 ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index e0334ffcb..81bed0532 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -107,6 +107,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 49d82222c..b300ae49a 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -304,6 +304,12 @@ namespace ICSharpCode.Decompiler.Tests RunForLibrary(cscOptions: cscOptions); } + [Test] + public void PatternMatching([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions); + } + [Test] public void InitializerTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs index cb884bf78..bdfc8cbfa 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs @@ -21,7 +21,7 @@ using System.Collections.Generic; namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { - public class PatternMatching + public class OutVariables { public static void OutVarInShortCircuit(Dictionary d) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs new file mode 100644 index 000000000..abf06dc61 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -0,0 +1,36 @@ +using System; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + public class PatternMatching + { + public bool SimpleTypePattern(object x) + { + Use(x is string y); + if (x is string z) + { + Console.WriteLine(z); + } + return x is string w; + } + + public bool SimpleTypePatternWithShortcircuit(object x) + { + Use(F() && x is string y && y.Contains("a")); + if (F() && x is string z && z.Contains("a")) + { + Console.WriteLine(z); + } + return F() && x is string w && w.Contains("a"); + } + + private bool F() + { + return true; + } + + private void Use(bool x) + { + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 676c924d4..b33e95dc2 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -136,6 +136,7 @@ namespace ICSharpCode.Decompiler.CSharp // Inlining must be first, because it doesn't trigger re-runs. // Any other transform that opens up new inlining opportunities should call RequestRerun(). new ExpressionTransforms(), + new PatternMatchingTransform(), new DynamicIsEventAssignmentTransform(), new TransformAssignment(), // inline and compound assignments new NullCoalescingTransform(), diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index c5ebf5bf7..61869dc1d 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4338,6 +4338,11 @@ namespace ICSharpCode.Decompiler.CSharp } } + protected internal override TranslatedExpression VisitMatchInstruction(MatchInstruction inst, TranslationContext context) + { + throw new NotImplementedException(); + } + protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context) { string message = "Error"; diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 085c7d444..a1c53a689 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -93,6 +93,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index e5833cd16..6948c79f3 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -84,7 +84,7 @@ namespace ICSharpCode.Decompiler.IL comp(deconstruct.result1(c) == 1), match.type[D].deconstruct(d = deconstruct.result2(c)) { comp(deconstruct.result1(d) == 2), - comp(deconstruct.result2(d) == 2), + comp(deconstruct.result2(d) == 3), } } */ @@ -126,10 +126,15 @@ namespace ICSharpCode.Decompiler.IL testedOperand = m.testedOperand; return true; case Comp comp: - testedOperand = comp.Left; - return IsConstant(comp.Right); - case ILInstruction logicNot when logicNot.MatchLogicNot(out var operand): - return IsPatternMatch(operand, out testedOperand); + if (comp.MatchLogicNot(out var operand)) + { + return IsPatternMatch(operand, out testedOperand); + } + else + { + testedOperand = comp.Left; + return IsConstant(comp.Right); + } default: testedOperand = null; return false; @@ -145,6 +150,7 @@ namespace ICSharpCode.Decompiler.IL OpCode.LdcI4 => true, OpCode.LdcI8 => true, OpCode.LdNull => true, + OpCode.LdStr => true, _ => false }; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs new file mode 100644 index 000000000..ad9407c1f --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -0,0 +1,73 @@ +// Copyright (c) 2021 Daniel Grunwald, Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + + class PatternMatchingTransform : IStatementTransform + { + /// + /// stloc V(isinst T(testedOperand)) + /// call Use(..., comp.o(ldloc V != ldnull)) + /// => + /// call Use(..., match.type[T](V = testedOperand)) + /// + void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) + { + if (pos + 1 >= block.Instructions.Count) + return; + if (block.Instructions[pos] is not StLoc + { + Variable: var v, + Value: IsInst { Argument: var testedOperand, Type: var type } + }) + { + return; + } + if (!v.IsSingleDefinition) + return; + if (v.Kind is not (VariableKind.Local or VariableKind.StackSlot)) + return; + if (!v.Type.Equals(type)) + return; + + var result = ILInlining.FindLoadInNext(block.Instructions[pos + 1], v, testedOperand, InliningOptions.None); + if (result.Type != ILInlining.FindResultType.Found) + return; + if (result.LoadInst is not LdLoc) + return; + if (!result.LoadInst.Parent!.MatchCompNotEqualsNull(out _)) + return; + + context.Step($"Type pattern matching {v.Name}", block.Instructions[pos]); + // call Use(..., match.type[T](V = testedOperand)) + + var target = result.LoadInst.Parent; + var matchInstruction = new MatchInstruction(v, testedOperand) { + CheckNotNull = true, + CheckType = true + }; + target.ReplaceWith(matchInstruction.WithILRange(target)); + block.Instructions.RemoveAt(pos); + v.Kind = VariableKind.PatternLocal; + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs index d19643ca6..51911595d 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Diagnostics; From c641072685271a428ce837993ed700c1da264789 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 18 Jul 2021 20:57:33 +0200 Subject: [PATCH 02/15] Translate MatchInstruction to BinaryOperatorExpression with BinaryOperatorType.IsPattern. --- .../TestCases/Pretty/PatternMatching.cs | 36 ++++++ .../CSharp/CSharpDecompiler.cs | 1 + .../CSharp/ExpressionBuilder.cs | 39 +++++- .../OutputVisitor/CSharpOutputVisitor.cs | 13 +- .../OutputVisitor/InsertParenthesesVisitor.cs | 2 + .../Expressions/BinaryOperatorExpression.cs | 10 +- .../CSharp/Transforms/DeclareVariables.cs | 1 + ICSharpCode.Decompiler/DecompilerSettings.cs | 18 +++ .../ICSharpCode.Decompiler.csproj | 1 + .../IL/Transforms/PatternMatchingTransform.cs | 25 +++- .../RemoveInfeasiblePathTransform.cs | 116 ++++++++++++++++++ 11 files changed, 253 insertions(+), 9 deletions(-) create mode 100644 ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index abf06dc61..44e05776b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -24,6 +24,42 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return F() && x is string w && w.Contains("a"); } + public void SimpleTypePatternWithShortcircuitAnd(object x) + { + if (x is string z && z.Contains("a")) + { + Console.WriteLine(z); + } + else + { + Console.WriteLine(); + } + } + + public void SimpleTypePatternWithShortcircuitOr(object x) + { + if (!(x is string z) || z.Contains("a")) + { + Console.WriteLine(); + } + else + { + Console.WriteLine(z); + } + } + + public void SimpleTypePatternWithShortcircuitOr2(object x) + { + if (F() || !(x is string z)) + { + Console.WriteLine(); + } + else + { + Console.WriteLine(z); + } + } + private bool F() { return true; diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index b33e95dc2..2480f76a0 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -89,6 +89,7 @@ namespace ICSharpCode.Decompiler.CSharp new SplitVariables(), new ILInlining(), new InlineReturnTransform(), // must run before DetectPinnedRegions + new RemoveInfeasiblePathTransform(), new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms new YieldReturnDecompiler(), // must run after inlining but before loop detection new AsyncAwaitDecompiler(), // must run after inlining but before loop detection diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 61869dc1d..2fbdc8bf0 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4340,7 +4340,44 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitMatchInstruction(MatchInstruction inst, TranslationContext context) { - throw new NotImplementedException(); + var left = Translate(inst.TestedOperand); + var right = TranslatePattern(inst); + + return new BinaryOperatorExpression(left, BinaryOperatorType.IsPattern, right) + .WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.Boolean))) + .WithILInstruction(inst); + } + + ExpressionWithILInstruction TranslatePattern(ILInstruction pattern) + { + switch (pattern) + { + case MatchInstruction matchInstruction: + if (!matchInstruction.CheckType) + throw new NotImplementedException(); + if (matchInstruction.IsDeconstructCall) + throw new NotImplementedException(); + if (matchInstruction.IsDeconstructTuple) + throw new NotImplementedException(); + if (matchInstruction.SubPatterns.Any()) + throw new NotImplementedException(); + if (matchInstruction.HasDesignator) + { + SingleVariableDesignation designator = new SingleVariableDesignation { Identifier = matchInstruction.Variable.Name }; + designator.AddAnnotation(new ILVariableResolveResult(matchInstruction.Variable)); + return new DeclarationExpression { + Type = ConvertType(matchInstruction.Variable.Type), + Designation = designator + }.WithILInstruction(matchInstruction); + } + else + { + return new TypeReferenceExpression(ConvertType(matchInstruction.Variable.Type)) + .WithILInstruction(matchInstruction); + } + default: + throw new NotImplementedException(); + } } protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context) diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 987677f7a..c7766b1e7 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -822,6 +822,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor spacePolicy = policy.SpaceAroundShiftOperator; break; case BinaryOperatorType.NullCoalescing: + case BinaryOperatorType.IsPattern: spacePolicy = true; break; case BinaryOperatorType.Range: @@ -831,7 +832,15 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor throw new NotSupportedException("Invalid value for BinaryOperatorType"); } Space(spacePolicy); - WriteToken(BinaryOperatorExpression.GetOperatorRole(binaryOperatorExpression.Operator)); + TokenRole tokenRole = BinaryOperatorExpression.GetOperatorRole(binaryOperatorExpression.Operator); + if (tokenRole == BinaryOperatorExpression.IsKeywordRole) + { + WriteKeyword(tokenRole); + } + else + { + WriteToken(tokenRole); + } Space(spacePolicy); binaryOperatorExpression.Right.AcceptVisitor(this); EndNode(binaryOperatorExpression); @@ -2788,7 +2797,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor public virtual void VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation) { StartNode(singleVariableDesignation); - writer.WriteIdentifier(singleVariableDesignation.IdentifierToken); + WriteIdentifier(singleVariableDesignation.IdentifierToken); EndNode(singleVariableDesignation); } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs index 4b706e127..e279cf021 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs @@ -143,6 +143,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor return PrecedenceLevel.ConditionalOr; case BinaryOperatorType.NullCoalescing: return PrecedenceLevel.NullCoalescing; + case BinaryOperatorType.IsPattern: + return PrecedenceLevel.RelationalAndTypeTesting; default: throw new NotSupportedException("Invalid value for BinaryOperatorType"); } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs index 579d92e53..f64752e06 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs @@ -25,7 +25,6 @@ // THE SOFTWARE. using System; -using System.Collections.Generic; using System.Linq.Expressions; namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -55,6 +54,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public readonly static TokenRole ShiftRightRole = new TokenRole(">>"); public readonly static TokenRole NullCoalescingRole = new TokenRole("??"); public readonly static TokenRole RangeRole = new TokenRole(".."); + public readonly static TokenRole IsKeywordRole = IsExpression.IsKeywordRole; public readonly static Role LeftRole = new Role("Left", Expression.Null); public readonly static Role RightRole = new Role("Right", Expression.Null); @@ -155,6 +155,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return NullCoalescingRole; case BinaryOperatorType.Range: return RangeRole; + case BinaryOperatorType.IsPattern: + return IsKeywordRole; default: throw new NotSupportedException("Invalid value for BinaryOperatorType"); } @@ -265,6 +267,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax NullCoalescing, /// left .. right /// left and right are optional = may be Expression.Null - Range + Range, + + /// left is right + /// right must be a pattern + IsPattern, } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index 5964caf42..510821104 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -355,6 +355,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms case VariableKind.ExceptionStackSlot: case VariableKind.UsingLocal: case VariableKind.ForeachLocal: + case VariableKind.PatternLocal: return false; default: return true; diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index b7c467223..182b280d4 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -1446,6 +1446,24 @@ namespace ICSharpCode.Decompiler } } + bool patternMatching = true; + + /// + /// Gets/Sets whether C# 7.0 pattern matching should be detected. + /// + [Category("C# 7.0 / VS 2017")] + [Description("DecompilerSettings.PatternMatching")] + public bool PatternMatching { + get { return patternMatching; } + set { + if (patternMatching != value) + { + patternMatching = value; + OnPropertyChanged(); + } + } + } + bool staticLocalFunctions = true; /// diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index a1c53a689..962f6795a 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -94,6 +94,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index ad9407c1f..497439a37 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -18,7 +18,6 @@ #nullable enable - namespace ICSharpCode.Decompiler.IL.Transforms { @@ -32,6 +31,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { + if (!context.Settings.PatternMatching) + return; if (pos + 1 >= block.Instructions.Count) return; if (block.Instructions[pos] is not StLoc @@ -54,17 +55,33 @@ namespace ICSharpCode.Decompiler.IL.Transforms return; if (result.LoadInst is not LdLoc) return; - if (!result.LoadInst.Parent!.MatchCompNotEqualsNull(out _)) + bool invertCondition; + if (result.LoadInst.Parent!.MatchCompNotEqualsNull(out _)) + { + invertCondition = false; + + } + else if (result.LoadInst.Parent!.MatchCompEqualsNull(out _)) + { + invertCondition = true; + } + else + { return; + } context.Step($"Type pattern matching {v.Name}", block.Instructions[pos]); - // call Use(..., match.type[T](V = testedOperand)) + // call Use(..., match.type[T](V = testedOperand)) var target = result.LoadInst.Parent; - var matchInstruction = new MatchInstruction(v, testedOperand) { + ILInstruction matchInstruction = new MatchInstruction(v, testedOperand) { CheckNotNull = true, CheckType = true }; + if (invertCondition) + { + matchInstruction = Comp.LogicNot(matchInstruction); + } target.ReplaceWith(matchInstruction.WithILRange(target)); block.Instructions.RemoveAt(pos); v.Kind = VariableKind.PatternLocal; diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs new file mode 100644 index 000000000..7cfa2abd4 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs @@ -0,0 +1,116 @@ +// Copyright (c) 2021 Daniel Grunwald, Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + /// + /// Block IL_0018 (incoming: *) { + /// stloc s(ldc.i4 1) + /// br IL_0019 + /// } + /// + /// Block IL_0019 (incoming: > 1) { + /// if (logic.not(ldloc s)) br IL_0027 + /// br IL_001d + /// } + /// + /// replace br IL_0019 with br IL_0027 + /// + class RemoveInfeasiblePathTransform : IILTransform + { + void IILTransform.Run(ILFunction function, ILTransformContext context) + { + foreach (var container in function.Descendants.OfType()) + { + bool changed = false; + foreach (var block in container.Blocks) + { + changed |= DoTransform(block, context); + } + + if (changed) + { + container.SortBlocks(deleteUnreachableBlocks: true); + } + } + } + + + private bool DoTransform(Block block, ILTransformContext context) + { + if (!MatchBlock1(block, out var s, out int value, out var br)) + return false; + if (!MatchBlock2(br.TargetBlock, s, value, out var targetBlock)) + return false; + context.Step("RemoveInfeasiblePath", br); + br.TargetBlock = targetBlock; + return true; + } + + // Block IL_0018 (incoming: *) { + // stloc s(ldc.i4 1) + // br IL_0019 + // } + private bool MatchBlock1(Block block, [NotNullWhen(true)] out ILVariable? variable, out int constantValue, [NotNullWhen(true)] out Branch? branch) + { + variable = null; + constantValue = 0; + branch = null; + if (block.Instructions.Count != 2) + return false; + if (block.Instructions[0] is not StLoc + { + Variable: { Kind: VariableKind.StackSlot } s, + Value: LdcI4 { Value: 0 or 1 } valueInst + }) + { + return false; + } + if (block.Instructions[1] is not Branch br) + return false; + variable = s; + constantValue = valueInst.Value; + branch = br; + return true; + } + + // Block IL_0019 (incoming: > 1) { + // if (logic.not(ldloc s)) br IL_0027 + // br IL_001d + // } + bool MatchBlock2(Block block, ILVariable s, int constantValue, [NotNullWhen(true)] out Block? targetBlock) + { + targetBlock = null; + if (block.Instructions.Count != 2) + return false; + if (block.IncomingEdgeCount <= 1) + return false; + if (!block.MatchIfAtEndOfBlock(out var load, out var trueInst, out var falseInst)) + return false; + if (!load.MatchLdLoc(s)) + return false; + ILInstruction target = constantValue != 0 ? trueInst : falseInst; + return target.MatchBranch(out targetBlock); + } + } +} From c26d9ad6f1d9d6d2844b35be48379b9a6bd40ad9 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 18 Jul 2021 23:34:29 +0200 Subject: [PATCH 03/15] Mark stack slot for aggressive removal in InfeasiblePathTransform This helps with pattern matching in short circuiting operators. --- .../CSharp/CSharpDecompiler.cs | 2 +- ICSharpCode.Decompiler/IL/ILVariable.cs | 5 ++ .../IL/Transforms/RemoveDeadVariableInit.cs | 55 ++++++++++--------- .../RemoveInfeasiblePathTransform.cs | 1 + .../IL/Transforms/SplitVariables.cs | 4 +- 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 2480f76a0..200811b20 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -97,10 +97,10 @@ namespace ICSharpCode.Decompiler.CSharp new DetectExitPoints(), new LdLocaDupInitObjTransform(), new EarlyExpressionTransforms(), + new SplitVariables(), // split variables once again, because the stobj(ldloca V, ...) may open up new replacements // RemoveDeadVariableInit must run after EarlyExpressionTransforms so that stobj(ldloca V, ...) // is already collapsed into stloc(V, ...). new RemoveDeadVariableInit(), - new SplitVariables(), // split variables once again, because the stobj(ldloca V, ...) may open up new replacements new ControlFlowSimplification(), //split variables may enable new branch to leave inlining new DynamicCallSiteTransform(), new SwitchDetection(), diff --git a/ICSharpCode.Decompiler/IL/ILVariable.cs b/ICSharpCode.Decompiler/IL/ILVariable.cs index 07dee70ef..5f407b886 100644 --- a/ICSharpCode.Decompiler/IL/ILVariable.cs +++ b/ICSharpCode.Decompiler/IL/ILVariable.cs @@ -372,6 +372,11 @@ namespace ICSharpCode.Decompiler.IL /// public IField? StateMachineField; + /// + /// If enabled, remove dead stores to this variable as if the "Remove dead code" option is enabled. + /// + internal bool RemoveIfRedundant; + public ILVariable(VariableKind kind, IType type, int? index = null) { if (type == null) diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs index 6a95e4c3d..284914cfa 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs @@ -42,41 +42,44 @@ namespace ICSharpCode.Decompiler.IL.Transforms // This is necessary to remove useless stores generated by some compilers, e.g., the F# compiler. // In yield return + async, the C# compiler tends to store null/default(T) to variables // when the variable goes out of scope. - if (function.IsAsync || function.IsIterator || context.Settings.RemoveDeadStores) + bool removeDeadStores = function.IsAsync || function.IsIterator || context.Settings.RemoveDeadStores; + + var variableQueue = new Queue(function.Variables); + while (variableQueue.Count > 0) { - var variableQueue = new Queue(function.Variables); - while (variableQueue.Count > 0) + var v = variableQueue.Dequeue(); + if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot) + continue; + if (!(v.RemoveIfRedundant || removeDeadStores)) + continue; + // Skip variables that are captured in a mcs yield state-machine + // loads of these will only be visible after DelegateConstruction step. + if (function.StateMachineCompiledWithMono && v.StateMachineField != null) + continue; + if (v.LoadCount != 0 || v.AddressCount != 0) + continue; + foreach (var stloc in v.StoreInstructions.OfType().ToArray()) { - var v = variableQueue.Dequeue(); - if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot) - continue; - // Skip variables that are captured in a mcs yield state-machine - // loads of these will only be visible after DelegateConstruction step. - if (function.StateMachineCompiledWithMono && v.StateMachineField != null) - continue; - if (v.LoadCount != 0 || v.AddressCount != 0) - continue; - foreach (var stloc in v.StoreInstructions.OfType().ToArray()) + if (stloc.Parent is Block block) { - if (stloc.Parent is Block block) + context.Step($"Dead store to {v.Name}", stloc); + if (SemanticHelper.IsPure(stloc.Value.Flags)) + { + block.Instructions.Remove(stloc); + } + else { - if (SemanticHelper.IsPure(stloc.Value.Flags)) - { - block.Instructions.Remove(stloc); - } - else - { - stloc.ReplaceWith(stloc.Value); - } - if (stloc.Value is LdLoc ldloc) - { - variableQueue.Enqueue(ldloc.Variable); - } + stloc.ReplaceWith(stloc.Value); + } + if (stloc.Value is LdLoc ldloc) + { + variableQueue.Enqueue(ldloc.Variable); } } } } + // Try to infer IType of stack slots that are of StackType.Ref: foreach (var v in function.Variables) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs index 7cfa2abd4..07d74eca9 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs @@ -64,6 +64,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; context.Step("RemoveInfeasiblePath", br); br.TargetBlock = targetBlock; + s.RemoveIfRedundant = true; return true; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs index 621450a17..9ef159530 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs @@ -64,7 +64,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms case VariableKind.StackSlot: // stack slots: are already split by construction, // except for the locals-turned-stackslots in async functions - if (v.Function.IsAsync) + // or stack slots handled by the infeasible path transform + if (v.Function.IsAsync || v.RemoveIfRedundant) goto case VariableKind.Local; else return false; @@ -270,6 +271,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms v.HasGeneratedName = inst.Variable.HasGeneratedName; v.StateMachineField = inst.Variable.StateMachineField; v.HasInitialValue = false; // we'll set HasInitialValue when we encounter an uninit load + v.RemoveIfRedundant = inst.Variable.RemoveIfRedundant; newVariables.Add(representative, v); inst.Variable.Function.Variables.Add(v); } From 357d55d2add8be53d12a9c2c93c7c9120ad735ee Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 25 Jul 2021 10:12:10 +0200 Subject: [PATCH 04/15] Add support for old roslyn pattern matching pattern. --- .../IL/Transforms/PatternMatchingTransform.cs | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 497439a37..2f1adc343 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -28,29 +28,55 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// call Use(..., comp.o(ldloc V != ldnull)) /// => /// call Use(..., match.type[T](V = testedOperand)) + /// + /// - or - + /// + /// stloc S(isinst T(testedOperand)) + /// stloc V(ldloc S) + /// call Use(..., comp.o(ldloc S != ldnull)) + /// => + /// call Use(..., match.type[T](V = testedOperand)) /// void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { if (!context.Settings.PatternMatching) return; - if (pos + 1 >= block.Instructions.Count) - return; + int startPos = pos; if (block.Instructions[pos] is not StLoc { - Variable: var v, + Variable: var s, Value: IsInst { Argument: var testedOperand, Type: var type } }) { return; } - if (!v.IsSingleDefinition) + if (!s.IsSingleDefinition) return; - if (v.Kind is not (VariableKind.Local or VariableKind.StackSlot)) + if (s.Kind is not (VariableKind.Local or VariableKind.StackSlot)) return; + pos++; + ILVariable v; + if (block.Instructions.ElementAtOrDefault(pos) is StLoc stloc && stloc.Value.MatchLdLoc(s)) + { + v = stloc.Variable; + pos++; + if (!v.IsSingleDefinition) + return; + if (v.Kind is not (VariableKind.Local or VariableKind.StackSlot)) + return; + if (s.LoadCount != 2) + return; + } + else + { + v = s; + } + if (!v.Type.Equals(type)) return; - - var result = ILInlining.FindLoadInNext(block.Instructions[pos + 1], v, testedOperand, InliningOptions.None); + if (pos >= block.Instructions.Count) + return; + var result = ILInlining.FindLoadInNext(block.Instructions[pos], s, testedOperand, InliningOptions.None); if (result.Type != ILInlining.FindResultType.Found) return; if (result.LoadInst is not LdLoc) @@ -83,7 +109,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms matchInstruction = Comp.LogicNot(matchInstruction); } target.ReplaceWith(matchInstruction.WithILRange(target)); - block.Instructions.RemoveAt(pos); + block.Instructions.RemoveRange(startPos, pos - startPos); v.Kind = VariableKind.PatternLocal; } } From 83727ea4b06df2006a210fdc0a8e53168c3d8daa Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 25 Jul 2021 13:58:23 +0200 Subject: [PATCH 05/15] Add support for value type patterns --- .../TestCases/Pretty/PatternMatching.cs | 144 +++++++++++++ .../CSharp/CSharpDecompiler.cs | 3 +- .../ICSharpCode.Decompiler.csproj | 1 + .../IL/Transforms/ILInlining.cs | 10 +- .../PatternMatchingRefTypesTransform.cs | 115 ++++++++++ .../IL/Transforms/PatternMatchingTransform.cs | 200 ++++++++++++------ 6 files changed, 403 insertions(+), 70 deletions(-) create mode 100644 ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 44e05776b..5fdc53317 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -60,11 +60,155 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + public void SimpleTypePatternValueTypesCondition(object x) + { + if (x is int i) + { + Console.WriteLine("Integer: " + i); + } + else + { + Console.WriteLine("else"); + } + } + + public void SimpleTypePatternValueTypesCondition2() + { + if (GetObject() is int i) + { + Console.WriteLine("Integer: " + i); + } + else + { + Console.WriteLine("else"); + } + } + + public void SimpleTypePatternValueTypesWithShortcircuitAnd(object x) + { + if (x is int i && i.GetHashCode() > 0) + { + Console.WriteLine("Positive integer: " + i); + } + else + { + Console.WriteLine("else"); + } + } + + public void SimpleTypePatternValueTypesWithShortcircuitOr(object x) + { + if (!(x is int z) || z.GetHashCode() > 0) + { + Console.WriteLine(); + } + else + { + Console.WriteLine(z); + } + } + + public void SimpleTypePatternValueTypesWithShortcircuitOr2(object x) + { + if (F() || !(x is int z)) + { + Console.WriteLine(); + } + else + { + Console.WriteLine(z); + } + } + +#if CS71 + public void SimpleTypePatternGenerics(object x) + { + if (x is T t) + { + Console.WriteLine(typeof(T).FullName + ": " + t); + } + else + { + Console.WriteLine("not a " + typeof(T).FullName); + } + } + + public void SimpleTypePatternGenericRefType(object x) where T : class + { + if (x is T t) + { + Console.WriteLine(typeof(T).FullName + ": " + t); + } + else + { + Console.WriteLine("not a " + typeof(T).FullName); + } + } + + public void SimpleTypePatternGenericValType(object x) where T : struct + { + if (x is T t) + { + Console.WriteLine(typeof(T).FullName + ": " + t); + } + else + { + Console.WriteLine("not a " + typeof(T).FullName); + } + } +#endif + + public void SimpleTypePatternValueTypesWithShortcircuitAndMultiUse(object x) + { + if (x is int i && i.GetHashCode() > 0 && i % 2 == 0) + { + Console.WriteLine("Positive integer: " + i); + } + else + { + Console.WriteLine("else"); + } + } + + public void SimpleTypePatternValueTypesWithShortcircuitAndMultiUse2(object x) + { + if ((x is int i && i.GetHashCode() > 0 && i % 2 == 0) || F()) + { + Console.WriteLine("true"); + } + else + { + Console.WriteLine("else"); + } + } + + public void SimpleTypePatternValueTypesWithShortcircuitAndMultiUse3(object x) + { + if (F() || (x is int i && i.GetHashCode() > 0 && i % 2 == 0)) + { + Console.WriteLine("true"); + } + else + { + Console.WriteLine("else"); + } + } + + public void SimpleTypePatternValueTypes() + { + Use(F() && GetObject() is int y && y.GetHashCode() > 0 && y % 2 == 0); + } + private bool F() { return true; } + private object GetObject() + { + throw new NotImplementedException(); + } + private void Use(bool x) { } diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 200811b20..cfa783999 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -103,6 +103,7 @@ namespace ICSharpCode.Decompiler.CSharp new RemoveDeadVariableInit(), new ControlFlowSimplification(), //split variables may enable new branch to leave inlining new DynamicCallSiteTransform(), + new PatternMatchingTransform(), new SwitchDetection(), new SwitchOnStringTransform(), new SwitchOnNullableTransform(), @@ -137,7 +138,7 @@ namespace ICSharpCode.Decompiler.CSharp // Inlining must be first, because it doesn't trigger re-runs. // Any other transform that opens up new inlining opportunities should call RequestRerun(). new ExpressionTransforms(), - new PatternMatchingTransform(), + new PatternMatchingRefTypesTransform(), new DynamicIsEventAssignmentTransform(), new TransformAssignment(), // inline and compound assignments new NullCoalescingTransform(), diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 962f6795a..638020a9a 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -93,6 +93,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index c06741edb..0db949e94 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -589,8 +589,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms //case OpCode.BinaryNumericInstruction when parent.SlotInfo == SwitchInstruction.ValueSlot: case OpCode.StringToInt when parent.SlotInfo == SwitchInstruction.ValueSlot: return true; - case OpCode.MatchInstruction when ((MatchInstruction)parent).IsDeconstructTuple: - return true; + case OpCode.MatchInstruction: + var match = (MatchInstruction)parent; + if (match.IsDeconstructTuple + || (match.CheckType && match.Variable.Type.IsReferenceType != true)) + { + return true; + } + break; } // decide based on the top-level target instruction into which we are inlining: switch (next.OpCode) diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs new file mode 100644 index 000000000..f3f77108f --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs @@ -0,0 +1,115 @@ +// Copyright (c) 2021 Daniel Grunwald, Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + class PatternMatchingRefTypesTransform : IStatementTransform + { + /// + /// stloc V(isinst T(testedOperand)) + /// call Use(..., comp.o(ldloc V != ldnull)) + /// => + /// call Use(..., match.type[T].notnull(V = testedOperand)) + /// + /// - or - + /// + /// stloc S(isinst T(testedOperand)) + /// stloc V(ldloc S) + /// call Use(..., comp.o(ldloc S != ldnull)) + /// => + /// call Use(..., match.type[T].notnull(V = testedOperand)) + /// + void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) + { + if (!context.Settings.PatternMatching) + return; + int startPos = pos; + if (block.Instructions[pos] is not StLoc + { + Variable: var s, + Value: IsInst { Argument: var testedOperand, Type: var type } + }) + { + return; + } + if (!s.IsSingleDefinition) + return; + if (s.Kind is not (VariableKind.Local or VariableKind.StackSlot)) + return; + pos++; + ILVariable v; + if (block.Instructions.ElementAtOrDefault(pos) is StLoc stloc && stloc.Value.MatchLdLoc(s)) + { + v = stloc.Variable; + pos++; + if (!v.IsSingleDefinition) + return; + if (v.Kind is not (VariableKind.Local or VariableKind.StackSlot)) + return; + if (s.LoadCount != 2) + return; + } + else + { + v = s; + } + + if (!v.Type.Equals(type)) + return; + if (pos >= block.Instructions.Count) + return; + var result = ILInlining.FindLoadInNext(block.Instructions[pos], s, testedOperand, InliningOptions.None); + if (result.Type != ILInlining.FindResultType.Found) + return; + if (result.LoadInst is not LdLoc) + return; + bool invertCondition; + if (result.LoadInst.Parent!.MatchCompNotEqualsNull(out _)) + { + invertCondition = false; + + } + else if (result.LoadInst.Parent!.MatchCompEqualsNull(out _)) + { + invertCondition = true; + } + else + { + return; + } + + context.Step($"Type pattern matching {v.Name}", block.Instructions[pos]); + // call Use(..., match.type[T](V = testedOperand)) + + var target = result.LoadInst.Parent; + ILInstruction matchInstruction = new MatchInstruction(v, testedOperand) { + CheckNotNull = true, + CheckType = true + }; + if (invertCondition) + { + matchInstruction = Comp.LogicNot(matchInstruction); + } + target.ReplaceWith(matchInstruction.WithILRange(target)); + block.Instructions.RemoveRange(startPos, pos - startPos); + v.Kind = VariableKind.PatternLocal; + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 2f1adc343..cf8234c8f 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -18,99 +18,165 @@ #nullable enable +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; + namespace ICSharpCode.Decompiler.IL.Transforms { - - class PatternMatchingTransform : IStatementTransform + class PatternMatchingTransform : IILTransform { - /// - /// stloc V(isinst T(testedOperand)) - /// call Use(..., comp.o(ldloc V != ldnull)) + + /// Block { + /// ... + /// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock + /// br unboxBlock + /// } + /// + /// Block unboxBlock (incoming: 1) { + /// stloc V(unbox.any T(ldloc testedOperand)) + /// if (nextCondition) br trueBlock + /// br falseBlock + /// } /// => - /// call Use(..., match.type[T](V = testedOperand)) + /// Block { + /// ... + /// if (logic.and(match.type[T].notnull(V = testedOperand), nextCondition)) br trueBlock + /// br falseBlock + /// } /// - /// - or - + /// -or- + /// Block { + /// ... + /// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock + /// br unboxBlock + /// } /// - /// stloc S(isinst T(testedOperand)) - /// stloc V(ldloc S) - /// call Use(..., comp.o(ldloc S != ldnull)) + /// Block unboxBlock (incoming: 1) { + /// stloc V(unbox.any T(ldloc testedOperand)) + /// ... + /// } /// => - /// call Use(..., match.type[T](V = testedOperand)) - /// - void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) + /// Block { + /// ... + /// if (match.type[T].notnull(V = testedOperand)) br unboxBlock + /// br falseBlock + /// } + void IILTransform.Run(ILFunction function, ILTransformContext context) { if (!context.Settings.PatternMatching) return; - int startPos = pos; - if (block.Instructions[pos] is not StLoc + foreach (var container in function.Descendants.OfType()) + { + foreach (var block in container.Blocks) { - Variable: var s, - Value: IsInst { Argument: var testedOperand, Type: var type } - }) + if (!MatchIsInstBlock(block, out var type, out var testedOperand, + out var unboxBlock, out var falseBlock)) + { + continue; + } + if (!MatchUnboxBlock(unboxBlock, type, testedOperand.Variable, falseBlock, + out var v, out var nextCondition, out var trueBlock, out var inverseNextCondition)) + { + continue; + } + context.Step($"PatternMatching with {v.Name}", block); + if (inverseNextCondition) + { + nextCondition = Comp.LogicNot(nextCondition); + } + var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!; + ILInstruction logicAnd = IfInstruction.LogicAnd(new MatchInstruction(v, testedOperand) { + CheckNotNull = true, + CheckType = true + }, nextCondition); + ifInst.Condition = logicAnd; + ((Branch)ifInst.TrueInst).TargetBlock = trueBlock; + ((Branch)block.Instructions.Last()).TargetBlock = falseBlock; + unboxBlock.Instructions.Clear(); + v.Kind = VariableKind.PatternLocal; + } + container.Blocks.RemoveAll(b => b.Instructions.Count == 0); + } + } + + /// ... + /// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock + /// br unboxBlock + private bool MatchIsInstBlock(Block block, + [NotNullWhen(true)] out IType? type, + [NotNullWhen(true)] out LdLoc? testedOperand, + [NotNullWhen(true)] out Block? unboxBlock, + [NotNullWhen(true)] out Block? falseBlock) + { + type = null; + testedOperand = null; + unboxBlock = null; + falseBlock = null; + if (!block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst)) + return false; + if (condition.MatchCompEqualsNull(out var arg)) { - return; + ExtensionMethods.Swap(ref trueInst, ref falseInst); } - if (!s.IsSingleDefinition) - return; - if (s.Kind is not (VariableKind.Local or VariableKind.StackSlot)) - return; - pos++; - ILVariable v; - if (block.Instructions.ElementAtOrDefault(pos) is StLoc stloc && stloc.Value.MatchLdLoc(s)) + else if (condition.MatchCompNotEqualsNull(out arg)) { - v = stloc.Variable; - pos++; - if (!v.IsSingleDefinition) - return; - if (v.Kind is not (VariableKind.Local or VariableKind.StackSlot)) - return; - if (s.LoadCount != 2) - return; + // do nothing } else { - v = s; + return false; } + if (!arg.MatchIsInst(out arg, out type)) + return false; + testedOperand = arg as LdLoc; + if (testedOperand == null) + return false; + return trueInst.MatchBranch(out unboxBlock) && falseInst.MatchBranch(out falseBlock) + && unboxBlock.Parent == block.Parent && falseBlock.Parent == block.Parent; + } - if (!v.Type.Equals(type)) - return; - if (pos >= block.Instructions.Count) - return; - var result = ILInlining.FindLoadInNext(block.Instructions[pos], s, testedOperand, InliningOptions.None); - if (result.Type != ILInlining.FindResultType.Found) - return; - if (result.LoadInst is not LdLoc) - return; - bool invertCondition; - if (result.LoadInst.Parent!.MatchCompNotEqualsNull(out _)) - { - invertCondition = false; + /// Block unboxBlock (incoming: 1) { + /// stloc V(unbox.any T(ldloc testedOperand)) + /// if (nextCondition) br trueBlock + /// br falseBlock + /// } + private bool MatchUnboxBlock(Block unboxBlock, IType type, ILVariable testedOperand, Block falseBlock, + [NotNullWhen(true)] out ILVariable? v, + [NotNullWhen(true)] out ILInstruction? nextCondition, + [NotNullWhen(true)] out Block? trueBlock, + out bool inverseCondition) + { + v = null; + nextCondition = null; + trueBlock = null; + inverseCondition = false; + if (unboxBlock.IncomingEdgeCount != 1 || unboxBlock.Instructions.Count != 3) + return false; - } - else if (result.LoadInst.Parent!.MatchCompEqualsNull(out _)) + if (!unboxBlock.Instructions[0].MatchStLoc(out v, out var value)) + return false; + if (!(value.MatchUnboxAny(out var arg, out var t) && t.Equals(type) && arg.MatchLdLoc(testedOperand))) + return false; + + if (!unboxBlock.MatchIfAtEndOfBlock(out nextCondition, out var trueInst, out var falseInst)) + return false; + + if (trueInst.MatchBranch(out trueBlock) && falseInst.MatchBranch(falseBlock)) { - invertCondition = true; + return true; } - else + else if (trueInst.MatchBranch(falseBlock) && falseInst.MatchBranch(out trueBlock)) { - return; + inverseCondition = true; + return true; } - - context.Step($"Type pattern matching {v.Name}", block.Instructions[pos]); - // call Use(..., match.type[T](V = testedOperand)) - - var target = result.LoadInst.Parent; - ILInstruction matchInstruction = new MatchInstruction(v, testedOperand) { - CheckNotNull = true, - CheckType = true - }; - if (invertCondition) + else { - matchInstruction = Comp.LogicNot(matchInstruction); + return false; } - target.ReplaceWith(matchInstruction.WithILRange(target)); - block.Instructions.RemoveRange(startPos, pos - startPos); - v.Kind = VariableKind.PatternLocal; } } } From 5fa820153367053fed14b81f0619e658b21d582d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 25 Jul 2021 15:05:44 +0200 Subject: [PATCH 06/15] Added support for isinst-unbox.any pattern with generic reference types and simplified value types pattern detection. --- .../TestCases/Pretty/PatternMatching.cs | 103 +++++++++--------- .../PatternMatchingRefTypesTransform.cs | 25 ++++- .../IL/Transforms/PatternMatchingTransform.cs | 73 +++---------- 3 files changed, 82 insertions(+), 119 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 5fdc53317..efeadf557 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -4,31 +4,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { public class PatternMatching { - public bool SimpleTypePattern(object x) + public void SimpleTypePattern(object x) { - Use(x is string y); - if (x is string z) + if (x is string value) { - Console.WriteLine(z); + Console.WriteLine(value); } - return x is string w; } - public bool SimpleTypePatternWithShortcircuit(object x) + public void TypePatternWithShortcircuit(object x) { - Use(F() && x is string y && y.Contains("a")); - if (F() && x is string z && z.Contains("a")) + Use(F() && x is string text && text.Contains("a")); + if (F() && x is string text2 && text2.Contains("a")) { - Console.WriteLine(z); + Console.WriteLine(text2); } - return F() && x is string w && w.Contains("a"); } - public void SimpleTypePatternWithShortcircuitAnd(object x) + public void TypePatternWithShortcircuitAnd(object x) { - if (x is string z && z.Contains("a")) + if (x is string text && text.Contains("a")) { - Console.WriteLine(z); + Console.WriteLine(text); } else { @@ -36,35 +33,35 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } - public void SimpleTypePatternWithShortcircuitOr(object x) + public void TypePatternWithShortcircuitOr(object x) { - if (!(x is string z) || z.Contains("a")) + if (!(x is string text) || text.Contains("a")) { Console.WriteLine(); } else { - Console.WriteLine(z); + Console.WriteLine(text); } } - public void SimpleTypePatternWithShortcircuitOr2(object x) + public void TypePatternWithShortcircuitOr2(object x) { - if (F() || !(x is string z)) + if (F() || !(x is string value)) { Console.WriteLine(); } else { - Console.WriteLine(z); + Console.WriteLine(value); } } - public void SimpleTypePatternValueTypesCondition(object x) + public void TypePatternValueTypesCondition(object x) { - if (x is int i) + if (x is int num) { - Console.WriteLine("Integer: " + i); + Console.WriteLine("Integer: " + num); } else { @@ -72,11 +69,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } - public void SimpleTypePatternValueTypesCondition2() + public void TypePatternValueTypesCondition2() { - if (GetObject() is int i) + if (GetObject() is int num) { - Console.WriteLine("Integer: " + i); + Console.WriteLine("Integer: " + num); } else { @@ -84,11 +81,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } - public void SimpleTypePatternValueTypesWithShortcircuitAnd(object x) + public void TypePatternValueTypesWithShortcircuitAnd(object x) { - if (x is int i && i.GetHashCode() > 0) + if (x is int num && num.GetHashCode() > 0) { - Console.WriteLine("Positive integer: " + i); + Console.WriteLine("Positive integer: " + num); } else { @@ -96,36 +93,35 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } - public void SimpleTypePatternValueTypesWithShortcircuitOr(object x) + public void TypePatternValueTypesWithShortcircuitOr(object x) { - if (!(x is int z) || z.GetHashCode() > 0) + if (!(x is int value) || value.GetHashCode() > 0) { Console.WriteLine(); } else { - Console.WriteLine(z); + Console.WriteLine(value); } } - public void SimpleTypePatternValueTypesWithShortcircuitOr2(object x) + public void TypePatternValueTypesWithShortcircuitOr2(object x) { - if (F() || !(x is int z)) + if (F() || !(x is int value)) { Console.WriteLine(); } else { - Console.WriteLine(z); + Console.WriteLine(value); } } -#if CS71 - public void SimpleTypePatternGenerics(object x) + public void TypePatternGenerics(object x) { - if (x is T t) + if (x is T val) { - Console.WriteLine(typeof(T).FullName + ": " + t); + Console.WriteLine(val.GetType().FullName); } else { @@ -133,11 +129,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } - public void SimpleTypePatternGenericRefType(object x) where T : class + public void TypePatternGenericRefType(object x) where T : class { - if (x is T t) + if (x is T val) { - Console.WriteLine(typeof(T).FullName + ": " + t); + Console.WriteLine(val.GetType().FullName); } else { @@ -145,24 +141,23 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } - public void SimpleTypePatternGenericValType(object x) where T : struct + public void TypePatternGenericValType(object x) where T : struct { - if (x is T t) + if (x is T val) { - Console.WriteLine(typeof(T).FullName + ": " + t); + Console.WriteLine(val.GetType().FullName); } else { Console.WriteLine("not a " + typeof(T).FullName); } } -#endif - public void SimpleTypePatternValueTypesWithShortcircuitAndMultiUse(object x) + public void TypePatternValueTypesWithShortcircuitAndMultiUse(object x) { - if (x is int i && i.GetHashCode() > 0 && i % 2 == 0) + if (x is int num && num.GetHashCode() > 0 && num % 2 == 0) { - Console.WriteLine("Positive integer: " + i); + Console.WriteLine("Positive integer: " + num); } else { @@ -170,9 +165,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } - public void SimpleTypePatternValueTypesWithShortcircuitAndMultiUse2(object x) + public void TypePatternValueTypesWithShortcircuitAndMultiUse2(object x) { - if ((x is int i && i.GetHashCode() > 0 && i % 2 == 0) || F()) + if ((x is int num && num.GetHashCode() > 0 && num % 2 == 0) || F()) { Console.WriteLine("true"); } @@ -182,9 +177,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } - public void SimpleTypePatternValueTypesWithShortcircuitAndMultiUse3(object x) + public void TypePatternValueTypesWithShortcircuitAndMultiUse3(object x) { - if (F() || (x is int i && i.GetHashCode() > 0 && i % 2 == 0)) + if (F() || (x is int num && num.GetHashCode() > 0 && num % 2 == 0)) { Console.WriteLine("true"); } @@ -194,9 +189,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } - public void SimpleTypePatternValueTypes() + public void TypePatternValueTypes() { - Use(F() && GetObject() is int y && y.GetHashCode() > 0 && y % 2 == 0); + Use(F() && GetObject() is int num && num.GetHashCode() > 0 && num % 2 == 0); } private bool F() diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs index f3f77108f..d83f62bf4 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs @@ -18,6 +18,8 @@ #nullable enable +using ICSharpCode.Decompiler.TypeSystem; + namespace ICSharpCode.Decompiler.IL.Transforms { class PatternMatchingRefTypesTransform : IStatementTransform @@ -41,14 +43,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!context.Settings.PatternMatching) return; int startPos = pos; - if (block.Instructions[pos] is not StLoc - { - Variable: var s, - Value: IsInst { Argument: var testedOperand, Type: var type } - }) - { + if (!block.Instructions[pos].MatchStLoc(out var s, out var value)) return; + IType? unboxType; + if (value is UnboxAny unboxAny) + { + // stloc S(unbox.any T(isinst T(testedOperand))) + unboxType = unboxAny.Type; + value = unboxAny.Argument; } + else + { + unboxType = null; + } + if (value is not IsInst { Argument: var testedOperand, Type: var type }) + return; + if (type.IsReferenceType != true) + return; + if (!(unboxType == null || type.Equals(unboxType))) + return; if (!s.IsSingleDefinition) return; if (s.Kind is not (VariableKind.Local or VariableKind.StackSlot)) diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index cf8234c8f..0da299ae0 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -28,26 +28,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms { class PatternMatchingTransform : IILTransform { - - /// Block { - /// ... - /// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock - /// br unboxBlock - /// } - /// - /// Block unboxBlock (incoming: 1) { - /// stloc V(unbox.any T(ldloc testedOperand)) - /// if (nextCondition) br trueBlock - /// br falseBlock - /// } - /// => - /// Block { - /// ... - /// if (logic.and(match.type[T].notnull(V = testedOperand), nextCondition)) br trueBlock - /// br falseBlock - /// } - /// - /// -or- /// Block { /// ... /// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock @@ -77,28 +57,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms { continue; } - if (!MatchUnboxBlock(unboxBlock, type, testedOperand.Variable, falseBlock, - out var v, out var nextCondition, out var trueBlock, out var inverseNextCondition)) + if (!MatchUnboxBlock(unboxBlock, type, testedOperand.Variable, out var v)) { continue; } context.Step($"PatternMatching with {v.Name}", block); - if (inverseNextCondition) - { - nextCondition = Comp.LogicNot(nextCondition); - } var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!; - ILInstruction logicAnd = IfInstruction.LogicAnd(new MatchInstruction(v, testedOperand) { + ifInst.Condition = new MatchInstruction(v, testedOperand) { CheckNotNull = true, CheckType = true - }, nextCondition); - ifInst.Condition = logicAnd; - ((Branch)ifInst.TrueInst).TargetBlock = trueBlock; + }; + ((Branch)ifInst.TrueInst).TargetBlock = unboxBlock; ((Branch)block.Instructions.Last()).TargetBlock = falseBlock; - unboxBlock.Instructions.Clear(); + unboxBlock.Instructions.RemoveAt(0); + // HACK: condition detection uses StartILOffset of blocks to decide which branch of if-else + // should become the then-branch. Change the unboxBlock StartILOffset from an offset inside + // the pattern matching machinery to an offset belonging to an instruction in the then-block. + unboxBlock.SetILRange(unboxBlock.Instructions[0]); v.Kind = VariableKind.PatternLocal; } - container.Blocks.RemoveAll(b => b.Instructions.Count == 0); } } @@ -140,20 +117,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// Block unboxBlock (incoming: 1) { /// stloc V(unbox.any T(ldloc testedOperand)) - /// if (nextCondition) br trueBlock - /// br falseBlock + /// ... /// } - private bool MatchUnboxBlock(Block unboxBlock, IType type, ILVariable testedOperand, Block falseBlock, - [NotNullWhen(true)] out ILVariable? v, - [NotNullWhen(true)] out ILInstruction? nextCondition, - [NotNullWhen(true)] out Block? trueBlock, - out bool inverseCondition) + private bool MatchUnboxBlock(Block unboxBlock, IType type, ILVariable testedOperand, + [NotNullWhen(true)] out ILVariable? v) { v = null; - nextCondition = null; - trueBlock = null; - inverseCondition = false; - if (unboxBlock.IncomingEdgeCount != 1 || unboxBlock.Instructions.Count != 3) + if (unboxBlock.IncomingEdgeCount != 1) return false; if (!unboxBlock.Instructions[0].MatchStLoc(out v, out var value)) @@ -161,22 +131,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!(value.MatchUnboxAny(out var arg, out var t) && t.Equals(type) && arg.MatchLdLoc(testedOperand))) return false; - if (!unboxBlock.MatchIfAtEndOfBlock(out nextCondition, out var trueInst, out var falseInst)) - return false; - - if (trueInst.MatchBranch(out trueBlock) && falseInst.MatchBranch(falseBlock)) - { - return true; - } - else if (trueInst.MatchBranch(falseBlock) && falseInst.MatchBranch(out trueBlock)) - { - inverseCondition = true; - return true; - } - else - { - return false; - } + return true; } } } From cd0c76d7b13985dd071bed2be7a7848155adc939 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 25 Jul 2021 15:52:37 +0200 Subject: [PATCH 07/15] Support leave instructions in RemoveInfeasiblePathTransform and transform match(x) ? true : false to match(x). --- .../IL/Transforms/ExpressionTransforms.cs | 10 +++++++++- .../IL/Transforms/RemoveInfeasiblePathTransform.cs | 12 ++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 827b902a3..56106209b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -589,11 +589,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms return; } } + if (MatchInstruction.IsPatternMatch(inst.Condition, out _) + && inst.TrueInst.MatchLdcI4(1) && inst.FalseInst.MatchLdcI4(0)) + { + context.Step("match(x) ? true : false -> match(x)", inst); + inst.Condition.AddILRange(inst); + inst.ReplaceWith(inst.Condition); + return; + } } IfInstruction HandleConditionalOperator(IfInstruction inst) { - // if (cond) stloc (A, V1) else stloc (A, V2) --> stloc (A, if (cond) V1 else V2) + // if (cond) stloc A(V1) else stloc A(V2) --> stloc A(if (cond) V1 else V2) Block trueInst = inst.TrueInst as Block; if (trueInst == null || trueInst.Instructions.Count != 1) return inst; diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs index 07d74eca9..3f4cbbefb 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs @@ -60,10 +60,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (!MatchBlock1(block, out var s, out int value, out var br)) return false; - if (!MatchBlock2(br.TargetBlock, s, value, out var targetBlock)) + if (!MatchBlock2(br.TargetBlock, s, value, out var exitInst)) return false; context.Step("RemoveInfeasiblePath", br); - br.TargetBlock = targetBlock; + br.ReplaceWith(exitInst.Clone()); s.RemoveIfRedundant = true; return true; } @@ -99,9 +99,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms // if (logic.not(ldloc s)) br IL_0027 // br IL_001d // } - bool MatchBlock2(Block block, ILVariable s, int constantValue, [NotNullWhen(true)] out Block? targetBlock) + bool MatchBlock2(Block block, ILVariable s, int constantValue, [NotNullWhen(true)] out ILInstruction? exitInst) { - targetBlock = null; + exitInst = null; if (block.Instructions.Count != 2) return false; if (block.IncomingEdgeCount <= 1) @@ -110,8 +110,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (!load.MatchLdLoc(s)) return false; - ILInstruction target = constantValue != 0 ? trueInst : falseInst; - return target.MatchBranch(out targetBlock); + exitInst = constantValue != 0 ? trueInst : falseInst; + return exitInst is Branch or Leave { Value: Nop }; } } } From 2b26e5013b8875d0cd130fdb92d2252a8e4f4c8b Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 25 Jul 2021 16:25:27 +0200 Subject: [PATCH 08/15] Add Roslyn 2.x pattern for value type pattern matching. --- .../IL/Transforms/PatternMatchingTransform.cs | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 0da299ae0..8f0c99fa4 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -30,12 +30,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms { /// Block { /// ... + /// [stloc temp(ldloc testedOperand)] /// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock /// br unboxBlock /// } /// /// Block unboxBlock (incoming: 1) { - /// stloc V(unbox.any T(ldloc testedOperand)) + /// stloc V(unbox.any T(ldloc temp)) /// ... /// } /// => @@ -57,7 +58,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms { continue; } - if (!MatchUnboxBlock(unboxBlock, type, testedOperand.Variable, out var v)) + StLoc? tempStore = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 3) as StLoc; + if (tempStore == null || !tempStore.Value.MatchLdLoc(testedOperand.Variable)) + { + tempStore = null; + } + if (!MatchUnboxBlock(unboxBlock, type, out var unboxOperand, out var v)) + { + continue; + } + if (unboxOperand == testedOperand.Variable) + { + // do nothing + } + else if (unboxOperand == tempStore?.Variable) + { + if (!(tempStore.Variable.IsSingleDefinition && tempStore.Variable.LoadCount == 1)) + continue; + } + else { continue; } @@ -70,6 +89,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms ((Branch)ifInst.TrueInst).TargetBlock = unboxBlock; ((Branch)block.Instructions.Last()).TargetBlock = falseBlock; unboxBlock.Instructions.RemoveAt(0); + if (unboxOperand == tempStore?.Variable) + { + block.Instructions.Remove(tempStore); + } // HACK: condition detection uses StartILOffset of blocks to decide which branch of if-else // should become the then-branch. Change the unboxBlock StartILOffset from an offset inside // the pattern matching machinery to an offset belonging to an instruction in the then-block. @@ -119,16 +142,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// stloc V(unbox.any T(ldloc testedOperand)) /// ... /// } - private bool MatchUnboxBlock(Block unboxBlock, IType type, ILVariable testedOperand, + private bool MatchUnboxBlock(Block unboxBlock, IType type, [NotNullWhen(true)] out ILVariable? testedOperand, [NotNullWhen(true)] out ILVariable? v) { v = null; + testedOperand = null; if (unboxBlock.IncomingEdgeCount != 1) return false; if (!unboxBlock.Instructions[0].MatchStLoc(out v, out var value)) return false; - if (!(value.MatchUnboxAny(out var arg, out var t) && t.Equals(type) && arg.MatchLdLoc(testedOperand))) + if (!(value.MatchUnboxAny(out var arg, out var t) && t.Equals(type) && arg.MatchLdLoc(out testedOperand))) return false; return true; From cd787c5549493a237518a9bbf6deedc51b74badf Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 25 Jul 2021 18:08:39 +0200 Subject: [PATCH 09/15] Fix DecompilerSettings.PatternMatching should only be active in case >= CS70. --- ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs | 2 +- .../TestCases/Pretty/ShortCircuit.cs | 6 ------ ICSharpCode.Decompiler/DecompilerSettings.cs | 3 ++- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index b300ae49a..dffc1d64d 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -486,7 +486,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { - RunForLibrary(cscOptions: cscOptions); + RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp6)); } [Test] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs index f753d91c3..4e7f7e3b1 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs @@ -320,15 +320,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty if (!F(1)) { } - if (F(2) && F(3)) - { - } if (F(4) || F(5)) { } - if (F(0) && F(1) && !F(2) && (F(3) || F(4))) - { - } E(); } #endif diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 182b280d4..6f901f084 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -104,6 +104,7 @@ namespace ICSharpCode.Decompiler discards = false; localFunctions = false; deconstruction = false; + patternMatching = false; } if (languageVersion < CSharp.LanguageVersion.CSharp7_2) { @@ -157,7 +158,7 @@ namespace ICSharpCode.Decompiler return CSharp.LanguageVersion.CSharp7_2; // C# 7.1 missing if (outVariables || throwExpressions || tupleTypes || tupleConversions - || discards || localFunctions) + || discards || localFunctions || deconstruction || patternMatching) return CSharp.LanguageVersion.CSharp7; if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation || stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers From e50d221e062f4e92316eb8cf54ed2e7b9bf48512 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 29 Aug 2021 19:56:57 +0200 Subject: [PATCH 10/15] Adjust NullableLiftingTransform to match new patterns due to RemoveInfeasiblePathTransform. --- .../Helpers/Tester.cs | 2 + .../TestCases/Pretty/LiftedOperators.cs | 2 +- .../IL/Transforms/NullableLiftingTransform.cs | 122 +++++++++++++----- 3 files changed, 92 insertions(+), 34 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 150f540bd..eea0ebd0c 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -308,6 +308,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers if (flags.HasFlag(CompilerOptions.UseRoslyn2_10_0) || flags.HasFlag(CompilerOptions.UseRoslynLatest)) { + preprocessorSymbols.Add("ROSLYN2"); preprocessorSymbols.Add("CS70"); preprocessorSymbols.Add("CS71"); preprocessorSymbols.Add("CS72"); @@ -315,6 +316,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers } if (flags.HasFlag(CompilerOptions.UseRoslynLatest)) { + preprocessorSymbols.Add("ROSLYN3"); preprocessorSymbols.Add("CS73"); preprocessorSymbols.Add("CS80"); preprocessorSymbols.Add("VB16"); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs index 56f9c2729..4eacfacde 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs @@ -367,7 +367,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { Console.WriteLine(); } -#if ROSLYN +#if ROSLYN2 // Roslyn 2.9 started invoking op_Equality even if the source code says 'a != b' if (!(a == b)) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs index 45be72188..835c91d67 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs @@ -137,6 +137,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return false; } + + bool AnalyzeNegatedCondition(ILInstruction condition) + { + return condition.MatchLogicNot(out var arg) && AnalyzeCondition(arg); + } #endregion #region Main lifting logic @@ -211,11 +216,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms // => comp.lifted[C#](lhs, rhs) return LiftCSharpComparison(comp, comp.Kind); } - else if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst)) + if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst)) { // comp(lhs, rhs) ? false : (v1 != null && ... && vn != null) return LiftCSharpComparison(comp, comp.Kind.Negate()); } + if (falseInst.MatchLdcI4(1) && AnalyzeNegatedCondition(trueInst)) + { + // comp(lhs, rhs) ? !(v1 != null && ... && vn != null) : true + // => !comp.lifted[C#](lhs, rhs) + ILInstruction result = LiftCSharpComparison(comp, comp.Kind); + if (result == null) + return result; + return Comp.LogicNot(result); + } + if (trueInst.MatchLdcI4(1) && AnalyzeNegatedCondition(falseInst)) + { + // comp(lhs, rhs) ? true : !(v1 != null && ... && vn != null) + ILInstruction result = LiftCSharpComparison(comp, comp.Kind.Negate()); + if (result == null) + return result; + return Comp.LogicNot(result); + } } } ILVariable v; @@ -544,7 +566,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return null; } - Call LiftCSharpUserEqualityComparison(CompOrDecimal hasValueComp, ComparisonKind newComparisonKind, ILInstruction nestedIfInst) + ILInstruction LiftCSharpUserEqualityComparison(CompOrDecimal hasValueComp, ComparisonKind newComparisonKind, ILInstruction nestedIfInst) { // User-defined equality operator: // if (comp(call get_HasValue(ldloca nullable1) == call get_HasValue(ldloca nullable2))) @@ -576,11 +598,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms return null; if (!falseInst.MatchLdcI4(newComparisonKind == ComparisonKind.Equality ? 1 : 0)) return null; + bool trueInstNegated = false; + while (trueInst.MatchLogicNot(out var arg)) + { + trueInstNegated = !trueInstNegated; + trueInst = arg; + } if (!(trueInst is Call call)) return null; if (!(call.Method.IsOperator && call.Arguments.Count == 2)) return null; - if (call.Method.Name != (newComparisonKind == ComparisonKind.Equality ? "op_Equality" : "op_Inequality")) + bool expectEqualityOperator = (newComparisonKind == ComparisonKind.Equality) ^ trueInstNegated; + if (call.Method.Name != (expectEqualityOperator ? "op_Equality" : "op_Inequality")) return null; var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method); if (liftedOperator == null) @@ -594,13 +623,66 @@ namespace ICSharpCode.Decompiler.IL.Transforms ) { context.Step("NullableLiftingTransform: C# user-defined (in)equality comparison", nestedIfInst); - return new Call(liftedOperator) { + ILInstruction replacement = new Call(liftedOperator) { Arguments = { left, right }, ConstrainedTo = call.ConstrainedTo, ILStackWasEmpty = call.ILStackWasEmpty, IsTail = call.IsTail, }.WithILRange(call); + if (trueInstNegated) + { + replacement = Comp.LogicNot(replacement); + } + return replacement; + } + return null; + } + + ILInstruction LiftCSharpUserComparison(ILInstruction trueInst, ILInstruction falseInst) + { + // (v1 != null && ... && vn != null) ? trueInst : falseInst + bool trueInstNegated = false; + while (trueInst.MatchLogicNot(out var arg)) + { + trueInstNegated = !trueInstNegated; + trueInst = arg; + } + if (trueInst is Call call && !call.IsLifted + && CSharp.Resolver.CSharpOperators.IsComparisonOperator(call.Method) + && falseInst.MatchLdcI4((call.Method.Name == "op_Inequality") ^ trueInstNegated ? 1 : 0)) + { + // (v1 != null && ... && vn != null) ? call op_LessThan(lhs, rhs) : ldc.i4(0) + var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method); + if ((call.Method.Name == "op_Equality" || call.Method.Name == "op_Inequality") && nullableVars.Count != 1) + { + // Equality is special (returns true if both sides are null), only handle it + // in the normal code path if we're dealing with only a single nullable var + // (comparing nullable with non-nullable). + return null; + } + if (liftedOperator == null) + { + return null; + } + context.Step("Lift user-defined comparison operator", trueInst); + var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1], + call.Method.Parameters[0].Type, call.Method.Parameters[1].Type); + if (left != null && right != null && bits.All(0, nullableVars.Count)) + { + ILInstruction result = new Call(liftedOperator) { + Arguments = { left, right }, + ConstrainedTo = call.ConstrainedTo, + ILStackWasEmpty = call.ILStackWasEmpty, + IsTail = call.IsTail + }.WithILRange(call); + if (trueInstNegated) + { + result = Comp.LogicNot(result); + } + return result; + } } + return null; } #endregion @@ -641,35 +723,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType() }; } - else if (trueInst is Call call && !call.IsLifted - && CSharp.Resolver.CSharpOperators.IsComparisonOperator(call.Method) - && falseInst.MatchLdcI4(call.Method.Name == "op_Inequality" ? 1 : 0)) - { - // (v1 != null && ... && vn != null) ? call op_LessThan(lhs, rhs) : ldc.i4(0) - var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method); - if ((call.Method.Name == "op_Equality" || call.Method.Name == "op_Inequality") && nullableVars.Count != 1) - { - // Equality is special (returns true if both sides are null), only handle it - // in the normal code path if we're dealing with only a single nullable var - // (comparing nullable with non-nullable). - liftedOperator = null; - } - if (liftedOperator != null) - { - context.Step("Lift user-defined comparison operator", trueInst); - var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1], - call.Method.Parameters[0].Type, call.Method.Parameters[1].Type); - if (left != null && right != null && bits.All(0, nullableVars.Count)) - { - return new Call(liftedOperator) { - Arguments = { left, right }, - ConstrainedTo = call.ConstrainedTo, - ILStackWasEmpty = call.ILStackWasEmpty, - IsTail = call.IsTail - }.WithILRange(call); - } - } - } + ILInstruction result = LiftCSharpUserComparison(trueInst, falseInst); + if (result != null) + return result; } ILInstruction lifted; if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0])) From dbbcbb87fe8604e2741ee9e23987de59c37229db Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 5 Sep 2021 18:33:30 +0200 Subject: [PATCH 11/15] Make pattern matching transform a simple ILTransform for both reference and value types. Check that the true branch dominates all uses of the pattern variable. --- .../TestCases/Pretty/PatternMatching.cs | 13 + .../CSharp/CSharpDecompiler.cs | 1 - .../ICSharpCode.Decompiler.csproj | 1 - .../Transforms/EarlyExpressionTransforms.cs | 54 ++++ .../IL/Transforms/ExpressionTransforms.cs | 41 +-- .../PatternMatchingRefTypesTransform.cs | 128 -------- .../IL/Transforms/PatternMatchingTransform.cs | 306 ++++++++++++++---- 7 files changed, 319 insertions(+), 225 deletions(-) delete mode 100644 ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index efeadf557..173e038bd 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -194,6 +194,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Use(F() && GetObject() is int num && num.GetHashCode() > 0 && num % 2 == 0); } + public static void NotTypePatternVariableUsedOutsideTrueBranch(object x) + { + var text = x as string; + if (text != null && text.Length > 5) + { + Console.WriteLine("pattern matches"); + } + if (text != null && text.Length > 10) + { + Console.WriteLine("other use!"); + } + } + private bool F() { return true; diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index cfa783999..f43be94ff 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -138,7 +138,6 @@ namespace ICSharpCode.Decompiler.CSharp // Inlining must be first, because it doesn't trigger re-runs. // Any other transform that opens up new inlining opportunities should call RequestRerun(). new ExpressionTransforms(), - new PatternMatchingRefTypesTransform(), new DynamicIsEventAssignmentTransform(), new TransformAssignment(), // inline and compound assignments new NullCoalescingTransform(), diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 638020a9a..962f6795a 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -93,7 +93,6 @@ - diff --git a/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs index a4fb74a3b..b0b4dfd02 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs @@ -40,6 +40,60 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } + protected internal override void VisitComp(Comp inst) + { + base.VisitComp(inst); + FixComparisonKindLdNull(inst, context); + } + + internal static void FixComparisonKindLdNull(Comp inst, ILTransformContext context) + { + if (inst.IsLifted) + { + return; + } + if (inst.Right.MatchLdNull()) + { + if (inst.Kind == ComparisonKind.GreaterThan) + { + context.Step("comp(left > ldnull) => comp(left != ldnull)", inst); + inst.Kind = ComparisonKind.Inequality; + } + else if (inst.Kind == ComparisonKind.LessThanOrEqual) + { + context.Step("comp(left <= ldnull) => comp(left == ldnull)", inst); + inst.Kind = ComparisonKind.Equality; + } + } + else if (inst.Left.MatchLdNull()) + { + if (inst.Kind == ComparisonKind.LessThan) + { + context.Step("comp(ldnull < right) => comp(ldnull != right)", inst); + inst.Kind = ComparisonKind.Inequality; + } + else if (inst.Kind == ComparisonKind.GreaterThanOrEqual) + { + context.Step("comp(ldnull >= right) => comp(ldnull == right)", inst); + inst.Kind = ComparisonKind.Equality; + } + } + + if (inst.Right.MatchLdNull() && inst.Left.MatchBox(out var arg, out var type) && type.Kind == TypeKind.TypeParameter) + { + if (inst.Kind == ComparisonKind.Equality) + { + context.Step("comp(box T(..) == ldnull) -> comp(.. == ldnull)", inst); + inst.Left = arg; + } + if (inst.Kind == ComparisonKind.Inequality) + { + context.Step("comp(box T(..) != ldnull) -> comp(.. != ldnull)", inst); + inst.Left = arg; + } + } + } + protected internal override void VisitStObj(StObj inst) { base.VisitStObj(inst); diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 56106209b..f9fa9a7b8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -113,32 +113,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return; } - if (inst.Right.MatchLdNull()) - { - if (inst.Kind == ComparisonKind.GreaterThan) - { - context.Step("comp(left > ldnull) => comp(left != ldnull)", inst); - inst.Kind = ComparisonKind.Inequality; - } - else if (inst.Kind == ComparisonKind.LessThanOrEqual) - { - context.Step("comp(left <= ldnull) => comp(left == ldnull)", inst); - inst.Kind = ComparisonKind.Equality; - } - } - else if (inst.Left.MatchLdNull()) - { - if (inst.Kind == ComparisonKind.LessThan) - { - context.Step("comp(ldnull < right) => comp(ldnull != right)", inst); - inst.Kind = ComparisonKind.Inequality; - } - else if (inst.Kind == ComparisonKind.GreaterThanOrEqual) - { - context.Step("comp(ldnull >= right) => comp(ldnull == right)", inst); - inst.Kind = ComparisonKind.Equality; - } - } + EarlyExpressionTransforms.FixComparisonKindLdNull(inst, context); var rightWithoutConv = inst.Right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend); if (rightWithoutConv.MatchLdcI4(0) @@ -183,20 +158,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms inst.Right.AddILRange(rightWithoutConv); } } - - if (inst.Right.MatchLdNull() && inst.Left.MatchBox(out arg, out var type) && type.Kind == TypeKind.TypeParameter) - { - if (inst.Kind == ComparisonKind.Equality) - { - context.Step("comp(box T(..) == ldnull) -> comp(.. == ldnull)", inst); - inst.Left = arg; - } - if (inst.Kind == ComparisonKind.Inequality) - { - context.Step("comp(box T(..) != ldnull) -> comp(.. != ldnull)", inst); - inst.Left = arg; - } - } } protected internal override void VisitConv(Conv inst) diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs deleted file mode 100644 index d83f62bf4..000000000 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2021 Daniel Grunwald, Siegfried Pammer -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -#nullable enable - -using ICSharpCode.Decompiler.TypeSystem; - -namespace ICSharpCode.Decompiler.IL.Transforms -{ - class PatternMatchingRefTypesTransform : IStatementTransform - { - /// - /// stloc V(isinst T(testedOperand)) - /// call Use(..., comp.o(ldloc V != ldnull)) - /// => - /// call Use(..., match.type[T].notnull(V = testedOperand)) - /// - /// - or - - /// - /// stloc S(isinst T(testedOperand)) - /// stloc V(ldloc S) - /// call Use(..., comp.o(ldloc S != ldnull)) - /// => - /// call Use(..., match.type[T].notnull(V = testedOperand)) - /// - void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) - { - if (!context.Settings.PatternMatching) - return; - int startPos = pos; - if (!block.Instructions[pos].MatchStLoc(out var s, out var value)) - return; - IType? unboxType; - if (value is UnboxAny unboxAny) - { - // stloc S(unbox.any T(isinst T(testedOperand))) - unboxType = unboxAny.Type; - value = unboxAny.Argument; - } - else - { - unboxType = null; - } - if (value is not IsInst { Argument: var testedOperand, Type: var type }) - return; - if (type.IsReferenceType != true) - return; - if (!(unboxType == null || type.Equals(unboxType))) - return; - if (!s.IsSingleDefinition) - return; - if (s.Kind is not (VariableKind.Local or VariableKind.StackSlot)) - return; - pos++; - ILVariable v; - if (block.Instructions.ElementAtOrDefault(pos) is StLoc stloc && stloc.Value.MatchLdLoc(s)) - { - v = stloc.Variable; - pos++; - if (!v.IsSingleDefinition) - return; - if (v.Kind is not (VariableKind.Local or VariableKind.StackSlot)) - return; - if (s.LoadCount != 2) - return; - } - else - { - v = s; - } - - if (!v.Type.Equals(type)) - return; - if (pos >= block.Instructions.Count) - return; - var result = ILInlining.FindLoadInNext(block.Instructions[pos], s, testedOperand, InliningOptions.None); - if (result.Type != ILInlining.FindResultType.Found) - return; - if (result.LoadInst is not LdLoc) - return; - bool invertCondition; - if (result.LoadInst.Parent!.MatchCompNotEqualsNull(out _)) - { - invertCondition = false; - - } - else if (result.LoadInst.Parent!.MatchCompEqualsNull(out _)) - { - invertCondition = true; - } - else - { - return; - } - - context.Step($"Type pattern matching {v.Name}", block.Instructions[pos]); - // call Use(..., match.type[T](V = testedOperand)) - - var target = result.LoadInst.Parent; - ILInstruction matchInstruction = new MatchInstruction(v, testedOperand) { - CheckNotNull = true, - CheckType = true - }; - if (invertCondition) - { - matchInstruction = Comp.LogicNot(matchInstruction); - } - target.ReplaceWith(matchInstruction.WithILRange(target)); - block.Instructions.RemoveRange(startPos, pos - startPos); - v.Kind = VariableKind.PatternLocal; - } - } -} diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 8f0c99fa4..ddaa25e80 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -18,16 +18,216 @@ #nullable enable +using System; +using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; +using System.Xml.Linq; +using ICSharpCode.Decompiler.CSharp.Resolver; +using ICSharpCode.Decompiler.IL.ControlFlow; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; +using static ICSharpCode.Decompiler.TypeSystem.ReflectionHelper; + namespace ICSharpCode.Decompiler.IL.Transforms { class PatternMatchingTransform : IILTransform { + + void IILTransform.Run(ILFunction function, ILTransformContext context) + { + if (!context.Settings.PatternMatching) + return; + foreach (var container in function.Descendants.OfType()) + { + ControlFlowGraph? cfg = null; + foreach (var block in container.Blocks) + { + if (PatternMatchValueTypes(block, container, context, ref cfg)) + { + continue; + } + if (PatternMatchRefTypes(block, container, context, ref cfg)) + { + continue; + } + } + } + } + + /// Block { + /// ... + /// stloc V(isinst T(testedOperand)) + /// if (comp.o(ldloc V == ldnull)) br falseBlock + /// br trueBlock + /// } + /// + /// All other uses of V are in blocks dominated by trueBlock. + /// => + /// Block { + /// ... + /// if (match.type[T].notnull(V = testedOperand)) br trueBlock + /// br falseBlock + /// } + /// + /// - or - + /// + /// Block { + /// stloc s(isinst T(testedOperand)) + /// stloc v(ldloc s) + /// if (logic.not(comp.o(ldloc s != ldnull))) br falseBlock + /// br trueBlock + /// } + /// => + /// Block { + /// ... + /// if (match.type[T].notnull(V = testedOperand)) br trueBlock + /// br falseBlock + /// } + /// + /// All other uses of V are in blocks dominated by trueBlock. + private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg) + { + if (!block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst)) + return false; + int pos = block.Instructions.Count - 3; + if (condition.MatchLdLoc(out var conditionVar)) + { + // stloc conditionVar(comp.o(ldloc s == ldnull)) + // if (logic.not(ldloc conditionVar)) br trueBlock + if (pos < 0) + return false; + if (!(conditionVar.IsSingleDefinition && conditionVar.LoadCount == 1 + && conditionVar.Kind == VariableKind.StackSlot)) + { + return false; + } + if (!block.Instructions[pos].MatchStLoc(conditionVar, out condition)) + return false; + pos--; + } + if (condition.MatchCompEqualsNull(out var arg)) + { + ExtensionMethods.Swap(ref trueInst, ref falseInst); + } + else if (condition.MatchCompNotEqualsNull(out arg)) + { + // do nothing + } + else + { + return false; + } + if (!arg.MatchLdLoc(out var s)) + return false; + if (!s.IsSingleDefinition) + return false; + if (s.Kind is not (VariableKind.Local or VariableKind.StackSlot)) + return false; + if (pos < 0) + return false; + // stloc V(isinst T(testedOperand)) + ILInstruction storeToV = block.Instructions[pos]; + if (!storeToV.MatchStLoc(out var v, out var value)) + return false; + if (value.MatchLdLoc(s)) + { + // stloc v(ldloc s) + pos--; + if (!block.Instructions[pos].MatchStLoc(s, out value)) + return false; + if (!v.IsSingleDefinition) + return false; + if (v.Kind is not (VariableKind.Local or VariableKind.StackSlot)) + return false; + if (s.LoadCount != 2) + return false; + } + else + { + if (v != s) + return false; + } + IType? unboxType; + if (value is UnboxAny unboxAny) + { + // stloc S(unbox.any T(isinst T(testedOperand))) + unboxType = unboxAny.Type; + value = unboxAny.Argument; + } + else + { + unboxType = null; + } + if (value is not IsInst { Argument: var testedOperand, Type: var type }) + return false; + if (type.IsReferenceType != true) + return false; + if (!(unboxType == null || type.Equals(unboxType))) + return false; + + if (!v.Type.Equals(type)) + return false; + if (!CheckAllUsesDominatedBy(v, container, trueInst, storeToV, context, ref cfg)) + return false; + context.Step($"Type pattern matching {v.Name}", block); + // if (match.type[T].notnull(V = testedOperand)) br trueBlock + + var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!; + + ifInst.Condition = new MatchInstruction(v, testedOperand) { + CheckNotNull = true, + CheckType = true + }.WithILRange(ifInst.Condition); + ifInst.TrueInst = trueInst; + block.Instructions[block.Instructions.Count - 1] = falseInst; + block.Instructions.RemoveRange(pos, ifInst.ChildIndex - pos); + v.Kind = VariableKind.PatternLocal; + return true; + } + + private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst, + ILInstruction storeToV, ILTransformContext context, ref ControlFlowGraph? cfg) + { + var targetBlock = trueInst as Block; + if (targetBlock == null && !trueInst.MatchBranch(out targetBlock)) + { + return false; + } + + if (targetBlock.Parent != container) + return false; + cfg ??= new ControlFlowGraph(container, context.CancellationToken); + var targetBlockNode = cfg.GetNode(targetBlock); + Debug.Assert(v.StoreInstructions.Count == 1); + var uses = v.LoadInstructions.Concat(v.AddressInstructions) + .Concat(v.StoreInstructions.Cast()); + foreach (var use in uses) + { + if (use == storeToV) + continue; + Block? found = null; + for (ILInstruction? current = use; current != null; current = current.Parent) + { + if (current.Parent == container) + { + found = (Block)current; + break; + } + } + if (found == null) + return false; + var node = cfg.GetNode(found); + if (!targetBlockNode.Dominates(node)) + return false; + } + return true; + } + /// Block { /// ... /// [stloc temp(ldloc testedOperand)] @@ -45,61 +245,56 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// if (match.type[T].notnull(V = testedOperand)) br unboxBlock /// br falseBlock /// } - void IILTransform.Run(ILFunction function, ILTransformContext context) + private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg) { - if (!context.Settings.PatternMatching) - return; - foreach (var container in function.Descendants.OfType()) + if (!MatchIsInstBlock(block, out var type, out var testedOperand, + out var unboxBlock, out var falseBlock)) { - foreach (var block in container.Blocks) - { - if (!MatchIsInstBlock(block, out var type, out var testedOperand, - out var unboxBlock, out var falseBlock)) - { - continue; - } - StLoc? tempStore = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 3) as StLoc; - if (tempStore == null || !tempStore.Value.MatchLdLoc(testedOperand.Variable)) - { - tempStore = null; - } - if (!MatchUnboxBlock(unboxBlock, type, out var unboxOperand, out var v)) - { - continue; - } - if (unboxOperand == testedOperand.Variable) - { - // do nothing - } - else if (unboxOperand == tempStore?.Variable) - { - if (!(tempStore.Variable.IsSingleDefinition && tempStore.Variable.LoadCount == 1)) - continue; - } - else - { - continue; - } - context.Step($"PatternMatching with {v.Name}", block); - var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!; - ifInst.Condition = new MatchInstruction(v, testedOperand) { - CheckNotNull = true, - CheckType = true - }; - ((Branch)ifInst.TrueInst).TargetBlock = unboxBlock; - ((Branch)block.Instructions.Last()).TargetBlock = falseBlock; - unboxBlock.Instructions.RemoveAt(0); - if (unboxOperand == tempStore?.Variable) - { - block.Instructions.Remove(tempStore); - } - // HACK: condition detection uses StartILOffset of blocks to decide which branch of if-else - // should become the then-branch. Change the unboxBlock StartILOffset from an offset inside - // the pattern matching machinery to an offset belonging to an instruction in the then-block. - unboxBlock.SetILRange(unboxBlock.Instructions[0]); - v.Kind = VariableKind.PatternLocal; - } + return false; + } + StLoc? tempStore = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 3) as StLoc; + if (tempStore == null || !tempStore.Value.MatchLdLoc(testedOperand.Variable)) + { + tempStore = null; } + if (!MatchUnboxBlock(unboxBlock, type, out var unboxOperand, out var v, out var storeToV)) + { + return false; + } + if (unboxOperand == testedOperand.Variable) + { + // do nothing + } + else if (unboxOperand == tempStore?.Variable) + { + if (!(tempStore.Variable.IsSingleDefinition && tempStore.Variable.LoadCount == 1)) + return false; + } + else + { + return false; + } + if (!CheckAllUsesDominatedBy(v, container, unboxBlock, storeToV, context, ref cfg)) + return false; + context.Step($"PatternMatching with {v.Name}", block); + var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!; + ifInst.Condition = new MatchInstruction(v, testedOperand) { + CheckNotNull = true, + CheckType = true + }; + ((Branch)ifInst.TrueInst).TargetBlock = unboxBlock; + ((Branch)block.Instructions.Last()).TargetBlock = falseBlock; + unboxBlock.Instructions.RemoveAt(0); + if (unboxOperand == tempStore?.Variable) + { + block.Instructions.Remove(tempStore); + } + // HACK: condition detection uses StartILOffset of blocks to decide which branch of if-else + // should become the then-branch. Change the unboxBlock StartILOffset from an offset inside + // the pattern matching machinery to an offset belonging to an instruction in the then-block. + unboxBlock.SetILRange(unboxBlock.Instructions[0]); + v.Kind = VariableKind.PatternLocal; + return true; } /// ... @@ -143,14 +338,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// ... /// } private bool MatchUnboxBlock(Block unboxBlock, IType type, [NotNullWhen(true)] out ILVariable? testedOperand, - [NotNullWhen(true)] out ILVariable? v) + [NotNullWhen(true)] out ILVariable? v, [NotNullWhen(true)] out ILInstruction? storeToV) { v = null; + storeToV = null; testedOperand = null; if (unboxBlock.IncomingEdgeCount != 1) return false; - - if (!unboxBlock.Instructions[0].MatchStLoc(out v, out var value)) + storeToV = unboxBlock.Instructions[0]; + if (!storeToV.MatchStLoc(out v, out var value)) return false; if (!(value.MatchUnboxAny(out var arg, out var t) && t.Equals(type) && arg.MatchLdLoc(out testedOperand))) return false; From 3f62216c85c5d8b7f5be078d289928d078462715 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 3 Oct 2021 18:29:43 +0200 Subject: [PATCH 12/15] Fix simple type patterns. --- .../TestCases/Pretty/PatternMatching.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index 173e038bd..dbfec51eb 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -105,6 +105,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } +#if ROSLYN3 || OPT + // Roslyn 2.x generates a complex infeasible path in debug builds, which RemoveInfeasiblePathTransform + // currently cannot handle. Because this would increase the complexity of that transform, we ignore + // this case. public void TypePatternValueTypesWithShortcircuitOr2(object x) { if (F() || !(x is int value)) @@ -116,6 +120,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Console.WriteLine(value); } } +#endif public void TypePatternGenerics(object x) { @@ -196,7 +201,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void NotTypePatternVariableUsedOutsideTrueBranch(object x) { - var text = x as string; + string text = x as string; if (text != null && text.Length > 5) { Console.WriteLine("pattern matches"); From 55f1125f9436cf14004148c109e40a7ee8b65754 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 3 Oct 2021 19:05:10 +0200 Subject: [PATCH 13/15] Ensure that pattern locals get distinct variable names. --- ICSharpCode.Decompiler/IL/ILVariable.cs | 4 ++++ .../IntroduceNativeIntTypeOnLocals.cs | 1 + .../IL/Transforms/PatternMatchingTransform.cs | 23 ++++++------------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/ILVariable.cs b/ICSharpCode.Decompiler/IL/ILVariable.cs index 5f407b886..f70707a36 100644 --- a/ICSharpCode.Decompiler/IL/ILVariable.cs +++ b/ICSharpCode.Decompiler/IL/ILVariable.cs @@ -101,6 +101,7 @@ namespace ICSharpCode.Decompiler.IL case VariableKind.ExceptionLocal: case VariableKind.ForeachLocal: case VariableKind.UsingLocal: + case VariableKind.PatternLocal: case VariableKind.PinnedLocal: case VariableKind.PinnedRegionLocal: case VariableKind.DisplayClassLocal: @@ -173,6 +174,7 @@ namespace ICSharpCode.Decompiler.IL { case VariableKind.Local: case VariableKind.ForeachLocal: + case VariableKind.PatternLocal: case VariableKind.PinnedLocal: case VariableKind.PinnedRegionLocal: case VariableKind.UsingLocal: @@ -541,6 +543,8 @@ namespace ICSharpCode.Decompiler.IL return false; if (x.Kind == VariableKind.StackSlot || y.Kind == VariableKind.StackSlot) return false; + if (x.Kind == VariableKind.PatternLocal || y.Kind == VariableKind.PatternLocal) + return false; if (!(x.Function == y.Function && x.Kind == y.Kind)) return false; if (x.Index != null) diff --git a/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs b/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs index aa78ecedd..910a4726a 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs @@ -36,6 +36,7 @@ namespace ICSharpCode.Decompiler.IL { if (variable.Kind != VariableKind.Local && variable.Kind != VariableKind.StackSlot && + variable.Kind != VariableKind.PatternLocal && variable.Kind != VariableKind.ForeachLocal && variable.Kind != VariableKind.UsingLocal) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index ddaa25e80..8e2bb4f14 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -19,20 +19,14 @@ #nullable enable using System; -using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Threading; -using System.Xml.Linq; -using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.IL.ControlFlow; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; -using static ICSharpCode.Decompiler.TypeSystem.ReflectionHelper; - namespace ICSharpCode.Decompiler.IL.Transforms { class PatternMatchingTransform : IILTransform @@ -110,11 +104,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; pos--; } - if (condition.MatchCompEqualsNull(out var arg)) + if (condition.MatchCompEqualsNull(out var loadInNullCheck)) { ExtensionMethods.Swap(ref trueInst, ref falseInst); } - else if (condition.MatchCompNotEqualsNull(out arg)) + else if (condition.MatchCompNotEqualsNull(out loadInNullCheck)) { // do nothing } @@ -122,7 +116,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return false; } - if (!arg.MatchLdLoc(out var s)) + if (!loadInNullCheck.MatchLdLoc(out var s)) return false; if (!s.IsSingleDefinition) return false; @@ -140,8 +134,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms pos--; if (!block.Instructions[pos].MatchStLoc(s, out value)) return false; - if (!v.IsSingleDefinition) - return false; if (v.Kind is not (VariableKind.Local or VariableKind.StackSlot)) return false; if (s.LoadCount != 2) @@ -172,7 +164,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!v.Type.Equals(type)) return false; - if (!CheckAllUsesDominatedBy(v, container, trueInst, storeToV, context, ref cfg)) + if (!CheckAllUsesDominatedBy(v, container, trueInst, storeToV, loadInNullCheck, context, ref cfg)) return false; context.Step($"Type pattern matching {v.Name}", block); // if (match.type[T].notnull(V = testedOperand)) br trueBlock @@ -191,7 +183,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst, - ILInstruction storeToV, ILTransformContext context, ref ControlFlowGraph? cfg) + ILInstruction storeToV, ILInstruction? loadInNullCheck, ILTransformContext context, ref ControlFlowGraph? cfg) { var targetBlock = trueInst as Block; if (targetBlock == null && !trueInst.MatchBranch(out targetBlock)) @@ -203,12 +195,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; cfg ??= new ControlFlowGraph(container, context.CancellationToken); var targetBlockNode = cfg.GetNode(targetBlock); - Debug.Assert(v.StoreInstructions.Count == 1); var uses = v.LoadInstructions.Concat(v.AddressInstructions) .Concat(v.StoreInstructions.Cast()); foreach (var use in uses) { - if (use == storeToV) + if (use == storeToV || use == loadInNullCheck) continue; Block? found = null; for (ILInstruction? current = use; current != null; current = current.Parent) @@ -274,7 +265,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return false; } - if (!CheckAllUsesDominatedBy(v, container, unboxBlock, storeToV, context, ref cfg)) + if (!CheckAllUsesDominatedBy(v, container, unboxBlock, storeToV, null, context, ref cfg)) return false; context.Step($"PatternMatching with {v.Name}", block); var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!; From 665c731cfc3a76eadc39db2069c6489ca76f04db Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 3 Oct 2021 19:49:40 +0200 Subject: [PATCH 14/15] Move PatternMatchingTransform after LoopDetection. --- .../TestCases/Pretty/PatternMatching.cs | 19 +++++++++++++++++++ .../CSharp/CSharpDecompiler.cs | 2 +- .../IL/Transforms/PatternMatchingTransform.cs | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs index dbfec51eb..064db32bc 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs @@ -212,6 +212,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + public static void NotTypePatternBecauseVarIsNotDefAssignedInCaseOfFallthrough(object x) + { +#if OPT + string obj = x as string; + if (obj == null) + { + Console.WriteLine("pattern doesn't match"); + } + Console.WriteLine(obj == null); +#else + string text = x as string; + if (text == null) + { + Console.WriteLine("pattern doesn't match"); + } + Console.WriteLine(text == null); +#endif + } + private bool F() { return true; diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index f43be94ff..7007924e5 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -103,7 +103,6 @@ namespace ICSharpCode.Decompiler.CSharp new RemoveDeadVariableInit(), new ControlFlowSimplification(), //split variables may enable new branch to leave inlining new DynamicCallSiteTransform(), - new PatternMatchingTransform(), new SwitchDetection(), new SwitchOnStringTransform(), new SwitchOnNullableTransform(), @@ -121,6 +120,7 @@ namespace ICSharpCode.Decompiler.CSharp }, // re-run DetectExitPoints after loop detection new DetectExitPoints(), + new PatternMatchingTransform(), // must run after LoopDetection and before ConditionDetection new BlockILTransform { // per-block transforms PostOrderTransforms = { new ConditionDetection(), diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs index 8e2bb4f14..6eb2863f5 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs @@ -193,6 +193,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (targetBlock.Parent != container) return false; + if (targetBlock.IncomingEdgeCount != 1) + return false; cfg ??= new ControlFlowGraph(container, context.CancellationToken); var targetBlockNode = cfg.GetNode(targetBlock); var uses = v.LoadInstructions.Concat(v.AddressInstructions) From 96db0a5472c49c77347c085c93ebadcc80750650 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 4 Oct 2021 17:58:02 +0200 Subject: [PATCH 15/15] Add support for pattern matching in UsingTransform. --- .../IL/Transforms/UsingTransform.cs | 125 +++++++++++++----- 1 file changed, 93 insertions(+), 32 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs index 53795a1d3..e59257f78 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs @@ -37,12 +37,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms this.context = context; for (int i = block.Instructions.Count - 1; i >= 0; i--) { - if (!TransformUsing(block, i) && !TransformUsingVB(block, i) && !TransformAsyncUsing(block, i)) + if (TransformUsing(block, i)) + { + continue; + } + if (TransformUsingVB(block, i)) + { + continue; + } + if (TransformAsyncUsing(block, i)) + { continue; - // This happens in some cases: - // Use correct index after transformation. - if (i >= block.Instructions.Count) - i = block.Instructions.Count; + } } } @@ -74,9 +80,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// bool TransformUsing(Block block, int i) { - if (i < 1) + if (i + 1 >= block.Instructions.Count) return false; - if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst)) + if (!(block.Instructions[i + 1] is TryFinally tryFinally) || !(block.Instructions[i] is StLoc storeInst)) return false; if (!(storeInst.Value.MatchLdNull() || CheckResourceType(storeInst.Variable.Type))) return false; @@ -88,12 +94,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (storeInst.Variable.StoreInstructions.Count > 1) return false; - if (!(tryFinally.FinallyBlock is BlockContainer container) || !MatchDisposeBlock(container, storeInst.Variable, storeInst.Value.MatchLdNull())) + if (!(tryFinally.FinallyBlock is BlockContainer container)) + return false; + if (!MatchDisposeBlock(container, storeInst.Variable, storeInst.Value.MatchLdNull())) return false; context.Step("UsingTransform", tryFinally); storeInst.Variable.Kind = VariableKind.UsingLocal; - block.Instructions.RemoveAt(i); - block.Instructions[i - 1] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock) { + block.Instructions.RemoveAt(i + 1); + block.Instructions[i] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock) { IsRefStruct = context.Settings.IntroduceRefModifiersOnStructs && storeInst.Variable.Type.Kind == TypeKind.Struct && storeInst.Variable.Type.IsByRefLike }.WithILRange(storeInst); return true; @@ -127,6 +135,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// bool TransformUsingVB(Block block, int i) { + if (i >= block.Instructions.Count) + return false; if (!(block.Instructions[i] is TryFinally tryFinally)) return false; if (!(tryFinally.TryBlock is BlockContainer tryContainer && tryContainer.EntryPoint.Instructions.FirstOrDefault() is StLoc storeInst)) @@ -175,41 +185,64 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull, in string disposeMethodFullName = "System.IDisposable.Dispose", KnownTypeCode disposeTypeCode = KnownTypeCode.IDisposable) + /// finally BlockContainer { + /// Block IL_0012(incoming: 1) { + /// if (comp(ldloc obj != ldnull)) Block IL_001a { + /// callvirt Dispose(obj) + /// } + /// leave IL_0012(nop) + /// } + /// } + bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull, + in string disposeMethodFullName = "System.IDisposable.Dispose", + KnownTypeCode disposeTypeCode = KnownTypeCode.IDisposable) { var entryPoint = container.EntryPoint; - if (entryPoint.Instructions.Count < 2 || entryPoint.Instructions.Count > 3 || entryPoint.IncomingEdgeCount != 1) + if (entryPoint.IncomingEdgeCount != 1) return false; - int leaveIndex = entryPoint.Instructions.Count == 2 ? 1 : 2; - int checkIndex = entryPoint.Instructions.Count == 2 ? 0 : 1; - int castIndex = entryPoint.Instructions.Count == 3 ? 0 : -1; - var checkInst = entryPoint.Instructions[checkIndex]; + int pos = 0; bool isReference = objVar.Type.IsReferenceType != false; - if (castIndex > -1) + // optional: + // stloc temp(isinst TDisposable(ldloc obj)) + if (entryPoint.Instructions.ElementAtOrDefault(pos).MatchStLoc(out var tempVar, out var isinst)) { - if (!entryPoint.Instructions[castIndex].MatchStLoc(out var tempVar, out var isinst)) - return false; - if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) || !disposableType.IsKnownType(disposeTypeCode)) + if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) + || !disposableType.IsKnownType(disposeTypeCode)) + { return false; + } if (!tempVar.IsSingleDefinition) return false; isReference = true; - if (!MatchDisposeCheck(tempVar, checkInst, isReference, usingNull, out int numObjVarLoadsInCheck, disposeMethodFullName, disposeTypeCode)) - return false; - if (tempVar.LoadCount != numObjVarLoadsInCheck) - return false; + pos++; + objVar = tempVar; } - else + // if (comp(ldloc obj != ldnull)) Block IL_001a { + // callvirt Dispose(obj) + // } + var checkInst = entryPoint.Instructions.ElementAtOrDefault(pos); + if (checkInst == null) + return false; + if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, + out int numObjVarLoadsInCheck, disposeMethodFullName, disposeTypeCode)) { - if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, out _, disposeMethodFullName, disposeTypeCode)) - return false; + return false; } - if (!entryPoint.Instructions[leaveIndex].MatchLeave(container, out var returnValue) || !returnValue.MatchNop()) + // make sure the (optional) temporary is used only in the dispose check + if (pos > 0 && objVar.LoadCount != numObjVarLoadsInCheck) return false; - return true; + pos++; + // make sure, the finally ends in a leave(nop) instruction. + if (!entryPoint.Instructions.ElementAtOrDefault(pos).MatchLeave(container, out var returnValue)) + return false; + if (!returnValue.MatchNop()) + return false; + // leave is the last instruction + return (pos + 1) == entryPoint.Instructions.Count; } - bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, out int numObjVarLoadsInCheck, string disposeMethodFullName, KnownTypeCode disposeTypeCode) + bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, + out int numObjVarLoadsInCheck, string disposeMethodFullName, KnownTypeCode disposeTypeCode) { numObjVarLoadsInCheck = 2; ILInstruction disposeInvocation; @@ -283,7 +316,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // reference types have a null check. if (!checkInst.MatchIfInstruction(out var condition, out var disposeInst)) return false; - if (!condition.MatchCompNotEquals(out var left, out var right) || !left.MatchLdLoc(objVar) || !right.MatchLdNull()) + if (!MatchNullCheckOrTypeCheck(condition, ref objVar, disposeTypeCode)) return false; if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1) return false; @@ -348,6 +381,32 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } + bool MatchNullCheckOrTypeCheck(ILInstruction condition, ref ILVariable objVar, KnownTypeCode disposeType) + { + if (condition.MatchCompNotEquals(out var left, out var right)) + { + if (!left.MatchLdLoc(objVar) || !right.MatchLdNull()) + return false; + return true; + } + if (condition is MatchInstruction + { + CheckNotNull: true, + CheckType: true, + TestedOperand: LdLoc { Variable: var v }, + Variable: var newObjVar + }) + { + if (v != objVar) + return false; + if (!newObjVar.Type.IsKnownType(disposeType)) + return false; + objVar = newObjVar; + return true; + } + return false; + } + /// /// stloc test(resourceExpression) /// .try BlockContainer { @@ -371,7 +430,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// private bool TransformAsyncUsing(Block block, int i) { - if (i < 1 || !context.Settings.AsyncUsingAndForEachStatement) + if (!context.Settings.AsyncUsingAndForEachStatement) + return false; + if (i < 1 || i >= block.Instructions.Count) return false; if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst)) return false;