Browse Source

Add new switch(string) pattern for Roslyn.

pull/1425/head
Siegfried Pammer 6 years ago
parent
commit
9c62f11e51
  1. 124
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

124
ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.Decompiler.IL.ControlFlow;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
@ -44,6 +45,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -44,6 +45,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
foreach (var block in function.Descendants.OfType<Block>()) {
bool changed = false;
if (block.IncomingEdgeCount == 0)
continue;
for (int i = block.Instructions.Count - 1; i >= 0; i--) {
if (SimplifyCascadingIfStatements(block.Instructions, ref i)) {
changed = true;
@ -782,58 +785,114 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -782,58 +785,114 @@ namespace ICSharpCode.Decompiler.IL.Transforms
bool MatchRoslynSwitchOnString(InstructionCollection<ILInstruction> instructions, ref int i)
{
if (i < 1) return false;
if (i >= instructions.Count - 1) return false;
// stloc switchValueVar(switchValue)
// if (comp(ldloc switchValueVar == ldnull)) br nullCase
// br nextBlock
InstructionCollection<ILInstruction> switchBlockInstructions = instructions;
int switchBlockInstructionsOffset = i;
Block nullValueCaseBlock = null;
if (instructions[i].MatchIfInstruction(out var condition, out var exitBlockJump)
&& condition.MatchCompEquals(out var left, out var right) && right.MatchLdNull())
{
var nextBlockJump = instructions[i + 1] as Branch;
if (nextBlockJump == null || nextBlockJump.TargetBlock.IncomingEdgeCount != 1)
return false;
if (!exitBlockJump.MatchBranch(out nullValueCaseBlock))
return false;
switchBlockInstructions = nextBlockJump.TargetBlock.Instructions;
switchBlockInstructionsOffset = 0;
}
// stloc switchValueVar(call ComputeStringHash(switchValue))
// switch (ldloc switchValueVar) {
// case [211455823..211455824): br caseBlock1
// ... more cases ...
// case [long.MinValue..-365098645),...,[1697255802..long.MaxValue]: br defaultBlock
// }
if (!(instructions[i] is SwitchInstruction switchInst && switchInst.Value.MatchLdLoc(out var switchValueVar) &&
MatchComputeStringHashCall(instructions[i - 1], switchValueVar, out LdLoc switchValueLoad)))
if (!(switchBlockInstructionsOffset + 1 < switchBlockInstructions.Count && switchBlockInstructions[switchBlockInstructionsOffset + 1] is SwitchInstruction switchInst && switchInst.Value.MatchLdLoc(out var switchValueVar) &&
MatchComputeStringHashCall(switchBlockInstructions[switchBlockInstructionsOffset], switchValueVar, out LdLoc switchValueLoad)))
return false;
var stringValues = new List<(int, string, Block)>();
int index = 0;
SwitchSection defaultSection = switchInst.Sections.MaxBy(s => s.Labels.Count());
Block exitOrDefaultBlock = null;
foreach (var section in switchInst.Sections) {
if (section == defaultSection) continue;
// extract target block
if (!section.Body.MatchBranch(out Block target))
return false;
if (!MatchRoslynCaseBlockHead(target, switchValueLoad.Variable, out Block body, out string stringValue))
if (!MatchRoslynCaseBlockHead(target, switchValueLoad.Variable, out Block body, out Block currentExitBlock, out string stringValue))
return false;
if (exitOrDefaultBlock != null && exitOrDefaultBlock != currentExitBlock)
return false;
exitOrDefaultBlock = currentExitBlock;
stringValues.Add((index++, stringValue, body));
}
ILInstruction switchValueInst = switchValueLoad;
// stloc switchValueLoadVariable(switchValue)
// stloc switchValueVar(call ComputeStringHash(ldloc switchValueLoadVariable))
// switch (ldloc switchValueVar) {
bool keepAssignmentBefore;
// if the switchValueLoad.Variable is only used in the compiler generated case equality checks, we can remove it.
if (i > 1 && instructions[i - 2].MatchStLoc(switchValueLoad.Variable, out var switchValueTmp) &&
switchValueLoad.Variable.IsSingleDefinition && switchValueLoad.Variable.LoadCount == switchInst.Sections.Count)
{
switchValueInst = switchValueTmp;
keepAssignmentBefore = false;
} else {
keepAssignmentBefore = true;
if (nullValueCaseBlock != null && exitOrDefaultBlock != nullValueCaseBlock) {
stringValues.Add((index++, null, nullValueCaseBlock));
}
var defaultLabel = new LongSet(new LongInterval(0, index)).Invert();
var newSwitch = new SwitchInstruction(new StringToInt(switchValueInst, stringValues.Select(item => item.Item2).ToArray()));
newSwitch.Sections.AddRange(stringValues.Select(section => new SwitchSection { Labels = new Util.LongSet(section.Item1), Body = new Branch(section.Item3) }));
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = defaultSection.Body });
instructions[i].ReplaceWith(newSwitch);
if (keepAssignmentBefore) {
newSwitch.AddILRange(instructions[i - 1]);
instructions.RemoveAt(i - 1);
i--;
ILInstruction switchValueInst = switchValueLoad;
if (instructions == switchBlockInstructions) {
// stloc switchValueLoadVariable(switchValue)
// stloc switchValueVar(call ComputeStringHash(ldloc switchValueLoadVariable))
// switch (ldloc switchValueVar) {
bool keepAssignmentBefore;
// if the switchValueLoad.Variable is only used in the compiler generated case equality checks, we can remove it.
if (i >= 1 && instructions[i - 1].MatchStLoc(switchValueLoad.Variable, out var switchValueTmp) &&
switchValueLoad.Variable.IsSingleDefinition && switchValueLoad.Variable.LoadCount == switchInst.Sections.Count) {
switchValueInst = switchValueTmp;
keepAssignmentBefore = false;
} else {
keepAssignmentBefore = true;
}
// replace stloc switchValueVar(call ComputeStringHash(...)) with new switch instruction
var newSwitch = ReplaceWithSwitchInstruction(i);
// remove old switch instruction
newSwitch.AddILRange(instructions[i + 1]);
instructions.RemoveAt(i + 1);
// remove extra assignment
if (!keepAssignmentBefore) {
newSwitch.AddILRange(instructions[i - 1]);
instructions.RemoveRange(i - 1, 1);
i -= 1;
}
} else {
newSwitch.AddILRange(instructions[i - 2]);
instructions.RemoveRange(i - 2, 2);
i -= 2;
bool keepAssignmentBefore;
// if the switchValueLoad.Variable is only used in the compiler generated case equality checks, we can remove it.
if (i >= 2 && instructions[i - 2].MatchStLoc(out var temporary, out var temporaryValue) && instructions[i - 1].MatchStLoc(switchValueLoad.Variable, out var tempLoad) && tempLoad.MatchLdLoc(temporary)) {
switchValueInst = temporaryValue;
keepAssignmentBefore = false;
} else {
keepAssignmentBefore = true;
}
// replace null check with new switch instruction
var newSwitch = ReplaceWithSwitchInstruction(i);
newSwitch.AddILRange(switchInst);
// remove jump instruction to switch block
newSwitch.AddILRange(instructions[i + 1]);
instructions.RemoveAt(i + 1);
// remove extra assignment
if (!keepAssignmentBefore) {
newSwitch.AddILRange(instructions[i - 2]);
instructions.RemoveRange(i - 2, 2);
i -= 2;
}
}
return true;
SwitchInstruction ReplaceWithSwitchInstruction(int offset)
{
var defaultLabel = new LongSet(new LongInterval(0, index)).Invert();
var newSwitch = new SwitchInstruction(new StringToInt(switchValueInst, stringValues.Select(item => item.Item2).ToArray()));
newSwitch.Sections.AddRange(stringValues.Select(section => new SwitchSection { Labels = new LongSet(section.Item1), Body = new Branch(section.Item3) }));
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = defaultSection.Body });
instructions[offset].ReplaceWith(newSwitch);
return newSwitch;
}
}
/// <summary>
@ -841,9 +900,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -841,9 +900,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// if (call op_Equality(ldloc V_0, ldstr "Fifth case")) br body
/// br exit
/// </summary>
bool MatchRoslynCaseBlockHead(Block target, ILVariable switchValueVar, out Block body, out string stringValue)
bool MatchRoslynCaseBlockHead(Block target, ILVariable switchValueVar, out Block body, out Block defaultOrExitBlock, out string stringValue)
{
body = null;
defaultOrExitBlock = null;
stringValue = null;
if (target.Instructions.Count != 2)
return false;
@ -851,11 +911,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -851,11 +911,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (MatchStringEqualityComparison(condition, switchValueVar, out stringValue)) {
var exitBranch = target.Instructions[1];
if (!(exitBranch.MatchBranch(out _) || exitBranch.MatchLeave(out _)))
if (!(exitBranch.MatchBranch(out defaultOrExitBlock) || exitBranch.MatchLeave(out _)))
return false;
return bodyBranch.MatchBranch(out body) && body != null;
} else if (condition.MatchLogicNot(out condition) && MatchStringEqualityComparison(condition, switchValueVar, out stringValue)) {
if (!(bodyBranch.MatchBranch(out _) || bodyBranch.MatchLeave(out _)))
if (!(bodyBranch.MatchBranch(out defaultOrExitBlock) || bodyBranch.MatchLeave(out _)))
return false;
return target.Instructions[1].MatchBranch(out body) && body != null;
} else {

Loading…
Cancel
Save