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); }
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); }
private static ControlFlowNode FindContinue(ControlFlowNode loopHead) { // potential continue target var pred = loopHead.Predecessors.OnlyOrDefault(p => p != loopHead && loopHead.Dominates(p)); if (pred == null) { return(loopHead); } // match for loop increment block if (pred.Successors.Count == 1) { if (HighLevelLoopTransform.MatchIncrementBlock((Block)pred.UserData, out var target) && target == loopHead.UserData) { return(pred); } } // match do-while condition if (pred.Successors.Count <= 2) { if (HighLevelLoopTransform.MatchDoWhileConditionBlock((Block)pred.UserData, out var t1, out var t2) && (t1 == loopHead.UserData || t2 == loopHead.UserData)) { return(pred); } } return(loopHead); }
/// <summary> /// Validates an exit point. /// /// An exit point is invalid iff there is a node reachable from the exit point that /// is dominated by the loop head, but not by the exit point. /// (i.e. this method returns false iff the exit point's dominance frontier contains /// a node dominated by the loop head. but we implement this the slow way because /// we don't have dominance frontiers precomputed) /// </summary> /// <remarks> /// We need this because it's possible that there's a return block (thus reverse-unreachable node ignored by post-dominance) /// that is reachable both directly from the loop, and from the exit point. /// </remarks> bool ValidateExitPoint(ControlFlowNode loopHead, ControlFlowNode exitPoint) { var cfg = context.ControlFlowGraph; return(IsValid(exitPoint)); bool IsValid(ControlFlowNode node) { if (!cfg.HasReachableExit(node)) { // Optimization: if the dominance frontier is empty, we don't need // to check every node. return(true); } foreach (var succ in node.Successors) { if (loopHead != succ && loopHead.Dominates(succ) && !exitPoint.Dominates(succ)) { return(false); } } foreach (var child in node.DominatorTreeChildren) { if (!IsValid(child)) { return(false); } } return(true); } }
ControlFlowNode[] PrepareReverseCFG(ControlFlowNode loopHead, bool treatBackEdgesAsExits) { 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 exitNode = new ControlFlowNode { UserIndex = -1 }; rev[cfg.Length] = exitNode; for (int i = 0; i < cfg.Length; i++) { if (!loopHead.Dominates(cfg[i])) { continue; } // Add reverse edges for all edges in cfg foreach (var succ in cfg[i].Successors) { if (loopHead.Dominates(succ) && (!treatBackEdgesAsExits || loopHead != succ)) { rev[succ.UserIndex].AddEdgeTo(rev[i]); } else { exitNode.AddEdgeTo(rev[i]); } } if (context.ControlFlowGraph.HasDirectExitOutOfContainer(cfg[i])) { exitNode.AddEdgeTo(rev[i]); } } Dominance.ComputeDominance(exitNode, context.CancellationToken); return(rev); }
/// <summary> /// Gets whether <c>potentialBranchInstruction</c> is a branch to a block /// that is dominated by <c>cfgNode</c>. /// If this function returns true, we replace the branch instruction with the block itself. /// </summary> bool IsUsableBranchToChild(ControlFlowNode cfgNode, ILInstruction potentialBranchInstruction) { Branch br = potentialBranchInstruction as Branch; if (br == null) { return(false); } var targetBlock = br.TargetBlock; return(targetBlock.Parent == currentContainer && targetBlock.IncomingEdgeCount == 1 && targetBlock.FinalInstruction.OpCode == OpCode.Nop && cfgNode.Dominates(context.ControlFlowGraph.GetNode(targetBlock))); }
/// <summary> /// Gets whether <c>potentialBranchInstruction</c> is a branch to a block that is dominated by <c>cfgNode</c>. /// If this function returns true, we replace the branch instruction with the block itself. /// </summary> private bool CanInline(ILInstruction exitInst) { if (exitInst is Branch branch && branch.TargetBlock.Parent == currentContainer && branch.TargetBlock.IncomingEdgeCount == 1) { // if the incoming edge count is 1, then this must be the sole branch, and dominance is already ensured Debug.Assert(cfgNode.Dominates(context.ControlFlowGraph.GetNode(branch.TargetBlock))); // can't have "final instructions" in control flow blocks Debug.Assert(branch.TargetBlock.FinalInstruction is Nop); return(true); } return(false); }
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); }
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> 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 = null; ILLabel trueLabel; ILLabel falseLabel; // It has to be just brtrue - any preceding code would introduce goto error.Assert(!basicBlock.MatchLastAndBr(GMCode.Bt, out trueLabel, out condExpr, out falseLabel), "Unrolled loop"); if (basicBlock.MatchLastAndBr(GMCode.Bt, out trueLabel, out condExpr, out falseLabel) || basicBlock.MatchLastAndBr(GMCode.Pushenv, out falseLabel, out condExpr, out trueLabel) || // built it the same way from the dissasembler, this needs inverted basicBlock.MatchLastAndBr(GMCode.Repeat, out trueLabel, out condExpr, out falseLabel)) // repeate loop is built like a while { GMCode loopType = (basicBlock.Body.ElementAt(basicBlock.Body.Count - 2) as ILExpression).Code; 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); bool mustNegate = false; if (loopContents.Contains(falseTarget) || falseTarget == node) { // Negate the condition mustNegate = true; // condExpr = new ILExpression(GMCode.Not, null, condExpr); ILLabel tmp = trueLabel; trueLabel = falseLabel; falseLabel = tmp; } // HACK 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); } //Debug.Assert(false); Debug.Assert(condExpr != null); switch (loopType) { case GMCode.Pushenv: basicBlock.Body.RemoveTail(GMCode.Pushenv, GMCode.B); basicBlock.Body.Add(new ILWithStatement() { Condition = condExpr, // we never negate Body = new ILBlock() { EntryGoto = new ILExpression(GMCode.B, trueLabel), Body = FindLoops(loopContents, node, false) } }); break; case GMCode.Bt: basicBlock.Body.RemoveTail(GMCode.Bt, GMCode.B); basicBlock.Body.Add(new ILWhileLoop() { Condition = mustNegate ? condExpr.NegateCondition() : condExpr, Body = new ILBlock() { EntryGoto = new ILExpression(GMCode.B, trueLabel), Body = FindLoops(loopContents, node, false) } }); break; case GMCode.Repeat: basicBlock.Body.RemoveTail(GMCode.Repeat, GMCode.B); basicBlock.Body.Add(new ILRepeat() { Condition = condExpr, // we never negate Body = new ILBlock() { EntryGoto = new ILExpression(GMCode.B, trueLabel), Body = FindLoops(loopContents, node, false) } }); break; } basicBlock.Body.Add(new ILExpression(GMCode.B, falseLabel)); result.Add(basicBlock); scope.ExceptWith(loopContents); } } // Fallback method: while(true) if (scope.Contains(node)) { Debug.Assert(false); result.Add(new ILBasicBlock() { Body = new List <ILNode>() { ILLabel.Generate("Loop"), new ILWhileLoop() { Condition = new ILExpression(GMCode.Constant, new ILValue(true)), Body = new ILBlock() { EntryGoto = new ILExpression(GMCode.B, (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); }
static bool HasSingleEdgeEnteringBlock(ControlFlowNode node) { return(node.Incoming.Count(edge => !node.Dominates(edge.Source)) == 1); }
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); }
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> /// 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));
/// <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); } }
/// <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> /// 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); }