Compiler implementation of try catch inlining

Notable changes:
1) Wiring of the graph now allows for inlinees graph ending in
   TryBoundary, or Goto in some special cases.
2) Building a graph with try catch for inlining may add an extra
   Goto block.
3) Oat version bump.
4) Reduced kMaximumNumberOfCumulatedDexRegisters from 32 to 20.

Bug: 227283224
Test: art/test/testrunner/testrunner.py --host --64 --optimizing -b
Change-Id: Ic2fd956de24b72d1de29b4cd3d0b2a1ddab231d8
diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc
index e7826bb..b28c2d9 100644
--- a/compiler/optimizing/builder.cc
+++ b/compiler/optimizing/builder.cc
@@ -96,7 +96,50 @@
   return false;
 }
 
-GraphAnalysisResult HGraphBuilder::BuildGraph() {
+static bool NeedsExtraGotoBlock(HBasicBlock* block) {
+  if (!block->IsSingleTryBoundary()) {
+    return false;
+  }
+
+  const HTryBoundary* boundary = block->GetLastInstruction()->AsTryBoundary();
+  DCHECK(boundary->GetNormalFlowSuccessor()->IsExitBlock());
+  DCHECK(!boundary->IsEntry());
+
+  const HInstruction* last_instruction = block->GetSinglePredecessor()->GetLastInstruction();
+  DCHECK(last_instruction->IsReturn() ||
+         last_instruction->IsReturnVoid() ||
+         last_instruction->IsThrow());
+
+  return !last_instruction->IsThrow();
+}
+
+void HGraphBuilder::MaybeAddExtraGotoBlocks() {
+  if (graph_->GetExitBlock() == nullptr) return;
+
+  bool added_block = false;
+  for (size_t pred = 0, size = graph_->GetExitBlock()->GetPredecessors().size(); pred < size;
+       ++pred) {
+    HBasicBlock* predecessor = graph_->GetExitBlock()->GetPredecessors()[pred];
+    if (NeedsExtraGotoBlock(predecessor)) {
+      added_block = true;
+      graph_->SplitEdge(predecessor, graph_->GetExitBlock())
+          ->AddInstruction(new (graph_->GetAllocator()) HGoto(predecessor->GetDexPc()));
+    }
+  }
+
+  // TODO(solanes): Avoid recomputing the full dominator tree by manually updating the relevant
+  // information (loop information, dominance, try catch information).
+  if (added_block) {
+    DCHECK(!graph_->HasIrreducibleLoops())
+        << "Recomputing loop information in graphs with irreducible loops "
+        << "is unsupported, as it could lead to loop header changes";
+    graph_->ClearLoopInformation();
+    graph_->ClearDominanceInformation();
+    graph_->BuildDominatorTree();
+  }
+}
+
+GraphAnalysisResult HGraphBuilder::BuildGraph(bool build_for_inline) {
   DCHECK(code_item_accessor_.HasCodeItem());
   DCHECK(graph_->GetBlocks().empty());
 
@@ -147,7 +190,13 @@
     return kAnalysisInvalidBytecode;
   }
 
-  // 5) Type the graph and eliminate dead/redundant phis.
+  // 5) When inlining, we want to add a Goto block if we have Return/ReturnVoid->TryBoundary->Exit
+  // since we will have Return/ReturnVoid->TryBoundary->`continue to normal execution` once inlined.
+  if (build_for_inline) {
+    MaybeAddExtraGotoBlocks();
+  }
+
+  // 6) Type the graph and eliminate dead/redundant phis.
   return ssa_builder.BuildSsa();
 }
 
diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h
index 580769e..a59e782 100644
--- a/compiler/optimizing/builder.h
+++ b/compiler/optimizing/builder.h
@@ -46,7 +46,7 @@
                 const CodeItemDebugInfoAccessor& accessor,
                 DataType::Type return_type = DataType::Type::kInt32);
 
-  GraphAnalysisResult BuildGraph();
+  GraphAnalysisResult BuildGraph(bool build_for_inline = false);
   void BuildIntrinsicGraph(ArtMethod* method);
 
   static constexpr const char* kBuilderPassName = "builder";
@@ -54,6 +54,10 @@
  private:
   bool SkipCompilation(size_t number_of_branches);
 
+  // When inlining, we sometimes want to add an extra Goto block before the Exit block. This is done
+  // in the building phase as we do not allow the inlining phase to add new instructions.
+  void MaybeAddExtraGotoBlocks();
+
   HGraph* const graph_;
   const DexFile* const dex_file_;
   const CodeItemDebugInfoAccessor code_item_accessor_;  // null for intrinsic graph.
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index cfde561..b739f3f 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -58,7 +58,7 @@
 
 // Limit the number of dex registers that we accumulate while inlining
 // to avoid creating large amount of nested environments.
-static constexpr size_t kMaximumNumberOfCumulatedDexRegisters = 32;
+static constexpr size_t kMaximumNumberOfCumulatedDexRegisters = 20;
 
 // Limit recursive call inlining, which do not benefit from too
 // much inlining compared to code locality.
@@ -72,6 +72,9 @@
 // Controls the use of inline caches in AOT mode.
 static constexpr bool kUseAOTInlineCaches = true;
 
+// Controls the use of inlining try catches.
+static constexpr bool kInlineTryCatches = true;
+
 // We check for line numbers to make sure the DepthString implementation
 // aligns the output nicely.
 #define LOG_INTERNAL(msg) \
@@ -504,10 +507,27 @@
 
   DCHECK(!invoke_instruction->IsInvokeStaticOrDirect());
 
+  // No try catch inlining allowed here, or recursively. For try catch inlining we are banking on
+  // the fact that we have a unique dex pc list. We cannot guarantee that for some TryInline methods
+  // e.g. `TryInlinePolymorphicCall`.
+  // TODO(solanes): Setting `try_catch_inlining_allowed_` to false here covers all cases from
+  // `TryInlineFromCHA` and from `TryInlineFromInlineCache` as well (e.g.
+  // `TryInlinePolymorphicCall`). Reassess to see if we can inline inline catch blocks in
+  // `TryInlineFromCHA`, `TryInlineMonomorphicCall` and `TryInlinePolymorphicCallToSameTarget`.
+
+  // We store the value to restore it since we will use the same HInliner instance for other inlinee
+  // candidates.
+  const bool previous_value = try_catch_inlining_allowed_;
+  try_catch_inlining_allowed_ = false;
+
   if (TryInlineFromCHA(invoke_instruction)) {
+    try_catch_inlining_allowed_ = previous_value;
     return true;
   }
-  return TryInlineFromInlineCache(invoke_instruction);
+
+  const bool result = TryInlineFromInlineCache(invoke_instruction);
+  try_catch_inlining_allowed_ = previous_value;
+  return result;
 }
 
 bool HInliner::TryInlineFromCHA(HInvoke* invoke_instruction) {
@@ -1407,9 +1427,25 @@
   }
 
   if (accessor.TriesSize() != 0) {
-    LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchCallee)
-        << "Method " << method->PrettyMethod() << " is not inlined because of try block";
-    return false;
+    if (!kInlineTryCatches) {
+      LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchDisabled)
+          << "Method " << method->PrettyMethod()
+          << " is not inlined because inlining try catches is disabled globally";
+      return false;
+    }
+    const bool inlined_into_try_catch =
+        // Direct parent is a try catch.
+        invoke_instruction->GetBlock()->GetTryCatchInformation() != nullptr ||
+        // Indirect parent is a try catch.
+        !try_catch_inlining_allowed_;
+    if (inlined_into_try_catch) {
+      LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchCallee)
+          << "Method " << method->PrettyMethod()
+          << " is not inlined because it has a try catch and we are not supporting it for this"
+          << " particular call. This is could be because e.g. it would be inlined inside another"
+          << " try catch, we arrived here from TryInlinePolymorphicCall, etc.";
+      return false;
+    }
   }
 
   if (invoke_instruction->IsInvokeStaticOrDirect() &&
@@ -1517,8 +1553,7 @@
     return false;
   }
 
-  if (!TryBuildAndInlineHelper(
-          invoke_instruction, method, receiver_type, return_replacement)) {
+  if (!TryBuildAndInlineHelper(invoke_instruction, method, receiver_type, return_replacement)) {
     return false;
   }
 
@@ -1844,7 +1879,15 @@
 
   bool has_one_return = false;
   for (HBasicBlock* predecessor : exit_block->GetPredecessors()) {
-    if (predecessor->GetLastInstruction()->IsThrow()) {
+    const HInstruction* last_instruction = predecessor->GetLastInstruction();
+    // On inlinees, we can have Throw -> TryBoundary -> Exit. To check for the actual last
+    // instruction, we have to skip it.
+    if (last_instruction->IsTryBoundary()) {
+      predecessor = predecessor->GetSinglePredecessor();
+      last_instruction = predecessor->GetLastInstruction();
+    }
+
+    if (last_instruction->IsThrow()) {
       if (target_block->IsTryBlock()) {
         // TODO(ngeoffray): Support adding HTryBoundary in Hgraph::InlineInto.
         LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchCaller)
@@ -2062,7 +2105,7 @@
                         codegen_,
                         inline_stats_);
 
-  if (builder.BuildGraph() != kAnalysisSuccess) {
+  if (builder.BuildGraph(/* build_for_inline= */ true) != kAnalysisSuccess) {
     LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedCannotBuild)
         << "Method " << callee_dex_file.PrettyMethod(method_index)
         << " could not be built, so cannot be inlined";
@@ -2071,7 +2114,15 @@
 
   SubstituteArguments(callee_graph, invoke_instruction, receiver_type, dex_compilation_unit);
 
-  RunOptimizations(callee_graph, code_item, dex_compilation_unit);
+  const bool try_catch_inlining_allowed_for_recursive_inline =
+      // It was allowed previously.
+      try_catch_inlining_allowed_ &&
+      // The current invoke is not in a try or a catch.
+      invoke_instruction->GetBlock()->GetTryCatchInformation() == nullptr;
+  RunOptimizations(callee_graph,
+                   code_item,
+                   dex_compilation_unit,
+                   try_catch_inlining_allowed_for_recursive_inline);
 
   size_t number_of_instructions = 0;
   if (!CanInlineBody(callee_graph, invoke_instruction, &number_of_instructions)) {
@@ -2109,7 +2160,8 @@
 
 void HInliner::RunOptimizations(HGraph* callee_graph,
                                 const dex::CodeItem* code_item,
-                                const DexCompilationUnit& dex_compilation_unit) {
+                                const DexCompilationUnit& dex_compilation_unit,
+                                bool try_catch_inlining_allowed_for_recursive_inline) {
   // Note: if the outermost_graph_ is being compiled OSR, we should not run any
   // optimization that could lead to a HDeoptimize. The following optimizations do not.
   HDeadCodeElimination dce(callee_graph, inline_stats_, "dead_code_elimination$inliner");
@@ -2155,7 +2207,8 @@
                    total_number_of_dex_registers_ + accessor.RegistersSize(),
                    total_number_of_instructions_ + number_of_instructions,
                    this,
-                   depth_ + 1);
+                   depth_ + 1,
+                   try_catch_inlining_allowed_for_recursive_inline);
   inliner.Run();
 }
 
diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h
index e33160e..712d3b5 100644
--- a/compiler/optimizing/inliner.h
+++ b/compiler/optimizing/inliner.h
@@ -42,7 +42,8 @@
            size_t total_number_of_dex_registers,
            size_t total_number_of_instructions,
            HInliner* parent,
-           size_t depth = 0,
+           size_t depth,
+           bool try_catch_inlining_allowed,
            const char* name = kInlinerPassName)
       : HOptimization(outer_graph, name, stats),
         outermost_graph_(outermost_graph),
@@ -54,6 +55,7 @@
         parent_(parent),
         depth_(depth),
         inlining_budget_(0),
+        try_catch_inlining_allowed_(try_catch_inlining_allowed),
         inline_stats_(nullptr) {}
 
   bool Run() override;
@@ -91,7 +93,7 @@
                                ArtMethod* resolved_method,
                                ReferenceTypeInfo receiver_type,
                                HInstruction** return_replacement)
-    REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Substitutes parameters in the callee graph with their values from the caller.
   void SubstituteArguments(HGraph* callee_graph,
@@ -103,8 +105,9 @@
   // Run simple optimizations on `callee_graph`.
   void RunOptimizations(HGraph* callee_graph,
                         const dex::CodeItem* code_item,
-                        const DexCompilationUnit& dex_compilation_unit)
-    REQUIRES_SHARED(Locks::mutator_lock_);
+                        const DexCompilationUnit& dex_compilation_unit,
+                        bool try_catch_inlining_allowed_for_recursive_inline)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Try to recognize known simple patterns and replace invoke call with appropriate instructions.
   bool TryPatternSubstitution(HInvoke* invoke_instruction,
@@ -318,6 +321,9 @@
   // The budget left for inlining, in number of instructions.
   size_t inlining_budget_;
 
+  // States if we are allowing try catch inlining to occur at this particular instance of inlining.
+  bool try_catch_inlining_allowed_;
+
   // Used to record stats about optimizations on the inlined graph.
   // If the inlining is successful, these stats are merged to the caller graph's stats.
   OptimizingCompilerStats* inline_stats_;
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index 19731ae..8c88c50 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -2703,7 +2703,8 @@
 
 void HGraph::UpdateLoopAndTryInformationOfNewBlock(HBasicBlock* block,
                                                    HBasicBlock* reference,
-                                                   bool replace_if_back_edge) {
+                                                   bool replace_if_back_edge,
+                                                   bool has_more_specific_try_catch_info) {
   if (block->IsLoopHeader()) {
     // Clear the information of which blocks are contained in that loop. Since the
     // information is stored as a bit vector based on block ids, we have to update
@@ -2730,11 +2731,16 @@
     }
   }
 
-  // Copy TryCatchInformation if `reference` is a try block, not if it is a catch block.
-  TryCatchInformation* try_catch_info = reference->IsTryBlock()
-      ? reference->GetTryCatchInformation()
-      : nullptr;
-  block->SetTryCatchInformation(try_catch_info);
+  DCHECK_IMPLIES(has_more_specific_try_catch_info, reference->GetTryCatchInformation() == nullptr)
+      << "We don't allow to inline try catches inside of other try catches.";
+
+  // Update the TryCatchInformation, if we are not inlining a try catch.
+  if (!has_more_specific_try_catch_info) {
+    // Copy TryCatchInformation if `reference` is a try block, not if it is a catch block.
+    TryCatchInformation* try_catch_info =
+        reference->IsTryBlock() ? reference->GetTryCatchInformation() : nullptr;
+    block->SetTryCatchInformation(try_catch_info);
+  }
 }
 
 HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) {
@@ -2847,12 +2853,14 @@
     // and (4) to the blocks that apply.
     for (HBasicBlock* current : GetReversePostOrder()) {
       if (current != exit_block_ && current != entry_block_ && current != first) {
-        DCHECK(current->GetTryCatchInformation() == nullptr);
         DCHECK(current->GetGraph() == this);
         current->SetGraph(outer_graph);
         outer_graph->AddBlock(current);
         outer_graph->reverse_post_order_[++index_of_at] = current;
-        UpdateLoopAndTryInformationOfNewBlock(current, at,  /* replace_if_back_edge= */ false);
+        UpdateLoopAndTryInformationOfNewBlock(current,
+                                              at,
+                                              /* replace_if_back_edge= */ false,
+                                              current->GetTryCatchInformation() != nullptr);
       }
     }
 
@@ -2866,16 +2874,47 @@
 
     // Update all predecessors of the exit block (now the `to` block)
     // to not `HReturn` but `HGoto` instead. Special case throwing blocks
-    // to now get the outer graph exit block as successor. Note that the inliner
-    // currently doesn't support inlining methods with try/catch.
+    // to now get the outer graph exit block as successor.
     HPhi* return_value_phi = nullptr;
     bool rerun_dominance = false;
     bool rerun_loop_analysis = false;
     for (size_t pred = 0; pred < to->GetPredecessors().size(); ++pred) {
       HBasicBlock* predecessor = to->GetPredecessors()[pred];
       HInstruction* last = predecessor->GetLastInstruction();
+
+      // At this point we might either have:
+      // A) Return/ReturnVoid/Throw as the last instruction
+      // B) `Return/ReturnVoid->TryBoundary->Goto` as the last instruction chain
+      // C) `Throw->TryBoundary` as the last instruction chain
+
+      const bool saw_goto = last->IsGoto();
+      if (saw_goto) {
+        DCHECK(predecessor->IsSingleGoto());
+        predecessor = predecessor->GetSinglePredecessor();
+        last = predecessor->GetLastInstruction();
+      }
+
+      const bool saw_try_boundary = last->IsTryBoundary();
+      if (saw_try_boundary) {
+        DCHECK(predecessor->IsSingleTryBoundary());
+        DCHECK(!last->AsTryBoundary()->IsEntry());
+        predecessor = predecessor->GetSinglePredecessor();
+        last = predecessor->GetLastInstruction();
+      }
+
+      // Check that if we have an instruction chain, it is one of the allowed ones.
+      DCHECK_IMPLIES(saw_goto, saw_try_boundary);
+      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());
         --pred;
         // We need to re-run dominance information, as the exit block now has
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 8db8c02..44e342f 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -486,9 +486,11 @@
   // Update the loop and try membership of `block`, which was spawned from `reference`.
   // In case `reference` is a back edge, `replace_if_back_edge` notifies whether `block`
   // should be the new back edge.
+  // `has_more_specific_try_catch_info` will be set to true when inlining a try catch.
   void UpdateLoopAndTryInformationOfNewBlock(HBasicBlock* block,
                                              HBasicBlock* reference,
-                                             bool replace_if_back_edge);
+                                             bool replace_if_back_edge,
+                                             bool has_more_specific_try_catch_info = false);
 
   // Need to add a couple of blocks to test if the loop body is entered and
   // put deoptimization instructions, etc.
diff --git a/compiler/optimizing/optimization.cc b/compiler/optimizing/optimization.cc
index 7bf6dbb..2b7bedd 100644
--- a/compiler/optimizing/optimization.cc
+++ b/compiler/optimizing/optimization.cc
@@ -239,6 +239,7 @@
                                        /* total_number_of_instructions= */ 0,
                                        /* parent= */ nullptr,
                                        /* depth= */ 0,
+                                       /* try_catch_inlining_allowed= */ true,
                                        pass_name);
         break;
       }
diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h
index 18b3a60..313f3ef 100644
--- a/compiler/optimizing/optimizing_compiler_stats.h
+++ b/compiler/optimizing/optimizing_compiler_stats.h
@@ -94,6 +94,7 @@
   kNotInlinedInfiniteLoop,
   kNotInlinedTryCatchCaller,
   kNotInlinedTryCatchCallee,
+  kNotInlinedTryCatchDisabled,
   kNotInlinedRegisterAllocator,
   kNotInlinedCannotBuild,
   kNotInlinedNeverInlineAnnotation,
diff --git a/runtime/oat.h b/runtime/oat.h
index 341e70b..32034f6 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,8 +32,8 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } };
-  // Last oat version changed reason: Don't use instrumentation stubs for native methods.
-  static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '2', '7', '\0' } };
+  // Last oat version changed reason: Try catch inlining.
+  static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '2', '8', '\0' } };
 
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
   static constexpr const char* kDebuggableKey = "debuggable";
diff --git a/test/2237-checker-inline-multidex/expected-stdout.txt b/test/2237-checker-inline-multidex/expected-stdout.txt
index 571349a..c2c127f 100644
--- a/test/2237-checker-inline-multidex/expected-stdout.txt
+++ b/test/2237-checker-inline-multidex/expected-stdout.txt
@@ -2,3 +2,4 @@
 def
 ghi
 class Multi$Multi2
+122
diff --git a/test/2237-checker-inline-multidex/info.txt b/test/2237-checker-inline-multidex/info.txt
index fc6dc60..2e639ba 100644
--- a/test/2237-checker-inline-multidex/info.txt
+++ b/test/2237-checker-inline-multidex/info.txt
@@ -1 +1 @@
-Checks that we inline across dex files, even when we need an environment.
+Checks that we inline across dex files.
diff --git a/test/2237-checker-inline-multidex/src-multidex/Multi.java b/test/2237-checker-inline-multidex/src-multidex/Multi.java
index cd234c3..c830c59 100644
--- a/test/2237-checker-inline-multidex/src-multidex/Multi.java
+++ b/test/2237-checker-inline-multidex/src-multidex/Multi.java
@@ -38,4 +38,12 @@
   }
 
   private class Multi2 {}
+
+  public static int $inline$TryCatch(String str) {
+    try {
+      return Integer.parseInt(str);
+    } catch (NumberFormatException ex) {
+      return -1;
+    }
+  }
 }
diff --git a/test/2237-checker-inline-multidex/src/Main.java b/test/2237-checker-inline-multidex/src/Main.java
index 7ab2e7f..7a2ca82 100644
--- a/test/2237-checker-inline-multidex/src/Main.java
+++ b/test/2237-checker-inline-multidex/src/Main.java
@@ -16,57 +16,73 @@
 
 public class Main {
   public static void main(String[] args) {
-    System.out.println(testNeedsEnvironment());
-    System.out.println(testNeedsBssEntryString());
-    System.out.println(testNeedsBssEntryInvoke());
-    System.out.println(testClass());
+    // Test that the cross-dex inlining is working for HInstructions that need an environment.
+    System.out.println($noinline$testNeedsEnvironment());
+
+    // Test that the cross-dex inlining is working for HInstructions that need a bss entry.
+    System.out.println($noinline$testNeedsBssEntryString());
+    System.out.println($noinline$testNeedsBssEntryInvoke());
+    System.out.println($noinline$testClass());
+
+    // Test that we are able to inline try catches across dex files.
+    System.out.println($noinline$testTryCatch());
   }
 
-  /// CHECK-START: java.lang.String Main.testNeedsEnvironment() inliner (before)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsEnvironment() inliner (before)
   /// CHECK:       InvokeStaticOrDirect method_name:Multi.$inline$NeedsEnvironmentMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsEnvironment() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsEnvironment() inliner (after)
   /// CHECK-NOT:   InvokeStaticOrDirect method_name:Multi.$inline$NeedsEnvironmentMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsEnvironment() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsEnvironment() inliner (after)
   /// CHECK:       StringBuilderAppend
-  public static String testNeedsEnvironment() {
+  private static String $noinline$testNeedsEnvironment() {
     return Multi.$inline$NeedsEnvironmentMultiDex("abc");
   }
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryString() inliner (before)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryString() inliner (before)
   /// CHECK:       InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryStringMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryString() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryString() inliner (after)
   /// CHECK-NOT:   InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryStringMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryString() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryString() inliner (after)
   /// CHECK:       LoadString load_kind:BssEntry
-  public static String testNeedsBssEntryString() {
+  private static String $noinline$testNeedsBssEntryString() {
     return Multi.$inline$NeedsBssEntryStringMultiDex();
   }
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryInvoke() inliner (before)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryInvoke() inliner (before)
   /// CHECK:       InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryInvokeMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryInvoke() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryInvoke() inliner (after)
   /// CHECK-NOT:   InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryInvokeMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryInvoke() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryInvoke() inliner (after)
   /// CHECK:       InvokeStaticOrDirect method_name:Multi.$noinline$InnerInvokeMultiDex method_load_kind:BssEntry
-  public static String testNeedsBssEntryInvoke() {
+  private static String $noinline$testNeedsBssEntryInvoke() {
     return Multi.$inline$NeedsBssEntryInvokeMultiDex();
   }
 
-  /// CHECK-START: java.lang.Class Main.testClass() inliner (before)
+  /// CHECK-START: java.lang.Class Main.$noinline$testClass() inliner (before)
   /// CHECK:       InvokeStaticOrDirect method_name:Multi.NeedsBssEntryClassMultiDex
 
-  /// CHECK-START: java.lang.Class Main.testClass() inliner (after)
+  /// CHECK-START: java.lang.Class Main.$noinline$testClass() inliner (after)
   /// CHECK-NOT:   InvokeStaticOrDirect method_name:Multi.NeedsBssEntryClassMultiDex
 
-  /// CHECK-START: java.lang.Class Main.testClass() inliner (after)
+  /// CHECK-START: java.lang.Class Main.$noinline$testClass() inliner (after)
   /// CHECK:       LoadClass load_kind:BssEntry class_name:Multi$Multi2
-  public static Class<?> testClass() {
+  private static Class<?> $noinline$testClass() {
     return Multi.NeedsBssEntryClassMultiDex();
   }
+
+
+  /// CHECK-START: int Main.$noinline$testTryCatch() inliner (before)
+  /// CHECK-NOT:   TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testTryCatch() inliner (after)
+  /// CHECK:       TryBoundary
+  private static int $noinline$testTryCatch() {
+    return Multi.$inline$TryCatch("123") + Multi.$inline$TryCatch("abc");
+  }
 }
diff --git a/test/2241-checker-inline-try-catch/expected-stderr.txt b/test/2241-checker-inline-try-catch/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/expected-stderr.txt
diff --git a/test/2241-checker-inline-try-catch/expected-stdout.txt b/test/2241-checker-inline-try-catch/expected-stdout.txt
new file mode 100644
index 0000000..6d127d1
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/expected-stdout.txt
@@ -0,0 +1,2 @@
+Finally, a worthy opponent!
+Our battle it will be legendary!
diff --git a/test/2241-checker-inline-try-catch/info.txt b/test/2241-checker-inline-try-catch/info.txt
new file mode 100644
index 0000000..3b86006
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/info.txt
@@ -0,0 +1 @@
+Tests that we inline try catches, as long as we don't inline inside of other try catches.
diff --git a/test/2241-checker-inline-try-catch/src/Main.java b/test/2241-checker-inline-try-catch/src/Main.java
new file mode 100644
index 0000000..a76a99b
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/src/Main.java
@@ -0,0 +1,284 @@
+/*
+ * 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) {
+    $noinline$testSingleTryCatch();
+    $noinline$testSingleTryCatchTwice();
+    $noinline$testSingleTryCatchDifferentInputs();
+    $noinline$testDifferentTryCatches();
+    $noinline$testTryCatchFinally();
+    $noinline$testTryCatchFinallyDifferentInputs();
+    $noinline$testRecursiveTryCatch();
+    $noinline$testDoNotInlineInsideTryOrCatch();
+    $noinline$testBeforeAfterTryCatch();
+    $noinline$testDifferentTypes();
+    $noinline$testRawThrow();
+    $noinline$testRawThrowTwice();
+    $noinline$testThrowCaughtInOuterMethod();
+  }
+
+  public static void $noinline$assertEquals(int expected, int result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  // Basic try catch inline.
+  private static void $noinline$testSingleTryCatch() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+  }
+
+  // Two instances of the same method with a try catch.
+  private static void $noinline$testSingleTryCatchTwice() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+  }
+
+  // Triggering both normal and the exceptional flow.
+  private static void $noinline$testSingleTryCatchDifferentInputs() {
+    $noinline$assertEquals(1, $inline$OOBTryCatch(null));
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+    int[] filled_numbers = {42};
+    $noinline$assertEquals(42, $inline$OOBTryCatch(filled_numbers));
+  }
+
+
+  // Two different try catches, with the same catch's dex_pc.
+  private static void $noinline$testDifferentTryCatches() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+    $noinline$assertEquals(2, $inline$OtherOOBTryCatch(numbers));
+  }
+
+  // Basic try/catch/finally.
+  private static void $noinline$testTryCatchFinally() {
+    int[] numbers = {};
+    $noinline$assertEquals(3, $inline$OOBTryCatchFinally(numbers));
+  }
+
+  // Triggering both normal and the exceptional flow.
+  private static void $noinline$testTryCatchFinallyDifferentInputs() {
+    $noinline$assertEquals(3, $inline$OOBTryCatchFinally(null));
+    int[] numbers = {};
+    $noinline$assertEquals(3, $inline$OOBTryCatchFinally(numbers));
+    int[] filled_numbers = {42};
+    $noinline$assertEquals(42, $inline$OOBTryCatchFinally(filled_numbers));
+  }
+
+  // Test that we can inline even when the try catch is several levels deep.
+  private static void $noinline$testRecursiveTryCatch() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatchLevel4(numbers));
+  }
+
+  // Tests that we don't inline inside outer tries or catches.
+  /// CHECK-START: void Main.$noinline$testDoNotInlineInsideTryOrCatch() inliner (before)
+  /// CHECK:       InvokeStaticOrDirect method_name:Main.DoNotInlineOOBTryCatch
+  /// CHECK:       InvokeStaticOrDirect method_name:Main.DoNotInlineOOBTryCatch
+
+  /// CHECK-START: void Main.$noinline$testDoNotInlineInsideTryOrCatch() inliner (after)
+  /// CHECK:       InvokeStaticOrDirect method_name:Main.DoNotInlineOOBTryCatch
+  /// CHECK:       InvokeStaticOrDirect method_name:Main.DoNotInlineOOBTryCatch
+  private static void $noinline$testDoNotInlineInsideTryOrCatch() {
+    int val = 0;
+    try {
+      int[] numbers = {};
+      val = DoNotInlineOOBTryCatch(numbers);
+    } catch (Exception ex) {
+      unreachable();
+      // This is unreachable but we will still compile it so it works for checker purposes
+      int[] numbers = {};
+      DoNotInlineOOBTryCatch(numbers);
+    }
+    $noinline$assertEquals(1, val);
+  }
+
+  // Tests that outer tries or catches don't affect as long as we are not inlining the inner
+  // try/catch inside of them.
+  private static void $noinline$testBeforeAfterTryCatch() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+
+    // Unrelated try catch does not block inlining outside of it. We fill it in to make sure it is
+    // still there by the time the inliner runs.
+    int val = 0;
+    try {
+      int[] other_array = {};
+      val = other_array[0];
+    } catch (Exception ex) {
+      $noinline$assertEquals(0, val);
+      val = 1;
+    }
+    $noinline$assertEquals(1, val);
+
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+  }
+
+  // Tests different try catch types in the same outer method.
+  private static void $noinline$testDifferentTypes() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+    $noinline$assertEquals(2, $inline$OtherOOBTryCatch(numbers));
+    $noinline$assertEquals(123, $inline$ParseIntTryCatch("123"));
+    $noinline$assertEquals(-1, $inline$ParseIntTryCatch("abc"));
+  }
+
+  // Tests a raw throw (rather than an instruction that happens to throw).
+  private static void $noinline$testRawThrow() {
+    $noinline$assertEquals(1, $inline$rawThrowCaught());
+  }
+
+  // Tests a raw throw twice.
+  private static void $noinline$testRawThrowTwice() {
+    $noinline$assertEquals(1, $inline$rawThrowCaught());
+    $noinline$assertEquals(1, $inline$rawThrowCaught());
+  }
+
+  // Tests that the outer method can successfully catch the throw in the inner method.
+  private static void $noinline$testThrowCaughtInOuterMethod() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$testThrowCaughtInOuterMethod_simpleTryCatch(numbers));
+    $noinline$assertEquals(1, $inline$testThrowCaughtInOuterMethod_simpleTryCatch_inliningInner(numbers));
+    $noinline$assertEquals(1, $inline$testThrowCaughtInOuterMethod_withFinally(numbers));
+  }
+
+  // Building blocks for the test functions.
+  private static int $inline$OOBTryCatch(int[] array) {
+    try {
+      return array[0];
+    } catch (Exception e) {
+      return 1;
+    }
+  }
+
+  private static int $inline$OtherOOBTryCatch(int[] array) {
+    try {
+      return array[0];
+    } catch (Exception e) {
+      return 2;
+    }
+  }
+
+  private static int $inline$OOBTryCatchFinally(int[] array) {
+    int val = 0;
+    try {
+      val = 1;
+      return array[0];
+    } catch (Exception e) {
+      val = 2;
+    } finally {
+      val = 3;
+    }
+    return val;
+  }
+
+  // If we make the depthness a parameter, we wouldn't be able to mark as $inline$ and we would
+  // need extra CHECKer statements.
+  private static int $inline$OOBTryCatchLevel4(int[] array) {
+    return $inline$OOBTryCatchLevel3(array);
+  }
+
+  private static int $inline$OOBTryCatchLevel3(int[] array) {
+    return $inline$OOBTryCatchLevel2(array);
+  }
+
+  private static int $inline$OOBTryCatchLevel2(int[] array) {
+    return $inline$OOBTryCatchLevel1(array);
+  }
+
+  private static int $inline$OOBTryCatchLevel1(int[] array) {
+    return $inline$OOBTryCatch(array);
+  }
+
+  private static int DoNotInlineOOBTryCatch(int[] array) {
+    try {
+      return array[0];
+    } catch (Exception e) {
+      return 1;
+    }
+  }
+
+  private static void unreachable() {
+    throw new Error("Unreachable");
+  }
+
+  private static int $inline$ParseIntTryCatch(String str) {
+    try {
+      return Integer.parseInt(str);
+    } catch (NumberFormatException ex) {
+      return -1;
+    }
+  }
+
+  private static int $inline$rawThrowCaught() {
+    try {
+      throw new Error();
+    } catch (Error e) {
+      return 1;
+    }
+  }
+
+  private static int $inline$testThrowCaughtInOuterMethod_simpleTryCatch(int[] array) {
+    int val = 0;
+    try {
+      $noinline$throwingMethod(array);
+    } catch (Exception ex) {
+      val = 1;
+    }
+    return val;
+  }
+
+  private static int $noinline$throwingMethod(int[] array) {
+    return array[0];
+  }
+
+  private static int $inline$testThrowCaughtInOuterMethod_simpleTryCatch_inliningInner(int[] array) {
+    int val = 0;
+    try {
+      $inline$throwingMethod(array);
+    } catch (Exception ex) {
+      val = 1;
+    }
+    return val;
+  }
+
+  private static int $inline$throwingMethod(int[] array) {
+    return array[0];
+  }
+
+  private static int $inline$testThrowCaughtInOuterMethod_withFinally(int[] array) {
+    int val = 0;
+    try {
+      $noinline$throwingMethodWithFinally(array);
+    } catch (Exception ex) {
+      System.out.println("Our battle it will be legendary!");
+      val = 1;
+    }
+    return val;
+  }
+
+  private static int $noinline$throwingMethodWithFinally(int[] array) {
+    try {
+      return array[0];
+    } finally {
+      System.out.println("Finally, a worthy opponent!");
+    }
+  }
+}
diff --git a/test/542-inline-trycatch/src/Main.java b/test/542-inline-trycatch/src/Main.java
index 5a6e06f..5e33ed6 100644
--- a/test/542-inline-trycatch/src/Main.java
+++ b/test/542-inline-trycatch/src/Main.java
@@ -33,17 +33,6 @@
     return is_hex ? Integer.parseInt(str, 16) : Integer.parseInt(str);
   }
 
-  // We expect methods with try/catch to not be inlined. Inlined try/catch
-  // blocks are not supported at the moment.
-
-  private static int $noinline$TryCatch(String str) {
-    try {
-      return Integer.parseInt(str);
-    } catch (NumberFormatException ex) {
-      return -1;
-    }
-  }
-
   public static void testSingleBlockFromTry() {
     int val = 0;
 
@@ -117,49 +106,11 @@
     assertEquals(32, val);
   }
 
-  public static void testTryCatchFromTry() {
-    int val = 0;
-
-    try {
-      val = $noinline$TryCatch("42");
-    } catch (NumberFormatException ex) {
-      unreachable();
-    }
-    assertEquals(42, val);
-
-    try {
-      val = $noinline$TryCatch("xyz");
-    } catch (NumberFormatException ex) {
-      unreachable();
-    }
-    assertEquals(-1, val);
-  }
-
-  public static void testTryCatchFromCatch() {
-    int val = 0;
-
-    try {
-      throwException();
-    } catch (Exception ex) {
-      val = $noinline$TryCatch("42");
-    }
-    assertEquals(42, val);
-
-    try {
-      throwException();
-    } catch (Exception ex) {
-      val = $noinline$TryCatch("xyz");
-    }
-    assertEquals(-1, val);
-  }
-
   public static void main(String[] args) {
     testSingleBlockFromTry();
     testSingleBlockFromCatch();
     testMultipleBlocksFromTry();
     testMultipleBlocksFromCatch();
-    testTryCatchFromTry();
-    testTryCatchFromCatch();
   }
 
   private static void assertEquals(int expected, int actual) {