/// <summary> /// Returns the maximum stack depth required by these CIL instructions. /// </summary> /// <returns>The integer value of the stck depth.</returns> public int GetMaxStackDepthRequired() { if (tide == 0) return 0; // Store the code blocks we find SCG.List<CodeBlock> codeBlocks = new SCG.List<CodeBlock>(); SCG.Dictionary<CILLabel, CodeBlock> cbTable = new SCG.Dictionary<CILLabel, CodeBlock>(); SCG.List<CodeBlock> extraStartingBlocks = new SCG.List<CodeBlock>(); // Start a default code block CodeBlock codeBlock = new CodeBlock(this); codeBlock.StartIndex = 0; // // Identify the code blocks // for (int i = 0; i < tide; i++) { /* Handling the tail instruction: * The tail instruction has not been handled even though * it indicates the end of a code block is coming. The * reason for this is because any valid tail instruction * must be followed by a call* instruction and then a ret * instruction. Given a ret instruction must be the second * next instruction anyway it has been decided to just let * the end block be caught then. */ // If we reach a branch instruction or a switch instruction // then end the current code block inclusive of the instruction. if ((buffer[i] is BranchInstr) || (buffer[i] is SwitchInstr)) { // Close the old block codeBlock.EndIndex = i; if (codeBlock.EndIndex >= codeBlock.StartIndex) // Don't add empty blocks codeBlocks.Add(codeBlock); // Open a new block codeBlock = new CodeBlock(this); codeBlock.StartIndex = i + 1; // If we reach a label then we need to start a new // code block as the label is an entry point. } else if (buffer[i] is CILLabel) { // Close the old block codeBlock.EndIndex = i - 1; if (codeBlock.EndIndex >= codeBlock.StartIndex) // Don't add empty blocks codeBlocks.Add(codeBlock); // Open a new block codeBlock = new CodeBlock(this); codeBlock.StartIndex = i; // Set this label as the entry point for the code block codeBlock.EntryLabel = (CILLabel)buffer[i]; // AND ... list in the dictionary. cbTable.Add(codeBlock.EntryLabel, codeBlock); // Check for the ret, throw, rethrow, or jmp instruction as they also end a block } else if (buffer[i] is Instr) { if ( (((Instr)buffer[i]).GetOp() == Op.ret) || (((Instr)buffer[i]).GetOp() == Op.throwOp) || (((Instr)buffer[i]).GetOp() == Op.rethrow) || ((buffer[i] is MethInstr) && (((MethInstr)buffer[i]).GetMethodOp() == MethodOp.jmp)) ) { // Close the old block codeBlock.EndIndex = i; if (codeBlock.EndIndex >= codeBlock.StartIndex) // Don't add empty blocks codeBlocks.Add(codeBlock); // Open a new block // In theory this should never happen but just in case // someone feels like adding dead code it is supported. codeBlock = new CodeBlock(this); codeBlock.StartIndex = i + 1; } } } // Close the last block codeBlock.EndIndex = tide - 1; if (codeBlock.EndIndex >= codeBlock.StartIndex) // Don't add empty blocks codeBlocks.Add(codeBlock); codeBlock = null; // Check how many code blocks there are. If an blocks return 0. if (codeBlocks.Count == 0) return 0; // // Loop through each code block and calculate the delta distance // for (int j = 0; j < codeBlocks.Count; j++) { CodeBlock block = codeBlocks[j]; int maxDepth = 0; int currentDepth = 0; // Loop through each instruction to work out the max depth for (int i = block.StartIndex; i <= block.EndIndex; i++) { // Get the depth after the next instruction currentDepth += buffer[i].GetDeltaDistance(); // If the new current depth is greater then the maxDepth adjust the maxDepth to reflect if (currentDepth > maxDepth) maxDepth = currentDepth; } // Set the depth of the block block.MaxDepth = maxDepth; block.DeltaDistance = currentDepth; // // Link up the next blocks // // If the block ends with a branch statement set the jump and fall through. if (buffer[block.EndIndex] is BranchInstr) { BranchInstr branchInst = (BranchInstr)buffer[block.EndIndex]; // If this is not a "br" or "br.s" then set the fall through code block if ((branchInst.GetBranchOp() != BranchOp.br) && (branchInst.GetBranchOp() != BranchOp.br_s)) // If there is a following code block set it as the fall through if (j < (codeBlocks.Count - 1)) block.NextBlocks.Add(codeBlocks[j + 1]); // Set the code block we are jumping to CodeBlock cb; cbTable.TryGetValue(branchInst.GetDest(), out cb); if (cb == null) throw new Exception("Missing Branch Label"); block.NextBlocks.Add(cb); // If the block ends in a switch instruction work out the possible next blocks } else if (buffer[block.EndIndex] is SwitchInstr) { SwitchInstr switchInstr = (SwitchInstr)buffer[block.EndIndex]; // If there is a following code block set it as the fall through if (j < (codeBlocks.Count - 1)) block.NextBlocks.Add(codeBlocks[j + 1]); // Add each destination block foreach (CILLabel label in switchInstr.GetDests()) { // Check all of the code blocks to find the jump destination CodeBlock cb; cbTable.TryGetValue(label, out cb); if (cb == null) throw new Exception("Missing Case Label"); block.NextBlocks.Add(cb); } // So long as the block doesn't end with a terminating instruction like ret or throw, just fall through to the next block } else if (!IsTerminatingInstruction(buffer[block.EndIndex])) { // If there is a following code block set it as the fall through if (j < (codeBlocks.Count - 1)) block.NextBlocks.Add(codeBlocks[j + 1]); } } // // Join up any exception blocks // if (exceptions != null) { foreach (TryBlock tryBlock in exceptions) { // Try to find the code block where this try block starts CodeBlock tryCodeBlock; cbTable.TryGetValue(tryBlock.Start, out tryCodeBlock); // Declare that the entry to this code block must be empty tryCodeBlock.RequireEmptyEntry = true; // Work with each of the handlers foreach (HandlerBlock hb in tryBlock.GetHandlers()) { // Find the code block where this handler block starts. CodeBlock handlerCodeBlock; cbTable.TryGetValue(hb.Start, out handlerCodeBlock); // If the code block is a catch or filter block increment the delta // distance by 1. This is to factor in the exception object that will // be secretly placed on the stack by the runtime engine. // However, this also means that the MaxDepth is up by one also! if (hb is Catch || hb is Filter) { handlerCodeBlock.DeltaDistance++; handlerCodeBlock.MaxDepth++; } // If the code block is a filter block increment the delta distance by 1 // This is to factor in the exception object that will be placed on the stack. // if (hb is Filter) handlerCodeBlock.DeltaDistance++; // Add this handler to the list of starting places extraStartingBlocks.Add(handlerCodeBlock); } } } // // Traverse the code blocks and get the depth // // Get the max depth at the starting entry point int finalMaxDepth = TraverseMaxDepth(codeBlocks[0]); // Check the additional entry points // If the additional points have a greater depth update the max depth foreach (CodeBlock cb in extraStartingBlocks) { // int tmpMaxDepth = cb.TraverseMaxDepth(); int tmpMaxDepth = TraverseMaxDepth(cb); if (tmpMaxDepth > finalMaxDepth) finalMaxDepth = tmpMaxDepth; } // Return the max depth we have found return finalMaxDepth; }
private static int TraverseMaxDepth(CodeBlock entryBlock) { int max = 0; SCG.Queue<CodeBlock> worklist = new SCG.Queue<CodeBlock>(); entryBlock.Visited = true; entryBlock.LastVisitEntryDepth = 0; worklist.Enqueue(entryBlock); while (worklist.Count > 0) { int count = worklist.Count; CodeBlock unit = worklist.Dequeue(); int maxDepth = unit.LastVisitEntryDepth + unit.MaxDepth; int exitDepth = unit.LastVisitEntryDepth + unit.DeltaDistance; if (maxDepth > max) max = maxDepth; foreach (CodeBlock succ in unit.NextBlocks) { if (succ.Visited) { if (succ.LastVisitEntryDepth != exitDepth) throw new InvalidStackDepth("inconsistent stack depth at offset " + succ.StartIndex.ToString()); } else { succ.Visited = true; succ.LastVisitEntryDepth = exitDepth; worklist.Enqueue(succ); } } } return max; }