From 83727ea4b06df2006a210fdc0a8e53168c3d8daa Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 25 Jul 2021 13:58:23 +0200 Subject: [PATCH] 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; } } }