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

Loading…
Cancel
Save