diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index ced0883cc..f799ef1a4 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -67,7 +67,16 @@ namespace ICSharpCode.Decompiler.CSharp // is already collapsed into stloc(V, ...). new RemoveDeadVariableInit(), new SwitchDetection(), - new LoopDetection(), + new BlockILTransform { // per-block transforms + PostOrderTransforms = { + // Even though it's a post-order block-transform as most other transforms, + // let's keep LoopDetection separate for now until there's a compelling + // reason to combine it with the other block transforms. + // If we ran loop detection after some if structures are already detected, + // we might make our life introducing good exit points more difficult. + new LoopDetection() + } + }, new BlockILTransform { // per-block transforms PostOrderTransforms = { //new UseExitPoints(), diff --git a/ICSharpCode.Decompiler/FlowAnalysis/Dominance.cs b/ICSharpCode.Decompiler/FlowAnalysis/Dominance.cs index 48eae66cf..a6b91e50f 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/Dominance.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/Dominance.cs @@ -116,21 +116,24 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// /// Computes a BitSet where - /// result[i] == true iff cfg[i] is reachable and there is some node that is reachable from cfg[i] but not dominated by cfg[i]. - /// This is similar to "does cfg[i] have a non-empty dominance frontier", except that it uses non-strict dominance - /// where the definition of dominance frontiers uses "strictly dominates". + /// result[i] == true iff cfg[i] is reachable and there is some node that is + /// reachable from cfg[i] but not dominated by cfg[i]. + /// + /// This is similar to "does cfg[i] have a non-empty dominance frontier?", + /// except that it uses non-strict dominance where the definition of dominance frontiers + /// uses "strictly dominates". /// /// Precondition: /// Dominance was computed for cfg and cfg[i].UserIndex == i for all i. /// - public static BitSet MarkNodesWithReachableExits(IReadOnlyList cfg) + public static BitSet MarkNodesWithReachableExits(ControlFlowNode[] cfg) { #if DEBUG - for (int i = 0; i < cfg.Count; i++) { + for (int i = 0; i < cfg.Length; i++) { Debug.Assert(cfg[i].UserIndex == i); } #endif - BitSet nonEmpty = new BitSet(cfg.Count); + BitSet nonEmpty = new BitSet(cfg.Length); foreach (var j in cfg) { // If j is a join-point (more than one incoming edge): // `j.IsReachable && j.ImmediateDominator == null` is the root node, which counts as an extra incoming edge diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 053577c15..b64221f27 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -275,6 +275,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs index eed2d8f5c..ee84fda70 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs @@ -35,6 +35,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow public class ConditionDetection : IBlockTransform { BlockTransformContext context; + BlockContainer currentContainer; /// /// Builds structured control flow for the block associated with the control flow node. @@ -46,6 +47,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow public void Run(Block block, BlockTransformContext context) { this.context = context; + this.currentContainer = (BlockContainer)block.Parent; // We only embed blocks into this block if they aren't referenced anywhere else, // so those blocks are dominated by this block. @@ -236,14 +238,20 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow return false; } + /// + /// Gets whether potentialBranchInstruction is a branch to a block + /// that is dominated by cfgNode. + /// If this function returns true, we replace the branch instruction with the block itself. + /// bool IsUsableBranchToChild(ControlFlowNode cfgNode, ILInstruction potentialBranchInstruction) { Branch br = potentialBranchInstruction as Branch; if (br == null) return false; var targetBlock = br.TargetBlock; - return targetBlock.Parent == context.Container && cfgNode.Dominates(context.GetNode(targetBlock)) - && targetBlock.IncomingEdgeCount == 1 && targetBlock.FinalInstruction.OpCode == OpCode.Nop; + return targetBlock.Parent == currentContainer + && targetBlock.IncomingEdgeCount == 1 && targetBlock.FinalInstruction.OpCode == OpCode.Nop + && cfgNode.Dominates(context.ControlFlowGraph.GetNode(targetBlock)); } private void HandleSwitchInstruction(ControlFlowNode cfgNode, Block block, SwitchInstruction sw, ref ILInstruction exitInst) diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowGraph.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowGraph.cs new file mode 100644 index 000000000..d0a488f29 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowGraph.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ICSharpCode.Decompiler.FlowAnalysis; +using ICSharpCode.Decompiler.Util; + +namespace ICSharpCode.Decompiler.IL.ControlFlow +{ + /// + /// Holds the control flow graph. + /// A separate graph is computed for each BlockContainer at the start of the block transforms + /// (before loop detection). + /// + public class ControlFlowGraph + { + readonly BlockContainer container; + + /// + /// The container for which the ControlFlowGraph was created. + /// + /// This may differ from the container currently holding a block, + /// because a transform could have moved the block since the CFG was created. + /// + public BlockContainer Container { get { return container; } } + + /// + /// Nodes array, indexed by original block index. + /// + /// Originally cfg[i].UserData == container.Blocks[i], + /// but the ILAst blocks may be moved/reordered by transforms. + /// + internal readonly ControlFlowNode[] cfg; + + /// + /// Dictionary from Block to ControlFlowNode. + /// Unlike the cfg array, this can be used to discover control flow nodes even after + /// blocks were moved/reordered by transforms. + /// + readonly Dictionary dict = new Dictionary(); + + /// + /// nodeHasDirectExitOutOfContainer[i] == true iff cfg[i] directly contains a + /// branch/leave instruction leaving the container. + /// + readonly BitSet nodeHasDirectExitOutOfContainer; + + /// + /// nodeHasReachableExit[i] == true iff there is a path from cfg[i] to a node not dominated by cfg[i], + /// or if there is a path from cfg[i] to a branch/leave instruction leaving the container. + /// + readonly BitSet nodeHasReachableExit; + + /// + /// Constructs a control flow graph for the blocks in the given block container. + /// + /// Return statements, exceptions, or branches leaving the block container are not + /// modeled by the control flow graph. + /// + public ControlFlowGraph(BlockContainer container, CancellationToken cancellationToken = default(CancellationToken)) + { + this.container = container; + this.cfg = new ControlFlowNode[container.Blocks.Count]; + this.nodeHasDirectExitOutOfContainer = new BitSet(cfg.Length); + for (int i = 0; i < cfg.Length; i++) { + Block block = container.Blocks[i]; + cfg[i] = new ControlFlowNode { UserIndex = i, UserData = block }; + dict.Add(block, cfg[i]); + } + + CreateEdges(cancellationToken); + Dominance.ComputeDominance(cfg[0], cancellationToken); + this.nodeHasReachableExit = Dominance.MarkNodesWithReachableExits(cfg); + this.nodeHasReachableExit.UnionWith(FindNodesWithExitsOutOfContainer()); + } + + void CreateEdges(CancellationToken cancellationToken) + { + for (int i = 0; i < container.Blocks.Count; i++) { + cancellationToken.ThrowIfCancellationRequested(); + var block = container.Blocks[i]; + var sourceNode = cfg[i]; + foreach (var node in block.Descendants) { + if (node is Branch branch) { + if (branch.TargetBlock.Parent == container) { + sourceNode.AddEdgeTo(cfg[container.Blocks.IndexOf(branch.TargetBlock)]); + } else if (branch.TargetBlock.IsDescendantOf(container)) { + // Internal control flow within a nested container. + } else { + // Branch out of this container into a parent container. + // Like return statements and exceptional exits, + // we ignore this for the CFG and the dominance calculation. + // However, it's relevant for HasReachableExit(). + nodeHasDirectExitOutOfContainer.Set(i); + } + } else if (node is Leave leave && !leave.TargetContainer.IsDescendantOf(block)) { + // Leave instructions (like other exits out of the container) + // are ignored for the CFG and dominance, + // but is relevant for HasReachableExit(). + nodeHasDirectExitOutOfContainer.Set(i); + } + } + } + } + + BitSet FindNodesWithExitsOutOfContainer() + { + // Also mark the nodes that exit the block container altogether. + // Invariant: leaving[n.UserIndex] == true implies leaving[n.ImmediateDominator.UserIndex] == true + var leaving = new BitSet(cfg.Length); + foreach (var node in cfg) { + if (leaving[node.UserIndex]) + continue; + if (nodeHasDirectExitOutOfContainer[node.UserIndex]) { + for (ControlFlowNode p = node; p != null; p = p.ImmediateDominator) { + if (leaving[p.UserIndex]) { + // we can stop marking when we've reached an already-marked node + break; + } + leaving.Set(p.UserIndex); + } + } + } + return leaving; + } + + bool LeavesCurrentBlockContainer(Block block) + { + foreach (var node in block.Descendants) { + if (node is Branch branch && !branch.TargetBlock.IsDescendantOf(container)) { + // control flow that isn't internal to the block container + return true; + } + if (node is Leave leave && !leave.TargetContainer.IsDescendantOf(block)) { + return true; + } + } + return false; + } + + /// + /// Gets the ControlFlowNode for the block. + /// + /// Precondition: the block belonged to the container at the start of the block transforms + /// (when the control flow graph was created). + /// + public ControlFlowNode GetNode(Block block) + { + return dict[block]; + } + + /// + /// Returns true iff there is a control flow path from node to one of the following: + /// * branch or leave instruction leaving this.Container + /// * branch instruction within this container to another node that is not dominated by node. + /// + /// If this function returns false, the only way control flow can leave the set of nodes + /// dominated by node is by executing a return or throw instruction. + /// + public bool HasReachableExit(ControlFlowNode node) + { + Debug.Assert(cfg[node.UserIndex] == node); + return nodeHasReachableExit[node.UserIndex]; + } + + /// + /// Gets whether the control flow node directly contains a branch/leave instruction + /// exiting the container. + /// + public bool HasDirectExitOutOfContainer(ControlFlowNode node) + { + Debug.Assert(cfg[node.UserIndex] == node); + return nodeHasDirectExitOutOfContainer[node.UserIndex]; + } + } +} diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs index cfa8599cf..689d8aa4e 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs @@ -190,6 +190,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } } + /* /// /// Like DetectExitPoints, but only uses existing exit points /// (by replacing compatible instructions with Leave), @@ -202,6 +203,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow public void Run(Block block, BlockTransformContext context) { + Debug.Assert(block.Parent == context.Container); currentContainer = context.Container; exitPoint = DetectExitPoints.GetExit(context.Container); Visit(block); @@ -227,4 +229,5 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } } } + */ } diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs index ed3b5b5ca..e214c081a 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs @@ -35,113 +35,44 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// don't include more instructions than strictly necessary. /// * Loop detection should run after the 'return block' is duplicated (ControlFlowSimplification). /// - public class LoopDetection : IILTransform + public class LoopDetection : IBlockTransform { - #region Construct Control Flow Graph - /// - /// Constructs a control flow graph for the blocks in the given block container. - /// The graph nodes will have the same indices as the blocks in the block container. - /// Return statements, exceptions, or branches leaving the block container are not - /// modeled by the control flow graph. - /// - internal static ControlFlowNode[] BuildCFG(BlockContainer bc) - { - ControlFlowNode[] nodes = new ControlFlowNode[bc.Blocks.Count]; - for (int i = 0; i < bc.Blocks.Count; i++) { - nodes[i] = new ControlFlowNode { UserIndex = i, UserData = bc.Blocks[i] }; - } - - // Create edges: - for (int i = 0; i < bc.Blocks.Count; i++) { - var block = bc.Blocks[i]; - var sourceNode = nodes[i]; - foreach (var branch in block.Descendants.OfType()) { - if (branch.TargetBlock.Parent == bc) { - sourceNode.AddEdgeTo(nodes[bc.Blocks.IndexOf(branch.TargetBlock)]); - } else { - // Note: edges into different block containers are ignored: - // Either they point to a nested block container in the source block, - // in which case we can ignore them for control flow purposes; - // or they jump to a parent block container, in which case they act - // like a return statement or exceptional exit. - } - } - } - - return nodes; - } - #endregion - - /// - /// Run loop detection for all block containers in the function (including nested lambda functions). - /// - public void Run(ILFunction function, ILTransformContext context) - { - foreach (var blockContainer in function.Descendants.OfType()) { - Run(blockContainer, context); - } - } - - ILTransformContext context; - - ControlFlowNode[] cfg; - - /// - /// nodeHasReachableExit[i] == true iff there is a path from cfg[i] to a node not dominated by cfg[i], - /// or if there is a path from cfg[i] to a branch/leave instruction leaving the currentBlockContainer. - /// - BitSet nodeHasReachableExit; - - /// - /// nodeHasDirectExitOutOfContainer[i] == true iff cfg[i] directly contains a branch/leave instruction leaving the currentBlockContainer. - /// - BitSet nodeHasDirectExitOutOfContainer; + BlockTransformContext context; /// Block container corresponding to the current cfg. BlockContainer currentBlockContainer; /// - /// Run loop detection for blocks in the block container. + /// Check whether 'block' is a loop head; and construct a loop instruction + /// (nested BlockContainer) if it is. /// - public void Run(BlockContainer blockContainer, ILTransformContext context) + public void Run(Block block, BlockTransformContext context) { this.context = context; - this.currentBlockContainer = blockContainer; - this.cfg = BuildCFG(blockContainer); - this.nodeHasReachableExit = null; // will be computed on-demand - this.nodeHasDirectExitOutOfContainer = null; // will be computed on-demand - - var entryPoint = cfg[0]; - Dominance.ComputeDominance(entryPoint, context.CancellationToken); - FindLoops(entryPoint); - - this.cfg = null; - this.nodeHasReachableExit = null; - this.nodeHasDirectExitOutOfContainer = null; - this.currentBlockContainer = null; - this.context = null; - } - - /// - /// Recurse into the dominator tree and find back edges/natural loops. - /// - /// - /// 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. - /// - /// Preconditions: - /// * dominance was computed for h - /// * all blocks in the dominator subtree starting at h are in the same BlockContainer - /// * the visited flag is set to false - /// - void FindLoops(ControlFlowNode h) - { + // 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. + + 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 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(); loop.Add(h); @@ -158,21 +89,36 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow // 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 nested loops + node.Visited = false; // reset visited flag so that we can find outer loops Debug.Assert(h.Dominates(node), "The loop body must be dominated by the loop head"); } ConstructLoop(loop, exitPoint); } + } + + /// + /// Recurse into the dominator tree and find back edges/natural loops. + /// + /// + /// + /// Preconditions: + /// * dominance was computed for h + /// * all blocks in the dominator subtree starting at h are in the same BlockContainer + /// * the visited flag is set to false + /// + void FindLoops(ControlFlowNode h) + { // Recurse into the dominator tree to find other possible loop heads foreach (var child in h.DominatorTreeChildren) { FindLoops(child); } + } #region ExtendLoop @@ -259,7 +205,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// void ExtendLoop(ControlFlowNode loopHead, List loop, out ControlFlowNode exitPoint) { - ComputeNodesWithReachableExits(); exitPoint = FindExitPoint(loopHead, loop); Debug.Assert(!loop.Contains(exitPoint), "Cannot pick an exit point that is part of the natural loop"); if (exitPoint != null) { @@ -279,55 +224,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } } - void ComputeNodesWithReachableExits() - { - if (nodeHasReachableExit != null) - return; - nodeHasReachableExit = Dominance.MarkNodesWithReachableExits(cfg); - nodeHasDirectExitOutOfContainer = new BitSet(cfg.Length); - // Also mark the nodes that exit the block container altogether. - // Invariant: leaving[n.UserIndex] == true implies leaving[n.ImmediateDominator.UserIndex] == true - var leaving = new BitSet(cfg.Length); - foreach (var node in cfg) { - if (leaving[node.UserIndex]) - continue; - if (LeavesCurrentBlockContainer((Block)node.UserData)) { - nodeHasDirectExitOutOfContainer.Set(node.UserIndex); - for (ControlFlowNode p = node; p != null; p = p.ImmediateDominator) { - if (leaving[p.UserIndex]) { - // we can stop marking when we've reached an already-marked node - break; - } - leaving.Set(p.UserIndex); - } - } - } - nodeHasReachableExit.UnionWith(leaving); - } - - bool LeavesCurrentBlockContainer(Block block) - { - foreach (var branch in block.Descendants.OfType()) { - if (!branch.TargetBlock.IsDescendantOf(currentBlockContainer)) { - // control flow that isn't internal to the block container - return true; - } - } - foreach (var leave in block.Descendants.OfType()) { - if (!leave.TargetContainer.IsDescendantOf(block)) { - return true; - } - } - return false; - } - /// /// Finds a suitable single exit point for the specified loop. /// /// This method must not write to the Visited flags on the CFG. ControlFlowNode FindExitPoint(ControlFlowNode loopHead, IReadOnlyList naturalLoop) { - if (!nodeHasReachableExit[loopHead.UserIndex]) { + if (!context.ControlFlowGraph.HasReachableExit(loopHead)) { // 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. @@ -341,6 +244,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow // 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); //ControlFlowNode.ExportGraph(cfg).Show("cfg"); //ControlFlowNode.ExportGraph(revCfg).Show("rev"); @@ -382,7 +286,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow foreach (var child in node.DominatorTreeChildren) { codeAmount += PickExitPoint(child, ref exitPoint, ref exitPointCodeAmount); } - if (codeAmount > exitPointCodeAmount && !nodeHasReachableExit[node.UserIndex]) { + if (codeAmount > exitPointCodeAmount && !context.ControlFlowGraph.HasReachableExit(node)) { // dominanceFrontier(node) is empty // -> there are no nodes n so that `node` dominates a predecessor of n but not n itself // -> there is no control flow out of `node` back into the loop, so it's usable as exit point @@ -394,7 +298,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow ControlFlowNode[] PrepareReverseCFG(ControlFlowNode loopHead) { - ControlFlowNode[] cfg = this.cfg; + 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 }; @@ -412,7 +316,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow exitNode.AddEdgeTo(rev[i]); } } - if (nodeHasDirectExitOutOfContainer[i]) { + if (context.ControlFlowGraph.HasDirectExitOutOfContainer(cfg[i])) { exitNode.AddEdgeTo(rev[i]); } } @@ -487,6 +391,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// void ConstructLoop(List loop, ControlFlowNode exitPoint) { + exitPoint = null; // TODO Block oldEntryPoint = (Block)loop[0].UserData; Block exitTargetBlock = (Block)exitPoint?.UserData; @@ -507,13 +412,23 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow // and thus cannot be the target of branch instructions outside the loop. for (int i = 1; i < loop.Count; i++) { Block block = (Block)loop[i].UserData; - Debug.Assert(block.ChildIndex != 0); - var oldParent = ((BlockContainer)block.Parent); - int oldChildIndex = block.ChildIndex; - loopContainer.Blocks.Add(block); - oldParent.Blocks.SwapRemoveAt(oldChildIndex); + // some blocks might already be in use by nested loops that were detected earlier; + // don't move those (they'll be implicitly moved when the block containing the + // nested loop container is moved). + if (block.Parent == currentBlockContainer) { + Debug.Assert(block.ChildIndex != 0); + int oldChildIndex = block.ChildIndex; + loopContainer.Blocks.Add(block); + currentBlockContainer.Blocks.SwapRemoveAt(oldChildIndex); + } + } + for (int i = 1; i < loop.Count; i++) { + // Verify that we moved all loop blocks into the loop container. + // If we wanted to move any blocks already in use by a nested loop, + // this means we check that the whole nested loop got moved. + Block block = (Block)loop[i].UserData; + Debug.Assert(block.IsDescendantOf(loopContainer)); } - // Rewrite branches within the loop from oldEntryPoint to newEntryPoint: foreach (var branch in loopContainer.Descendants.OfType()) { if (branch.TargetBlock == oldEntryPoint) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs index a4c027173..fb62f81f2 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs @@ -14,7 +14,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } /// - /// Parameter class holding various arguments for . + /// Parameter class holding various arguments for . /// public class BlockTransformContext : ILTransformContext { @@ -23,11 +23,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// public ILFunction Function { get; set; } - /// - /// The container containing the block currently being processed. - /// - public BlockContainer Container { get; set; } - /// /// The block to process. /// @@ -39,22 +34,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// The control flow node corresponding to the block being processed. /// + /// + /// Identical to ControlFlowGraph.GetNode(Block). + /// Note: the control flow graph is not up-to-date, but was created at the start of the + /// block transforms (before loop detection). + /// public ControlFlowNode ControlFlowNode { get; set; } - internal readonly Dictionary cfg = new Dictionary(); - - public BlockTransformContext(ILTransformContext context) : base(context) - { - } - /// - /// Gets the ControlFlowNode for the block. - /// Precondition: the block belonged to the Container at the start of the block transforms - /// (when the control flow graph was created). + /// Gets the control flow graph. + /// + /// Note: the control flow graph is not up-to-date, but was created at the start of the + /// block transforms (before loop detection). /// - public ControlFlowNode GetNode(Block block) + public ControlFlowGraph ControlFlowGraph { get; set; } + + public BlockTransformContext(ILTransformContext context) : base(context) { - return cfg[block]; } } @@ -73,15 +69,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms blockContext.Function = function; foreach (var container in function.Descendants.OfType().ToList()) { context.CancellationToken.ThrowIfCancellationRequested(); - var cfg = LoopDetection.BuildCFG(container); - Dominance.ComputeDominance(cfg[0], context.CancellationToken); - - blockContext.Container = container; - blockContext.cfg.Clear(); - for (int i = 0; i < cfg.Length; i++) { - blockContext.cfg.Add(container.Blocks[i], cfg[i]); - } - VisitBlock(cfg[0], blockContext); + blockContext.ControlFlowGraph = new ControlFlowGraph(container, context.CancellationToken); + VisitBlock(blockContext.ControlFlowGraph.GetNode(container.EntryPoint), blockContext); // TODO: handle unreachable code? } } diff --git a/ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs b/ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs index 737efba64..35919b2ec 100644 --- a/ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs +++ b/ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs @@ -21,6 +21,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.Tests.Helpers; using Microsoft.Win32; @@ -152,7 +153,16 @@ namespace ICSharpCode.Decompiler.Tests { Directory.CreateDirectory(dir); foreach (string subdir in Directory.EnumerateDirectories(dir)) { - Directory.Delete(subdir, true); + for (int attempt = 0; ; attempt++) { + try { + Directory.Delete(subdir, true); + break; + } catch (IOException) { + if (attempt >= 10) + throw; + Thread.Sleep(100); + } + } } foreach (string file in Directory.EnumerateFiles(dir)) { File.Delete(file);