Browse Source

Add support for value type patterns

pull/2461/head
Siegfried Pammer 4 years ago
parent
commit
83727ea4b0
  1. 144
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs
  2. 3
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  4. 10
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  5. 115
      ICSharpCode.Decompiler/IL/Transforms/PatternMatchingRefTypesTransform.cs
  6. 200
      ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs

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

@ -60,11 +60,155 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -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<T>(object x)
{
if (x is T t)
{
Console.WriteLine(typeof(T).FullName + ": " + t);
}
else
{
Console.WriteLine("not a " + typeof(T).FullName);
}
}
public void SimpleTypePatternGenericRefType<T>(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<T>(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)
{
}

3
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

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

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -93,6 +93,7 @@ @@ -93,6 +93,7 @@
<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" />

10
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -589,8 +589,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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)

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

@ -0,0 +1,115 @@ @@ -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
{
/// <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] 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;
}
}
}

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

@ -18,99 +18,165 @@ @@ -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
{
/// <summary>
/// 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))
/// </summary>
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<BlockContainer>())
{
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;
}
}
}

Loading…
Cancel
Save