void MarkReachable(ControlFlowNode node) { if (node.PreviousStatement != null) reachableEndPoints.Add(node.PreviousStatement); if (node.NextStatement != null) reachableStatements.Add(node.NextStatement); foreach (var edge in node.Outgoing) { if (visitedNodes.Add(edge.To)) stack.Push(edge.To); } }
protected override bool CanReachModification(ControlFlowNode node, Statement start, IDictionary<Statement, IList<Node>> modifications) { if (base.CanReachModification (node, start, modifications)) return true; if (node.NextStatement != start) { var usingStatement = node.PreviousStatement as UsingStatement; if (usingStatement != null) { if (modifications.ContainsKey(usingStatement)) return true; if (usingStatement.ResourceAcquisition is Statement && modifications.ContainsKey ((Statement)usingStatement.ResourceAcquisition)) return true; } } return false; }
public ControlFlowEdge(ControlFlowNode source, ControlFlowNode target, JumpType type) { this.Source = source; this.Target = target; this.Type = type; }
private void DetectSwitchBody(Block block, SwitchInstruction switchInst) { Debug.Assert(block.Instructions.Last() == switchInst); ControlFlowNode h = context.ControlFlowNode; // CFG node for our switch head Debug.Assert(h.UserData == block); Debug.Assert(!TreeTraversal.PreOrder(h, n => n.DominatorTreeChildren).Any(n => n.Visited)); var nodesInSwitch = new List <ControlFlowNode>(); nodesInSwitch.Add(h); h.Visited = true; ExtendLoop(h, nodesInSwitch, out var exitPoint, isSwitch: true); if (exitPoint != null && exitPoint.Predecessors.Count == 1 && !context.ControlFlowGraph.HasReachableExit(exitPoint)) { // If the exit point is reachable from just one single "break;", // it's better to move the code into the switch. // (unlike loops which should not be nested unless necessary, // nesting switches makes it clearer in which cases a piece of code is reachable) nodesInSwitch.AddRange(TreeTraversal.PreOrder(exitPoint, p => p.DominatorTreeChildren)); exitPoint = null; } context.Step("Create BlockContainer for switch", switchInst); // Sort blocks in the loop in reverse post-order to make the output look a bit nicer. // (if the loop doesn't contain nested loops, this is a topological sort) nodesInSwitch.Sort((a, b) => b.PostOrderNumber.CompareTo(a.PostOrderNumber)); Debug.Assert(nodesInSwitch[0] == h); foreach (var node in nodesInSwitch) { node.Visited = false; // reset visited flag so that we can find outer loops Debug.Assert(h.Dominates(node) || !node.IsReachable, "The switch body must be dominated by the switch head"); } BlockContainer switchContainer = new BlockContainer(); Block newEntryPoint = new Block(); newEntryPoint.ILRange = switchInst.ILRange; switchContainer.Blocks.Add(newEntryPoint); newEntryPoint.Instructions.Add(switchInst); block.Instructions[block.Instructions.Count - 1] = switchContainer; Block exitTargetBlock = (Block)exitPoint?.UserData; if (exitTargetBlock != null) { block.Instructions.Add(new Branch(exitTargetBlock)); } MoveBlocksIntoContainer(nodesInSwitch, switchContainer); // Rewrite branches within the loop from oldEntryPoint to newEntryPoint: foreach (var branch in switchContainer.Descendants.OfType <Branch>()) { if (branch.TargetBlock == exitTargetBlock) { branch.ReplaceWith(new Leave(switchContainer) { ILRange = branch.ILRange }); } } }
/// <summary> /// Check whether 'block' is a loop head; and construct a loop instruction /// (nested BlockContainer) if it is. /// </summary> public void Run(Block block, BlockTransformContext context) { this.context = context; // LoopDetection runs early enough so that block should still // be in the original container at this point. Debug.Assert(block.Parent == context.ControlFlowGraph.Container); this.currentBlockContainer = context.ControlFlowGraph.Container; // Because this is a post-order block transform, we can assume that // any nested loops within this loop have already been constructed. if (block.Instructions.Last() is SwitchInstruction switchInst) { // Switch instructions support "break;" just like loops DetectSwitchBody(block, switchInst); } ControlFlowNode h = context.ControlFlowNode; // CFG node for our potential loop head Debug.Assert(h.UserData == block); Debug.Assert(!TreeTraversal.PreOrder(h, n => n.DominatorTreeChildren).Any(n => n.Visited)); List <ControlFlowNode> loop = null; foreach (var t in h.Predecessors) { if (h.Dominates(t)) { // h->t is a back edge, and h is a loop header // Add the natural loop of t->h to the loop. // Definitions: // * A back edge is an edge t->h so that h dominates t. // * The natural loop of the back edge is the smallest set of nodes // that includes the back edge and has no predecessors outside the set // except for the predecessor of the header. if (loop == null) { loop = new List <ControlFlowNode>(); loop.Add(h); // Mark loop header as visited so that the pre-order traversal // stops at the loop header. h.Visited = true; } t.TraversePreOrder(n => n.Predecessors, loop.Add); } } if (loop != null) { var headBlock = (Block)h.UserData; context.Step($"Construct loop with head {headBlock.Label}", headBlock); // loop now is the union of all natural loops with loop head h. // Try to extend the loop to reduce the number of exit points: ExtendLoop(h, loop, out var exitPoint); // Sort blocks in the loop in reverse post-order to make the output look a bit nicer. // (if the loop doesn't contain nested loops, this is a topological sort) loop.Sort((a, b) => b.PostOrderNumber.CompareTo(a.PostOrderNumber)); Debug.Assert(loop[0] == h); foreach (var node in loop) { node.Visited = false; // reset visited flag so that we can find outer loops Debug.Assert(h.Dominates(node) || !node.IsReachable, "The loop body must be dominated by the loop head"); } ConstructLoop(loop, exitPoint); } }
/// <summary> /// Returns the children in a loop dominator tree, with an optional exit point /// Avoids returning continue statements when analysing switches (because increment blocks can be dominated) /// </summary> IEnumerable <ControlFlowNode> DominatorTreeChildren(ControlFlowNode n, ControlFlowNode exitPoint) => n.DominatorTreeChildren.Where(c => c != exitPoint && (!isSwitch || !loopContext.MatchContinue(c)));
/// <summary> /// Finds a suitable single exit point for the specified loop. /// </summary> /// <returns> /// 1) If a suitable exit point was found: the control flow block that should be reached when breaking from the loop /// 2) If the loop should not have any exit point (extend by all dominated blocks): NoExitPoint /// 3) otherwise (exit point unknown, heuristically extend loop): null /// </returns> /// <remarks>This method must not write to the Visited flags on the CFG.</remarks> internal ControlFlowNode FindExitPoint(ControlFlowNode loopHead, IReadOnlyList <ControlFlowNode> naturalLoop) { bool hasReachableExit = HasReachableExit(loopHead); if (!hasReachableExit) { // Case 1: // There are no nodes n so that loopHead dominates a predecessor of n but not n itself // -> we could build a loop with zero exit points. if (IsPossibleForeachLoop((Block)loopHead.UserData, out var exitBranch)) { if (exitBranch != null) { // let's see if the target of the exit branch is a suitable exit point var cfgNode = loopHead.Successors.FirstOrDefault(n => n.UserData == exitBranch.TargetBlock); if (cfgNode != null && loopHead.Dominates(cfgNode) && !context.ControlFlowGraph.HasReachableExit(cfgNode)) { return(cfgNode); } } return(NoExitPoint); } ControlFlowNode exitPoint = null; int exitPointILOffset = -1; ConsiderReturnAsExitPoint((Block)loopHead.UserData, ref exitPoint, ref exitPointILOffset); foreach (var node in loopHead.DominatorTreeChildren) { PickExitPoint(node, ref exitPoint, ref exitPointILOffset); } return(exitPoint); } else { // Case 2: // We need to pick our exit point so that all paths from the loop head // to the reachable exits run through that exit point. var cfg = context.ControlFlowGraph.cfg; var revCfg = PrepareReverseCFG(loopHead, out int exitNodeArity); //ControlFlowNode.ExportGraph(cfg).Show("cfg"); //ControlFlowNode.ExportGraph(revCfg).Show("rev"); ControlFlowNode commonAncestor = revCfg[loopHead.UserIndex]; Debug.Assert(commonAncestor.IsReachable); foreach (ControlFlowNode cfgNode in naturalLoop) { ControlFlowNode revNode = revCfg[cfgNode.UserIndex]; if (revNode.IsReachable) { commonAncestor = Dominance.FindCommonDominator(commonAncestor, revNode); } } // All paths from within the loop to a reachable exit run through 'commonAncestor'. // However, this doesn't mean that 'commonAncestor' is valid as an exit point. // We walk up the post-dominator tree until we've got a valid exit point: ControlFlowNode exitPoint; while (commonAncestor.UserIndex >= 0) { exitPoint = cfg[commonAncestor.UserIndex]; Debug.Assert(exitPoint.Visited == naturalLoop.Contains(exitPoint)); // It's possible that 'commonAncestor' is itself part of the natural loop. // If so, it's not a valid exit point. if (!exitPoint.Visited && ValidateExitPoint(loopHead, exitPoint)) { // we found an exit point return(exitPoint); } commonAncestor = commonAncestor.ImmediateDominator; } // least common post-dominator is the artificial exit node // This means we're in one of two cases: // * The loop might have multiple exit points. // -> we should return null // * The loop has a single exit point that wasn't considered during post-dominance analysis. // (which means the single exit isn't dominated by the loop head) // -> we should return NoExitPoint so that all code dominated by the loop head is included into the loop if (exitNodeArity > 1) { return(null); } // Exit node is on the very edge of the tree, and isn't important for determining inclusion // Still necessary for switch detection to insert correct leave statements if (exitNodeArity == 1 && isSwitch) { return(loopContext.GetBreakTargets(loopHead).Distinct().Single()); } // If exitNodeArity == 0, we should maybe look test if our exits out of the block container are all compatible? // but I don't think it hurts to have a bit too much code inside the loop in this rare case. return(NoExitPoint); } }
/// <summary> /// Constructor. /// </summary> /// <param name="cfg">ControlFlowGraph</param> /// <param name="summary">MethodSummary</param> /// <param name="loopExitNode">ControlFlowNode</param> internal LoopHeadControlFlowNode(IGraph <IControlFlowNode> cfg, MethodSummary summary, ControlFlowNode loopExitNode) : base(cfg, summary) { this.LoopExitNode = loopExitNode; }
protected virtual bool CanReachModification (ControlFlowNode node, Statement start, IDictionary<Statement, IList<Node>> modifications) { return node.NextStatement != null && node.NextStatement != start && modifications.ContainsKey (node.NextStatement); }
static HashSet <ControlFlowNode> FindLoopContent(HashSet <ControlFlowNode> scope, ControlFlowNode head) { HashSet <ControlFlowNode> agenda = new HashSet <ControlFlowNode>(); for (int i = 0; i < head.Incoming.Count; i++) { var p = head.Incoming[i].Source; if (head.Dominates(p)) { agenda.Add(p); } } HashSet <ControlFlowNode> result = new HashSet <ControlFlowNode>(); while (agenda.Count > 0) { ControlFlowNode addNode = agenda.First(); agenda.Remove(addNode); if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) { for (int i = 0; i < addNode.Incoming.Count; i++) { agenda.Add(addNode.Incoming[i].Source); } } } if (scope.Contains(head)) { result.Add(head); } return(result); }
static HashSet <ControlFlowNode> FindDominatedNodes(HashSet <ControlFlowNode> scope, ControlFlowNode head) { HashSet <ControlFlowNode> agenda = new HashSet <ControlFlowNode>(); HashSet <ControlFlowNode> result = new HashSet <ControlFlowNode>(); agenda.Add(head); while (agenda.Count > 0) { ControlFlowNode addNode = agenda.First(); agenda.Remove(addNode); if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) { for (int i = 0; i < addNode.Outgoing.Count; i++) { agenda.Add(addNode.Outgoing[i].Target); } } } return(result); }
List <ILNode> FindConditions(HashSet <ControlFlowNode> scope, ControlFlowNode entryNode) { List <ILNode> result = new List <ILNode>(); // Do not modify entry data scope = new HashSet <ControlFlowNode>(scope); Stack <ControlFlowNode> agenda = new Stack <ControlFlowNode>(); agenda.Push(entryNode); while (agenda.Count > 0) { ControlFlowNode node = agenda.Pop(); // Find a block that represents a simple condition if (scope.Contains(node)) { ILBasicBlock block = (ILBasicBlock)node.UserData; { // Switch IList <ILExpression> cases; ILLabel[] caseLabels; ILExpression switchCondition; ILLabel fallLabel; // IList<ILExpression> cases; out IList<ILExpression> arg, out ILLabel fallLabel) // matches a switch statment, not sure how the hell I am going to do this if (block.MatchLastAndBr(GMCode.Switch, out caseLabels, out cases, out fallLabel)) { // Debug.Assert(fallLabel == endBlock); // this should be true switchCondition = cases[0]; // thats the switch arg cases.RemoveAt(0); // remove the switch condition // Replace the switch code with ILSwitch ILSwitch ilSwitch = new ILSwitch() { Condition = switchCondition }; block.Body.RemoveTail(GMCode.Switch, GMCode.B); block.Body.Add(ilSwitch); block.Body.Add(new ILExpression(GMCode.B, fallLabel)); result.Add(block); // Remove the item so that it is not picked up as content scope.RemoveOrThrow(node); // Pull in code of cases ControlFlowNode fallTarget = null; labelToCfNode.TryGetValue(fallLabel, out fallTarget); HashSet <ControlFlowNode> frontiers = new HashSet <ControlFlowNode>(); 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.FirstOrDefault(b => b.EntryGoto.Operand == condLabel); if (caseBlock == null) { caseBlock = new ILSwitch.CaseBlock() { Values = new List <ILExpression>(), EntryGoto = new ILExpression(GMCode.B, condLabel) }; ilSwitch.CaseBlocks.Add(caseBlock); ControlFlowNode condTarget = null; labelToCfNode.TryGetValue(condLabel, out condTarget); if (condTarget != null && !frontiers.Contains(condTarget)) { HashSet <ControlFlowNode> content = FindDominatedNodes(scope, condTarget); scope.ExceptWith(content); foreach (var con in FindConditions(content, condTarget)) { caseBlock.Body.Add(con); } // 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 ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++) }, new ILExpression(GMCode.LoopOrSwitchBreak, null) } }); } } if (cases[i].Code != GMCode.DefaultCase) { caseBlock.Values.Add(cases[i].Arguments[0]); } else { caseBlock.Values.Add(new ILExpression(GMCode.DefaultCase, null)); } } // Heuristis to determine if we want to use fallthough as default case if (fallTarget != null && !frontiers.Contains(fallTarget)) { HashSet <ControlFlowNode> content = FindDominatedNodes(scope, fallTarget); if (content.Any()) { var caseBlock = new ILSwitch.CaseBlock() { EntryGoto = new ILExpression(GMCode.B, fallLabel) }; ilSwitch.CaseBlocks.Add(caseBlock); block.Body.RemoveTail(GMCode.B); scope.ExceptWith(content); foreach (var con in FindConditions(content, fallTarget)) { caseBlock.Body.Add(con); } // 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 ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++) }, new ILExpression(GMCode.LoopOrSwitchBreak, null) } }); } } } // Debug.Assert((block.Body.First() as ILLabel).Name != "L1938"); // Two-way branch ILLabel trueLabel; ILLabel falseLabel; IList <ILExpression> condExprs; if (block.MatchLastAndBr(GMCode.Bf, out falseLabel, out condExprs, out trueLabel) && condExprs[0].Code != GMCode.Pop) // its resolved { ILExpression condExpr = condExprs[0]; IList <ILNode> body = block.Body; // this is a simple condition, skip anything short curiket for now // Match a condition patern // Convert the brtrue to ILCondition ILCondition ilCond = new ILCondition() { Condition = condExpr, TrueBlock = new ILBlock() { EntryGoto = new ILExpression(GMCode.B, trueLabel) }, FalseBlock = new ILBlock() { EntryGoto = new ILExpression(GMCode.B, falseLabel) } }; block.Body.RemoveTail(GMCode.Bf, GMCode.B); block.Body.Add(ilCond); 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 if (trueTarget != null && HasSingleEdgeEnteringBlock(trueTarget)) { HashSet <ControlFlowNode> content = FindDominatedNodes(scope, trueTarget); scope.ExceptWith(content); foreach (var con in FindConditions(content, trueTarget)) { ilCond.TrueBlock.Body.Add(con); } } if (falseTarget != null && HasSingleEdgeEnteringBlock(falseTarget)) { HashSet <ControlFlowNode> content = FindDominatedNodes(scope, falseTarget); scope.ExceptWith(content); foreach (var con in FindConditions(content, falseTarget)) { ilCond.FalseBlock.Body.Add(con); } } } } // Add the node now so that we have good ordering if (scope.Contains(node)) { result.Add((ILNode)node.UserData); scope.Remove(node); } } // depth-first traversal of dominator tree for (int i = node.DominatorTreeChildren.Count - 1; i >= 0; i--) { agenda.Push(node.DominatorTreeChildren[i]); } } // Add whatever is left foreach (var node in scope) { result.Add((ILNode)node.UserData); } return(result); }
internal AdjacencyCollection(ControlFlowNode <TContents> owner, ControlFlowEdgeType edgeType) { EdgeType = edgeType; Owner = owner ?? throw new ArgumentNullException(nameof(owner)); }
/// <summary> /// Obtains all edges to the provided neighbour, if any. /// </summary> /// <param name="target">The neighbouring node.</param> /// <returns>The edges.</returns> public IEnumerable <ControlFlowEdge <TContents> > GetEdgesToNeighbour(ControlFlowNode <TContents> target) => GetEdges(target);
List <ILNode> FindConditions(HashSet <ControlFlowNode> scope, ControlFlowNode entryNode) { List <ILNode> result = new List <ILNode>(); // Do not modify entry data scope = new HashSet <ControlFlowNode>(scope); Stack <ControlFlowNode> agenda = new Stack <ControlFlowNode>(); agenda.Push(entryNode); while (agenda.Count > 0) { ControlFlowNode node = agenda.Pop(); // 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.MatchLastAndBr(ILCode.Switch, out caseLabels, out switchArg, out fallLabel)) { // Replace the switch code with ILSwitch ILSwitch ilSwitch = new ILSwitch() { Condition = switchArg }; block.Body.RemoveTail(ILCode.Switch, ILCode.Br); block.Body.Add(ilSwitch); block.Body.Add(new ILExpression(ILCode.Br, fallLabel)); 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 <ILExpression> 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 <ControlFlowNode> frontiers = new HashSet <ControlFlowNode>(); 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.FirstOrDefault(b => b.EntryGoto.Operand == condLabel); if (caseBlock == null) { caseBlock = new ILSwitch.CaseBlock() { Values = new List <int>(), 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 <ControlFlowNode> 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 ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++) }, 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 <ControlFlowNode> content = FindDominatedNodes(scope, fallTarget); if (content.Any()) { var caseBlock = new ILSwitch.CaseBlock() { EntryGoto = new ILExpression(ILCode.Br, fallLabel) }; ilSwitch.CaseBlocks.Add(caseBlock); block.Body.RemoveTail(ILCode.Br); 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 ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++) }, new ILExpression(ILCode.LoopOrSwitchBreak, null) } }); } } } // Two-way branch ILExpression condExpr; ILLabel trueLabel; ILLabel falseLabel; if (block.MatchLastAndBr(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.RemoveTail(ILCode.Brtrue, ILCode.Br); block.Body.Add(ilCond); 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 if (trueTarget != null && HasSingleEdgeEnteringBlock(trueTarget)) { HashSet <ControlFlowNode> content = FindDominatedNodes(scope, trueTarget); scope.ExceptWith(content); ilCond.TrueBlock.Body.AddRange(FindConditions(content, trueTarget)); } if (falseTarget != null && HasSingleEdgeEnteringBlock(falseTarget)) { HashSet <ControlFlowNode> 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); } } // depth-first traversal of dominator tree for (int i = node.DominatorTreeChildren.Count - 1; i >= 0; i--) { agenda.Push(node.DominatorTreeChildren[i]); } } // Add whatever is left foreach (var node in scope) { result.Add((ILNode)node.UserData); } return(result); }
static HashSet <ControlFlowNode> FindDominatedNodes(HashSet <ControlFlowNode> scope, ControlFlowNode head) { HashSet <ControlFlowNode> agenda = new HashSet <ControlFlowNode>(); HashSet <ControlFlowNode> result = new HashSet <ControlFlowNode>(); 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); }
private void UpdateScopeStack(ControlFlowNode <TInstruction> node, IndexableStack <ScopeInfo> scopeStack) { var activeRegions = node.GetSituatedRegions() .Reverse() .ToArray(); int largestPossibleCommonDepth = Math.Min(scopeStack.Count, activeRegions.Length); // Figure out common region depth. int commonDepth = 1; while (commonDepth < largestPossibleCommonDepth) { if (scopeStack[commonDepth].Region != activeRegions[commonDepth]) { break; } commonDepth++; } // Leave for every left region a scope block. while (scopeStack.Count > commonDepth) { scopeStack.Pop(); } // Enter for every entered region a new block. while (scopeStack.Count < activeRegions.Length) { // Create new scope block. var enteredRegion = activeRegions[scopeStack.Count]; // Add new cope block to the current scope. var currentScope = scopeStack.Peek(); if (enteredRegion is ExceptionHandlerRegion <TInstruction> ehRegion) { // We entered an exception handler region. var ehBlock = new ExceptionHandlerBlock <TInstruction>(); currentScope.AddBlock(ehBlock); scopeStack.Push(new ScopeInfo(ehRegion, ehBlock)); } else if (enteredRegion.ParentRegion is ExceptionHandlerRegion <TInstruction> parentEhRegion) { // We entered one of the exception handler sub regions. Figure out which one it is. var enteredBlock = default(ScopeBlock <TInstruction>); if (!(currentScope.Block is ExceptionHandlerBlock <TInstruction> ehBlock)) { throw new InvalidOperationException("The parent scope is not an exception handler scope."); } if (parentEhRegion.ProtectedRegion == enteredRegion) { // We entered the protected region. enteredBlock = ehBlock.ProtectedBlock; } else { // We entered a handler region. enteredBlock = new ScopeBlock <TInstruction>(); ehBlock.HandlerBlocks.Add(enteredBlock); } // Push the entered scope. scopeStack.Push(new ScopeInfo(parentEhRegion.ProtectedRegion, enteredBlock)); } else { // Fall back method: just enter a new scope block. var scopeBlock = new ScopeBlock <TInstruction>(); currentScope.AddBlock(scopeBlock); scopeStack.Push(new ScopeInfo(enteredRegion, scopeBlock)); } } }
ControlFlowGraph BuildGraph(List <ILNode> nodes, ILLabel entryLabel) { int index = 0; List <ControlFlowNode> cfNodes = new List <ControlFlowNode>(); ControlFlowNode entryPoint = new ControlFlowNode(index++, 0, ControlFlowNodeType.EntryPoint); cfNodes.Add(entryPoint); ControlFlowNode regularExit = new ControlFlowNode(index++, 0xffffffff, ControlFlowNodeType.RegularExit); cfNodes.Add(regularExit); ControlFlowNode exceptionalExit = new ControlFlowNode(index++, 0xffffffff, ControlFlowNodeType.ExceptionalExit); cfNodes.Add(exceptionalExit); // Create graph nodes labelToCfNode = new Dictionary <ILLabel, ControlFlowNode>(); Dictionary <ILNode, ControlFlowNode> astNodeToCfNode = new Dictionary <ILNode, ControlFlowNode>(); foreach (ILBasicBlock node in nodes) { ControlFlowNode cfNode = new ControlFlowNode(index++, 0xffffffff, ControlFlowNodeType.Normal); cfNodes.Add(cfNode); astNodeToCfNode[node] = cfNode; cfNode.UserData = node; // Find all contained labels foreach (ILLabel label in node.GetSelfAndChildrenRecursive <ILLabel>()) { 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 <ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) { ControlFlowNode destination; // Labels which are out of out scope will not be in the collection // Insert self edge only if we are sure we are a loop if (labelToCfNode.TryGetValue(target, out destination) && (destination != source || target == node.Body.FirstOrDefault())) { ControlFlowEdge edge = new ControlFlowEdge(source, destination, JumpType.Normal); source.Outgoing.Add(edge); destination.Incoming.Add(edge); } } } return(new ControlFlowGraph(cfNodes.ToArray())); }
public bool MatchContinue(ControlFlowNode node) => MatchContinue(node, out var _);
/// <summary> /// Constructs a new control flow graph. /// Each node cfg[i] has a corresponding node rev[i]. /// Edges are only created for nodes dominated by loopHead, and are in reverse from their direction /// in the primary CFG. /// An artificial exit node is used for edges that leave the set of nodes dominated by loopHead, /// or that leave the block Container. /// </summary> /// <param name="loopHead">Entry point of the loop.</param> /// <param name="exitNodeArity">out: The number of different CFG nodes. /// Possible values: /// 0 = no CFG nodes used as exit nodes (although edges leaving the block container might still be exits); /// 1 = a single CFG node (not dominated by loopHead) was used as an exit node; /// 2 = more than one CFG node (not dominated by loopHead) was used as an exit node. /// </param> /// <returns></returns> ControlFlowNode[] PrepareReverseCFG(ControlFlowNode loopHead, out int exitNodeArity) { ControlFlowNode[] cfg = context.ControlFlowGraph.cfg; ControlFlowNode[] rev = new ControlFlowNode[cfg.Length + 1]; for (int i = 0; i < cfg.Length; i++) { rev[i] = new ControlFlowNode { UserIndex = i, UserData = cfg[i].UserData }; } ControlFlowNode nodeTreatedAsExitNode = null; bool multipleNodesTreatedAsExitNodes = false; ControlFlowNode exitNode = new ControlFlowNode { UserIndex = -1 }; rev[cfg.Length] = exitNode; for (int i = 0; i < cfg.Length; i++) { if (!loopHead.Dominates(cfg[i]) || isSwitch && cfg[i] != loopHead && loopContext.MatchContinue(cfg[i])) { continue; } // Add reverse edges for all edges in cfg foreach (var succ in cfg[i].Successors) { // edges to outer loops still count as exits (labelled continue not implemented) if (isSwitch && loopContext.MatchContinue(succ, 1)) { continue; } if (loopHead.Dominates(succ)) { rev[succ.UserIndex].AddEdgeTo(rev[i]); } else { if (nodeTreatedAsExitNode == null) { nodeTreatedAsExitNode = succ; } if (nodeTreatedAsExitNode != succ) { multipleNodesTreatedAsExitNodes = true; } exitNode.AddEdgeTo(rev[i]); } } if (context.ControlFlowGraph.HasDirectExitOutOfContainer(cfg[i])) { exitNode.AddEdgeTo(rev[i]); } } if (multipleNodesTreatedAsExitNodes) { exitNodeArity = 2; // more than 1 } else if (nodeTreatedAsExitNode != null) { exitNodeArity = 1; } else { exitNodeArity = 0; } Dominance.ComputeDominance(exitNode, context.CancellationToken); return(rev); }
public bool MatchContinue(ControlFlowNode node, int depth) => MatchContinue(node, out int _depth) && depth == _depth;
/// <summary> /// Extension of ControlFlowGraph.HasReachableExit /// Uses loopContext.GetBreakTargets().Any() when analyzing switches to avoid /// classifying continue blocks as reachable exits. /// </summary> bool HasReachableExit(ControlFlowNode node) => isSwitch ? loopContext.GetBreakTargets(node).Any() : context.ControlFlowGraph.HasReachableExit(node);
public bool MatchContinue(ControlFlowNode node, out int depth) => continueDepth.TryGetValue(node, out depth);
/// <summary> /// Finds a suitable single exit point for the specified loop. /// </summary> /// <returns> /// 1) If a suitable exit point was found: the control flow block that should be reached when breaking from the loop /// 2) If the loop should not have any exit point (extend by all dominated blocks): NoExitPoint /// 3) otherwise (exit point unknown, heuristically extend loop): null /// </returns> /// <remarks>This method must not write to the Visited flags on the CFG.</remarks> ControlFlowNode FindExitPoint(ControlFlowNode loopHead, IReadOnlyList <ControlFlowNode> naturalLoop, bool treatBackEdgesAsExits) { bool hasReachableExit = context.ControlFlowGraph.HasReachableExit(loopHead); if (!hasReachableExit && treatBackEdgesAsExits) { // If we're analyzing the switch, there's no reachable exit, but the loopHead (=switchHead) block // is also a loop head, we consider the back-edge a reachable exit for the switch. hasReachableExit = loopHead.Predecessors.Any(p => loopHead.Dominates(p)); } if (!hasReachableExit) { // Case 1: // There are no nodes n so that loopHead dominates a predecessor of n but not n itself // -> we could build a loop with zero exit points. if (IsPossibleForeachLoop((Block)loopHead.UserData, out var exitBranch)) { if (exitBranch != null) { // let's see if the target of the exit branch is a suitable exit point var cfgNode = loopHead.Successors.FirstOrDefault(n => n.UserData == exitBranch.TargetBlock); if (cfgNode != null && loopHead.Dominates(cfgNode) && !context.ControlFlowGraph.HasReachableExit(cfgNode)) { return(cfgNode); } } return(NoExitPoint); } ControlFlowNode exitPoint = null; int exitPointILOffset = -1; foreach (var node in loopHead.DominatorTreeChildren) { PickExitPoint(node, ref exitPoint, ref exitPointILOffset); } return(exitPoint); } else { // Case 2: // We need to pick our exit point so that all paths from the loop head // to the reachable exits run through that exit point. var cfg = context.ControlFlowGraph.cfg; var revCfg = PrepareReverseCFG(loopHead, treatBackEdgesAsExits); //ControlFlowNode.ExportGraph(cfg).Show("cfg"); //ControlFlowNode.ExportGraph(revCfg).Show("rev"); ControlFlowNode commonAncestor = revCfg[loopHead.UserIndex]; Debug.Assert(commonAncestor.IsReachable); foreach (ControlFlowNode cfgNode in naturalLoop) { ControlFlowNode revNode = revCfg[cfgNode.UserIndex]; if (revNode.IsReachable) { commonAncestor = Dominance.FindCommonDominator(commonAncestor, revNode); } } // All paths from within the loop to a reachable exit run through 'commonAncestor'. // However, this doesn't mean that 'commonAncestor' is valid as an exit point. // We walk up the post-dominator tree until we've got a valid exit point: ControlFlowNode exitPoint; while (commonAncestor.UserIndex >= 0) { exitPoint = cfg[commonAncestor.UserIndex]; Debug.Assert(exitPoint.Visited == naturalLoop.Contains(exitPoint)); // It's possible that 'commonAncestor' is itself part of the natural loop. // If so, it's not a valid exit point. if (!exitPoint.Visited && ValidateExitPoint(loopHead, exitPoint)) { // we found an exit point return(exitPoint); } commonAncestor = commonAncestor.ImmediateDominator; } // least common post-dominator is the artificial exit node return(null); } }
public int GetContinueDepth(ControlFlowNode node) => MatchContinue(node, out var depth) ? depth : 0;
/// <summary> /// This function implements a heuristic algorithm that tries to reduce the number of exit /// points. It is only used as fall-back when it is impossible to use a single exit point. /// </summary> /// <remarks> /// This heuristic loop extension algorithm traverses the loop head's dominator tree in pre-order. /// For each candidate node, we detect whether adding it to the loop reduces the number of exit points. /// If it does, the candidate is added to the loop. /// /// Adding a node to the loop has two effects on the the number of exit points: /// * exit points that were added to the loop are no longer exit points, thus reducing the total number of exit points /// * successors of the newly added nodes might be new, additional exit points /// /// Requires and maintains the invariant that a node is marked as visited iff it is contained in the loop. /// </remarks> void ExtendLoopHeuristic(ControlFlowNode loopHead, List <ControlFlowNode> loop, ControlFlowNode candidate) { Debug.Assert(candidate.Visited == loop.Contains(candidate)); if (!candidate.Visited) { // This node not yet part of the loop, but might be added List <ControlFlowNode> additionalNodes = new List <ControlFlowNode>(); // Find additionalNodes nodes and mark them as visited. candidate.TraversePreOrder(n => n.Predecessors, additionalNodes.Add); // This means Visited now represents the candiate extended loop. // Determine new exit points that are reachable from the additional nodes // (note: some of these might have previously been exit points, too) var newExitPoints = additionalNodes.SelectMany(n => n.Successors).Where(n => !n.Visited).ToHashSet(); // Make visited represent the unextended loop, so that we can measure the exit points // in the old state. foreach (var node in additionalNodes) { node.Visited = false; } // Measure number of added and removed exit points int removedExitPoints = additionalNodes.Count(IsExitPoint); int addedExitPoints = newExitPoints.Count(n => !IsExitPoint(n)); if (removedExitPoints > addedExitPoints) { // We can reduce the number of exit points by adding the candidate node to the loop. candidate.TraversePreOrder(n => n.Predecessors, loop.Add); } } // Pre-order traversal of dominator tree foreach (var node in candidate.DominatorTreeChildren) { ExtendLoopHeuristic(loopHead, loop, node); } }
/// <summary> /// Lists all potential targets for break; statements from a domination tree, /// assuming the domination tree must be exited via either break; or continue; /// /// First list all nodes in the dominator tree (excluding continue nodes) /// Then return the all successors not contained within said tree. /// /// Note that node will be returned once for each outgoing edge. /// Labelled continue statements (depth > 1) are counted as break targets /// </summary> internal IEnumerable <ControlFlowNode> GetBreakTargets(ControlFlowNode dominator) => TreeTraversal.PreOrder(dominator, n => n.DominatorTreeChildren.Where(c => !MatchContinue(c))) .SelectMany(n => n.Successors) .Where(n => !dominator.Dominates(n) && !MatchContinue(n, 1));
public ControlFlowLink(Condition condition, ControlFlowNode target) { Condition = condition; Target = target; }
/// <summary> /// A flow node contains only two instructions, the first of which is an IfInstruction /// A short circuit expression is comprised of a root block ending in an IfInstruction and one or more flow nodes /// </summary> static bool IsFlowNode(ControlFlowNode n) => ((Block)n.UserData).Instructions.FirstOrDefault() is IfInstruction;
List <ILNode> FindLoops(HashSet <ControlFlowNode> scope, ControlFlowNode entryPoint, bool excludeEntryPoint) { List <ILNode> result = new List <ILNode>(); // Do not modify entry data scope = new HashSet <ControlFlowNode>(scope); Queue <ControlFlowNode> agenda = new Queue <ControlFlowNode>(); 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 <ControlFlowNode> loopContents = FindLoopContent(scope, node); // If the first expression is a loop condition ILBasicBlock basicBlock = (ILBasicBlock)node.UserData; ILExpression condExpr; ILLabel trueLabel; ILLabel falseLabel; // It has to be just brtrue - any preceding code would introduce goto if (basicBlock.MatchSingleAndBr(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 <ControlFlowNode> 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.RemoveTail(ILCode.Brtrue, ILCode.Br); basicBlock.Body.Add(new ILWhileLoop() { Condition = condExpr, BodyBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, trueLabel), Body = FindLoops(loopContents, node, false) } }); basicBlock.Body.Add(new ILExpression(ILCode.Br, falseLabel)); result.Add(basicBlock); scope.ExceptWith(loopContents); } } // Fallback method: while(true) if (scope.Contains(node)) { result.Add(new ILBasicBlock() { Body = new List <ILNode>() { new ILLabel() { Name = "Loop_" + (nextLabelIndex++) }, new ILWhileLoop() { BodyBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, (ILLabel)basicBlock.Body.First()), Body = FindLoops(loopContents, node, true) } }, }, }); 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); }
public void Complex() { // Example graph from: // http://www.sable.mcgill.ca/~hendren/621/ControlFlowAnalysis_Handouts.pdf // (slide 57) var cfg = new ControlFlowGraph <int>(IntArchitecture.Instance); var nodes = new ControlFlowNode <int> [11]; for (int i = 0; i < nodes.Length; i++) { nodes[i] = new ControlFlowNode <int>(i, i); cfg.Nodes.Add(nodes[i]); } nodes[0].ConnectWith(nodes[1]); nodes[1].ConnectWith(nodes[2], ControlFlowEdgeType.Conditional); nodes[1].ConnectWith(nodes[3]); nodes[2].ConnectWith(nodes[3]); nodes[3].ConnectWith(nodes[4]); nodes[4].ConnectWith(nodes[3], ControlFlowEdgeType.Conditional); nodes[4].ConnectWith(nodes[5]); nodes[4].ConnectWith(nodes[6], ControlFlowEdgeType.Conditional); nodes[5].ConnectWith(nodes[7]); nodes[6].ConnectWith(nodes[7]); nodes[7].ConnectWith(nodes[8]); nodes[7].ConnectWith(nodes[4], ControlFlowEdgeType.Conditional); nodes[8].ConnectWith(nodes[9]); nodes[8].ConnectWith(nodes[10], ControlFlowEdgeType.Conditional); nodes[8].ConnectWith(nodes[3], ControlFlowEdgeType.Conditional); nodes[9].ConnectWith(nodes[1]); nodes[10].ConnectWith(nodes[7]); cfg.Entrypoint = nodes[0]; var tree = DominatorTree.FromGraph(cfg); Assert.Empty(tree.GetDominanceFrontier(nodes[0])); Assert.Equal(new HashSet <INode> { nodes[1] }, tree.GetDominanceFrontier(nodes[1])); Assert.Equal(new HashSet <INode> { nodes[3] }, tree.GetDominanceFrontier(nodes[2])); Assert.Equal(new HashSet <INode> { nodes[1], nodes[3] }, tree.GetDominanceFrontier(nodes[3])); Assert.Equal(new HashSet <INode> { nodes[1], nodes[3], nodes[4] }, tree.GetDominanceFrontier(nodes[4])); Assert.Equal(new HashSet <INode> { nodes[7] }, tree.GetDominanceFrontier(nodes[5])); Assert.Equal(new HashSet <INode> { nodes[7] }, tree.GetDominanceFrontier(nodes[6])); Assert.Equal(new HashSet <INode> { nodes[1], nodes[3], nodes[4], nodes[7] }, tree.GetDominanceFrontier(nodes[7])); Assert.Equal(new HashSet <INode> { nodes[1], nodes[3], nodes[7] }, tree.GetDominanceFrontier(nodes[8])); Assert.Equal(new HashSet <INode> { nodes[1] }, tree.GetDominanceFrontier(nodes[9])); Assert.Equal(new HashSet <INode> { nodes[7] }, tree.GetDominanceFrontier(nodes[10])); }
static bool HasSingleEdgeEnteringBlock(ControlFlowNode node) { return(node.Incoming.Count(edge => !node.Dominates(edge.Source)) == 1); }
protected virtual bool CanReachModification(ControlFlowNode node, Statement start, IDictionary <Statement, IList <Node> > modifications) { return(node.NextStatement != null && node.NextStatement != start && modifications.ContainsKey(node.NextStatement)); }
static HashSet <ControlFlowNode> FindLoopContent(HashSet <ControlFlowNode> scope, ControlFlowNode head) { var viaBackEdges = head.Predecessors.Where(p => head.Dominates(p)); HashSet <ControlFlowNode> agenda = new HashSet <ControlFlowNode>(viaBackEdges); HashSet <ControlFlowNode> result = new HashSet <ControlFlowNode>(); 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); }
/// <summary> /// Constructor. /// </summary> /// <param name="cfg">ControlFlowGraph</param> /// <param name="summary">MethodSummary</param> /// <param name="loopExitNode">ControlFlowNode</param> internal LoopHeadControlFlowNode(IGraph<IControlFlowNode> cfg, MethodSummary summary, ControlFlowNode loopExitNode) : base(cfg, summary) { this.LoopExitNode = loopExitNode; }