Browse Source

Add documentation to InlineReturnTransform and SwitchOnStringTransform

pull/887/head
Siegfried Pammer 9 years ago
parent
commit
ef4dd6431e
  1. 88
      ICSharpCode.Decompiler/IL/Transforms/InlineReturnTransform.cs
  2. 43
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

88
ICSharpCode.Decompiler/IL/Transforms/InlineReturnTransform.cs

@ -23,61 +23,73 @@ using System.Linq;
namespace ICSharpCode.Decompiler.IL.Transforms namespace ICSharpCode.Decompiler.IL.Transforms
{ {
/// <summary> /// <summary>
/// This transform duplicates return blocks if they return a local variable that was assigned right before thie return. /// This transform duplicates return blocks if they return a local variable that was assigned right before the return.
/// </summary> /// </summary>
class InlineReturnTransform : IILTransform class InlineReturnTransform : IILTransform
{ {
public void Run(ILFunction function, ILTransformContext context) public void Run(ILFunction function, ILTransformContext context)
{ {
var instructionsToModify = new List<(BlockContainer, Block, Branch)>(); var instructionsToModify = new List<(BlockContainer, Block, Branch)>();
var possibleReturnVars = new Queue<(ILVariable, Block)>();
var tempList = new List<(BlockContainer, Block, Branch)>();
// Process all leave instructions in a leave-block, that is a block consisting solely of a leave instruction.
foreach (var leave in function.Descendants.OfType<Leave>()) { foreach (var leave in function.Descendants.OfType<Leave>()) {
if (!(leave.Parent is Block b && b.Instructions.Count == 1)) if (!(leave.Parent is Block leaveBlock && leaveBlock.Instructions.Count == 1))
continue; continue;
// Skip, if the leave instruction has no value or the value is not a load of a local variable.
if (!leave.Value.MatchLdLoc(out var returnVar) || returnVar.Kind != VariableKind.Local) if (!leave.Value.MatchLdLoc(out var returnVar) || returnVar.Kind != VariableKind.Local)
continue; continue;
possibleReturnVars.Enqueue((returnVar, b)); // If all instructions can be modified, add item to the global list.
if (CanModifyInstructions(returnVar, leaveBlock, out var list));
instructionsToModify.AddRange(list);
} }
while (possibleReturnVars.Count > 0) { foreach (var (container, b, br) in instructionsToModify) {
var (returnVar, leaveBlock) = possibleReturnVars.Dequeue(); Block block = b;
bool transform = true; // if there is only one branch to this return block, move it to the matching container.
foreach (StLoc store in returnVar.StoreInstructions.OfType<StLoc>()) { // otherwise duplicate the return block.
if (!(store.Parent is Block storeBlock)) { if (block.IncomingEdgeCount == 1) {
transform = false; block.Remove();
break; } else {
} block = (Block)block.Clone();
if (store.ChildIndex + 2 != storeBlock.Instructions.Count) {
transform = false;
break;
}
if (!(storeBlock.Instructions[store.ChildIndex + 1] is Branch br)) {
transform = false;
break;
}
if (br.TargetBlock != leaveBlock) {
transform = false;
break;
}
var targetBlockContainer = BlockContainer.FindClosestContainer(store);
if (targetBlockContainer == null) {
transform = false;
break;
}
tempList.Add((targetBlockContainer, leaveBlock, br));
} }
if (transform) container.Blocks.Add(block);
instructionsToModify.AddRange(tempList); // adjust the target of the branch to the newly created block.
tempList.Clear(); br.TargetBlock = block;
} }
}
foreach (var (container, block, br) in instructionsToModify) { /// <summary>
var newBlock = (Block)block.Clone(); /// Determines a list of all store instructions that write to a given <paramref name="returnVar"/>.
container.Blocks.Add(newBlock); /// Returns false if any of these instructions does not meet the following criteria:
br.TargetBlock = newBlock; /// - must be a stloc
/// - must be a direct child of a block
/// - must be the penultimate instruction
/// - must be followed by a branch instruction to <paramref name="leaveBlock"/>
/// - must have a BlockContainer as ancestor.
/// Returns true, if all instructions meet these criteria, and <paramref name="instructionsToModify"/> contains a list of 3-tuples.
/// Each tuple consists of the target block container, the leave block, and the branch instruction that should be modified.
/// </summary>
static bool CanModifyInstructions(ILVariable returnVar, Block leaveBlock, out List<(BlockContainer, Block, Branch)> instructionsToModify)
{
instructionsToModify = new List<(BlockContainer, Block, Branch)>();
foreach (var inst in returnVar.StoreInstructions) {
if (!(inst is StLoc store))
return false;
if (!(store.Parent is Block storeBlock))
return false;
if (store.ChildIndex + 2 != storeBlock.Instructions.Count)
return false;
if (!(storeBlock.Instructions[store.ChildIndex + 1] is Branch br))
return false;
if (br.TargetBlock != leaveBlock)
return false;
var targetBlockContainer = BlockContainer.FindClosestContainer(store);
if (targetBlockContainer == null)
return false;
instructionsToModify.Add((targetBlockContainer, leaveBlock, br));
} }
return true;
} }
} }
} }

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

@ -25,6 +25,9 @@ using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms namespace ICSharpCode.Decompiler.IL.Transforms
{ {
/// <summary>
/// Detects switch-on-string patterns employed by the C# compiler and transforms them to an ILAst-switch-instruction.
/// </summary>
class SwitchOnStringTransform : IILTransform class SwitchOnStringTransform : IILTransform
{ {
public void Run(ILFunction function, ILTransformContext context) public void Run(ILFunction function, ILTransformContext context)
@ -78,6 +81,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
inst = null; inst = null;
blockAfterSwitch = null; blockAfterSwitch = null;
// match first block: checking switch-value for null or first value (Roslyn) // match first block: checking switch-value for null or first value (Roslyn)
// if (call op_Equality(ldloc switchValueVar, ldstr value)) br firstBlock
// -or-
// if (comp(ldloc switchValueVar == ldnull)) br defaultBlock
if (!(instructions[i].MatchIfInstruction(out var condition, out var firstBlockJump))) if (!(instructions[i].MatchIfInstruction(out var condition, out var firstBlockJump)))
return false; return false;
if (!firstBlockJump.MatchBranch(out var firstBlock)) if (!firstBlockJump.MatchBranch(out var firstBlock))
@ -85,32 +91,43 @@ namespace ICSharpCode.Decompiler.IL.Transforms
bool isLegacy; bool isLegacy;
Block defaultBlock; Block defaultBlock;
List<(string, Block)> values = new List<(string, Block)>(); List<(string, Block)> values = new List<(string, Block)>();
// match null check: this is used by the old C# compiler.
if (condition.MatchCompEquals(out var left, out var right) && right.MatchLdNull() && left.MatchLdLoc(out var switchValueVar)) { if (condition.MatchCompEquals(out var left, out var right) && right.MatchLdNull() && left.MatchLdLoc(out var switchValueVar)) {
isLegacy = true; isLegacy = true;
defaultBlock = firstBlock; defaultBlock = firstBlock;
// Roslyn: match call to operator ==(string, string)
} else if (MatchStringEqualityComparison(condition, out switchValueVar, out string value)) { } else if (MatchStringEqualityComparison(condition, out switchValueVar, out string value)) {
isLegacy = false; isLegacy = false;
defaultBlock = null; defaultBlock = null;
values.Add((value, firstBlock)); values.Add((value, firstBlock));
} else return false; } else return false;
// switchValueVar must be assigned only once.
if (!switchValueVar.IsSingleDefinition) if (!switchValueVar.IsSingleDefinition)
return false; return false;
// if instruction must be followed by a branch to the next case
if (!(instructions.ElementAtOrDefault(i + 1) is Branch nextCaseJump)) if (!(instructions.ElementAtOrDefault(i + 1) is Branch nextCaseJump))
return false; return false;
// extract all cases and add them to the values list.
Block currentCaseBlock = nextCaseJump.TargetBlock; Block currentCaseBlock = nextCaseJump.TargetBlock;
Block nextCaseBlock; Block nextCaseBlock;
while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, ref switchValueVar, out string value, out Block block)) != null) { while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, ref switchValueVar, out string value, out Block block)) != null) {
values.Add((value, block)); values.Add((value, block));
currentCaseBlock = nextCaseBlock; currentCaseBlock = nextCaseBlock;
} }
// the last instruction of the last case/default block must be either
// a branch to the a block after the switch statement or a leave instruction.
var container = BlockContainer.FindClosestContainer(firstBlock); var container = BlockContainer.FindClosestContainer(firstBlock);
if (!ExtractLastJumpFromBlock(currentCaseBlock, out var exitBlock) && !ExtractLastLeaveFromBlock(currentCaseBlock, container)) if (!ExtractLastJumpFromBlock(currentCaseBlock, out var exitBlock) && !ExtractLastLeaveFromBlock(currentCaseBlock, container))
return false; return false;
// We didn't find any cases, exit
if (values.Count == 0) if (values.Count == 0)
return false; return false;
// All case blocks must either leave the current block container or branch to the same block after the switch statement.
if (!(values.All(b => ExtractLastJumpFromBlock(b.Item2, out var nextExit) && IsExitBlock(nextExit, container)) || if (!(values.All(b => ExtractLastJumpFromBlock(b.Item2, out var nextExit) && IsExitBlock(nextExit, container)) ||
(exitBlock == null && values.All(b => ExtractLastLeaveFromBlock(b.Item2, container))))) (exitBlock == null && values.All(b => ExtractLastLeaveFromBlock(b.Item2, container)))))
return false; return false;
// if the block after the switch has the correct number of branches, generate the switch statement
// and return it and the block.
if (currentCaseBlock.IncomingEdgeCount == (isLegacy ? 2 : 1)) { if (currentCaseBlock.IncomingEdgeCount == (isLegacy ? 2 : 1)) {
var sections = new List<SwitchSection>(values.SelectWithIndex((index, b) => new SwitchSection { Labels = new LongSet(index), Body = new Branch(b.Item2) })); var sections = new List<SwitchSection>(values.SelectWithIndex((index, b) => new SwitchSection { Labels = new LongSet(index), Body = new Branch(b.Item2) }));
var stringToInt = new StringToInt(new LdLoc(switchValueVar), values.SelectArray(item => item.Item1)); var stringToInt = new StringToInt(new LdLoc(switchValueVar), values.SelectArray(item => item.Item1));
@ -146,6 +163,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return b == container; return b == container;
} }
/// <summary>
/// Each case consists of two blocks:
/// 1. block:
/// if (call op_Equality(ldloc switchVariable, ldstr value)) br caseBlock
/// br nextBlock
/// This method matches the above pattern or its inverted form:
/// the call to ==(string, string) is wrapped in logic.not and the branch targets are reversed.
/// Returns the next block that follows in the block-chain.
/// The <paramref name="switchVariable"/> is updated if the value gets copied to a different variable.
/// See comments below for more info.
/// </summary>
Block MatchCaseBlock(Block currentBlock, ref ILVariable switchVariable, out string value, out Block caseBlock) Block MatchCaseBlock(Block currentBlock, ref ILVariable switchVariable, out string value, out Block caseBlock)
{ {
value = null; value = null;
@ -167,10 +195,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!currentBlock.Instructions[1].MatchBranch(out nextBlock)) if (!currentBlock.Instructions[1].MatchBranch(out nextBlock))
return null; return null;
} }
// Sometimes the switch pattern uses one variable at the beginning for null checks
// and another variable for the if-else-if-else-pattern.
// both variables must be only assigned once and be of the type: System.String.
if (!MatchStringEqualityComparison(condition, out var newSwitchVariable, out value)) if (!MatchStringEqualityComparison(condition, out var newSwitchVariable, out value))
return null; return null;
if (!newSwitchVariable.IsSingleDefinition) if (!newSwitchVariable.IsSingleDefinition)
return null; return null;
// if the used variable differs and both variables are not related, return null:
if (switchVariable != newSwitchVariable && !(IsInitializedBy(switchVariable, newSwitchVariable) || IsInitializedBy(newSwitchVariable, switchVariable))) if (switchVariable != newSwitchVariable && !(IsInitializedBy(switchVariable, newSwitchVariable) || IsInitializedBy(newSwitchVariable, switchVariable)))
return null; return null;
if (!newSwitchVariable.Type.IsKnownType(KnownTypeCode.String)) if (!newSwitchVariable.Type.IsKnownType(KnownTypeCode.String))
@ -179,14 +211,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return nextBlock; return nextBlock;
} }
bool IsInitializedBy(ILVariable switchVariable, ILVariable newSwitchVariable) /// <summary>
/// Returns true if <paramref name="left"/> is only assigned once and the initialization is done by copying <paramref name="right"/>.
/// </summary>
bool IsInitializedBy(ILVariable left, ILVariable right)
{ {
if (!switchVariable.IsSingleDefinition) if (!left.IsSingleDefinition)
return false; return false;
var storeInst = switchVariable.StoreInstructions.OfType<StLoc>().SingleOrDefault(); var storeInst = left.StoreInstructions.OfType<StLoc>().SingleOrDefault();
if (storeInst == null) if (storeInst == null)
return false; return false;
return storeInst.Value.MatchLdLoc(newSwitchVariable); return storeInst.Value.MatchLdLoc(right);
} }
bool MatchLegacySwitchOnString(InstructionCollection<ILInstruction> instructions, int i, out SwitchInstruction inst, out Block blockAfterSwitch) bool MatchLegacySwitchOnString(InstructionCollection<ILInstruction> instructions, int i, out SwitchInstruction inst, out Block blockAfterSwitch)

Loading…
Cancel
Save