ART: Implement try/catch blocks in Builder

This patch enables the GraphBuilder to generate blocks and edges which
represent the exceptional control flow when try/catch blocks are
present in the code. Actual compilation is still delegated to Quick
and Baseline ignores the additional code.

To represent the relationship between try and catch blocks, Builder
splits the edges which enter/exit a try block and links the newly
created blocks to the corresponding exception handlers. This layout
will later enable the SsaBuilder to correctly infer the dominators of
the catch blocks and to produce the appropriate reverse post ordering.
It will not, however, allow for building the complete SSA form of the
catch blocks and consequently optimizing such blocks.

To this end, a new TryBoundary control-flow instruction is introduced.
Codegen treats it the same as a Goto but it allows for additional
successors (the handlers).

Change-Id: I415b985596d5bebb7b1bb358a46e08b7b04bb53a
diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc
index e527e8b..742429c 100644
--- a/compiler/optimizing/builder.cc
+++ b/compiler/optimizing/builder.cc
@@ -259,6 +259,165 @@
   return false;
 }
 
+bool HGraphBuilder::IsBlockInPcRange(HBasicBlock* block,
+                                     uint32_t dex_pc_start,
+                                     uint32_t dex_pc_end) {
+  uint32_t dex_pc = block->GetDexPc();
+  return block != entry_block_
+      && block != exit_block_
+      && dex_pc >= dex_pc_start
+      && dex_pc < dex_pc_end;
+}
+
+void HGraphBuilder::CreateBlocksForTryCatch(const DexFile::CodeItem& code_item) {
+  if (code_item.tries_size_ == 0) {
+    return;
+  }
+
+  // Create branch targets at the start/end of the TryItem range. These are
+  // places where the program might fall through into/out of the a block and
+  // where TryBoundary instructions will be inserted later. Other edges which
+  // enter/exit the try blocks are a result of branches/switches.
+  for (size_t idx = 0; idx < code_item.tries_size_; ++idx) {
+    const DexFile::TryItem* try_item = DexFile::GetTryItems(code_item, idx);
+    uint32_t dex_pc_start = try_item->start_addr_;
+    uint32_t dex_pc_end = dex_pc_start + try_item->insn_count_;
+    FindOrCreateBlockStartingAt(dex_pc_start);
+    if (dex_pc_end < code_item.insns_size_in_code_units_) {
+      // TODO: Do not create block if the last instruction cannot fall through.
+      FindOrCreateBlockStartingAt(dex_pc_end);
+    } else {
+      // The TryItem spans until the very end of the CodeItem (or beyond if
+      // invalid) and therefore cannot have any code afterwards.
+    }
+  }
+
+  // Create branch targets for exception handlers.
+  const uint8_t* handlers_ptr = DexFile::GetCatchHandlerData(code_item, 0);
+  uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr);
+  for (uint32_t idx = 0; idx < handlers_size; ++idx) {
+    CatchHandlerIterator iterator(handlers_ptr);
+    for (; iterator.HasNext(); iterator.Next()) {
+      uint32_t address = iterator.GetHandlerAddress();
+      HBasicBlock* block = FindOrCreateBlockStartingAt(address);
+      block->SetIsCatchBlock();
+    }
+    handlers_ptr = iterator.EndDataPointer();
+  }
+}
+
+void HGraphBuilder::InsertTryBoundaryBlocks(const DexFile::CodeItem& code_item) {
+  if (code_item.tries_size_ == 0) {
+    return;
+  }
+
+  for (size_t idx = 0; idx < code_item.tries_size_; ++idx) {
+    const DexFile::TryItem* try_item = DexFile::GetTryItems(code_item, idx);
+    uint32_t try_start = try_item->start_addr_;
+    uint32_t try_end = try_start + try_item->insn_count_;
+
+    // Iterate over all blocks in the dex pc range of the TryItem and:
+    //   (a) split edges which enter/exit the try range,
+    //   (b) create TryBoundary instructions in the new blocks,
+    //   (c) link the new blocks to corresponding exception handlers.
+    for (uint32_t inner_pc = try_start; inner_pc < try_end; ++inner_pc) {
+      HBasicBlock* try_block = FindBlockStartingAt(inner_pc);
+      if (try_block == nullptr) {
+        continue;
+      }
+
+      // Find predecessors which are not covered by the same TryItem range. Such
+      // edges enter the try block and will have a TryBoundary inserted.
+      for (size_t i = 0; i < try_block->GetPredecessors().Size(); ++i) {
+        HBasicBlock* predecessor = try_block->GetPredecessors().Get(i);
+        HTryBoundary* try_boundary = nullptr;
+        if (predecessor->IsSingleTryBoundary()) {
+          try_boundary = predecessor->GetLastInstruction()->AsTryBoundary();
+          if (try_boundary->GetNormalFlowSuccessor() == try_block
+              && try_block->IsFirstIndexOfPredecessor(predecessor, i)) {
+            // The edge was already split because of an exit from a neighbouring
+            // TryItem and `predecessor` is the block with a TryBoundary created
+            // between the two original blocks. We do not split the edge again.
+            DCHECK(!IsBlockInPcRange(predecessor->GetSinglePredecessor(), try_start, try_end));
+            DCHECK(try_boundary->IsTryExit());
+            DCHECK(!try_boundary->IsTryEntry());
+            try_boundary->SetIsTryEntry();
+          } else {
+            // This is an edge between a previously created TryBoundary and its
+            // handler. We skip it because it is exceptional flow.
+            DCHECK(try_block->IsCatchBlock());
+            DCHECK(try_boundary->HasExceptionHandler(try_block));
+            continue;
+          }
+        } else if (!IsBlockInPcRange(predecessor, try_start, try_end)) {
+          // This is an entry point into the TryItem and the edge has not been
+          // split yet. That means that either `predecessor` is not in a TryItem,
+          // or it is in a different TryItem and we happened to iterate over
+          // this block first. We split the edge and `predecessor` may add its
+          // own exception handlers later.
+          try_boundary = new (arena_) HTryBoundary(/* is_entry */ true, /* is_exit */ false);
+          HBasicBlock* try_entry_block = graph_->SplitEdge(predecessor, try_block);
+          try_entry_block->AddInstruction(try_boundary);
+        } else {
+          // Not an edge on the boundary of the try block.
+          continue;
+        }
+        DCHECK(try_boundary != nullptr);
+
+        // Link the TryBoundary block to the handlers of this TryItem.
+        for (CatchHandlerIterator it(code_item, *try_item); it.HasNext(); it.Next()) {
+          try_boundary->AddExceptionHandler(FindBlockStartingAt(it.GetHandlerAddress()));
+        }
+      }
+
+      // Find successors which are not covered by the same TryItem range. Such
+      // edges exit the try block and will have a TryBoundary inserted.
+      for (size_t i = 0; i < try_block->GetSuccessors().Size(); ++i) {
+        HBasicBlock* successor = try_block->GetSuccessors().Get(i);
+        HTryBoundary* try_boundary = nullptr;
+        if (successor->IsSingleTryBoundary()) {
+          // The edge was already split because of an entry into a neighbouring
+          // TryItem. We do not split the edge again.
+          try_boundary = successor->GetLastInstruction()->AsTryBoundary();
+          DCHECK_EQ(try_block, successor->GetSinglePredecessor());
+          DCHECK(try_boundary->IsTryEntry());
+          DCHECK(!try_boundary->IsTryExit());
+          DCHECK(!IsBlockInPcRange(try_boundary->GetNormalFlowSuccessor(), try_start, try_end));
+          try_boundary->SetIsTryExit();
+        } else if (!IsBlockInPcRange(successor, try_start, try_end)) {
+          // This is an exit out of the TryItem and the edge has not been split
+          // yet. That means that either `successor` is not in a TryItem, or it
+          // is in a different TryItem and we happened to iterate over this
+          // block first. We split the edge and `successor` may add its own
+          // exception handlers later.
+          HInstruction* last_instruction = try_block->GetLastInstruction();
+          if (last_instruction->IsReturn() || last_instruction->IsReturnVoid()) {
+            DCHECK_EQ(successor, exit_block_);
+            // Control flow exits the try block with a Return(Void). Because
+            // splitting the edge would invalidate the invariant that Return
+            // always jumps to Exit, we move the Return outside the try block.
+            HBasicBlock* return_block = try_block->SplitBefore(last_instruction);
+            graph_->AddBlock(return_block);
+            successor = return_block;
+          }
+          try_boundary = new (arena_) HTryBoundary(/* is_entry */ false, /* is_exit */ true);
+          HBasicBlock* try_exit_block = graph_->SplitEdge(try_block, successor);
+          try_exit_block->AddInstruction(try_boundary);
+        } else {
+          // Not an edge on the boundary of the try block.
+          continue;
+        }
+        DCHECK(try_boundary != nullptr);
+
+        // Link the TryBoundary block to the handlers of this TryItem.
+        for (CatchHandlerIterator it(code_item, *try_item); it.HasNext(); it.Next()) {
+          try_boundary->AddExceptionHandler(FindBlockStartingAt(it.GetHandlerAddress()));
+        }
+      }
+    }
+  }
+}
+
 bool HGraphBuilder::BuildGraph(const DexFile::CodeItem& code_item) {
   DCHECK(graph_->GetBlocks().IsEmpty());
 
@@ -292,24 +451,7 @@
     return false;
   }
 
-  // Also create blocks for catch handlers.
-  if (code_item.tries_size_ != 0) {
-    const uint8_t* handlers_ptr = DexFile::GetCatchHandlerData(code_item, 0);
-    uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr);
-    for (uint32_t idx = 0; idx < handlers_size; ++idx) {
-      CatchHandlerIterator iterator(handlers_ptr);
-      for (; iterator.HasNext(); iterator.Next()) {
-        uint32_t address = iterator.GetHandlerAddress();
-        HBasicBlock* block = FindBlockStartingAt(address);
-        if (block == nullptr) {
-          block = new (arena_) HBasicBlock(graph_, address);
-          branch_targets_.Put(address, block);
-        }
-        block->SetIsCatchBlock();
-      }
-      handlers_ptr = iterator.EndDataPointer();
-    }
-  }
+  CreateBlocksForTryCatch(code_item);
 
   InitializeParameters(code_item.ins_size_);
 
@@ -325,18 +467,24 @@
     code_ptr += instruction.SizeInCodeUnits();
   }
 
-  // Add the exit block at the end to give it the highest id.
-  graph_->AddBlock(exit_block_);
-  exit_block_->AddInstruction(new (arena_) HExit());
   // Add the suspend check to the entry block.
   entry_block_->AddInstruction(new (arena_) HSuspendCheck(0));
   entry_block_->AddInstruction(new (arena_) HGoto());
+  // Add Exit to the exit block.
+  exit_block_->AddInstruction(new (arena_) HExit());
 
+  // Iterate over blocks covered by TryItems and insert TryBoundaries at entry
+  // and exit points. This requires all control-flow instructions and
+  // non-exceptional edges to have been created.
+  InsertTryBoundaryBlocks(code_item);
+
+  // Add the exit block at the end to give it the highest id.
+  graph_->AddBlock(exit_block_);
   return true;
 }
 
-void HGraphBuilder::MaybeUpdateCurrentBlock(size_t index) {
-  HBasicBlock* block = FindBlockStartingAt(index);
+void HGraphBuilder::MaybeUpdateCurrentBlock(size_t dex_pc) {
+  HBasicBlock* block = FindBlockStartingAt(dex_pc);
   if (block == nullptr) {
     return;
   }
@@ -371,10 +519,8 @@
       (*number_of_branches)++;
       int32_t target = instruction.GetTargetOffset() + dex_pc;
       // Create a block for the target instruction.
-      if (FindBlockStartingAt(target) == nullptr) {
-        block = new (arena_) HBasicBlock(graph_, target);
-        branch_targets_.Put(target, block);
-      }
+      FindOrCreateBlockStartingAt(target);
+
       dex_pc += instruction.SizeInCodeUnits();
       code_ptr += instruction.SizeInCodeUnits();
 
@@ -383,9 +529,8 @@
           // In the normal case we should never hit this but someone can artificially forge a dex
           // file to fall-through out the method code. In this case we bail out compilation.
           return false;
-        } else if (FindBlockStartingAt(dex_pc) == nullptr) {
-          block = new (arena_) HBasicBlock(graph_, dex_pc);
-          branch_targets_.Put(dex_pc, block);
+        } else {
+          FindOrCreateBlockStartingAt(dex_pc);
         }
       }
     } else if (instruction.IsSwitch()) {
@@ -401,10 +546,7 @@
       for (size_t i = 0; i < num_entries; ++i) {
         // The target of the case.
         uint32_t target = dex_pc + table.GetEntryAt(i + offset);
-        if (FindBlockStartingAt(target) == nullptr) {
-          block = new (arena_) HBasicBlock(graph_, target);
-          branch_targets_.Put(target, block);
-        }
+        FindOrCreateBlockStartingAt(target);
 
         // The next case gets its own block.
         if (i < num_entries) {
@@ -421,9 +563,8 @@
         // file to fall-through out the method code. In this case we bail out compilation.
         // (A switch can fall-through so we don't need to check CanFlowThrough().)
         return false;
-      } else if (FindBlockStartingAt(dex_pc) == nullptr) {
-        block = new (arena_) HBasicBlock(graph_, dex_pc);
-        branch_targets_.Put(dex_pc, block);
+      } else {
+        FindOrCreateBlockStartingAt(dex_pc);
       }
     } else {
       code_ptr += instruction.SizeInCodeUnits();
@@ -433,9 +574,19 @@
   return true;
 }
 
-HBasicBlock* HGraphBuilder::FindBlockStartingAt(int32_t index) const {
-  DCHECK_GE(index, 0);
-  return branch_targets_.Get(index);
+HBasicBlock* HGraphBuilder::FindBlockStartingAt(int32_t dex_pc) const {
+  DCHECK_GE(dex_pc, 0);
+  DCHECK_LT(static_cast<size_t>(dex_pc), branch_targets_.Size());
+  return branch_targets_.Get(dex_pc);
+}
+
+HBasicBlock* HGraphBuilder::FindOrCreateBlockStartingAt(int32_t dex_pc) {
+  HBasicBlock* block = FindBlockStartingAt(dex_pc);
+  if (block == nullptr) {
+    block = new (arena_) HBasicBlock(graph_, dex_pc);
+    branch_targets_.Put(dex_pc, block);
+  }
+  return block;
 }
 
 template<typename T>
@@ -2108,15 +2259,29 @@
 
     case Instruction::MOVE_RESULT:
     case Instruction::MOVE_RESULT_WIDE:
-    case Instruction::MOVE_RESULT_OBJECT:
+    case Instruction::MOVE_RESULT_OBJECT: {
       if (latest_result_ == nullptr) {
         // Only dead code can lead to this situation, where the verifier
         // does not reject the method.
       } else {
-        UpdateLocal(instruction.VRegA(), latest_result_);
+        // An Invoke/FilledNewArray and its MoveResult could have landed in
+        // different blocks if there was a try/catch block boundary between
+        // them. We need to insert the StoreLocal after the result definition
+        // but before any potential Temporaries.
+        HStoreLocal* update_local =
+            new (arena_) HStoreLocal(GetLocalAt(instruction.VRegA()), latest_result_);
+        HBasicBlock* block = latest_result_->GetBlock();
+        if (latest_result_->IsInvoke()) {
+          block->InsertInstructionAfter(update_local, latest_result_);
+        } else {
+          DCHECK(latest_result_->IsNewArray());
+          DCHECK(latest_result_->GetNext()->IsTemporary());
+          block->InsertInstructionAfter(update_local, latest_result_->GetNext());
+        }
         latest_result_ = nullptr;
       }
       break;
+    }
 
     case Instruction::CMP_LONG: {
       Binop_23x_cmp(instruction, Primitive::kPrimLong, HCompare::kNoBias, dex_pc);
diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h
index 052aaf8..58d85e9 100644
--- a/compiler/optimizing/builder.h
+++ b/compiler/optimizing/builder.h
@@ -94,8 +94,12 @@
   bool ComputeBranchTargets(const uint16_t* start,
                             const uint16_t* end,
                             size_t* number_of_branches);
-  void MaybeUpdateCurrentBlock(size_t index);
-  HBasicBlock* FindBlockStartingAt(int32_t index) const;
+  void MaybeUpdateCurrentBlock(size_t dex_pc);
+  HBasicBlock* FindBlockStartingAt(int32_t dex_pc) const;
+  HBasicBlock* FindOrCreateBlockStartingAt(int32_t dex_pc);
+  bool IsBlockInPcRange(HBasicBlock* block, uint32_t dex_pc_start, uint32_t dex_pc_end);
+  void CreateBlocksForTryCatch(const DexFile::CodeItem& code_item);
+  void InsertTryBoundaryBlocks(const DexFile::CodeItem& code_item);
 
   void InitializeLocals(uint16_t count);
   HLocal* GetLocalAt(int register_index) const;
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc
index cd10935..4607ebe 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -146,7 +146,7 @@
 HBasicBlock* CodeGenerator::GetNextBlockToEmit() const {
   for (size_t i = current_block_index_ + 1; i < block_order_->Size(); ++i) {
     HBasicBlock* block = block_order_->Get(i);
-    if (!block->IsSingleGoto()) {
+    if (!block->IsSingleJump()) {
       return block;
     }
   }
@@ -154,7 +154,7 @@
 }
 
 HBasicBlock* CodeGenerator::FirstNonEmptyBlock(HBasicBlock* block) const {
-  while (block->IsSingleGoto()) {
+  while (block->IsSingleJump()) {
     block = block->GetSuccessors().Get(0);
   }
   return block;
@@ -214,7 +214,7 @@
     // Don't generate code for an empty block. Its predecessors will branch to its successor
     // directly. Also, the label of that block will not be emitted, so this helps catch
     // errors where we reference that label.
-    if (block->IsSingleGoto()) continue;
+    if (block->IsSingleJump()) continue;
     Bind(block);
     for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
       HInstruction* current = it.Current();
diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc
index 9abe2e7..ff9373a 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -431,7 +431,7 @@
     // FirstNonEmptyBlock() which could lead to adjusting a label more than once.
     DCHECK_LT(static_cast<size_t>(block->GetBlockId()), block_labels_.Size());
     Label* block_label = &block_labels_.GetRawStorage()[block->GetBlockId()];
-    DCHECK_EQ(block_label->IsBound(), !block->IsSingleGoto());
+    DCHECK_EQ(block_label->IsBound(), !block->IsSingleJump());
     if (block_label->IsBound()) {
       __ AdjustLabelPosition(block_label);
     }
@@ -962,12 +962,7 @@
       || !IsLeafMethod());
 }
 
-void LocationsBuilderARM::VisitGoto(HGoto* got) {
-  got->SetLocations(nullptr);
-}
-
-void InstructionCodeGeneratorARM::VisitGoto(HGoto* got) {
-  HBasicBlock* successor = got->GetSuccessor();
+void InstructionCodeGeneratorARM::HandleGoto(HInstruction* got, HBasicBlock* successor) {
   DCHECK(!successor->IsExitBlock());
 
   HBasicBlock* block = got->GetBlock();
@@ -988,6 +983,25 @@
   }
 }
 
+void LocationsBuilderARM::VisitGoto(HGoto* got) {
+  got->SetLocations(nullptr);
+}
+
+void InstructionCodeGeneratorARM::VisitGoto(HGoto* got) {
+  HandleGoto(got, got->GetSuccessor());
+}
+
+void LocationsBuilderARM::VisitTryBoundary(HTryBoundary* try_boundary) {
+  try_boundary->SetLocations(nullptr);
+}
+
+void InstructionCodeGeneratorARM::VisitTryBoundary(HTryBoundary* try_boundary) {
+  HBasicBlock* successor = try_boundary->GetNormalFlowSuccessor();
+  if (!successor->IsExitBlock()) {
+    HandleGoto(try_boundary, successor);
+  }
+}
+
 void LocationsBuilderARM::VisitExit(HExit* exit) {
   exit->SetLocations(nullptr);
 }
diff --git a/compiler/optimizing/code_generator_arm.h b/compiler/optimizing/code_generator_arm.h
index 953e733..a96ecbd 100644
--- a/compiler/optimizing/code_generator_arm.h
+++ b/compiler/optimizing/code_generator_arm.h
@@ -211,6 +211,7 @@
   void DivRemByPowerOfTwo(HBinaryOperation* instruction);
   void GenerateDivRemWithAnyConstant(HBinaryOperation* instruction);
   void GenerateDivRemConstantIntegral(HBinaryOperation* instruction);
+  void HandleGoto(HInstruction* got, HBasicBlock* successor);
 
   ArmAssembler* const assembler_;
   CodeGeneratorARM* const codegen_;
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 7ec6b54..9b7124d 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -1971,12 +1971,7 @@
   // Will be generated at use site.
 }
 
-void LocationsBuilderARM64::VisitGoto(HGoto* got) {
-  got->SetLocations(nullptr);
-}
-
-void InstructionCodeGeneratorARM64::VisitGoto(HGoto* got) {
-  HBasicBlock* successor = got->GetSuccessor();
+void InstructionCodeGeneratorARM64::HandleGoto(HInstruction* got, HBasicBlock* successor) {
   DCHECK(!successor->IsExitBlock());
   HBasicBlock* block = got->GetBlock();
   HInstruction* previous = got->GetPrevious();
@@ -1995,6 +1990,25 @@
   }
 }
 
+void LocationsBuilderARM64::VisitGoto(HGoto* got) {
+  got->SetLocations(nullptr);
+}
+
+void InstructionCodeGeneratorARM64::VisitGoto(HGoto* got) {
+  HandleGoto(got, got->GetSuccessor());
+}
+
+void LocationsBuilderARM64::VisitTryBoundary(HTryBoundary* try_boundary) {
+  try_boundary->SetLocations(nullptr);
+}
+
+void InstructionCodeGeneratorARM64::VisitTryBoundary(HTryBoundary* try_boundary) {
+  HBasicBlock* successor = try_boundary->GetNormalFlowSuccessor();
+  if (!successor->IsExitBlock()) {
+    HandleGoto(try_boundary, successor);
+  }
+}
+
 void InstructionCodeGeneratorARM64::GenerateTestAndBranch(HInstruction* instruction,
                                                           vixl::Label* true_target,
                                                           vixl::Label* false_target,
diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h
index bbe3adc..2c61038 100644
--- a/compiler/optimizing/code_generator_arm64.h
+++ b/compiler/optimizing/code_generator_arm64.h
@@ -181,7 +181,7 @@
   void DivRemByPowerOfTwo(HBinaryOperation* instruction);
   void GenerateDivRemWithAnyConstant(HBinaryOperation* instruction);
   void GenerateDivRemIntegral(HBinaryOperation* instruction);
-
+  void HandleGoto(HInstruction* got, HBasicBlock* successor);
 
   Arm64Assembler* const assembler_;
   CodeGeneratorARM64* const codegen_;
diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc
index ab684d4..aa4fd26 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -1938,12 +1938,7 @@
   // Will be generated at use site.
 }
 
-void LocationsBuilderMIPS64::VisitGoto(HGoto* got) {
-  got->SetLocations(nullptr);
-}
-
-void InstructionCodeGeneratorMIPS64::VisitGoto(HGoto* got) {
-  HBasicBlock* successor = got->GetSuccessor();
+void InstructionCodeGeneratorMIPS64::HandleGoto(HInstruction* got, HBasicBlock* successor) {
   DCHECK(!successor->IsExitBlock());
   HBasicBlock* block = got->GetBlock();
   HInstruction* previous = got->GetPrevious();
@@ -1962,6 +1957,25 @@
   }
 }
 
+void LocationsBuilderMIPS64::VisitGoto(HGoto* got) {
+  got->SetLocations(nullptr);
+}
+
+void InstructionCodeGeneratorMIPS64::VisitGoto(HGoto* got) {
+  HandleGoto(got, got->GetSuccessor());
+}
+
+void LocationsBuilderMIPS64::VisitTryBoundary(HTryBoundary* try_boundary) {
+  try_boundary->SetLocations(nullptr);
+}
+
+void InstructionCodeGeneratorMIPS64::VisitTryBoundary(HTryBoundary* try_boundary) {
+  HBasicBlock* successor = try_boundary->GetNormalFlowSuccessor();
+  if (!successor->IsExitBlock()) {
+    HandleGoto(try_boundary, successor);
+  }
+}
+
 void InstructionCodeGeneratorMIPS64::GenerateTestAndBranch(HInstruction* instruction,
                                                            Label* true_target,
                                                            Label* false_target,
diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h
index ec36496..c724a21 100644
--- a/compiler/optimizing/code_generator_mips64.h
+++ b/compiler/optimizing/code_generator_mips64.h
@@ -196,6 +196,7 @@
                              Label* true_target,
                              Label* false_target,
                              Label* always_true_target);
+  void HandleGoto(HInstruction* got, HBasicBlock* successor);
 
   Mips64Assembler* const assembler_;
   CodeGeneratorMIPS64* const codegen_;
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 4d106c4..6c82fe9 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -842,12 +842,7 @@
   }
 }
 
-void LocationsBuilderX86::VisitGoto(HGoto* got) {
-  got->SetLocations(nullptr);
-}
-
-void InstructionCodeGeneratorX86::VisitGoto(HGoto* got) {
-  HBasicBlock* successor = got->GetSuccessor();
+void InstructionCodeGeneratorX86::HandleGoto(HInstruction* got, HBasicBlock* successor) {
   DCHECK(!successor->IsExitBlock());
 
   HBasicBlock* block = got->GetBlock();
@@ -867,6 +862,25 @@
   }
 }
 
+void LocationsBuilderX86::VisitGoto(HGoto* got) {
+  got->SetLocations(nullptr);
+}
+
+void InstructionCodeGeneratorX86::VisitGoto(HGoto* got) {
+  HandleGoto(got, got->GetSuccessor());
+}
+
+void LocationsBuilderX86::VisitTryBoundary(HTryBoundary* try_boundary) {
+  try_boundary->SetLocations(nullptr);
+}
+
+void InstructionCodeGeneratorX86::VisitTryBoundary(HTryBoundary* try_boundary) {
+  HBasicBlock* successor = try_boundary->GetNormalFlowSuccessor();
+  if (!successor->IsExitBlock()) {
+    HandleGoto(try_boundary, successor);
+  }
+}
+
 void LocationsBuilderX86::VisitExit(HExit* exit) {
   exit->SetLocations(nullptr);
 }
diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h
index 1ad89c9..623e832 100644
--- a/compiler/optimizing/code_generator_x86.h
+++ b/compiler/optimizing/code_generator_x86.h
@@ -201,6 +201,7 @@
                              Label* true_target,
                              Label* false_target,
                              Label* always_true_target);
+  void HandleGoto(HInstruction* got, HBasicBlock* successor);
 
   X86Assembler* const assembler_;
   CodeGeneratorX86* const codegen_;
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index a8f57cc..22f5d96 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -786,12 +786,7 @@
   }
 }
 
-void LocationsBuilderX86_64::VisitGoto(HGoto* got) {
-  got->SetLocations(nullptr);
-}
-
-void InstructionCodeGeneratorX86_64::VisitGoto(HGoto* got) {
-  HBasicBlock* successor = got->GetSuccessor();
+void InstructionCodeGeneratorX86_64::HandleGoto(HInstruction* got, HBasicBlock* successor) {
   DCHECK(!successor->IsExitBlock());
 
   HBasicBlock* block = got->GetBlock();
@@ -811,6 +806,25 @@
   }
 }
 
+void LocationsBuilderX86_64::VisitGoto(HGoto* got) {
+  got->SetLocations(nullptr);
+}
+
+void InstructionCodeGeneratorX86_64::VisitGoto(HGoto* got) {
+  HandleGoto(got, got->GetSuccessor());
+}
+
+void LocationsBuilderX86_64::VisitTryBoundary(HTryBoundary* try_boundary) {
+  try_boundary->SetLocations(nullptr);
+}
+
+void InstructionCodeGeneratorX86_64::VisitTryBoundary(HTryBoundary* try_boundary) {
+  HBasicBlock* successor = try_boundary->GetNormalFlowSuccessor();
+  if (!successor->IsExitBlock()) {
+    HandleGoto(try_boundary, successor);
+  }
+}
+
 void LocationsBuilderX86_64::VisitExit(HExit* exit) {
   exit->SetLocations(nullptr);
 }
diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h
index a18e89a..c2aa56b 100644
--- a/compiler/optimizing/code_generator_x86_64.h
+++ b/compiler/optimizing/code_generator_x86_64.h
@@ -202,6 +202,7 @@
                              Label* true_target,
                              Label* false_target,
                              Label* always_true_target);
+  void HandleGoto(HInstruction* got, HBasicBlock* successor);
 
   X86_64Assembler* const assembler_;
   CodeGeneratorX86_64* const codegen_;
diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc
index fd28f0b..d7e6bd8 100644
--- a/compiler/optimizing/graph_checker.cc
+++ b/compiler/optimizing/graph_checker.cc
@@ -81,7 +81,10 @@
   }
 
   // Ensure `block` ends with a branch instruction.
-  if (!block->EndsWithControlFlowInstruction()) {
+  // This invariant is not enforced on non-SSA graphs. Graph built from DEX with
+  // dead code that falls out of the method will not end with a control-flow
+  // instruction. Such code is removed during the SSA-building DCE phase.
+  if (GetGraph()->IsInSsaForm() && !block->EndsWithControlFlowInstruction()) {
     AddError(StringPrintf("Block %d does not end with a branch instruction.",
                           block->GetBlockId()));
   }
@@ -253,6 +256,22 @@
   }
 }
 
+void GraphChecker::VisitReturn(HReturn* ret) {
+  if (!ret->GetBlock()->GetSingleSuccessor()->IsExitBlock()) {
+    AddError(StringPrintf("%s:%d does not jump to the exit block.",
+                          ret->DebugName(),
+                          ret->GetId()));
+  }
+}
+
+void GraphChecker::VisitReturnVoid(HReturnVoid* ret) {
+  if (!ret->GetBlock()->GetSingleSuccessor()->IsExitBlock()) {
+    AddError(StringPrintf("%s:%d does not jump to the exit block.",
+                          ret->DebugName(),
+                          ret->GetId()));
+  }
+}
+
 void SSAChecker::VisitBasicBlock(HBasicBlock* block) {
   super_type::VisitBasicBlock(block);
 
diff --git a/compiler/optimizing/graph_checker.h b/compiler/optimizing/graph_checker.h
index b4314da..bafa69d 100644
--- a/compiler/optimizing/graph_checker.h
+++ b/compiler/optimizing/graph_checker.h
@@ -48,6 +48,10 @@
   // Check that the HasBoundsChecks() flag is set for bounds checks.
   void VisitBoundsCheck(HBoundsCheck* check) OVERRIDE;
 
+  // Check that the Return and ReturnVoid jump to the exit block.
+  void VisitReturn(HReturn* ret) OVERRIDE;
+  void VisitReturnVoid(HReturnVoid* ret) OVERRIDE;
+
   // Was the last visit of the graph valid?
   bool IsValid() const {
     return errors_.empty();
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index 7d723ef..30d61ef 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -252,8 +252,22 @@
     AddIndent();
     output_ << "successors";
     for (size_t i = 0, e = block->GetSuccessors().Size(); i < e; ++i) {
-      HBasicBlock* successor = block->GetSuccessors().Get(i);
-      output_ << " \"B" << successor->GetBlockId() << "\" ";
+      if (!block->IsExceptionalSuccessor(i)) {
+        HBasicBlock* successor = block->GetSuccessors().Get(i);
+        output_ << " \"B" << successor->GetBlockId() << "\" ";
+      }
+    }
+    output_<< std::endl;
+  }
+
+  void PrintExceptionHandlers(HBasicBlock* block) {
+    AddIndent();
+    output_ << "xhandlers";
+    for (size_t i = 0, e = block->GetSuccessors().Size(); i < e; ++i) {
+      if (block->IsExceptionalSuccessor(i)) {
+        HBasicBlock* handler = block->GetSuccessors().Get(i);
+        output_ << " \"B" << handler->GetBlockId() << "\" ";
+      }
     }
     if (block->IsExitBlock() &&
         (disasm_info_ != nullptr) &&
@@ -365,6 +379,15 @@
                                       << std::noboolalpha;
   }
 
+  void VisitTryBoundary(HTryBoundary* try_boundary) OVERRIDE {
+    StartAttributeStream("is_entry") << std::boolalpha
+                                     << try_boundary->IsTryEntry()
+                                     << std::noboolalpha;
+    StartAttributeStream("is_exit") << std::boolalpha
+                                    << try_boundary->IsTryExit()
+                                    << std::noboolalpha;
+  }
+
   bool IsPass(const char* name) {
     return strcmp(pass_name_, name) == 0;
   }
@@ -579,8 +602,14 @@
     }
     PrintPredecessors(block);
     PrintSuccessors(block);
-    PrintEmptyProperty("xhandlers");
-    PrintEmptyProperty("flags");
+    PrintExceptionHandlers(block);
+
+    if (block->IsCatchBlock()) {
+      PrintProperty("flags", "catch_block");
+    } else {
+      PrintEmptyProperty("flags");
+    }
+
     if (block->GetDominator() != nullptr) {
       PrintProperty("dominator", "B", block->GetDominator()->GetBlockId());
     }
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index a6390af..881f9ec 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -189,15 +189,20 @@
   ssa_builder.BuildSsa();
 }
 
-void HGraph::SplitCriticalEdge(HBasicBlock* block, HBasicBlock* successor) {
-  // Insert a new node between `block` and `successor` to split the
-  // critical edge.
+HBasicBlock* HGraph::SplitEdge(HBasicBlock* block, HBasicBlock* successor) {
   HBasicBlock* new_block = new (arena_) HBasicBlock(this, successor->GetDexPc());
   AddBlock(new_block);
-  new_block->AddInstruction(new (arena_) HGoto());
   // Use `InsertBetween` to ensure the predecessor index and successor index of
   // `block` and `successor` are preserved.
   new_block->InsertBetween(block, successor);
+  return new_block;
+}
+
+void HGraph::SplitCriticalEdge(HBasicBlock* block, HBasicBlock* successor) {
+  // Insert a new node between `block` and `successor` to split the
+  // critical edge.
+  HBasicBlock* new_block = SplitEdge(block, successor);
+  new_block->AddInstruction(new (arena_) HGoto());
   if (successor->IsLoopHeader()) {
     // If we split at a back edge boundary, make the new block the back edge.
     HLoopInformation* info = successor->GetLoopInformation();
@@ -1019,6 +1024,35 @@
   }
 }
 
+HBasicBlock* HBasicBlock::SplitBefore(HInstruction* cursor) {
+  DCHECK(!graph_->IsInSsaForm()) << "Support for SSA form not implemented";
+  DCHECK_EQ(cursor->GetBlock(), this);
+
+  HBasicBlock* new_block = new (GetGraph()->GetArena()) HBasicBlock(GetGraph(), GetDexPc());
+  new_block->instructions_.first_instruction_ = cursor;
+  new_block->instructions_.last_instruction_ = instructions_.last_instruction_;
+  instructions_.last_instruction_ = cursor->previous_;
+  if (cursor->previous_ == nullptr) {
+    instructions_.first_instruction_ = nullptr;
+  } else {
+    cursor->previous_->next_ = nullptr;
+    cursor->previous_ = nullptr;
+  }
+
+  new_block->instructions_.SetBlockOfInstructions(new_block);
+  AddInstruction(new (GetGraph()->GetArena()) HGoto());
+
+  for (size_t i = 0, e = GetSuccessors().Size(); i < e; ++i) {
+    HBasicBlock* successor = GetSuccessors().Get(i);
+    new_block->successors_.Add(successor);
+    successor->predecessors_.Put(successor->GetPredecessorIndexOf(this), new_block);
+  }
+  successors_.Reset();
+  AddSuccessor(new_block);
+
+  return new_block;
+}
+
 HBasicBlock* HBasicBlock::SplitAfter(HInstruction* cursor) {
   DCHECK(!cursor->IsControlFlow());
   DCHECK_NE(instructions_.last_instruction_, cursor);
@@ -1048,14 +1082,24 @@
   return new_block;
 }
 
+bool HBasicBlock::IsExceptionalSuccessor(size_t idx) const {
+  return !GetInstructions().IsEmpty()
+      && GetLastInstruction()->IsTryBoundary()
+      && GetLastInstruction()->AsTryBoundary()->IsExceptionalSuccessor(idx);
+}
+
+static bool HasOnlyOneInstruction(const HBasicBlock& block) {
+  return block.GetPhis().IsEmpty()
+      && !block.GetInstructions().IsEmpty()
+      && block.GetFirstInstruction() == block.GetLastInstruction();
+}
+
 bool HBasicBlock::IsSingleGoto() const {
-  HLoopInformation* loop_info = GetLoopInformation();
-  DCHECK(EndsWithControlFlowInstruction());
-  return GetPhis().IsEmpty()
-         && GetFirstInstruction() == GetLastInstruction()
-         && GetLastInstruction()->IsGoto()
-         // Back edges generate the suspend check.
-         && (loop_info == nullptr || !loop_info->IsBackEdge(*this));
+  return HasOnlyOneInstruction(*this) && GetLastInstruction()->IsGoto();
+}
+
+bool HBasicBlock::IsSingleTryBoundary() const {
+  return HasOnlyOneInstruction(*this) && GetLastInstruction()->IsTryBoundary();
 }
 
 bool HBasicBlock::EndsWithControlFlowInstruction() const {
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 0f5b1ab..f2e9e22 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -39,6 +39,7 @@
 class HDoubleConstant;
 class HEnvironment;
 class HFloatConstant;
+class HGraphBuilder;
 class HGraphVisitor;
 class HInstruction;
 class HIntConstant;
@@ -207,6 +208,12 @@
   // Removes `block` from the graph.
   void DeleteDeadBlock(HBasicBlock* block);
 
+  // Splits the edge between `block` and `successor` while preserving the
+  // indices in the predecessor/successor lists. If there are multiple edges
+  // between the blocks, the lowest indices are used.
+  // Returns the new block which is empty and has the same dex pc as `successor`.
+  HBasicBlock* SplitEdge(HBasicBlock* block, HBasicBlock* successor);
+
   void SplitCriticalEdge(HBasicBlock* block, HBasicBlock* successor);
   void SimplifyLoop(HBasicBlock* header);
 
@@ -566,6 +573,15 @@
   }
 
   bool IsSingleGoto() const;
+  bool IsSingleTryBoundary() const;
+
+  // Returns true if this block emits nothing but a jump.
+  bool IsSingleJump() const {
+    HLoopInformation* loop_info = GetLoopInformation();
+    return (IsSingleGoto() || IsSingleTryBoundary())
+           // Back edges generate a suspend check.
+           && (loop_info == nullptr || !loop_info->IsBackEdge(*this));
+  }
 
   void AddBackEdge(HBasicBlock* back_edge) {
     if (loop_information_ == nullptr) {
@@ -674,7 +690,7 @@
     successors_.Put(1, temp);
   }
 
-  size_t GetPredecessorIndexOf(HBasicBlock* predecessor) {
+  size_t GetPredecessorIndexOf(HBasicBlock* predecessor) const {
     for (size_t i = 0, e = predecessors_.Size(); i < e; ++i) {
       if (predecessors_.Get(i) == predecessor) {
         return i;
@@ -683,7 +699,7 @@
     return -1;
   }
 
-  size_t GetSuccessorIndexOf(HBasicBlock* successor) {
+  size_t GetSuccessorIndexOf(HBasicBlock* successor) const {
     for (size_t i = 0, e = successors_.Size(); i < e; ++i) {
       if (successors_.Get(i) == successor) {
         return i;
@@ -692,6 +708,32 @@
     return -1;
   }
 
+  HBasicBlock* GetSinglePredecessor() const {
+    DCHECK_EQ(GetPredecessors().Size(), 1u);
+    return GetPredecessors().Get(0);
+  }
+
+  HBasicBlock* GetSingleSuccessor() const {
+    DCHECK_EQ(GetSuccessors().Size(), 1u);
+    return GetSuccessors().Get(0);
+  }
+
+  // Returns whether the first occurrence of `predecessor` in the list of
+  // predecessors is at index `idx`.
+  bool IsFirstIndexOfPredecessor(HBasicBlock* predecessor, size_t idx) const {
+    DCHECK_EQ(GetPredecessors().Get(idx), predecessor);
+    return GetPredecessorIndexOf(predecessor) == idx;
+  }
+
+  // Returns whether successor at index `idx` is an exception handler.
+  bool IsExceptionalSuccessor(size_t idx) const;
+
+  // Split the block into two blocks just before `cursor`. Returns the newly
+  // created, latter block. Note that this method will create a Goto at the end
+  // of the former block and will create an edge between them. It will not,
+  // however, update the graph, reverse post order or loop information.
+  HBasicBlock* SplitBefore(HInstruction* cursor);
+
   // Split the block into two blocks just after `cursor`. Returns the newly
   // created block. Note that this method just updates raw block information,
   // like predecessors, successors, dominators, and instruction list. It does not
@@ -913,6 +955,7 @@
   M(SuspendCheck, Instruction)                                          \
   M(Temporary, Instruction)                                             \
   M(Throw, Instruction)                                                 \
+  M(TryBoundary, Instruction)                                           \
   M(TypeConversion, Instruction)                                        \
   M(UShr, BinaryOperation)                                              \
   M(Xor, BinaryOperation)                                               \
@@ -1858,7 +1901,7 @@
   bool IsControlFlow() const OVERRIDE { return true; }
 
   HBasicBlock* GetSuccessor() const {
-    return GetBlock()->GetSuccessors().Get(0);
+    return GetBlock()->GetSingleSuccessor();
   }
 
   DECLARE_INSTRUCTION(Goto);
@@ -1892,6 +1935,65 @@
   DISALLOW_COPY_AND_ASSIGN(HIf);
 };
 
+
+// Abstract instruction which marks the beginning and/or end of a try block and
+// links it to the respective exception handlers. Behaves the same as a Goto in
+// non-exceptional control flow.
+// Normal-flow successor is stored at index zero, exception handlers under
+// higher indices in no particular order.
+class HTryBoundary : public HTemplateInstruction<0> {
+ public:
+  HTryBoundary(bool is_entry, bool is_exit)
+      : HTemplateInstruction(SideEffects::None()), is_entry_(is_entry), is_exit_(is_exit) {}
+
+  bool IsControlFlow() const OVERRIDE { return true; }
+
+  // Returns the block's non-exceptional successor (index zero).
+  HBasicBlock* GetNormalFlowSuccessor() const { return GetBlock()->GetSuccessors().Get(0); }
+
+  // Returns whether `handler` is among its exception handlers (non-zero index
+  // successors).
+  bool HasExceptionHandler(HBasicBlock* handler) const {
+    DCHECK(handler->IsCatchBlock());
+    return GetBlock()->GetSuccessors().Contains(handler, /* start_from */ 1);
+  }
+
+  // Returns whether successor at index `idx` is an exception handler.
+  bool IsExceptionalSuccessor(size_t idx) const {
+    DCHECK_LT(idx, GetBlock()->GetSuccessors().Size());
+    bool is_handler = (idx != 0);
+    DCHECK(!is_handler || GetBlock()->GetSuccessors().Get(idx)->IsCatchBlock());
+    return is_handler;
+  }
+
+  // If not present already, adds `handler` to its block's list of exception
+  // handlers.
+  void AddExceptionHandler(HBasicBlock* handler) {
+    if (!HasExceptionHandler(handler)) {
+      GetBlock()->AddSuccessor(handler);
+    }
+  }
+
+  bool IsTryEntry() const { return is_entry_; }
+  bool IsTryExit() const { return is_exit_; }
+
+  DECLARE_INSTRUCTION(TryBoundary);
+
+ private:
+  // Only for debugging purposes.
+  bool is_entry_;
+  bool is_exit_;
+
+  // Only set by HGraphBuilder.
+  void SetIsTryEntry() { is_entry_ = true; }
+  void SetIsTryExit() { is_exit_ = true; }
+
+  friend HGraphBuilder;
+
+  DISALLOW_COPY_AND_ASSIGN(HTryBoundary);
+};
+
+
 // Deoptimize to interpreter, upon checking a condition.
 class HDeoptimize : public HTemplateInstruction<1> {
  public:
diff --git a/compiler/utils/growable_array.h b/compiler/utils/growable_array.h
index e4b1e7d..f85e026 100644
--- a/compiler/utils/growable_array.h
+++ b/compiler/utils/growable_array.h
@@ -46,8 +46,8 @@
       }
     }
 
-    bool Contains(T value) const {
-      for (size_t i = 0; i < num_used_; ++i) {
+    bool Contains(T value, size_t start_from = 0) const {
+      for (size_t i = start_from; i < num_used_; ++i) {
         if (elem_list_[i] == value) {
           return true;
         }
diff --git a/test/510-checker-try-catch/expected.txt b/test/510-checker-try-catch/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/510-checker-try-catch/expected.txt
diff --git a/test/510-checker-try-catch/info.txt b/test/510-checker-try-catch/info.txt
new file mode 100644
index 0000000..9ffcb19
--- /dev/null
+++ b/test/510-checker-try-catch/info.txt
@@ -0,0 +1 @@
+Tests the generation of try/catch blocks in Optimizing.
\ No newline at end of file
diff --git a/test/510-checker-try-catch/smali/Builder.smali b/test/510-checker-try-catch/smali/Builder.smali
new file mode 100644
index 0000000..a388aa1
--- /dev/null
+++ b/test/510-checker-try-catch/smali/Builder.smali
@@ -0,0 +1,914 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+.class public LBuilder;
+
+.super Ljava/lang/Object;
+
+# Basic test case with two try blocks and three catch handlers, one of which
+# is shared by the two tries.
+
+## CHECK-START: int Builder.testMultipleTryCatch(int, int, int) builder (after)
+
+## CHECK:  name             "B0"
+## CHECK:  successors       "<<BEnterTry1:B\d+>>"
+## CHECK:  <<Minus1:i\d+>>  IntConstant -1
+## CHECK:  <<Minus2:i\d+>>  IntConstant -2
+## CHECK:  <<Minus3:i\d+>>  IntConstant -3
+
+## CHECK:  name             "<<BTry1:B\d+>>"
+## CHECK:  predecessors     "<<BEnterTry1>>"
+## CHECK:  successors       "<<BExitTry1:B\d+>>"
+## CHECK:  DivZeroCheck
+
+## CHECK:  name             "<<BAdd:B\d+>>"
+## CHECK:  predecessors     "<<BExitTry1>>"
+## CHECK:  successors       "<<BEnterTry2:B\d+>>"
+## CHECK:  Add
+
+## CHECK:  name             "<<BTry2:B\d+>>"
+## CHECK:  predecessors     "<<BEnterTry2>>"
+## CHECK:  successors       "<<BExitTry2:B\d+>>"
+## CHECK:  DivZeroCheck
+
+## CHECK:  name             "<<BReturn:B\d+>>"
+## CHECK:  predecessors     "<<BExitTry2>>" "<<BCatch1:B\d+>>" "<<BCatch2:B\d+>>" "<<BCatch3:B\d+>>"
+## CHECK:  Return
+
+## CHECK:  name             "<<BCatch1>>"
+## CHECK:  predecessors     "<<BEnterTry1>>" "<<BExitTry1>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus1>>]
+
+## CHECK:  name             "<<BCatch2>>"
+## CHECK:  predecessors     "<<BEnterTry2>>" "<<BExitTry2>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus2>>]
+
+## CHECK:  name             "<<BCatch3>>"
+## CHECK:  predecessors     "<<BEnterTry1>>" "<<BExitTry1>>" "<<BEnterTry2>>" "<<BExitTry2>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus3>>]
+
+## CHECK:  name             "<<BEnterTry1>>"
+## CHECK:  predecessors     "B0"
+## CHECK:  successors       "<<BTry1>>"
+## CHECK:  xhandlers        "<<BCatch1>>" "<<BCatch3>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExitTry1>>"
+## CHECK:  predecessors     "<<BTry1>>"
+## CHECK:  successors       "<<BAdd>>"
+## CHECK:  xhandlers        "<<BCatch1>>" "<<BCatch3>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+## CHECK:  name             "<<BEnterTry2>>"
+## CHECK:  predecessors     "<<BAdd>>"
+## CHECK:  successors       "<<BTry2>>"
+## CHECK:  xhandlers        "<<BCatch2>>" "<<BCatch3>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExitTry2>>"
+## CHECK:  predecessors     "<<BTry2>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  xhandlers        "<<BCatch2>>" "<<BCatch3>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+.method public static testMultipleTryCatch(III)I
+    .registers 3
+
+    :try_start_1
+    div-int/2addr p0, p1
+    :try_end_1
+    .catch Ljava/lang/ArithmeticException; {:try_start_1 .. :try_end_1} :catch_arith
+    .catchall {:try_start_1 .. :try_end_1} :catch_other
+
+    add-int/2addr p0, p0
+
+    :try_start_2
+    div-int/2addr p0, p2
+    :try_end_2
+    .catch Ljava/lang/OutOfMemoryError; {:try_start_2 .. :try_end_2} :catch_mem
+    .catchall {:try_start_2 .. :try_end_2} :catch_other
+
+    :return
+    return p0
+
+    :catch_arith
+    const/4 p0, -0x1
+    goto :return
+
+    :catch_mem
+    const/4 p0, -0x2
+    goto :return
+
+    :catch_other
+    const/4 p0, -0x3
+    goto :return
+.end method
+
+# Test that multiple try-entry blocks are generated if there are multiple entry
+# points into the try block.
+
+## CHECK-START: int Builder.testMultipleEntries(int, int, int, int) builder (after)
+
+## CHECK:  name             "B0"
+## CHECK:  successors       "<<BIf:B\d+>>"
+## CHECK:  <<Minus1:i\d+>>  IntConstant -1
+
+## CHECK:  name             "<<BIf>>"
+## CHECK:  predecessors     "B0"
+## CHECK:  successors       "<<BEnterTry2:B\d+>>" "<<BThen:B\d+>>"
+## CHECK:  If
+
+## CHECK:  name             "<<BThen>>"
+## CHECK:  predecessors     "<<BIf>>"
+## CHECK:  successors       "<<BEnterTry1:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BTry1:B\d+>>"
+## CHECK:  predecessors     "<<BEnterTry1>>"
+## CHECK:  successors       "<<BTry2:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BTry2>>"
+## CHECK:  predecessors     "<<BEnterTry2>>" "<<BTry1>>"
+## CHECK:  successors       "<<BExitTry:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BReturn:B\d+>>"
+## CHECK:  predecessors     "<<BExitTry>>" "<<BCatch:B\d+>>"
+## CHECK:  Return
+
+## CHECK:  name             "<<BCatch>>"
+## CHECK:  predecessors     "<<BEnterTry1>>" "<<BEnterTry2>>" "<<BExitTry>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus1>>]
+
+## CHECK:  name             "<<BEnterTry1>>"
+## CHECK:  predecessors     "<<BThen>>"
+## CHECK:  successors       "<<BTry1>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BEnterTry2>>"
+## CHECK:  predecessors     "<<BIf>>"
+## CHECK:  successors       "<<BTry2>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExitTry>>"
+## CHECK:  predecessors     "<<BTry2>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+.method public static testMultipleEntries(IIII)I
+    .registers 4
+
+    if-eqz p2, :else
+
+    div-int/2addr p0, p1
+
+    :try_start
+    div-int/2addr p0, p2
+
+    :else
+    div-int/2addr p0, p3
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+
+    :return
+    return p0
+
+    :catch_all
+    const/4 p0, -0x1
+    goto :return
+
+.end method
+
+# Test that multiple try-exit blocks are generated if (normal) control flow can
+# jump out of the try block at multiple points.
+
+## CHECK-START: int Builder.testMultipleExits(int, int) builder (after)
+
+## CHECK:  name             "B0"
+## CHECK:  successors       "<<BEnterTry:B\d+>>"
+## CHECK:  <<Minus1:i\d+>>  IntConstant -1
+## CHECK:  <<Minus2:i\d+>>  IntConstant -2
+
+## CHECK:  name             "<<BTry:B\d+>>"
+## CHECK:  predecessors     "<<BEnterTry>>"
+## CHECK:  successors       "<<BExitTry1:B\d+>>" "<<BExitTry2:B\d+>>"
+## CHECK:  Div
+## CHECK:  If
+
+## CHECK:  name             "<<BReturn:B\d+>>"
+## CHECK:  predecessors     "<<BExitTry2>>" "<<BThen:B\d+>>" "<<BCatch:B\d+>>"
+## CHECK:  Return
+
+## CHECK:  name             "<<BThen>>"
+## CHECK:  predecessors     "<<BExitTry1>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  StoreLocal       [v0,<<Minus1>>]
+
+## CHECK:  name             "<<BCatch>>"
+## CHECK:  predecessors     "<<BEnterTry>>" "<<BExitTry1>>" "<<BExitTry2>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus2>>]
+
+## CHECK:  name             "<<BEnterTry>>"
+## CHECK:  predecessors     "B0"
+## CHECK:  successors       "<<BTry>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExitTry1>>"
+## CHECK:  predecessors     "<<BTry>>"
+## CHECK:  successors       "<<BThen>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+## CHECK:  name             "<<BExitTry2>>"
+## CHECK:  predecessors     "<<BTry>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+.method public static testMultipleExits(II)I
+    .registers 2
+
+    :try_start
+    div-int/2addr p0, p1
+    if-eqz p0, :then
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+
+    :return
+    return p0
+
+    :then
+    const/4 p0, -0x1
+    goto :return
+
+    :catch_all
+    const/4 p0, -0x2
+    goto :return
+.end method
+
+# Test that only one TryBoundary is inserted when an edge connects two different
+# try ranges.
+
+## CHECK-START: int Builder.testSharedBoundary(int, int, int) builder (after)
+
+## CHECK:  name             "B0"
+## CHECK:  successors       "<<BEnter1:B\d+>>"
+## CHECK:  <<Minus1:i\d+>>  IntConstant -1
+## CHECK:  <<Minus2:i\d+>>  IntConstant -2
+
+## CHECK:  name             "<<BTry1:B\d+>>"
+## CHECK:  predecessors     "<<BEnter1>>"
+## CHECK:  successors       "<<BExit1Enter2:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BTry2:B\d+>>"
+## CHECK:  predecessors     "<<BExit1Enter2>>"
+## CHECK:  successors       "<<BExit2:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BReturn:B\d+>>"
+## CHECK:  predecessors     "<<BExit2>>" "<<BCatch1:B\d+>>" "<<BCatch2:B\d+>>"
+## CHECK:  Return
+
+## CHECK:  name             "<<BCatch1>>"
+## CHECK:  predecessors     "<<BEnter1>>" "<<BExit1Enter2>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus1>>]
+
+## CHECK:  name             "<<BCatch2>>"
+## CHECK:  predecessors     "<<BExit1Enter2>>" "<<BExit2>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus2>>]
+
+## CHECK:  name             "<<BEnter1>>"
+## CHECK:  predecessors     "B0"
+## CHECK:  successors       "<<BTry1>>"
+## CHECK:  xhandlers        "<<BCatch1>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExit1Enter2>>"
+## CHECK:  predecessors     "<<BTry1>>"
+## CHECK:  successors       "<<BTry2>>"
+## CHECK:  xhandlers        "<<BCatch1>>" "<<BCatch2>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:true
+
+## CHECK:  name             "<<BExit2>>"
+## CHECK:  predecessors     "<<BTry2>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  xhandlers        "<<BCatch2>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+.method public static testSharedBoundary(III)I
+    .registers 3
+
+    :try_start_1
+    div-int/2addr p0, p1
+    :try_end_1
+    .catchall {:try_start_1 .. :try_end_1} :catch_all_1
+
+    :try_start_2
+    div-int/2addr p0, p2
+    :try_end_2
+    .catchall {:try_start_2 .. :try_end_2} :catch_all_2
+
+    :return
+    return p0
+
+    :catch_all_1
+    const/4 p0, -0x1
+    goto :return
+
+    :catch_all_2
+    const/4 p0, -0x2
+    goto :return
+.end method
+
+# Same as previous test, only the blocks are processed in the opposite order.
+
+## CHECK-START: int Builder.testSharedBoundary_Reverse(int, int, int) builder (after)
+
+## CHECK:  name             "B0"
+## CHECK:  successors       "<<BGoto:B\d+>>"
+## CHECK:  <<Minus1:i\d+>>  IntConstant -1
+## CHECK:  <<Minus2:i\d+>>  IntConstant -2
+
+## CHECK:  name             "<<BGoto>>"
+## CHECK:  successors       "<<BEnter2:B\d+>>"
+## CHECK:  Goto
+
+## CHECK:  name             "<<BTry1:B\d+>>"
+## CHECK:  predecessors     "<<BExit2Enter1:B\d+>>"
+## CHECK:  successors       "<<BExit1:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BTry2:B\d+>>"
+## CHECK:  predecessors     "<<BEnter2>>"
+## CHECK:  successors       "<<BExit2Enter1>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BReturn:B\d+>>"
+## CHECK:  predecessors     "<<BExit1>>" "<<BCatch1:B\d+>>" "<<BCatch2:B\d+>>"
+## CHECK:  Return
+
+## CHECK:  name             "<<BCatch1>>"
+## CHECK:  predecessors     "<<BExit2Enter1>>" "<<BExit1>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus1>>]
+
+## CHECK:  name             "<<BCatch2>>"
+## CHECK:  predecessors     "<<BEnter2>>" "<<BExit2Enter1>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus2>>]
+
+## CHECK:  name             "<<BExit2Enter1>>"
+## CHECK:  predecessors     "<<BTry2>>"
+## CHECK:  successors       "<<BTry1>>"
+## CHECK:  xhandlers        "<<BCatch1>>" "<<BCatch2>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:true
+
+## CHECK:  name             "<<BExit1>>"
+## CHECK:  predecessors     "<<BTry1>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  xhandlers        "<<BCatch1>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+## CHECK:  name             "<<BEnter2>>"
+## CHECK:  predecessors     "<<BGoto>>"
+## CHECK:  successors       "<<BTry2>>"
+## CHECK:  xhandlers        "<<BCatch2>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+.method public static testSharedBoundary_Reverse(III)I
+    .registers 3
+
+    goto :try_start_2
+
+    :try_start_1
+    div-int/2addr p0, p1
+    goto :return
+    :try_end_1
+    .catchall {:try_start_1 .. :try_end_1} :catch_all_1
+
+    :try_start_2
+    div-int/2addr p0, p2
+    goto :try_start_1
+    :try_end_2
+    .catchall {:try_start_2 .. :try_end_2} :catch_all_2
+
+    :return
+    return p0
+
+    :catch_all_1
+    const/4 p0, -0x1
+    goto :return
+
+    :catch_all_2
+    const/4 p0, -0x2
+    goto :return
+.end method
+
+# Test that nested tries are split into non-overlapping blocks and TryBoundary
+# blocks are correctly created between them.
+
+## CHECK-START: int Builder.testNestedTry(int, int, int, int) builder (after)
+
+## CHECK:  name             "B0"
+## CHECK:  <<Minus1:i\d+>>  IntConstant -1
+## CHECK:  <<Minus2:i\d+>>  IntConstant -2
+
+## CHECK:  name             "<<BTry1:B\d+>>"
+## CHECK:  predecessors     "<<BEnter1:B\d+>>"
+## CHECK:  successors       "<<BExit1Enter2:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BTry2:B\d+>>"
+## CHECK:  predecessors     "<<BExit1Enter2>>"
+## CHECK:  successors       "<<BExit2Enter3:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BTry3:B\d+>>"
+## CHECK:  predecessors     "<<BExit2Enter3>>"
+## CHECK:  successors       "<<BExit3:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BReturn:B\d+>>"
+## CHECK:  predecessors     "<<BExit3>>" "<<BCatchArith:B\d+>>" "<<BCatchAll:B\d+>>"
+
+## CHECK:  name             "<<BCatchArith>>"
+## CHECK:  predecessors     "<<BExit1Enter2>>" "<<BExit2Enter3>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus1>>]
+
+## CHECK:  name             "<<BCatchAll>>"
+## CHECK:  predecessors     "<<BEnter1>>" "<<BExit1Enter2>>" "<<BExit2Enter3>>" "<<BExit3>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus2>>]
+
+## CHECK:  name             "<<BEnter1>>"
+## CHECK:  predecessors     "B0"
+## CHECK:  successors       "<<BTry1>>"
+## CHECK:  xhandlers        "<<BCatchAll>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExit1Enter2>>"
+## CHECK:  predecessors     "<<BTry1>>"
+## CHECK:  successors       "<<BTry2>>"
+## CHECK:  xhandlers        "<<BCatchAll>>" "<<BCatchArith>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:true
+
+## CHECK:  name             "<<BExit2Enter3>>"
+## CHECK:  predecessors     "<<BTry2>>"
+## CHECK:  successors       "<<BTry3>>"
+## CHECK:  xhandlers        "<<BCatchArith>>" "<<BCatchAll>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:true
+
+## CHECK:  name             "<<BExit3>>"
+## CHECK:  predecessors     "<<BTry3>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  xhandlers        "<<BCatchAll>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+.method public static testNestedTry(IIII)I
+    .registers 4
+
+    :try_start_1
+    div-int/2addr p0, p1
+
+    :try_start_2
+    div-int/2addr p0, p2
+    :try_end_2
+    .catch Ljava/lang/ArithmeticException; {:try_start_2 .. :try_end_2} :catch_arith
+
+    div-int/2addr p0, p3
+    :try_end_1
+    .catchall {:try_start_1 .. :try_end_1} :catch_all
+
+    :return
+    return p0
+
+    :catch_arith
+    const/4 p0, -0x1
+    goto :return
+
+    :catch_all
+    const/4 p0, -0x2
+    goto :return
+.end method
+
+# Test control flow that enters a try block, leaves it and returns again.
+
+## CHECK-START: int Builder.testIncontinuousTry(int, int, int, int) builder (after)
+
+## CHECK:  name             "B0"
+## CHECK:  <<Minus1:i\d+>>  IntConstant -1
+
+## CHECK:  name             "<<BTry1:B\d+>>"
+## CHECK:  predecessors     "<<BEnterTry1:B\d+>>"
+## CHECK:  successors       "<<BExitTry1:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BTry2:B\d+>>"
+## CHECK:  predecessors     "<<BEnterTry2:B\d+>>"
+## CHECK:  successors       "<<BExitTry2:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BReturn:B\d+>>"
+## CHECK:  predecessors     "<<BExitTry2>>" "<<BCatch:B\d+>>"
+
+## CHECK:  name             "<<BOutside:B\d+>>"
+## CHECK:  predecessors     "<<BExitTry1>>"
+## CHECK:  successors       "<<BEnterTry2>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BCatch>>"
+## CHECK:  predecessors     "<<BEnterTry1>>" "<<BExitTry1>>" "<<BEnterTry2>>" "<<BExitTry2>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus1>>]
+
+## CHECK:  name             "<<BEnterTry1>>"
+## CHECK:  predecessors     "B0"
+## CHECK:  successors       "<<BTry1>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExitTry1>>"
+## CHECK:  predecessors     "<<BTry1>>"
+## CHECK:  successors       "<<BOutside>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+## CHECK:  name             "<<BEnterTry2>>"
+## CHECK:  predecessors     "<<BOutside>>"
+## CHECK:  successors       "<<BTry2>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExitTry2>>"
+## CHECK:  predecessors     "<<BTry2>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+.method public static testIncontinuousTry(IIII)I
+    .registers 4
+
+    :try_start
+    div-int/2addr p0, p1
+    goto :outside
+
+    :inside
+    div-int/2addr p0, p3
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+
+    :return
+    return p0
+
+    :outside
+    div-int/2addr p0, p2
+    goto :inside
+
+    :catch_all
+    const/4 p0, -0x1
+    goto :return
+.end method
+
+# Test that a TryBoundary is inserted between a Throw instruction and the exit
+# block when covered by a try range.
+
+## CHECK-START: int Builder.testThrow(java.lang.Exception) builder (after)
+
+## CHECK:  name             "B0"
+## CHECK:  successors       "<<BEnterTry:B\d+>>"
+## CHECK:  <<Minus1:i\d+>>  IntConstant -1
+
+## CHECK:  name             "<<BTry:B\d+>>"
+## CHECK:  predecessors     "<<BEnterTry>>"
+## CHECK:  successors       "<<BExitTry:B\d+>>"
+## CHECK:  Throw
+
+## CHECK:  name             "<<BCatch:B\d+>>"
+## CHECK:  predecessors     "<<BEnterTry>>" "<<BExitTry>>"
+## CHECK:  successors       "<<BExit:B\d+>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  StoreLocal       [v0,<<Minus1>>]
+
+## CHECK:  name             "<<BEnterTry>>"
+## CHECK:  predecessors     "B0"
+## CHECK:  successors       "<<BTry>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExitTry>>"
+## CHECK:  predecessors     "<<BTry>>"
+## CHECK:  successors       "<<BExit>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+## CHECK:  name             "<<BExit>>"
+## CHECK:  predecessors     "<<BExitTry>>" "<<BCatch>>"
+## CHECK:  Exit
+
+.method public static testThrow(Ljava/lang/Exception;)I
+    .registers 2
+
+    :try_start
+    throw p0
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+
+    :catch_all
+    const/4 v0, -0x1
+    return v0
+.end method
+
+# Test graph with a throw/catch loop.
+
+## CHECK-START: int Builder.testCatchLoop(int, int, int) builder (after)
+
+## CHECK:  name             "B0"
+## CHECK:  successors       "<<BEnterTry:B\d+>>"
+
+## CHECK:  name             "<<BTry:B\d+>>"
+## CHECK:  predecessors     "<<BEnterTry>>" "<<BEnterTry>>" "<<BExitTry:B\d+>>"
+## CHECK:  successors       "<<BExitTry>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  Div
+
+## CHECK:  name             "<<BReturn:B\d+>>"
+## CHECK:  predecessors     "<<BExitTry>>"
+
+## CHECK:  name             "<<BEnterTry>>"
+## CHECK:  predecessors     "B0"
+## CHECK:  successors       "<<BTry>>"
+## CHECK:  xhandlers        "<<BTry>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExitTry>>"
+## CHECK:  predecessors     "<<BTry>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  xhandlers        "<<BTry>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+.method public static testCatchLoop(III)I
+    .registers 4
+
+    :try_start
+    :catch_all
+    div-int/2addr p0, p2
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+
+    :return
+    return p0
+.end method
+
+# Test that handler edges are not split. In this scenario, the catch block is
+# only the handler of the try block.
+
+## CHECK-START: int Builder.testHandlerEdge1(int, int, int) builder (after)
+
+## CHECK:  name             "B0"
+## CHECK:  successors       "<<BEnterTry:B\d+>>"
+
+## CHECK:  name             "<<BTry:B\d+>>"
+## CHECK:  predecessors     "<<BEnterTry>>"
+## CHECK:  successors       "<<BCatch:B\d+>>"
+## CHECK:  Div
+
+## CHECK:  name             "<<BCatch>>"
+## CHECK:  predecessors     "<<BTry>>" "<<BEnterTry>>" "<<BExitTry:B\d+>>"
+## CHECK:  successors       "<<BExitTry>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  Div
+
+## CHECK:  name             "<<BReturn:B\d+>>"
+## CHECK:  predecessors     "<<BExitTry>>"
+
+## CHECK:  name             "<<BEnterTry>>"
+## CHECK:  predecessors     "B0"
+## CHECK:  successors       "<<BTry>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExitTry>>"
+## CHECK:  predecessors     "<<BCatch>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  xhandlers        "<<BCatch>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+.method public static testHandlerEdge1(III)I
+    .registers 4
+
+    :try_start
+    div-int/2addr p0, p1
+
+    :catch_all
+    div-int/2addr p0, p2
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+
+    return p0
+.end method
+
+# Test that handler edges are not split. In this scenario, the catch block is
+# the handler and also the successor of the try block.
+
+## CHECK-START: int Builder.testHandlerEdge2(int, int, int) builder (after)
+
+## CHECK:  name             "B0"
+## CHECK:  successors       "<<BEnter1:B\d+>>"
+
+## CHECK:  name             "<<BTry1:B\d+>>"
+## CHECK:  predecessors     "<<BEnter1>>" "<<BExit1Enter2:B\d+>>" "<<BExit2:B\d+>>"
+## CHECK:  successors       "<<BExit1Enter2>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  Div
+
+## CHECK:  name             "<<BTry2:B\d+>>"
+## CHECK:  predecessors     "<<BExit1Enter2>>" "<<BEnter1>>" "<<BExit1Enter2>>"
+## CHECK:  successors       "<<BExit2>>"
+## CHECK:  flags            "catch_block"
+## CHECK:  Div
+
+## CHECK:  name             "<<BReturn:B\d+>>"
+## CHECK:  predecessors     "<<BExit2>>"
+## CHECK:  Return
+
+## CHECK:  name             "<<BEnter1>>"
+## CHECK:  predecessors     "B0"
+## CHECK:  successors       "<<BTry1>>"
+## CHECK:  xhandlers        "<<BTry2>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:false
+
+## CHECK:  name             "<<BExit1Enter2>>"
+## CHECK:  predecessors     "<<BTry1>>"
+## CHECK:  successors       "<<BTry2>>"
+## CHECK:  xhandlers        "<<BTry2>>" "<<BTry1>>"
+## CHECK:  TryBoundary      is_entry:true is_exit:true
+
+## CHECK:  name             "<<BExit2>>"
+## CHECK:  predecessors     "<<BTry2>>"
+## CHECK:  successors       "<<BReturn>>"
+## CHECK:  xhandlers        "<<BTry1>>"
+## CHECK:  TryBoundary      is_entry:false is_exit:true
+
+.method public static testHandlerEdge2(III)I
+    .registers 4
+
+    :try_start_1
+    :catch_all_1
+    div-int/2addr p0, p1
+    :try_end_1
+    .catchall {:try_start_1 .. :try_end_1} :catch_all_2
+
+    :try_start_2
+    :catch_all_2
+    div-int/2addr p0, p2
+    :try_end_2
+    .catchall {:try_start_2 .. :try_end_2} :catch_all_1
+
+    return p0
+.end method
+
+# Test that a MOVE_RESULT instruction is placed into the same block as the
+# INVOKE it follows, even if there is a try boundary between them.
+
+## CHECK-START: int Builder.testMoveResult_Invoke(int, int, int) builder (after)
+
+## CHECK:       <<Res:i\d+>> InvokeStaticOrDirect
+## CHECK-NEXT:  StoreLocal   [v0,<<Res>>]
+
+.method public static testMoveResult_Invoke(III)I
+    .registers 3
+
+    :try_start
+    invoke-static {p0, p1, p2}, LBuilder;->testCatchLoop(III)I
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+
+    move-result p0
+
+    :return
+    return p0
+
+    :catch_all
+    const/4 p0, -0x1
+    goto :return
+.end method
+
+# Test that a MOVE_RESULT instruction is placed into the same block as the
+# FILLED_NEW_ARRAY it follows, even if there is a try boundary between them.
+
+## CHECK-START: int[] Builder.testMoveResult_FilledNewArray(int, int, int) builder (after)
+
+## CHECK:      <<Res:l\d+>>  NewArray
+## CHECK-NEXT:               Temporary
+## CHECK-NEXT:               StoreLocal [v0,<<Res>>]
+## CHECK:                    ArraySet [<<Res>>,{{i\d+}},{{i\d+}}]
+## CHECK:                    ArraySet [<<Res>>,{{i\d+}},{{i\d+}}]
+## CHECK:                    ArraySet [<<Res>>,{{i\d+}},{{i\d+}}]
+
+.method public static testMoveResult_FilledNewArray(III)[I
+    .registers 3
+
+    :try_start
+    filled-new-array {p0, p1, p2}, [I
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+
+    move-result-object p0
+
+    :return
+    return-object p0
+
+    :catch_all
+    const/4 p0, 0x0
+    goto :return
+.end method
+
+# Test case for ReturnVoid inside a try block. Builder needs to move it outside
+# the try block so as to not split the ReturnVoid-Exit edge.
+# This invariant is enforced by GraphChecker.
+
+.method public static testReturnVoidInTry(II)V
+    .registers 2
+
+    :catch_all
+    :try_start
+    return-void
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+
+.end method
+
+# Test case for Return inside a try block. Builder needs to move it outside the
+# try block so as to not split the Return-Exit edge.
+# This invariant is enforced by GraphChecker.
+
+.method public static testReturnInTry(II)I
+    .registers 2
+
+    :try_start
+    div-int/2addr p0, p1
+    return p0
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+
+    :catch_all
+    const/4 v0, 0x0
+    return v0
+.end method
+
+# Test a (dead) try block which flows out of the method. The block will be
+# removed by DCE but needs to pass post-builder GraphChecker.
+
+## CHECK-START: int Builder.testDeadEndTry(int) builder (after)
+## CHECK-NOT:     TryBoundary is_exit:true
+
+.method public static testDeadEndTry(I)I
+    .registers 1
+
+    return p0
+
+    :catch_all
+    nop
+
+    :try_start
+    nop
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+.end method
diff --git a/test/510-checker-try-catch/src/Main.java b/test/510-checker-try-catch/src/Main.java
new file mode 100644
index 0000000..ae78ba0
--- /dev/null
+++ b/test/510-checker-try-catch/src/Main.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+
+  // Workaround for b/18051191.
+  class InnerClass {}
+
+  public static void main(String[] args) {}
+}