예제 #1
0
        /// <summary>
        /// Move the blocks associated with the loop into a new block container.
        /// </summary>
        void ConstructLoop(List <ControlFlowNode> loop, ControlFlowNode exitPoint)
        {
            Block oldEntryPoint   = (Block)loop[0].UserData;
            Block exitTargetBlock = (Block)exitPoint?.UserData;

            BlockContainer loopContainer = new BlockContainer(ContainerKind.Loop);
            Block          newEntryPoint = new Block();

            loopContainer.Blocks.Add(newEntryPoint);
            // Move contents of oldEntryPoint to newEntryPoint
            // (we can't move the block itself because it might be the target of branch instructions outside the loop)
            newEntryPoint.Instructions.ReplaceList(oldEntryPoint.Instructions);
            newEntryPoint.AddILRange(oldEntryPoint);
            oldEntryPoint.Instructions.ReplaceList(new[] { loopContainer });
            if (exitTargetBlock != null)
            {
                oldEntryPoint.Instructions.Add(new Branch(exitTargetBlock));
            }

            loopContainer.AddILRange(newEntryPoint);
            MoveBlocksIntoContainer(loop, loopContainer);

            // Rewrite branches within the loop from oldEntryPoint to newEntryPoint:
            foreach (var branch in loopContainer.Descendants.OfType <Branch>())
            {
                if (branch.TargetBlock == oldEntryPoint)
                {
                    branch.TargetBlock = newEntryPoint;
                }
                else if (branch.TargetBlock == exitTargetBlock)
                {
                    branch.ReplaceWith(new Leave(loopContainer).WithILRange(branch));
                }
            }
        }
예제 #2
0
        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));

            isSwitch    = true;
            loopContext = new SwitchDetection.LoopContext(context.ControlFlowGraph, h);

            var nodesInSwitch = new List <ControlFlowNode>();

            nodesInSwitch.Add(h);
            h.Visited = true;
            ExtendLoop(h, nodesInSwitch, out var exitPoint);
            if (exitPoint != null && h.Dominates(exitPoint) && exitPoint.Predecessors.Count == 1 && !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));
                foreach (var node in nodesInSwitch)
                {
                    node.Visited = true;
                }
                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), "The switch body must be dominated by the switch head");
            }

            BlockContainer switchContainer = new BlockContainer(ContainerKind.Switch);
            Block          newEntryPoint   = new Block();

            newEntryPoint.AddILRange(switchInst);
            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));
            }

            switchContainer.AddILRange(newEntryPoint);
            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).WithILRange(branch));
                }
            }

            isSwitch = false;
        }
예제 #3
0
        public static void Run(ILFunction function, ILTransformContext context)
        {
            if (!context.Settings.AwaitInCatchFinally)
            {
                return;
            }
            HashSet <BlockContainer> changedContainers = new HashSet <BlockContainer>();

            // analyze all try-catch statements in the function
            foreach (var tryCatch in function.Descendants.OfType <TryCatch>().ToArray())
            {
                if (!(tryCatch.Parent?.Parent is BlockContainer container))
                {
                    continue;
                }
                // await in finally uses a single catch block with catch-type object
                if (tryCatch.Handlers.Count != 1 || !(tryCatch.Handlers[0].Body is BlockContainer catchBlockContainer) || !tryCatch.Handlers[0].Variable.Type.IsKnownType(KnownTypeCode.Object))
                {
                    continue;
                }
                // and consists of an assignment to a temporary that is used outside the catch block
                // and a jump to the finally block
                var block = catchBlockContainer.EntryPoint;
                if (block.Instructions.Count < 2 || !block.Instructions[0].MatchStLoc(out var globalCopyVar, out var value) || !value.MatchLdLoc(tryCatch.Handlers[0].Variable))
                {
                    continue;
                }
                if (block.Instructions.Count == 3)
                {
                    if (!block.Instructions[1].MatchStLoc(out var globalCopyVarTemp, out value) || !value.MatchLdLoc(globalCopyVar))
                    {
                        continue;
                    }
                    globalCopyVar = globalCopyVarTemp;
                }
                if (!block.Instructions[block.Instructions.Count - 1].MatchBranch(out var entryPointOfFinally))
                {
                    continue;
                }
                // globalCopyVar should only be used once, at the end of the finally-block
                if (globalCopyVar.LoadCount != 1 || globalCopyVar.StoreCount > 2)
                {
                    continue;
                }
                var tempStore = globalCopyVar.LoadInstructions[0].Parent as StLoc;
                if (tempStore == null || !MatchExceptionCaptureBlock(tempStore, out var exitOfFinally, out var afterFinally, out var blocksToRemove))
                {
                    continue;
                }
                if (!MatchAfterFinallyBlock(ref afterFinally, blocksToRemove, out bool removeFirstInstructionInAfterFinally))
                {
                    continue;
                }
                var cfg = new ControlFlowGraph(container, context.CancellationToken);
                var exitOfFinallyNode       = cfg.GetNode(exitOfFinally);
                var entryPointOfFinallyNode = cfg.GetNode(entryPointOfFinally);

                var additionalBlocksInFinally = new HashSet <Block>();
                var invalidExits = new List <ControlFlowNode>();

                TraverseDominatorTree(entryPointOfFinallyNode);

                void TraverseDominatorTree(ControlFlowNode node)
                {
                    if (entryPointOfFinallyNode != node)
                    {
                        if (entryPointOfFinallyNode.Dominates(node))
                        {
                            additionalBlocksInFinally.Add((Block)node.UserData);
                        }
                        else
                        {
                            invalidExits.Add(node);
                        }
                    }

                    if (node == exitOfFinallyNode)
                    {
                        return;
                    }

                    foreach (var child in node.DominatorTreeChildren)
                    {
                        TraverseDominatorTree(child);
                    }
                }

                if (invalidExits.Any())
                {
                    continue;
                }

                context.Step("Inline finally block with await", tryCatch.Handlers[0]);

                foreach (var blockToRemove in blocksToRemove)
                {
                    blockToRemove.Remove();
                }
                var finallyContainer = new BlockContainer();
                entryPointOfFinally.Remove();
                if (removeFirstInstructionInAfterFinally)
                {
                    afterFinally.Instructions.RemoveAt(0);
                }
                changedContainers.Add(container);
                var outer = BlockContainer.FindClosestContainer(container.Parent);
                if (outer != null)
                {
                    changedContainers.Add(outer);
                }
                finallyContainer.Blocks.Add(entryPointOfFinally);
                finallyContainer.AddILRange(entryPointOfFinally);
                exitOfFinally.Instructions.RemoveRange(tempStore.ChildIndex, 3);
                exitOfFinally.Instructions.Add(new Leave(finallyContainer));
                foreach (var branchToFinally in container.Descendants.OfType <Branch>())
                {
                    if (branchToFinally.TargetBlock == entryPointOfFinally)
                    {
                        branchToFinally.ReplaceWith(new Branch(afterFinally));
                    }
                }
                foreach (var newBlock in additionalBlocksInFinally)
                {
                    newBlock.Remove();
                    finallyContainer.Blocks.Add(newBlock);
                    finallyContainer.AddILRange(newBlock);
                }
                tryCatch.ReplaceWith(new TryFinally(tryCatch.TryBlock, finallyContainer).WithILRange(tryCatch.TryBlock));
            }

            // clean up all modified containers
            foreach (var container in changedContainers)
            {
                container.SortBlocks(deleteUnreachableBlocks: true);
            }
        }