Browse Source

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.

pull/2461/head
Siegfried Pammer 4 years ago
parent
commit
dbbcbb87fe
  1. 13
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs
  2. 1
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  4. 54
      ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs
  5. 41
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  6. 128
      ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs
  7. 306
      ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs

13
ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs

@ -194,6 +194,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -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;

1
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -138,7 +138,6 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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(),

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -93,7 +93,6 @@ @@ -93,7 +93,6 @@
<Compile Include="IL\ControlFlow\AwaitInFinallyTransform.cs" />
<Compile Include="IL\Transforms\IntroduceNativeIntTypeOnLocals.cs" />
<Compile Include="IL\Transforms\LdLocaDupInitObjTransform.cs" />
<Compile Include="IL\Transforms\PatternMatchingRefTypesTransform.cs" />
<Compile Include="IL\Transforms\PatternMatchingTransform.cs" />
<Compile Include="IL\Transforms\RemoveInfeasiblePathTransform.cs" />
<Compile Include="Metadata\ReferenceLoadInfo.cs" />

54
ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs

@ -40,6 +40,60 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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);

41
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -113,32 +113,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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 @@ -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)

128
ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs

@ -1,128 +0,0 @@ @@ -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
{
/// <summary>
/// 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))
/// </summary>
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;
}
}
}

306
ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs

@ -18,16 +18,216 @@ @@ -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<BlockContainer>())
{
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<ILInstruction>(v.AddressInstructions)
.Concat(v.StoreInstructions.Cast<ILInstruction>());
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 @@ -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<BlockContainer>())
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 @@ -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;

Loading…
Cancel
Save