// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Linq; using ICSharpCode.Decompiler.FlowAnalysis; namespace ICSharpCode.Decompiler.ILAst { /// /// Description of LoopsAndConditions. /// public class LoopsAndConditions { Dictionary labelToCfNode = new Dictionary(); DecompilerContext context; uint nextLabelIndex = 0; public LoopsAndConditions(DecompilerContext context) { this.context = context; } public void FindLoops(ILBlock block) { if (block.Body.Count > 0) { ControlFlowGraph graph; graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); graph.ComputeDominance(context.CancellationToken); graph.ComputeDominanceFrontier(); block.Body = FindLoops(new HashSet(graph.Nodes.Skip(3)), graph.EntryPoint, false); } } public void FindConditions(ILBlock block) { if (block.Body.Count > 0) { ControlFlowGraph graph; graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); graph.ComputeDominance(context.CancellationToken); graph.ComputeDominanceFrontier(); block.Body = FindConditions(new HashSet(graph.Nodes.Skip(3)), graph.EntryPoint); } } ControlFlowGraph BuildGraph(List nodes, ILLabel entryLabel) { int index = 0; List cfNodes = new List(); ControlFlowNode entryPoint = new ControlFlowNode(index++, 0, ControlFlowNodeType.EntryPoint); cfNodes.Add(entryPoint); ControlFlowNode regularExit = new ControlFlowNode(index++, -1, ControlFlowNodeType.RegularExit); cfNodes.Add(regularExit); ControlFlowNode exceptionalExit = new ControlFlowNode(index++, -1, ControlFlowNodeType.ExceptionalExit); cfNodes.Add(exceptionalExit); // Create graph nodes labelToCfNode = new Dictionary(); Dictionary astNodeToCfNode = new Dictionary(); foreach(ILBasicBlock node in nodes) { ControlFlowNode cfNode = new ControlFlowNode(index++, -1, ControlFlowNodeType.Normal); cfNodes.Add(cfNode); astNodeToCfNode[node] = cfNode; cfNode.UserData = node; // Find all contained labels foreach(ILLabel label in node.GetSelfAndChildrenRecursive()) { labelToCfNode[label] = cfNode; } } // Entry endge ControlFlowNode entryNode = labelToCfNode[entryLabel]; ControlFlowEdge entryEdge = new ControlFlowEdge(entryPoint, entryNode, JumpType.Normal); entryPoint.Outgoing.Add(entryEdge); entryNode.Incoming.Add(entryEdge); // Create edges foreach(ILBasicBlock node in nodes) { ControlFlowNode source = astNodeToCfNode[node]; // Find all branches foreach(ILLabel target in node.GetSelfAndChildrenRecursive(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) { ControlFlowNode destination; // Labels which are out of out scope will not be int the collection // Insert self edge only if we are sure we are a loop if (labelToCfNode.TryGetValue(target, out destination) && (destination != source || target == node.EntryLabel)) { ControlFlowEdge edge = new ControlFlowEdge(source, destination, JumpType.Normal); source.Outgoing.Add(edge); destination.Incoming.Add(edge); } } } return new ControlFlowGraph(cfNodes.ToArray()); } List FindLoops(HashSet scope, ControlFlowNode entryPoint, bool excludeEntryPoint) { List result = new List(); // Do not modify entry data scope = new HashSet(scope); Queue agenda = new Queue(); agenda.Enqueue(entryPoint); while(agenda.Count > 0) { ControlFlowNode node = agenda.Dequeue(); // If the node is a loop header if (scope.Contains(node) && node.DominanceFrontier.Contains(node) && (node != entryPoint || !excludeEntryPoint)) { HashSet loopContents = FindLoopContent(scope, node); // If the first expression is a loop condition ILBasicBlock basicBlock = (ILBasicBlock)node.UserData; ILExpression condExpr; ILLabel trueLabel; ILLabel falseLabel; if(basicBlock.MatchSingle(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) { ControlFlowNode trueTarget; labelToCfNode.TryGetValue(trueLabel, out trueTarget); ControlFlowNode falseTarget; labelToCfNode.TryGetValue(falseLabel, out falseTarget); // If one point inside the loop and the other outside if ((!loopContents.Contains(trueTarget) && loopContents.Contains(falseTarget)) || (loopContents.Contains(trueTarget) && !loopContents.Contains(falseTarget)) ) { loopContents.RemoveOrThrow(node); scope.RemoveOrThrow(node); // If false means enter the loop if (loopContents.Contains(falseTarget) || falseTarget == node) { // Negate the condition condExpr = new ILExpression(ILCode.LogicNot, null, condExpr); ILLabel tmp = trueLabel; trueLabel = falseLabel; falseLabel = tmp; } ControlFlowNode postLoopTarget; labelToCfNode.TryGetValue(falseLabel, out postLoopTarget); if (postLoopTarget != null) { // Pull more nodes into the loop HashSet postLoopContents = FindDominatedNodes(scope, postLoopTarget); var pullIn = scope.Except(postLoopContents).Where(n => node.Dominates(n)); loopContents.UnionWith(pullIn); } // Use loop to implement the brtrue basicBlock.Body.RemoveAt(basicBlock.Body.Count - 1); basicBlock.Body.Add(new ILWhileLoop() { Condition = condExpr, BodyBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, trueLabel), Body = FindLoops(loopContents, node, false) } }); basicBlock.FallthoughGoto = new ILExpression(ILCode.Br, falseLabel); result.Add(basicBlock); scope.ExceptWith(loopContents); } } // Fallback method: while(true) if (scope.Contains(node)) { result.Add(new ILBasicBlock() { EntryLabel = new ILLabel() { Name = "Loop_" + (nextLabelIndex++) }, Body = new List() { new ILWhileLoop() { BodyBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel), Body = FindLoops(loopContents, node, true) } }, }, FallthoughGoto = null }); scope.ExceptWith(loopContents); } } // Using the dominator tree should ensure we find the the widest loop first foreach(var child in node.DominatorTreeChildren) { agenda.Enqueue(child); } } // Add whatever is left foreach(var node in scope) { result.Add((ILNode)node.UserData); } scope.Clear(); return result; } List FindConditions(HashSet scope, ControlFlowNode entryNode) { List result = new List(); // Do not modify entry data scope = new HashSet(scope); HashSet agenda = new HashSet(); agenda.Add(entryNode); while(agenda.Any()) { ControlFlowNode node = agenda.First(); // Attempt for a good order while(agenda.Contains(node.ImmediateDominator)) { node = node.ImmediateDominator; } agenda.Remove(node); // Find a block that represents a simple condition if (scope.Contains(node)) { ILBasicBlock block = (ILBasicBlock)node.UserData; { // Switch ILLabel[] caseLabels; ILExpression switchArg; ILLabel fallLabel; if (block.MatchLast(ILCode.Switch, out caseLabels, out switchArg, out fallLabel)) { // Replace the switch code with ILSwitch ILSwitch ilSwitch = new ILSwitch() { Condition = switchArg }; block.Body.RemoveAt(block.Body.Count - 1); block.Body.Add(ilSwitch); result.Add(block); // Remove the item so that it is not picked up as content scope.RemoveOrThrow(node); // Find the switch offset int addValue = 0; List subArgs; if (ilSwitch.Condition.Match(ILCode.Sub, out subArgs) && subArgs[1].Match(ILCode.Ldc_I4, out addValue)) { ilSwitch.Condition = subArgs[0]; } // Pull in code of cases ControlFlowNode fallTarget = null; labelToCfNode.TryGetValue(fallLabel, out fallTarget); HashSet frontiers = new HashSet(); if (fallTarget != null) frontiers.UnionWith(fallTarget.DominanceFrontier.Except(new [] { fallTarget })); foreach(ILLabel condLabel in caseLabels) { ControlFlowNode condTarget = null; labelToCfNode.TryGetValue(condLabel, out condTarget); if (condTarget != null) frontiers.UnionWith(condTarget.DominanceFrontier.Except(new [] { condTarget })); } for (int i = 0; i < caseLabels.Length; i++) { ILLabel condLabel = caseLabels[i]; // Find or create new case block ILSwitch.CaseBlock caseBlock = ilSwitch.CaseBlocks.Where(b => b.EntryGoto.Operand == condLabel).FirstOrDefault(); if (caseBlock == null) { caseBlock = new ILSwitch.CaseBlock() { Values = new List(), EntryGoto = new ILExpression(ILCode.Br, condLabel) }; ilSwitch.CaseBlocks.Add(caseBlock); ControlFlowNode condTarget = null; labelToCfNode.TryGetValue(condLabel, out condTarget); if (condTarget != null && !frontiers.Contains(condTarget)) { HashSet content = FindDominatedNodes(scope, condTarget); scope.ExceptWith(content); caseBlock.Body.AddRange(FindConditions(content, condTarget)); // Add explicit break which should not be used by default, but the goto removal might decide to use it caseBlock.Body.Add(new ILBasicBlock() { Body = { new ILExpression(ILCode.LoopOrSwitchBreak, null) } }); } } caseBlock.Values.Add(i + addValue); } // Heuristis to determine if we want to use fallthough as default case if (fallTarget != null && !frontiers.Contains(fallTarget)) { HashSet content = FindDominatedNodes(scope, fallTarget); if (content.Any()) { var caseBlock = new ILSwitch.CaseBlock() { EntryGoto = new ILExpression(ILCode.Br, fallLabel) }; ilSwitch.CaseBlocks.Add(caseBlock); block.FallthoughGoto = null; scope.ExceptWith(content); caseBlock.Body.AddRange(FindConditions(content, fallTarget)); // Add explicit break which should not be used by default, but the goto removal might decide to use it caseBlock.Body.Add(new ILBasicBlock() { Body = { new ILExpression(ILCode.LoopOrSwitchBreak, null) } }); } } } // Two-way branch ILExpression condExpr; ILLabel trueLabel; ILLabel falseLabel; if(block.MatchLast(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) { // Swap bodies since that seems to be the usual C# order ILLabel temp = trueLabel; trueLabel = falseLabel; falseLabel = temp; condExpr = new ILExpression(ILCode.LogicNot, null, condExpr); // Convert the brtrue to ILCondition ILCondition ilCond = new ILCondition() { Condition = condExpr, TrueBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, trueLabel) }, FalseBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, falseLabel) } }; block.Body.RemoveAt(block.Body.Count - 1); block.Body.Add(ilCond); block.FallthoughGoto = null; result.Add(block); // Remove the item immediately so that it is not picked up as content scope.RemoveOrThrow(node); ControlFlowNode trueTarget = null; labelToCfNode.TryGetValue(trueLabel, out trueTarget); ControlFlowNode falseTarget = null; labelToCfNode.TryGetValue(falseLabel, out falseTarget); // Pull in the conditional code HashSet frontiers = new HashSet(); if (trueTarget != null) frontiers.UnionWith(trueTarget.DominanceFrontier.Except(new [] { trueTarget })); if (falseTarget != null) frontiers.UnionWith(falseTarget.DominanceFrontier.Except(new [] { falseTarget })); if (trueTarget != null && !frontiers.Contains(trueTarget)) { HashSet content = FindDominatedNodes(scope, trueTarget); scope.ExceptWith(content); ilCond.TrueBlock.Body.AddRange(FindConditions(content, trueTarget)); } if (falseTarget != null && !frontiers.Contains(falseTarget)) { HashSet content = FindDominatedNodes(scope, falseTarget); scope.ExceptWith(content); ilCond.FalseBlock.Body.AddRange(FindConditions(content, falseTarget)); } } } // Add the node now so that we have good ordering if (scope.Contains(node)) { result.Add((ILNode)node.UserData); scope.Remove(node); } } // Using the dominator tree should ensure we find the the widest loop first foreach(var child in node.DominatorTreeChildren) { agenda.Add(child); } } // Add whatever is left foreach(var node in scope) { result.Add((ILNode)node.UserData); } return result; } static HashSet FindDominatedNodes(HashSet scope, ControlFlowNode head) { HashSet agenda = new HashSet(); HashSet result = new HashSet(); agenda.Add(head); while(agenda.Count > 0) { ControlFlowNode addNode = agenda.First(); agenda.Remove(addNode); if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) { foreach (var successor in addNode.Successors) { agenda.Add(successor); } } } return result; } static HashSet FindLoopContent(HashSet scope, ControlFlowNode head) { var viaBackEdges = head.Predecessors.Where(p => head.Dominates(p)); HashSet agenda = new HashSet(viaBackEdges); HashSet result = new HashSet(); while(agenda.Count > 0) { ControlFlowNode addNode = agenda.First(); agenda.Remove(addNode); if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) { foreach (var predecessor in addNode.Predecessors) { agenda.Add(predecessor); } } } if (scope.Contains(head)) result.Add(head); return result; } } }