Simplify SimplifyAlwaysThrows

Remove most of the preconditions of the optimization
since they are not needed e.g.:

* Ending with a Goto
* Not flowing to the Exit block
* The successor block having another predecessor
* Not doing the optimization in a catch block

Test: art/test/testrunner/testrunner.py --host --64 --optimizing -b
Change-Id: Idd0c722d1cd34fa6c394338a2e475a7ff60bd88a
diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc
index 0ce8bfa..8e3010d 100644
--- a/compiler/optimizing/dead_code_elimination.cc
+++ b/compiler/optimizing/dead_code_elimination.cc
@@ -236,8 +236,11 @@
 //         B2  Exit
 //
 // Rationale:
-// Removal of the never taken edge to B2 may expose
-// other optimization opportunities, such as code sinking.
+// Removal of the never taken edge to B2 may expose other optimization opportunities, such as code
+// sinking.
+//
+// Note: The example above is a simple one that uses a `goto` but we could end the block with an If,
+// for example.
 bool HDeadCodeElimination::SimplifyAlwaysThrows() {
   HBasicBlock* exit = graph_->GetExitBlock();
   if (!graph_->HasAlwaysThrowingInvokes() || exit == nullptr) {
@@ -248,70 +251,56 @@
 
   // Order does not matter, just pick one.
   for (HBasicBlock* block : graph_->GetReversePostOrder()) {
-    if (block->GetTryCatchInformation() != nullptr) {
+    if (block->IsTryBlock()) {
       // We don't want to perform the simplify always throws optimizations for throws inside of
-      // tries since those throws might not go to the exit block. We do that by checking the
-      // TryCatchInformation of the blocks.
-      //
-      // As a special case the `catch_block` is the first block of the catch and it has
-      // TryCatchInformation. Other blocks in the catch don't have try catch information (as long as
-      // they are not part of an outer try). Knowing if a `catch_block` is part of an outer try is
-      // possible by checking its successors, but other restrictions of the simplify always throws
-      // optimization will block `catch_block` nevertheless (e.g. only one predecessor) so it is not
-      // worth the effort.
-
-      // TODO(solanes): Maybe we can do a `goto catch` if inside of a try catch instead of going to
-      // the exit. If we do so, we have to take into account that we should go to the nearest valid
-      // catch i.e. one that would accept our exception type.
+      // tries since those throws might not go to the exit block.
       continue;
     }
 
-    if (block->GetLastInstruction()->IsGoto() &&
-        block->GetPhis().IsEmpty() &&
-        block->GetPredecessors().size() == 1u) {
-      HBasicBlock* pred = block->GetSinglePredecessor();
-      HBasicBlock* succ = block->GetSingleSuccessor();
-      // Ensure no computations are merged through throwing block. This does not prevent the
-      // optimization per se, but would require an elaborate clean up of the SSA graph.
-      if (succ != exit &&
-          !block->Dominates(pred) &&
-          pred->Dominates(succ) &&
-          succ->GetPredecessors().size() > 1u &&
-          succ->GetPhis().IsEmpty()) {
-        // We iterate to find the first instruction that always throws. If two instructions always
-        // throw, the first one will throw and the second one will never be reached.
-        HInstruction* throwing_instruction = nullptr;
-        for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
-          if (it.Current()->AlwaysThrows()) {
-            throwing_instruction = it.Current();
-            break;
-          }
-        }
-
-        if (throwing_instruction == nullptr) {
-          // No always-throwing instruction found. Continue with the rest of the blocks.
-          continue;
-        }
-
-        // We split the block at the throwing instruction, and the instructions after the throwing
-        // instructions will be disconnected from the graph after `block` points to the exit.
-        // `RemoveDeadBlocks` will take care of removing this new block and its instructions.
-        // Even though `SplitBefore` doesn't guarantee the graph to remain in SSA form, it is fine
-        // since we do not break it.
-        HBasicBlock* new_block = block->SplitBefore(throwing_instruction->GetNext(),
-                                                    /* require_graph_not_in_ssa_form= */ false);
-        DCHECK_EQ(block->GetSingleSuccessor(), new_block);
-        block->ReplaceSuccessor(new_block, exit);
-
-        rerun_dominance_and_loop_analysis = true;
-        MaybeRecordStat(stats_, MethodCompilationStat::kSimplifyThrowingInvoke);
-        // Perform a quick follow up optimization on object != null control dependences
-        // that is much cheaper to perform now than in a later phase.
-        if (RemoveNonNullControlDependences(pred, block)) {
-          MaybeRecordStat(stats_, MethodCompilationStat::kRemovedNullCheck);
-        }
+    // We iterate to find the first instruction that always throws. If two instructions always
+    // throw, the first one will throw and the second one will never be reached.
+    HInstruction* throwing_invoke = nullptr;
+    for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
+      if (it.Current()->IsInvoke() && it.Current()->AsInvoke()->AlwaysThrows()) {
+        throwing_invoke = it.Current();
+        break;
       }
     }
+
+    if (throwing_invoke == nullptr) {
+      // No always-throwing instruction found. Continue with the rest of the blocks.
+      continue;
+    }
+
+    // If we are already pointing at the exit block we could still remove the instructions
+    // between the always throwing instruction, and the exit block. If we have no other
+    // instructions, just continue since there's nothing to do.
+    if (block->GetSuccessors().size() == 1 &&
+        block->GetSingleSuccessor() == exit &&
+        block->GetLastInstruction()->GetPrevious() == throwing_invoke) {
+      continue;
+    }
+
+    // We split the block at the throwing instruction, and the instructions after the throwing
+    // instructions will be disconnected from the graph after `block` points to the exit.
+    // `RemoveDeadBlocks` will take care of removing this new block and its instructions.
+    // Even though `SplitBefore` doesn't guarantee the graph to remain in SSA form, it is fine
+    // since we do not break it.
+    HBasicBlock* new_block = block->SplitBefore(throwing_invoke->GetNext(),
+                                                /* require_graph_not_in_ssa_form= */ false);
+    DCHECK_EQ(block->GetSingleSuccessor(), new_block);
+    block->ReplaceSuccessor(new_block, exit);
+
+    rerun_dominance_and_loop_analysis = true;
+    MaybeRecordStat(stats_, MethodCompilationStat::kSimplifyThrowingInvoke);
+    // Perform a quick follow up optimization on object != null control dependences
+    // that is much cheaper to perform now than in a later phase.
+    // If there are multiple predecessors, none may end with a HIf as required in
+    // RemoveNonNullControlDependences because we split critical edges.
+    if (block->GetPredecessors().size() == 1u &&
+        RemoveNonNullControlDependences(block->GetSinglePredecessor(), block)) {
+      MaybeRecordStat(stats_, MethodCompilationStat::kRemovedNullCheck);
+    }
   }
 
   // We need to re-analyze the graph in order to run DCE afterwards.
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 8f7635a..58ce9ce 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -4734,7 +4734,7 @@
 
   void SetAlwaysThrows(bool always_throws) { SetPackedFlag<kFlagAlwaysThrows>(always_throws); }
 
-  bool AlwaysThrows() const override { return GetPackedFlag<kFlagAlwaysThrows>(); }
+  bool AlwaysThrows() const override final { return GetPackedFlag<kFlagAlwaysThrows>(); }
 
   bool CanBeMoved() const override { return IsIntrinsic() && !DoesAnyWrite(); }
 
diff --git a/test/2042-checker-dce-always-throw/src/Main.java b/test/2042-checker-dce-always-throw/src/Main.java
index 3ee9183..e164917 100644
--- a/test/2042-checker-dce-always-throw/src/Main.java
+++ b/test/2042-checker-dce-always-throw/src/Main.java
@@ -31,8 +31,20 @@
 
     // Test that we update the phis correctly after simplifying an always throwing method, and
     // recomputing dominance.
-    assertEquals(0, $noinline$testUpdatePhisCorrectly(1, 1));
-    assertEquals(0, $noinline$testDeleteAllUsesBeforeDeletingInstruction(1, 1));
+    assertEquals(0, $noinline$testUpdatePhisCorrectly(1));
+    assertEquals(0, $noinline$testDeleteAllUsesBeforeDeletingInstruction(1));
+
+    // SimplifyAlwaysThrows for blocks without a goto at the end
+    assertEquals(0, $noinline$testEndsWithIf(1, 1));
+    assertEquals(0, $noinline$testEndsWithReturn(1));
+    // Since we cannot use `assertEquals`, not throwing would be the success.
+    $noinline$testEndsWithReturnVoid(1);
+    assertEquals(0, $noinline$testEndsWithSwitch(1, 1));
+    assertEquals(0, $noinline$testEndsWithThrow(1));
+    assertEquals(0, $noinline$testEndsWithTryBoundary(1));
+
+    // SimplifyAlwaysThrows for invokes in catch blocks
+    assertEquals(0, $noinline$testInsideCatch(1));
   }
 
   private static void alwaysThrows() throws Error {
@@ -299,7 +311,7 @@
   // Check that when we perform SimplifyAlwaysThrows, that the phi for `phi_value` exists, and that
   // we correctly update it after running DCE.
 
-  /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int, int) dead_code_elimination$after_inlining (before)
+  /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int) dead_code_elimination$after_inlining (before)
   /// CHECK-DAG:   <<Const0:i\d+>> IntConstant 0
   /// CHECK-DAG:   <<Const5:i\d+>> IntConstant 5
   /// CHECK-DAG:   <<ReturnValue:i\d+>> Phi [<<Const0>>,<<Const5>>]
@@ -309,71 +321,51 @@
   /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
   /// CHECK-EVAL:  "<<ExitBlock>>" != "<<TargetBlock>>"
 
-  /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int) dead_code_elimination$after_inlining (after)
   /// CHECK-DAG:   <<Const0:i\d+>> IntConstant 0
   /// CHECK-DAG:   Return [<<Const0>>]
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
   /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
   /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
 
-  /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int) dead_code_elimination$after_inlining (after)
   /// CHECK-NOT:   Phi
-  private static int $noinline$testUpdatePhisCorrectly(int num, int other_num) {
+  private static int $noinline$testUpdatePhisCorrectly(int num) {
     int phi_value = 0;
     if (num == 0) {
       alwaysThrows();
-
-      // This while loop is here so that the `if (num == 0)` will be several blocks instead of
-      // just one. We use `other_num` here because otherwise we propagate the knowledge that `num`
-      // equals zero.
-      while (other_num == 0) {
-        // Assign to phi_value so that the loop is not empty.
-        phi_value = 2;
-      }
-
       phi_value = 5;
     }
     return phi_value;
   }
 
-
   // Test to check that we delete all uses before the instruction.
   private static int $noinline$foo(int num) {
     return num;
   }
 
-  /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int, int) dead_code_elimination$after_inlining (before)
+  /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int) dead_code_elimination$after_inlining (before)
   /// CHECK-DAG:   <<Const0:i\d+>> IntConstant 0
   /// CHECK-DAG:   <<Invoke:i\d+>> InvokeStaticOrDirect method_name:Main.$noinline$foo
   /// CHECK-DAG:   <<ReturnValue:i\d+>> Phi [<<Const0>>,<<Invoke>>]
   /// CHECK-DAG:   Return [<<ReturnValue>>]
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   TryBoundary block:<<InvokeBlock>>
   /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
-  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
-  /// CHECK-EVAL:  "<<ExitBlock>>" != "<<TargetBlock>>"
 
-  /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int) dead_code_elimination$after_inlining (after)
   /// CHECK-DAG:   <<Const0:i\d+>> IntConstant 0
   /// CHECK-DAG:   Return [<<Const0>>]
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
   /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
   /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
 
-  /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int) dead_code_elimination$after_inlining (after)
   /// CHECK-NOT:   Phi
-  private static int $noinline$testDeleteAllUsesBeforeDeletingInstruction(int num, int other_num) {
+  private static int $noinline$testDeleteAllUsesBeforeDeletingInstruction(int num) {
     int phi_value = 0;
     if (num == 0) {
       alwaysThrows();
-
-      // This while loop is here so that we have a Goto after `alwaysThrows` and therefore perform
-      // the `SimplifyAlwaysThrows` optimization. We use `other_num` here because otherwise we
-      // propagate the knowledge that `num` equals zero.
-      while (other_num == 0) {
-        // Assign to phi_value so that the loop is not empty.
-        phi_value = 2;
-      }
-
       try {
         phi_value = $noinline$foo(2);
       } catch (Error e) {
@@ -383,6 +375,187 @@
     return phi_value;
   }
 
+  /// CHECK-START: int Main.$noinline$testEndsWithIf(int, int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   If block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithIf(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithIf(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testEndsWithIf(int num, int other_num) {
+    if (num == 0) {
+      alwaysThrows();
+      if (other_num == 0) {
+        System.out.println("I am unrechable!");
+      }
+    }
+    return 0;
+  }
+
+  /// CHECK-START: int Main.$noinline$testEndsWithReturn(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Return block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithReturn(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithReturn(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testEndsWithReturn(int num) {
+    if (num == 0) {
+      alwaysThrows();
+      System.out.println("I am unrechable!");
+      return 1;
+    }
+    return 0;
+  }
+
+  /// CHECK-START: void Main.$noinline$testEndsWithReturnVoid(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   ReturnVoid block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: void Main.$noinline$testEndsWithReturnVoid(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: void Main.$noinline$testEndsWithReturnVoid(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static void $noinline$testEndsWithReturnVoid(int num) {
+    if (num == 0) {
+      alwaysThrows();
+      System.out.println("I am unrechable!");
+      return;
+    }
+    return;
+  }
+
+  /// CHECK-START: int Main.$noinline$testEndsWithSwitch(int, int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   PackedSwitch block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithSwitch(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithSwitch(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testEndsWithSwitch(int num, int other_num) {
+    if (num == 0) {
+      alwaysThrows();
+      System.out.println("I am unrechable!");
+      int result = 10;
+      switch (other_num) {
+        case 1:
+          result = 100;
+          break;
+        case 2:
+          result = 300;
+          break;
+        case 3:
+          result = 500;
+          break;
+        case 4:
+          result = 700;
+          break;
+      }
+      return result;
+    }
+    return 0;
+  }
+
+  /// CHECK-START: int Main.$noinline$testEndsWithThrow(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Throw block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithThrow(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithThrow(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testEndsWithThrow(int num) {
+    if (num == 0) {
+      alwaysThrows();
+      System.out.println("I am unrechable!");
+      throw new Error("Other error");
+    }
+    return 0;
+  }
+
+  /// CHECK-START: int Main.$noinline$testEndsWithTryBoundary(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   TryBoundary block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithTryBoundary(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithTryBoundary(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testEndsWithTryBoundary(int num) {
+    if (num == 0) {
+      alwaysThrows();
+      System.out.println("I am unrechable!");
+      try {
+        alwaysThrows();
+      } catch (Error e) {
+        return 1;
+      }
+    }
+    return 0;
+  }
+
+  // Empty method to forbid the try from disappearing
+  private static void $noinline$emptyMethod() {}
+
+  /// CHECK-START: int Main.$noinline$testInsideCatch(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Return block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testInsideCatch(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testInsideCatch(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testInsideCatch(int num) {
+    if (num == 0) {
+      try {
+        $noinline$emptyMethod();
+      } catch (Error e) {
+        alwaysThrows();
+        System.out.println("I am unrechable!");
+        return 1;
+      }
+    }
+    return 0;
+  }
+
   static void assertEquals(int expected, int actual) {
     if (expected != actual) {
       throw new AssertionError("Expected " + expected + " got " + actual);