diff --git a/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs index b642d6c80..26ec3c5f8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.Transforms { @@ -33,8 +34,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms foreach (var loop in function.Descendants.OfType()) { if (loop.Kind != ContainerKind.Loop) continue; - if (MatchWhileLoop(loop, out var loopBody)) { - MatchForLoop(loop, loopBody); + if (MatchWhileLoop(loop, out var condition, out var loopBody)) { + MatchForLoop(loop, condition, loopBody); continue; } if (MatchDoWhileLoop(loop)) @@ -42,7 +43,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - bool MatchWhileLoop(BlockContainer loop, out Block loopBody) + bool MatchWhileLoop(BlockContainer loop, out ILInstruction condition, out Block loopBody) { // while-loop: // if (loop-condition) br loop-content-block @@ -50,6 +51,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // -or- // if (loop-condition) block content-block // leave loop-container + condition = null; loopBody = null; if (loop.EntryPoint.Instructions.Count != 2) return false; @@ -57,6 +59,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (!ifInstruction.FalseInst.MatchNop()) return false; + condition = ifInstruction.Condition; var trueInst = ifInstruction.TrueInst; if (!loop.EntryPoint.Instructions[1].MatchLeave(loop)) return false; @@ -79,10 +82,83 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } + bool MatchDoWhileLoop(BlockContainer loop) + { + if (loop.EntryPoint.Instructions.Count < 2) + return false; + var last = loop.EntryPoint.Instructions.Last(); + var ifInstruction = loop.EntryPoint.Instructions.SecondToLastOrDefault() as IfInstruction; + if (!ifInstruction.FalseInst.MatchNop()) + return false; + bool swapBranches = false; + ILInstruction condition = ifInstruction.Condition; + while (condition.MatchLogicNot(out var arg)) { + swapBranches = !swapBranches; + condition = arg; + } + if (swapBranches) { + if (!ifInstruction.TrueInst.MatchLeave(loop)) + return false; + if (!last.MatchBranch(loop.EntryPoint)) + return false; + ifInstruction.FalseInst = ifInstruction.TrueInst; + ifInstruction.TrueInst = last; + ifInstruction.Condition = condition; + } else { + if (!ifInstruction.TrueInst.MatchBranch(loop.EntryPoint)) + return false; + if (!last.MatchLeave(loop)) + return false; + ifInstruction.Condition = condition; + ifInstruction.FalseInst = last; + } + Block conditionBlock = new Block(); + loop.Blocks.Add(conditionBlock); + loop.EntryPoint.Instructions.RemoveRange(ifInstruction.ChildIndex, 2); + conditionBlock.Instructions.Add(ifInstruction); + conditionBlock.AddILRange(ifInstruction.ILRange); + conditionBlock.AddILRange(last.ILRange); + loop.EntryPoint.Instructions.Add(new Branch(conditionBlock)); + loop.Kind = ContainerKind.DoWhile; + return false; + } + + bool MatchForLoop(BlockContainer loop, ILInstruction condition, Block whileLoopBody) + { + if (loop.EntryPoint.IncomingEdgeCount != 2) + return false; + var incrementBlock = loop.Blocks.SingleOrDefault( + b => b.Instructions.Last().MatchBranch(loop.EntryPoint) + && b.Instructions.SkipLast(1).All(IsSimpleStatement)); + if (incrementBlock == null) { + incrementBlock = whileLoopBody; + } + var last = incrementBlock.Instructions.LastOrDefault(); + var secondToLast = incrementBlock.Instructions.SecondToLastOrDefault(); + if (last == null || secondToLast == null) + return false; + if (!last.MatchBranch(loop.EntryPoint)) + return false; + if (!MatchIncrement(secondToLast, out var variable)) + return false; + if (!condition.Children.Any(c => c.MatchLdLoc(variable))) + return false; + int secondToLastIndex = secondToLast.ChildIndex; + var newIncremenBlock = new Block(); + loop.Blocks.Add(newIncremenBlock); + newIncremenBlock.Instructions.Add(secondToLast); + newIncremenBlock.Instructions.Add(last); + newIncremenBlock.AddILRange(secondToLast.ILRange); + incrementBlock.Instructions.RemoveRange(secondToLastIndex, 2); + incrementBlock.Instructions.Add(new Branch(newIncremenBlock)); + loop.Kind = ContainerKind.For; + return true; + } + /// /// The last instruction of the while body must be a branch to the loop head. /// - bool MatchWhileLoopBody(Block loopBody, BlockContainer loop) + static bool MatchWhileLoopBody(Block loopBody, BlockContainer loop) { if (loopBody.Instructions.Count == 0) return false; @@ -91,14 +167,35 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - bool MatchDoWhileLoop(BlockContainer loop) + static bool MatchIncrement(ILInstruction inst, out ILVariable variable) { - return false; + if (!inst.MatchStLoc(out variable, out var value)) + return false; + if (!value.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out var left, out var right)) { + if (value is CompoundAssignmentInstruction cai) { + left = cai.Target; + } else return false; + } + return left.MatchLdLoc(variable); } - bool MatchForLoop(BlockContainer loop, Block whileLoopBody) + /// + /// Gets whether the statement is 'simple' (usable as for loop iterator): + /// Currently we only accept calls and assignments. + /// + static bool IsSimpleStatement(ILInstruction inst) { - return false; + switch (inst.OpCode) { + case OpCode.Call: + case OpCode.CallVirt: + case OpCode.NewObj: + case OpCode.StLoc: + case OpCode.StObj: + case OpCode.CompoundAssignmentInstruction: + return true; + default: + return false; + } } } }