/// <summary> /// Ensures that every write to a pinned local is followed by a branch instruction. /// This ensures the 'pinning region' does not involve any half blocks, which makes it easier to extract. /// </summary> void SplitBlocksAtWritesToPinnedLocals(BlockContainer container) { for (int i = 0; i < container.Blocks.Count; i++) { var block = container.Blocks[i]; for (int j = 0; j < block.Instructions.Count - 1; j++) { var inst = block.Instructions[j]; if (inst.MatchStLoc(out ILVariable v, out var value) && v.Kind == VariableKind.PinnedLocal) { if (block.Instructions[j + 1].OpCode != OpCode.Branch) { // split block after j: context.Step("Split block after pinned local write", inst); var newBlock = new Block(); for (int k = j + 1; k < block.Instructions.Count; k++) { newBlock.Instructions.Add(block.Instructions[k]); } newBlock.AddILRange(newBlock.Instructions[0]); block.Instructions.RemoveRange(j + 1, newBlock.Instructions.Count); block.Instructions.Add(new Branch(newBlock)); container.Blocks.Insert(i + 1, newBlock); } // in case of re-pinning (e.g. C++/CLI assignment to pin_ptr variable), // it's possible for the new value to be dependent on the old. if (v.IsUsedWithin(value)) { // In this case, we need to un-inline the uses of the pinned local // so that they are split off into the block prior to the pinned local write var temp = context.Function.RegisterVariable(VariableKind.StackSlot, v.Type); block.Instructions.Insert(j++, new StLoc(temp, new LdLoc(v))); foreach (var descendant in value.Descendants) { if (descendant.MatchLdLoc(v)) { descendant.ReplaceWith(new LdLoc(temp).WithILRange(descendant)); } } } if (j > 0) { // split block before j: context.Step("Split block before pinned local write", inst); var newBlock = new Block(); newBlock.Instructions.Add(block.Instructions[j]); newBlock.Instructions.Add(block.Instructions[j + 1]); newBlock.AddILRange(newBlock.Instructions[0]); Debug.Assert(block.Instructions.Count == j + 2); block.Instructions.RemoveRange(j, 2); block.Instructions.Insert(j, new Branch(newBlock)); container.Blocks.Insert(i + 1, newBlock); } } } } }
/// <summary> /// Ensures that every write to a pinned local is followed by a branch instruction. /// This ensures the 'pinning region' does not involve any half blocks, which makes it easier to extract. /// </summary> void SplitBlocksAtWritesToPinnedLocals(BlockContainer container) { for (int i = 0; i < container.Blocks.Count; i++) { var block = container.Blocks[i]; for (int j = 0; j < block.Instructions.Count - 1; j++) { var inst = block.Instructions[j]; ILVariable v; if (inst.MatchStLoc(out v) && v.Kind == VariableKind.PinnedLocal) { if (block.Instructions[j + 1].OpCode != OpCode.Branch) { // split block after j: context.Step("Split block after pinned local write", inst); var newBlock = new Block(); for (int k = j + 1; k < block.Instructions.Count; k++) { newBlock.Instructions.Add(block.Instructions[k]); } newBlock.AddILRange(newBlock.Instructions[0]); block.Instructions.RemoveRange(j + 1, newBlock.Instructions.Count); block.Instructions.Add(new Branch(newBlock)); container.Blocks.Insert(i + 1, newBlock); } if (j > 0) { // split block before j: context.Step("Split block before pinned local write", inst); var newBlock = new Block(); newBlock.Instructions.Add(block.Instructions[j]); newBlock.Instructions.Add(block.Instructions[j + 1]); Debug.Assert(block.Instructions.Count == j + 2); block.Instructions.RemoveRange(j, 2); block.Instructions.Insert(j, new Branch(newBlock)); container.Blocks.Insert(i + 1, newBlock); } } } } }
static bool CombineBlockWithNextBlock(BlockContainer container, Block block, ILTransformContext context) { Debug.Assert(container == block.Parent); // Ensure the block will stay a basic block -- we don't want extended basic blocks prior to LoopDetection. if (block.Instructions.Count > 1 && block.Instructions[block.Instructions.Count - 2].HasFlag(InstructionFlags.MayBranch)) { return(false); } Branch br = block.Instructions.Last() as Branch; // Check whether we can combine the target block with this block if (br == null || br.TargetBlock.Parent != container || br.TargetBlock.IncomingEdgeCount != 1) { return(false); } if (br.TargetBlock == block) { return(false); // don't inline block into itself } context.Step("CombineBlockWithNextBlock", br); var targetBlock = br.TargetBlock; if (targetBlock.StartILOffset < block.StartILOffset && IsDeadTrueStore(block)) { // The C# compiler generates a dead store for the condition of while (true) loops. block.Instructions.RemoveRange(block.Instructions.Count - 3, 2); } if (block.ILRangeIsEmpty) { block.AddILRange(targetBlock); } block.Instructions.Remove(br); block.Instructions.AddRange(targetBlock.Instructions); targetBlock.Instructions.Clear(); // mark targetBlock for deletion return(true); }
bool MatchForLoop(BlockContainer loop, IfInstruction whileCondition, Block whileLoopBody) { // for loops have exactly two incoming edges at the entry point. if (loop.EntryPoint.IncomingEdgeCount != 2) { return(false); } // try to find an increment block: // consists of simple statements only. var incrementBlock = GetIncrementBlock(loop, whileLoopBody); if (incrementBlock != null) { // we found a possible increment block, just make sure, that there are at least three blocks: // - condition block // - loop body // - increment block if (incrementBlock.Instructions.Count <= 1 || loop.Blocks.Count < 3) { return(false); } context.Step("Transform to for loop", loop); // move the block to the end of the loop: loop.Blocks.MoveElementToEnd(incrementBlock); loop.Kind = ContainerKind.For; } else { // we need to move the increment statements into its own block: // last must be a branch entry-point var last = whileLoopBody.Instructions.LastOrDefault(); var secondToLast = whileLoopBody.Instructions.SecondToLastOrDefault(); if (last == null || secondToLast == null) { return(false); } if (!last.MatchBranch(loop.EntryPoint)) { return(false); } // we only deal with 'numeric' increments if (!MatchIncrement(secondToLast, out var incrementVariable)) { return(false); } // the increment variable must be local/stack variable if (incrementVariable.Kind == VariableKind.Parameter) { return(false); } // split conditions: var conditions = new List <ILInstruction>(); SplitConditions(whileCondition.Condition, conditions); IfInstruction forCondition = null; int numberOfConditions = 0; foreach (var condition in conditions) { // the increment variable must be used in the condition if (!condition.Descendants.Any(inst => inst.MatchLdLoc(incrementVariable))) { break; } // condition should not contain an assignment if (condition.Descendants.Any(IsAssignment)) { break; } if (forCondition == null) { forCondition = new IfInstruction(condition, whileCondition.TrueInst, whileCondition.FalseInst); } else { forCondition.Condition = IfInstruction.LogicAnd(forCondition.Condition, condition); } numberOfConditions++; } if (numberOfConditions == 0) { return(false); } context.Step("Transform to for loop", loop); // split condition block: whileCondition.ReplaceWith(forCondition); ExpressionTransforms.RunOnSingleStatement(forCondition, context); for (int i = conditions.Count - 1; i >= numberOfConditions; i--) { IfInstruction inst; whileLoopBody.Instructions.Insert(0, inst = new IfInstruction(Comp.LogicNot(conditions[i]), new Leave(loop))); ExpressionTransforms.RunOnSingleStatement(inst, context); } // create a new increment block and add it at the end: int secondToLastIndex = secondToLast.ChildIndex; var newIncremenBlock = new Block(); loop.Blocks.Add(newIncremenBlock); // move the increment instruction: newIncremenBlock.Instructions.Add(secondToLast); newIncremenBlock.Instructions.Add(last); newIncremenBlock.AddILRange(secondToLast.ILRange); whileLoopBody.Instructions.RemoveRange(secondToLastIndex, 2); whileLoopBody.Instructions.Add(new Branch(newIncremenBlock)); // complete transform. loop.Kind = ContainerKind.For; } return(true); }
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; }