Allow to inline invokes that sometimes throw into try blocks

We supported inlining these invokes into regular blocks, and we
can extend that support for try blocks too.

Bug: 227283224
Test: art/test/testrunner/testrunner.py --host --64 --optimizing -b
Change-Id: Id45a009adabc610f4bf7a0457880ad7b9d772178
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index 3e3b2d4..1f7ef2c 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -1883,7 +1883,6 @@
                              HInvoke* invoke,
                              size_t* out_number_of_instructions,
                              bool is_speculative) const {
-  const HBasicBlock* target_block = invoke->GetBlock();
   ArtMethod* const resolved_method = callee_graph->GetArtMethod();
 
   HBasicBlock* exit_block = callee_graph->GetExitBlock();
@@ -1905,14 +1904,7 @@
     }
 
     if (last_instruction->IsThrow()) {
-      if (target_block->IsTryBlock()) {
-        // TODO(ngeoffray): Support adding HTryBoundary in Hgraph::InlineInto.
-        LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchCaller)
-            << "Method " << resolved_method->PrettyMethod()
-            << " could not be inlined because one branch always throws and"
-            << " caller is in a try/catch block";
-        return false;
-      } else if (graph_->GetExitBlock() == nullptr) {
+      if (graph_->GetExitBlock() == nullptr) {
         // TODO(ngeoffray): Support adding HExit in the caller graph.
         LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedInfiniteLoop)
             << "Method " << resolved_method->PrettyMethod()
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index 270bb4f..85d0fe7 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -2919,23 +2919,45 @@
       DCHECK_IMPLIES(saw_goto, last->IsReturnVoid() || last->IsReturn());
 
       if (last->IsThrow()) {
-        DCHECK(!at->IsTryBlock());
         // The chain `Throw->TryBoundary` is allowed but not `Throw->TryBoundary->Goto` since that
         // would mean a Goto will point to exit after ReplaceSuccessor.
         DCHECK(!saw_goto);
 
-        // We either have `Throw->TryBoundary` or `Throw`. We want to point the whole chain to the
-        // exit, so we recompute `predecessor`
-        predecessor = to->GetPredecessors()[pred];
-        predecessor->ReplaceSuccessor(to, outer_graph->GetExitBlock());
+        if (at->IsTryBlock()) {
+          DCHECK(!saw_try_boundary) << "We don't support inlining of try blocks into try blocks.";
+          // Create a TryBoundary of kind:exit and point it to the Exit block.
+          HBasicBlock* new_block = outer_graph->SplitEdge(predecessor, to);
+          new_block->AddInstruction(
+              new (allocator) HTryBoundary(HTryBoundary::BoundaryKind::kExit, last->GetDexPc()));
+          new_block->ReplaceSuccessor(to, outer_graph->GetExitBlock());
+
+          // Copy information from the predecessor.
+          new_block->SetLoopInformation(predecessor->GetLoopInformation());
+          TryCatchInformation* try_catch_info = predecessor->GetTryCatchInformation();
+          new_block->SetTryCatchInformation(try_catch_info);
+          for (HBasicBlock* xhandler :
+               try_catch_info->GetTryEntry().GetBlock()->GetExceptionalSuccessors()) {
+            new_block->AddSuccessor(xhandler);
+          }
+          DCHECK(try_catch_info->GetTryEntry().HasSameExceptionHandlersAs(
+              *new_block->GetLastInstruction()->AsTryBoundary()));
+        } else {
+          // We either have `Throw->TryBoundary` or `Throw`. We want to point the whole chain to the
+          // exit, so we recompute `predecessor`
+          predecessor = to->GetPredecessors()[pred];
+          predecessor->ReplaceSuccessor(to, outer_graph->GetExitBlock());
+        }
+
         --pred;
         // We need to re-run dominance information, as the exit block now has
-        // a new dominator.
+        // a new predecessor and potential new dominator.
+        // TODO(solanes): See if it's worth it to hand-modify the domination chain instead of
+        // rerunning the dominance for the whole graph.
         rerun_dominance = true;
         if (predecessor->GetLoopInformation() != nullptr) {
-          // The exit block and blocks post dominated by the exit block do not belong
-          // to any loop. Because we do not compute the post dominators, we need to re-run
-          // loop analysis to get the loop information correct.
+          // The loop information might have changed e.g. `predecessor` might not be in a loop
+          // anymore. We only do this if `predecessor` has loop information as it is impossible for
+          // predecessor to end up in a loop if it wasn't in one before.
           rerun_loop_analysis = true;
         }
       } else {
diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h
index 02f1fe9..c741b02 100644
--- a/compiler/optimizing/optimizing_compiler_stats.h
+++ b/compiler/optimizing/optimizing_compiler_stats.h
@@ -95,7 +95,6 @@
   kNotInlinedIrreducibleLoop,
   kNotInlinedAlwaysThrows,
   kNotInlinedInfiniteLoop,
-  kNotInlinedTryCatchCaller,
   kNotInlinedTryCatchCallee,
   kNotInlinedTryCatchDisabled,
   kNotInlinedRegisterAllocator,
diff --git a/test/2250-inline-throw-into-try/expected-stderr.txt b/test/2250-inline-throw-into-try/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2250-inline-throw-into-try/expected-stderr.txt
diff --git a/test/2250-inline-throw-into-try/expected-stdout.txt b/test/2250-inline-throw-into-try/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2250-inline-throw-into-try/expected-stdout.txt
diff --git a/test/2250-inline-throw-into-try/info.txt b/test/2250-inline-throw-into-try/info.txt
new file mode 100644
index 0000000..4d4cab5
--- /dev/null
+++ b/test/2250-inline-throw-into-try/info.txt
@@ -0,0 +1 @@
+Tests that we inline methods that end with a throw, inside of try blocks.
diff --git a/test/2250-inline-throw-into-try/src/Main.java b/test/2250-inline-throw-into-try/src/Main.java
new file mode 100644
index 0000000..d562279
--- /dev/null
+++ b/test/2250-inline-throw-into-try/src/Main.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 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 {
+    public static void main(String[] args) throws Exception {
+        // Inline methods that sometimes throw.
+        $noinline$assertEquals(-1000, $noinline$testThrowsWithZero(0));
+        $noinline$assertEquals(1, $noinline$testThrowsWithZero(1));
+
+        // Tests that we can correctly inline even when the throw is not caught.
+        try {
+            $noinline$testThrowNotCaught(0);
+            unreachable();
+        } catch (Error expected) {
+        }
+    }
+
+    public static void $noinline$assertEquals(int expected, int result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
+
+    private static int $noinline$testThrowsWithZero(int value) {
+        try {
+            return $inline$throwsWithZeroOrReturns(value);
+        } catch (Error e) {
+            return -1000;
+        }
+    }
+
+    private static int $inline$throwsWithZeroOrReturns(int value) {
+        if (value == 0) {
+            throw new Error("Zero!");
+        } else {
+            return value;
+        }
+    }
+
+    private static int $noinline$testThrowNotCaught(int value) {
+        try {
+            return $inline$throwsWithZeroOrReturns(value);
+        } catch (Exception e) {
+            return -1000;
+        }
+    }
+
+    private static void unreachable() throws Exception{
+        throw new Exception("Unreachable");
+    }
+}