/// <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)); } } }
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; }
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); } }