diff --git a/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs index 1fdfe7bb4..cbffd4c39 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs @@ -26,6 +26,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { /// /// If possible, transforms plain ILAst loops into while (condition), do-while and for-loops. + /// For the invariants of the transforms . /// public class HighLevelLoopTransform : IILTransform { @@ -69,9 +70,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (trueInst is Block b) { context.Step("Transform to while (condition) loop", loop); + // move the loop body to its own block: loopBody = b; trueInst.ReplaceWith(new Branch(loopBody)); loop.Blocks.Insert(1, loopBody); + // sometimes the loop-content-block does not end with a leave/branch if (!loopBody.HasFlag(InstructionFlags.EndPointUnreachable)) loopBody.Instructions.Add(new Leave(loop)); } else if (trueInst is Branch br) { @@ -80,111 +83,214 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else { return false; } + // move the branch/leave instruction into the condition block ifInstruction.FalseInst = loop.EntryPoint.Instructions[1]; loop.EntryPoint.Instructions.RemoveAt(1); loop.Kind = ContainerKind.While; return true; } + /// + /// Matches a do-while loop and performs the following transformations: + /// - combine all compatible conditions into one IfInstruction. + /// - extract conditions into a condition block, or move the existing condition block to the end. + /// + /// + /// bool MatchDoWhileLoop(BlockContainer loop) { - if (loop.EntryPoint.Instructions.Count < 2) + (List conditions, ILInstruction exit, bool swap, bool split) = AnalyzeDoWhileConditions(loop); + // not a do-while loop, exit. + if (conditions == null || conditions.Count == 0) return false; - var last = loop.EntryPoint.Instructions.Last(); - var ifInstruction = loop.EntryPoint.Instructions.SecondToLastOrDefault() as IfInstruction; + context.Step("Transform to do-while loop", loop); + Block conditionBlock; + // first we remove all extracted instructions from the original block. + var originalBlock = (Block)exit.Parent; + originalBlock.Instructions.RemoveRange(originalBlock.Instructions.Count - conditions.Count - 1, conditions.Count + 1); + // we need to split the block: + if (split) { + // add a new block at the end and add a branch to the new block. + conditionBlock = new Block(); + loop.Blocks.Add(conditionBlock); + originalBlock.Instructions.Add(new Branch(conditionBlock)); + } else { + // move the condition block to the end. + conditionBlock = originalBlock; + loop.Blocks.MoveElementToEnd(originalBlock); + } + // combine all conditions and the exit instruction into one IfInstruction: + IfInstruction condition = null; + conditionBlock.AddILRange(exit.ILRange); + foreach (var inst in conditions) { + conditionBlock.AddILRange(inst.ILRange); + if (condition == null) { + condition = inst; + if (swap) { + // branches must be swapped and condition negated: + condition.Condition = Comp.LogicNot(condition.Condition); + condition.FalseInst = condition.TrueInst; + condition.TrueInst = exit; + } else { + condition.FalseInst = exit; + } + } else { + if (swap) { + condition.Condition = IfInstruction.LogicAnd(Comp.LogicNot(inst.Condition), condition.Condition); + } else { + condition.Condition = IfInstruction.LogicAnd(inst.Condition, condition.Condition); + } + } + } + // insert the combined conditions into the condition block: + conditionBlock.Instructions.Add(condition); + // simplify the condition: + new ExpressionTransforms().Run(conditionBlock, condition.ChildIndex, new StatementTransformContext(new BlockTransformContext(context))); + // transform complete + loop.Kind = ContainerKind.DoWhile; + return true; + } + + static (List conditions, ILInstruction exit, bool swap, bool split) AnalyzeDoWhileConditions(BlockContainer loop) + { + bool swap; + // we iterate over all blocks from the bottom, because the entry-point + // should only be considered as condition block, if there are no other blocks. + foreach (var block in loop.Blocks.Reverse()) { + // first we match the end of the block: + if (MatchDoWhileConditionBlock(loop, block, out swap)) { + // now collect all instructions that are usable as loop conditions + var conditions = CollectConditions(loop, block, swap); + // split only if the block is either the entry-point or contains other instructions as well. + var split = block == loop.EntryPoint || block.Instructions.Count > conditions.Count + 1; // + 1 is the final leave/branch. + return (conditions, block.Instructions.Last(), swap, split); + } + } + return (null, null, false, false); + } + + /// + /// Returns a list of all IfInstructions that can be used as loop conditon, i.e., + /// that have no false-instruction and have leave loop (if swapped) or branch entry-point as true-instruction. + /// + static List CollectConditions(BlockContainer loop, Block block, bool swap) + { + var list = new List(); + int i = block.Instructions.Count - 2; + while (i >= 0 && block.Instructions[i] is IfInstruction ifInst) { + if (!ifInst.FalseInst.MatchNop()) + break; + if (swap) { + if (!ifInst.TrueInst.MatchLeave(loop)) + break; + list.Add(ifInst); + } else { + if (!ifInst.TrueInst.MatchBranch(loop.EntryPoint)) + break; + list.Add(ifInst); + } + i--; + } + + return list; + } + + static bool MatchDoWhileConditionBlock(BlockContainer loop, Block block, out bool swapBranches) + { + // match the end of the block: + // if (condition) branch entry-point else nop + // leave loop + // -or- + // if (condition) leave loop else nop + // branch entry-point + swapBranches = false; + // empty block? + if (block.Instructions.Count < 2) + return false; + var last = block.Instructions.Last(); + var ifInstruction = block.Instructions.SecondToLastOrDefault() as IfInstruction; + // no IfInstruction or already transformed? if (ifInstruction == null || !ifInstruction.FalseInst.MatchNop()) return false; - bool swapBranches; + // if the last instruction is a branch + // we assume the branch instructions need to be swapped. if (last.MatchBranch(loop.EntryPoint)) swapBranches = true; else if (last.MatchLeave(loop)) swapBranches = false; else return false; + // match the IfInstruction if (swapBranches) { if (!ifInstruction.TrueInst.MatchLeave(loop)) return false; - context.Step("Transform to do-while loop", loop); - ifInstruction.FalseInst = ifInstruction.TrueInst; - ifInstruction.TrueInst = last; - ifInstruction.Condition = Comp.LogicNot(ifInstruction.Condition); } else { if (!ifInstruction.TrueInst.MatchBranch(loop.EntryPoint)) return false; - context.Step("Transform to do-while loop", loop); - ifInstruction.FalseInst = last; } - int i = loop.EntryPoint.Instructions.Count - 3; - var conditions = new List(); - while (i >= 0 && loop.EntryPoint.Instructions[i] is IfInstruction ifInstruction2) { - if (!ifInstruction2.FalseInst.MatchNop()) - break; - if (swapBranches) { - if (!ifInstruction2.TrueInst.MatchLeave(loop)) - break; - conditions.Add(Comp.LogicNot(ifInstruction2.Condition)); - } else { - if (!ifInstruction2.TrueInst.MatchBranch(loop.EntryPoint)) - break; - conditions.Add(ifInstruction2.Condition); - } - i--; - } - Block conditionBlock = new Block(); - loop.Blocks.Add(conditionBlock); - loop.EntryPoint.Instructions.RemoveRange(i + 1, conditions.Count + 2); - foreach (var inst in conditions) { - ifInstruction.Condition = IfInstruction.LogicAnd(ifInstruction.Condition, inst); - conditionBlock.AddILRange(inst.ILRange); - } - conditionBlock.Instructions.Add(ifInstruction); - conditionBlock.AddILRange(last.ILRange); - new ExpressionTransforms().Run(conditionBlock, 0, new StatementTransformContext(new BlockTransformContext(context))); - loop.EntryPoint.Instructions.Add(new Branch(conditionBlock)); - loop.Kind = ContainerKind.DoWhile; return true; } bool MatchForLoop(BlockContainer loop, ILInstruction condition, Block whileLoopBody) { + // for loops have exactly two incoming edges at the entry point. if (loop.EntryPoint.IncomingEdgeCount != 2) return false; + // try to find an increment block: + // consists of simple statements only. var incrementBlock = loop.Blocks.SingleOrDefault( b => b != whileLoopBody && b.Instructions.Last().MatchBranch(loop.EntryPoint) && b.Instructions.SkipLast(1).All(IsSimpleStatement)); if (incrementBlock != null) { + // we found a possible increment block, just make sure, that there are at least three blocks: + // - condition block + // - loop body + // - increment block if (incrementBlock.Instructions.Count <= 1 || loop.Blocks.Count < 3) return false; context.Step("Transform to for loop", loop); + // move the block to the end of the loop: loop.Blocks.MoveElementToEnd(incrementBlock); loop.Kind = ContainerKind.For; } else { + // we need to move the increment statements into its own block: + // last must be a branch entry-point var last = whileLoopBody.Instructions.LastOrDefault(); var secondToLast = whileLoopBody.Instructions.SecondToLastOrDefault(); if (last == null || secondToLast == null) return false; if (!last.MatchBranch(loop.EntryPoint)) return false; + // we only deal with 'numeric' increments if (!MatchIncrement(secondToLast, out var incrementVariable)) return false; + // the increment variable must be local/stack variable if (incrementVariable.Kind == VariableKind.Parameter) return false; + // the increment variable must be checked in the condition if (!condition.Descendants.Any(inst => inst.MatchLdLoc(incrementVariable))) return false; context.Step("Transform to for loop", loop); + // create a new increment block and add it at the end: int secondToLastIndex = secondToLast.ChildIndex; var newIncremenBlock = new Block(); loop.Blocks.Add(newIncremenBlock); + // move the increment instruction: newIncremenBlock.Instructions.Add(secondToLast); newIncremenBlock.Instructions.Add(last); newIncremenBlock.AddILRange(secondToLast.ILRange); whileLoopBody.Instructions.RemoveRange(secondToLastIndex, 2); whileLoopBody.Instructions.Add(new Branch(newIncremenBlock)); + // complete transform. loop.Kind = ContainerKind.For; } return true; } + /// + /// Returns true if the instruction is stloc v(add(ldloc v, arg)) + /// or stloc v(compound.assign(ldloc v, arg)) + /// public static bool MatchIncrement(ILInstruction inst, out ILVariable variable) { if (!inst.MatchStLoc(out variable, out var value))