Runtime implementation of try catch inlining

The main differences in the runtime are:
1) We now use a list of dex_pcs to find the correct catch handler
   instead of a single dex pc
2) We now need to restore vregs of the whole frame, which may be
   an inline frame.

Bug: 227283224
Test: art/test/testrunner/testrunner.py --host --64 --optimizing -b
Change-Id: I95d2f32088e1d420c83962a1693be18f3b63f8b4
diff --git a/compiler/exception_test.cc b/compiler/exception_test.cc
index f86e0b9..f55ba8e 100644
--- a/compiler/exception_test.cc
+++ b/compiler/exception_test.cc
@@ -194,15 +194,13 @@
 
   OatQuickMethodHeader* header = OatQuickMethodHeader::FromEntryPoint(
       method_g_->GetEntryPointFromQuickCompiledCode());
-  fake_stack.push_back(header->ToNativeQuickPc(
-      method_g_, kDexPc, /* is_for_catch_handler= */ false));  // return pc
+  fake_stack.push_back(header->ToNativeQuickPc(method_g_, kDexPc));  // return pc
 
   // Create/push fake 16byte stack frame for method g
   fake_stack.push_back(reinterpret_cast<uintptr_t>(method_g_));
   fake_stack.push_back(0);
   fake_stack.push_back(0);
-  fake_stack.push_back(header->ToNativeQuickPc(
-      method_g_, kDexPc, /* is_for_catch_handler= */ false));  // return pc
+  fake_stack.push_back(header->ToNativeQuickPc(method_g_, kDexPc));  // return pc
 
   // Create/push fake 16byte stack frame for method f
   fake_stack.push_back(reinterpret_cast<uintptr_t>(method_f_));
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc
index bd8db30..1c5912d 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -15,6 +15,7 @@
  */
 
 #include "code_generator.h"
+#include "base/globals.h"
 
 #ifdef ART_ENABLE_CODEGEN_arm
 #include "code_generator_arm_vixl.h"
@@ -1339,8 +1340,11 @@
       continue;
     }
 
-    // Get the outer dex_pc
-    uint32_t outer_dex_pc = block->GetDexPc();
+    // Get the outer dex_pc. We save the full environment list for DCHECK purposes in kIsDebugBuild.
+    std::vector<uint32_t> dex_pc_list_for_verification;
+    if (kIsDebugBuild) {
+      dex_pc_list_for_verification.push_back(block->GetDexPc());
+    }
     DCHECK(block->GetFirstInstruction()->IsNop());
     DCHECK(block->GetFirstInstruction()->AsNop()->NeedsEnvironment());
     HEnvironment* const environment = block->GetFirstInstruction()->GetEnvironment();
@@ -1348,15 +1352,26 @@
     HEnvironment* outer_environment = environment;
     while (outer_environment->GetParent() != nullptr) {
       outer_environment = outer_environment->GetParent();
+      if (kIsDebugBuild) {
+        dex_pc_list_for_verification.push_back(outer_environment->GetDexPc());
+      }
     }
-    outer_dex_pc = outer_environment->GetDexPc();
+
+    if (kIsDebugBuild) {
+      // dex_pc_list_for_verification is set from innnermost to outermost. Let's reverse it
+      // since we are expected to pass from outermost to innermost.
+      std::reverse(dex_pc_list_for_verification.begin(), dex_pc_list_for_verification.end());
+      DCHECK_EQ(dex_pc_list_for_verification.front(), outer_environment->GetDexPc());
+    }
 
     uint32_t native_pc = GetAddressOf(block);
-    stack_map_stream->BeginStackMapEntry(outer_dex_pc,
+    stack_map_stream->BeginStackMapEntry(outer_environment->GetDexPc(),
                                          native_pc,
                                          /* register_mask= */ 0,
                                          /* sp_mask= */ nullptr,
-                                         StackMap::Kind::Catch);
+                                         StackMap::Kind::Catch,
+                                         /* needs_vreg_info= */ true,
+                                         dex_pc_list_for_verification);
 
     EmitEnvironment(environment,
                     /* slow_path= */ nullptr,
diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc
index c13a355..31428d5 100644
--- a/compiler/optimizing/stack_map_stream.cc
+++ b/compiler/optimizing/stack_map_stream.cc
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "art_method-inl.h"
+#include "base/globals.h"
 #include "base/stl_util.h"
 #include "class_linker.h"
 #include "dex/dex_file.h"
@@ -101,16 +102,21 @@
   }
 }
 
-void StackMapStream::BeginStackMapEntry(uint32_t dex_pc,
-                                        uint32_t native_pc_offset,
-                                        uint32_t register_mask,
-                                        BitVector* stack_mask,
-                                        StackMap::Kind kind,
-                                        bool needs_vreg_info) {
+void StackMapStream::BeginStackMapEntry(
+    uint32_t dex_pc,
+    uint32_t native_pc_offset,
+    uint32_t register_mask,
+    BitVector* stack_mask,
+    StackMap::Kind kind,
+    bool needs_vreg_info,
+    const std::vector<uint32_t>& dex_pc_list_for_catch_verification) {
   DCHECK(in_method_) << "Call BeginMethod first";
   DCHECK(!in_stack_map_) << "Mismatched Begin/End calls";
   in_stack_map_ = true;
 
+  DCHECK_IMPLIES(!dex_pc_list_for_catch_verification.empty(), kind == StackMap::Kind::Catch);
+  DCHECK_IMPLIES(!dex_pc_list_for_catch_verification.empty(), kIsDebugBuild);
+
   current_stack_map_ = BitTableBuilder<StackMap>::Entry();
   current_stack_map_[StackMap::kKind] = static_cast<uint32_t>(kind);
   current_stack_map_[StackMap::kPackedNativePc] =
@@ -151,7 +157,8 @@
                                                                     instruction_set_);
         CHECK_EQ(stack_map.Row(), stack_map_index);
       } else if (kind == StackMap::Kind::Catch) {
-        StackMap stack_map = code_info.GetCatchStackMapForDexPc(dex_pc);
+        StackMap stack_map = code_info.GetCatchStackMapForDexPc(
+            ArrayRef<const uint32_t>(dex_pc_list_for_catch_verification));
         CHECK_EQ(stack_map.Row(), stack_map_index);
       }
       StackMap stack_map = code_info.GetStackMapAt(stack_map_index);
diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h
index 1aaa6ae..2ba6027 100644
--- a/compiler/optimizing/stack_map_stream.h
+++ b/compiler/optimizing/stack_map_stream.h
@@ -68,12 +68,15 @@
                    bool debuggable);
   void EndMethod(size_t code_size);
 
-  void BeginStackMapEntry(uint32_t dex_pc,
-                          uint32_t native_pc_offset,
-                          uint32_t register_mask = 0,
-                          BitVector* sp_mask = nullptr,
-                          StackMap::Kind kind = StackMap::Kind::Default,
-                          bool needs_vreg_info = true);
+  void BeginStackMapEntry(
+      uint32_t dex_pc,
+      uint32_t native_pc_offset,
+      uint32_t register_mask = 0,
+      BitVector* sp_mask = nullptr,
+      StackMap::Kind kind = StackMap::Kind::Default,
+      bool needs_vreg_info = true,
+      const std::vector<uint32_t>& dex_pc_list_for_catch_verification = std::vector<uint32_t>());
+
   void EndStackMapEntry();
 
   void AddDexRegisterEntry(DexRegisterLocation::Kind kind, int32_t value) {
diff --git a/runtime/oat_quick_method_header.cc b/runtime/oat_quick_method_header.cc
index 8fbf02a..f0764da 100644
--- a/runtime/oat_quick_method_header.cc
+++ b/runtime/oat_quick_method_header.cc
@@ -57,33 +57,61 @@
 
 uintptr_t OatQuickMethodHeader::ToNativeQuickPc(ArtMethod* method,
                                                 const uint32_t dex_pc,
-                                                bool is_for_catch_handler,
                                                 bool abort_on_failure) const {
   const void* entry_point = GetEntryPoint();
   DCHECK(!method->IsNative());
+  // For catch handlers use the ArrayRef<const uint32_t> version of ToNativeQuickPc.
+  DCHECK(!IsNterpMethodHeader());
+  DCHECK(IsOptimized());
+  // Search for the dex-to-pc mapping in stack maps.
+  CodeInfo code_info = CodeInfo::DecodeInlineInfoOnly(this);
+
+  StackMap stack_map = code_info.GetStackMapForDexPc(dex_pc);
+  if (stack_map.IsValid()) {
+    return reinterpret_cast<uintptr_t>(entry_point) + stack_map.GetNativePcOffset(kRuntimeISA);
+  }
+  if (abort_on_failure) {
+    ScopedObjectAccess soa(Thread::Current());
+    LOG(FATAL) << "Failed to find native offset for dex pc 0x" << std::hex << dex_pc << " in "
+               << method->PrettyMethod();
+  }
+  return UINTPTR_MAX;
+}
+
+uintptr_t OatQuickMethodHeader::ToNativeQuickPcForCatchHandlers(
+    ArtMethod* method,
+    ArrayRef<const uint32_t> dex_pc_list,
+    /* out */ uint32_t* stack_map_row,
+    bool abort_on_failure) const {
+  const void* entry_point = GetEntryPoint();
+  DCHECK(!method->IsNative());
   if (IsNterpMethodHeader()) {
-    // This should only be called on an nterp frame for getting a catch handler.
-    CHECK(is_for_catch_handler);
     return NterpGetCatchHandler();
   }
   DCHECK(IsOptimized());
   // Search for the dex-to-pc mapping in stack maps.
   CodeInfo code_info = CodeInfo::DecodeInlineInfoOnly(this);
 
-  // All stack maps are stored in the same CodeItem section, safepoint stack
-  // maps first, then catch stack maps. We use `is_for_catch_handler` to select
-  // the order of iteration.
-  StackMap stack_map =
-      LIKELY(is_for_catch_handler) ? code_info.GetCatchStackMapForDexPc(dex_pc)
-                                   : code_info.GetStackMapForDexPc(dex_pc);
+  StackMap stack_map = code_info.GetCatchStackMapForDexPc(dex_pc_list);
+  *stack_map_row = stack_map.Row();
   if (stack_map.IsValid()) {
     return reinterpret_cast<uintptr_t>(entry_point) +
            stack_map.GetNativePcOffset(kRuntimeISA);
   }
   if (abort_on_failure) {
+    std::stringstream ss;
+    bool first = true;
+    ss << "Failed to find native offset for dex pcs (from outermost to innermost) " << std::hex;
+    for (auto dex_pc : dex_pc_list) {
+      if (!first) {
+        ss << ", ";
+      }
+      first = false;
+      ss << "0x" << dex_pc;
+    }
     ScopedObjectAccess soa(Thread::Current());
-    LOG(FATAL) << "Failed to find native offset for dex pc 0x" << std::hex << dex_pc
-               << " in " << method->PrettyMethod();
+    ss << " in " << method->PrettyMethod();
+    LOG(FATAL) << ss.str();
   }
   return UINTPTR_MAX;
 }
diff --git a/runtime/oat_quick_method_header.h b/runtime/oat_quick_method_header.h
index 8e6d08e..769d67f 100644
--- a/runtime/oat_quick_method_header.h
+++ b/runtime/oat_quick_method_header.h
@@ -161,11 +161,17 @@
     return frame_size - core_spill_size - fpu_spill_size - kShouldDeoptimizeFlagSize;
   }
 
+  // For non-catch handlers. Only used in test code.
   uintptr_t ToNativeQuickPc(ArtMethod* method,
                             const uint32_t dex_pc,
-                            bool is_for_catch_handler,
                             bool abort_on_failure = true) const;
 
+  // For catch handlers.
+  uintptr_t ToNativeQuickPcForCatchHandlers(ArtMethod* method,
+                                            ArrayRef<const uint32_t> dex_pc_list,
+                                            /* out */ uint32_t* stack_map_row,
+                                            bool abort_on_failure = true) const;
+
   uint32_t ToDexPc(ArtMethod** frame,
                    const uintptr_t pc,
                    bool abort_on_failure = true) const
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index 82f5034..b9dd3e1 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -15,11 +15,14 @@
  */
 
 #include "quick_exception_handler.h"
+
 #include <ios>
 #include <queue>
+#include <sstream>
 
 #include "arch/context.h"
 #include "art_method-inl.h"
+#include "base/array_ref.h"
 #include "base/enums.h"
 #include "base/globals.h"
 #include "base/logging.h"  // For VLOG_IS_ON.
@@ -56,7 +59,6 @@
       handler_quick_frame_pc_(0),
       handler_method_header_(nullptr),
       handler_quick_arg0_(0),
-      handler_dex_pc_(0),
       clear_exception_(false),
       handler_frame_depth_(kInvalidFrameDepth),
       full_fragment_done_(false) {}
@@ -133,10 +135,12 @@
       uint32_t found_dex_pc = method->FindCatchBlock(to_find, dex_pc, &clear_exception);
       exception_handler_->SetClearException(clear_exception);
       if (found_dex_pc != dex::kDexNoIndex) {
-        exception_handler_->SetHandlerDexPc(found_dex_pc);
+        exception_handler_->SetHandlerDexPcList(ComputeDexPcList(found_dex_pc));
+        uint32_t stack_map_row = -1;
         exception_handler_->SetHandlerQuickFramePc(
-            GetCurrentOatQuickMethodHeader()->ToNativeQuickPc(
-                method, found_dex_pc, /* is_for_catch_handler= */ true));
+            GetCurrentOatQuickMethodHeader()->ToNativeQuickPcForCatchHandlers(
+                method, exception_handler_->GetHandlerDexPcList(), &stack_map_row));
+        exception_handler_->SetCatchStackMapRow(stack_map_row);
         exception_handler_->SetHandlerQuickFrame(GetCurrentQuickFrame());
         exception_handler_->SetHandlerMethodHeader(GetCurrentOatQuickMethodHeader());
         return false;  // End stack walk.
@@ -215,10 +219,24 @@
       }
       if (GetHandlerMethod() != nullptr) {
         const DexFile* dex_file = GetHandlerMethod()->GetDexFile();
-        int line_number =
-            annotations::GetLineNumFromPC(dex_file, GetHandlerMethod(), handler_dex_pc_);
+        DCHECK_GE(handler_dex_pc_list_.size(), 1u);
+        int line_number = annotations::GetLineNumFromPC(
+            dex_file, GetHandlerMethod(), handler_dex_pc_list_.front());
+
+        // We may have an inlined method. If so, we can add some extra logging.
+        std::stringstream ss;
+        ArtMethod* maybe_inlined_method = visitor.GetMethod();
+        if (maybe_inlined_method != GetHandlerMethod()) {
+          const DexFile* inlined_dex_file = maybe_inlined_method->GetDexFile();
+          DCHECK_GE(handler_dex_pc_list_.size(), 2u);
+          int inlined_line_number = annotations::GetLineNumFromPC(
+              inlined_dex_file, maybe_inlined_method, handler_dex_pc_list_.back());
+          ss << " which ends up calling inlined method " << maybe_inlined_method->PrettyMethod()
+             << " (line: " << inlined_line_number << ")";
+        }
+
         LOG(INFO) << "Handler: " << GetHandlerMethod()->PrettyMethod() << " (line: "
-                  << line_number << ")";
+                  << line_number << ")" << ss.str();
       }
     }
     // Exception was cleared as part of delivery.
@@ -283,15 +301,18 @@
     self_->DumpStack(LOG_STREAM(INFO) << "Setting catch phis: ");
   }
 
-  CodeItemDataAccessor accessor(GetHandlerMethod()->DexInstructionData());
-  const size_t number_of_vregs = accessor.RegistersSize();
   CodeInfo code_info(handler_method_header_);
 
   // Find stack map of the catch block.
-  StackMap catch_stack_map = code_info.GetCatchStackMapForDexPc(GetHandlerDexPc());
+  ArrayRef<const uint32_t> dex_pc_list = GetHandlerDexPcList();
+  DCHECK_GE(dex_pc_list.size(), 1u);
+  StackMap catch_stack_map = code_info.GetStackMapAt(GetCatchStackMapRow());
   DCHECK(catch_stack_map.IsValid());
-  DexRegisterMap catch_vreg_map = code_info.GetDexRegisterMapOf(catch_stack_map);
-  DCHECK_EQ(catch_vreg_map.size(), number_of_vregs);
+  DCHECK_EQ(catch_stack_map.Row(), code_info.GetCatchStackMapForDexPc(dex_pc_list).Row());
+  const uint32_t catch_depth = dex_pc_list.size() - 1;
+  const size_t number_of_registers = stack_visitor->GetNumberOfRegisters(&code_info, catch_depth);
+  DexRegisterMap catch_vreg_map =
+      code_info.GetDexRegisterMapOf(catch_stack_map, /* first= */ 0, number_of_registers);
 
   if (!catch_vreg_map.HasAnyLiveDexRegisters()) {
     return;
@@ -301,26 +322,47 @@
   StackMap throw_stack_map =
       code_info.GetStackMapForNativePcOffset(stack_visitor->GetNativePcOffset());
   DCHECK(throw_stack_map.IsValid());
-  DexRegisterMap throw_vreg_map = code_info.GetDexRegisterMapOf(throw_stack_map);
-  DCHECK_EQ(throw_vreg_map.size(), number_of_vregs);
+  const uint32_t throw_depth = stack_visitor->InlineDepth();
+  DCHECK_EQ(throw_depth, catch_depth);
+  DexRegisterMap throw_vreg_map =
+      code_info.GetDexRegisterMapOf(throw_stack_map, /* first= */ 0, number_of_registers);
+  DCHECK_EQ(throw_vreg_map.size(), catch_vreg_map.size());
 
-  // Copy values between them.
-  for (uint16_t vreg = 0; vreg < number_of_vregs; ++vreg) {
-    DexRegisterLocation::Kind catch_location = catch_vreg_map[vreg].GetKind();
-    if (catch_location == DexRegisterLocation::Kind::kNone) {
+  // First vreg that it is part of the catch's environment.
+  const size_t catch_vreg_start = catch_depth == 0
+    ? 0
+    : stack_visitor->GetNumberOfRegisters(&code_info, catch_depth - 1);
+
+  // We don't need to copy anything in the parent's environment.
+  for (size_t vreg = 0; vreg < catch_vreg_start; ++vreg) {
+    DexRegisterLocation::Kind catch_location_kind = catch_vreg_map[vreg].GetKind();
+    DCHECK(catch_location_kind == DexRegisterLocation::Kind::kNone ||
+           catch_location_kind == DexRegisterLocation::Kind::kConstant ||
+           catch_location_kind == DexRegisterLocation::Kind::kInStack)
+        << "Unexpected catch_location_kind: " << catch_location_kind;
+  }
+
+  // Copy values between the throw and the catch.
+  for (size_t vreg = catch_vreg_start; vreg < catch_vreg_map.size(); ++vreg) {
+    DexRegisterLocation::Kind catch_location_kind = catch_vreg_map[vreg].GetKind();
+    if (catch_location_kind == DexRegisterLocation::Kind::kNone) {
       continue;
     }
-    DCHECK(catch_location == DexRegisterLocation::Kind::kInStack);
 
-    // Get vreg value from its current location.
+    // Consistency checks.
+    DCHECK_EQ(catch_location_kind, DexRegisterLocation::Kind::kInStack);
     uint32_t vreg_value;
     VRegKind vreg_kind = ToVRegKind(throw_vreg_map[vreg].GetKind());
-    bool get_vreg_success =
-        stack_visitor->GetVReg(stack_visitor->GetMethod(),
-                               vreg,
-                               vreg_kind,
-                               &vreg_value,
-                               throw_vreg_map[vreg]);
+    DCHECK_NE(vreg_kind, kReferenceVReg)
+        << "The fast path in GetVReg doesn't expect a kReferenceVReg.";
+
+    // Get vreg value from its current location.
+    bool get_vreg_success = stack_visitor->GetVReg(stack_visitor->GetMethod(),
+                                                   vreg,
+                                                   vreg_kind,
+                                                   &vreg_value,
+                                                   throw_vreg_map[vreg],
+                                                   /* need_full_register_list= */ true);
     CHECK(get_vreg_success) << "VReg " << vreg << " was optimized out ("
                             << "method=" << ArtMethod::PrettyMethod(stack_visitor->GetMethod())
                             << ", dex_pc=" << stack_visitor->GetDexPc() << ", "
@@ -701,8 +743,10 @@
   if (!is_deoptimization_ &&
       handler_method_header_ != nullptr &&
       handler_method_header_->IsNterpMethodHeader()) {
+    // Interpreter procceses one method at a time i.e. not inlining
+    DCHECK_EQ(handler_dex_pc_list_.size(), 1u) << "We shouldn't have any inlined frames.";
     context_->SetNterpDexPC(reinterpret_cast<uintptr_t>(
-        GetHandlerMethod()->DexInstructions().Insns() + handler_dex_pc_));
+        GetHandlerMethod()->DexInstructions().Insns() + handler_dex_pc_list_.front()));
   }
   context_->DoLongJump();
   UNREACHABLE();
diff --git a/runtime/quick_exception_handler.h b/runtime/quick_exception_handler.h
index 9554f1d..c87e38d 100644
--- a/runtime/quick_exception_handler.h
+++ b/runtime/quick_exception_handler.h
@@ -18,10 +18,13 @@
 #define ART_RUNTIME_QUICK_EXCEPTION_HANDLER_H_
 
 #include <android-base/logging.h>
+#include <cstdint>
 
+#include "base/array_ref.h"
 #include "base/macros.h"
 #include "base/mutex.h"
 #include "deoptimization_kind.h"
+#include "stack_map.h"
 #include "stack_reference.h"
 
 namespace art {
@@ -103,12 +106,20 @@
     return *handler_quick_frame_;
   }
 
-  uint32_t GetHandlerDexPc() const {
-    return handler_dex_pc_;
+  ArrayRef<const uint32_t> GetHandlerDexPcList() const {
+    return ArrayRef<const uint32_t>(handler_dex_pc_list_);
   }
 
-  void SetHandlerDexPc(uint32_t dex_pc) {
-    handler_dex_pc_ = dex_pc;
+  void SetHandlerDexPcList(std::vector<uint32_t>&& handler_dex_pc_list) {
+    handler_dex_pc_list_ = std::move(handler_dex_pc_list);
+  }
+
+  uint32_t GetCatchStackMapRow() const {
+    return catch_stack_map_row_;
+  }
+
+  void SetCatchStackMapRow(uint32_t stack_map_row) {
+    catch_stack_map_row_ = stack_map_row;
   }
 
   bool GetClearException() const {
@@ -151,8 +162,11 @@
   const OatQuickMethodHeader* handler_method_header_;
   // The value for argument 0.
   uintptr_t handler_quick_arg0_;
-  // The handler's dex PC, zero implies an uncaught exception.
-  uint32_t handler_dex_pc_;
+  // The handler's dex PC list including the inline dex_pcs. The dex_pcs are ordered from outermost
+  // to innermost. An empty list implies an uncaught exception.
+  std::vector<uint32_t> handler_dex_pc_list_;
+  // StackMap row corresponding to the found catch.
+  uint32_t catch_stack_map_row_;
   // Should the exception be cleared as the catch block has no move-exception?
   bool clear_exception_;
   // Frame depth of the catch handler or the upcall.
diff --git a/runtime/stack.cc b/runtime/stack.cc
index 5ee20e5..c83814a 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -140,6 +140,29 @@
   }
 }
 
+std::vector<uint32_t> StackVisitor::ComputeDexPcList(uint32_t handler_dex_pc) const {
+  std::vector<uint32_t> result;
+  if (cur_shadow_frame_ == nullptr && cur_quick_frame_ != nullptr && IsInInlinedFrame()) {
+    const BitTableRange<InlineInfo>& infos = current_inline_frames_;
+    DCHECK_NE(infos.size(), 0u);
+
+    // Outermost dex_pc.
+    result.push_back(GetCurrentStackMap()->GetDexPc());
+
+    // The mid dex_pcs. Note that we skip the last one since we want to change that for
+    // `handler_dex_pc`.
+    for (size_t index = 0; index < infos.size() - 1; ++index) {
+      result.push_back(infos[index].GetDexPc());
+    }
+  }
+
+  // The innermost dex_pc has to be the handler dex_pc. In the case of no inline frames, it will be
+  // just the one dex_pc. In the case of inlining we will be replacing the innermost InlineInfo's
+  // dex_pc with this one.
+  result.push_back(handler_dex_pc);
+  return result;
+}
+
 extern "C" mirror::Object* artQuickGetProxyThisObject(ArtMethod** sp)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -213,7 +236,8 @@
                            uint16_t vreg,
                            VRegKind kind,
                            uint32_t* val,
-                           std::optional<DexRegisterLocation> location) const {
+                           std::optional<DexRegisterLocation> location,
+                           bool need_full_register_list) const {
   if (cur_quick_frame_ != nullptr) {
     DCHECK(context_ != nullptr);  // You can't reliably read registers without a context.
     DCHECK(m == GetMethod());
@@ -235,10 +259,10 @@
         // which does not decode the stack maps.
         result = GetVRegFromOptimizedCode(location.value(), val);
         // Compare to the slower overload.
-        DCHECK_EQ(result, GetVRegFromOptimizedCode(m, vreg, kind, &val2));
+        DCHECK_EQ(result, GetVRegFromOptimizedCode(m, vreg, kind, &val2, need_full_register_list));
         DCHECK_EQ(*val, val2);
       } else {
-        result = GetVRegFromOptimizedCode(m, vreg, kind, val);
+        result = GetVRegFromOptimizedCode(m, vreg, kind, val, need_full_register_list);
       }
     }
     if (kind == kReferenceVReg) {
@@ -261,16 +285,20 @@
   }
 }
 
+size_t StackVisitor::GetNumberOfRegisters(CodeInfo* code_info, int depth) const {
+  return depth == 0
+    ? code_info->GetNumberOfDexRegisters()
+    : current_inline_frames_[depth - 1].GetNumberOfDexRegisters();
+}
+
 bool StackVisitor::GetVRegFromOptimizedCode(ArtMethod* m,
                                             uint16_t vreg,
                                             VRegKind kind,
-                                            uint32_t* val) const {
+                                            uint32_t* val,
+                                            bool need_full_register_list) const {
   DCHECK_EQ(m, GetMethod());
   // Can't be null or how would we compile its instructions?
   DCHECK(m->GetCodeItem() != nullptr) << m->PrettyMethod();
-  CodeItemDataAccessor accessor(m->DexInstructionData());
-  uint16_t number_of_dex_registers = accessor.RegistersSize();
-  DCHECK_LT(vreg, number_of_dex_registers);
   const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();
   CodeInfo code_info(method_header);
 
@@ -278,13 +306,18 @@
   StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset);
   DCHECK(stack_map.IsValid());
 
-  DexRegisterMap dex_register_map = IsInInlinedFrame()
-      ? code_info.GetInlineDexRegisterMapOf(stack_map, current_inline_frames_.back())
-      : code_info.GetDexRegisterMapOf(stack_map);
+  DexRegisterMap dex_register_map = (IsInInlinedFrame() && !need_full_register_list)
+    ? code_info.GetInlineDexRegisterMapOf(stack_map, current_inline_frames_.back())
+    : code_info.GetDexRegisterMapOf(stack_map,
+                                    /* first= */ 0,
+                                    GetNumberOfRegisters(&code_info, InlineDepth()));
+
   if (dex_register_map.empty()) {
     return false;
   }
-  DCHECK_EQ(dex_register_map.size(), number_of_dex_registers);
+
+  const size_t number_of_dex_registers = dex_register_map.size();
+  DCHECK_LT(vreg, number_of_dex_registers);
   DexRegisterLocation::Kind location_kind = dex_register_map[vreg].GetKind();
   switch (location_kind) {
     case DexRegisterLocation::Kind::kInStack: {
diff --git a/runtime/stack.h b/runtime/stack.h
index bfda57b..56f38ae 100644
--- a/runtime/stack.h
+++ b/runtime/stack.h
@@ -192,6 +192,12 @@
 
   uint32_t GetDexPc(bool abort_on_failure = true) const REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Returns a vector of the inlined dex pcs, in order from outermost to innermost but it replaces
+  // the innermost one with `handler_dex_pc`. In essence, (outermost dex pc, mid dex pc #1, ..., mid
+  // dex pc #n-1, `handler_dex_pc`).
+  std::vector<uint32_t> ComputeDexPcList(uint32_t handler_dex_pc) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   ObjPtr<mirror::Object> GetThisObject() const REQUIRES_SHARED(Locks::mutator_lock_);
 
   size_t GetNativePcOffset() const REQUIRES_SHARED(Locks::mutator_lock_);
@@ -225,9 +231,8 @@
                uint16_t vreg,
                VRegKind kind,
                uint32_t* val,
-               std::optional<DexRegisterLocation> location =
-                   std::optional<DexRegisterLocation>()) const
-      REQUIRES_SHARED(Locks::mutator_lock_);
+               std::optional<DexRegisterLocation> location = std::optional<DexRegisterLocation>(),
+               bool need_full_register_list = false) const REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool GetVRegPair(ArtMethod* m, uint16_t vreg, VRegKind kind_lo, VRegKind kind_hi,
                    uint64_t* val) const
@@ -263,10 +268,16 @@
     return !current_inline_frames_.empty();
   }
 
+  size_t InlineDepth() const { return current_inline_frames_.size(); }
+
   InlineInfo GetCurrentInlinedFrame() const {
     return current_inline_frames_.back();
   }
 
+  const BitTableRange<InlineInfo>& GetCurrentInlinedFrames() const {
+    return current_inline_frames_;
+  }
+
   uintptr_t GetCurrentQuickFramePc() const {
     return cur_quick_frame_pc_;
   }
@@ -306,6 +317,11 @@
     return (should_deopt_flag & static_cast<uint8_t>(DeoptimizeFlagValue::kDebug)) != 0;
   }
 
+  // Return the number of dex register in the map from the outermost frame to the number of inlined
+  // frames indicated by `depth`. If `depth` is 0, grab just the registers from the outermost level.
+  // If it is greater than 0, grab as many inline frames as `depth` indicates.
+  size_t GetNumberOfRegisters(CodeInfo* code_info, int depth) const;
+
  private:
   // Private constructor known in the case that num_frames_ has already been computed.
   StackVisitor(Thread* thread,
@@ -334,7 +350,8 @@
   bool GetVRegFromOptimizedCode(ArtMethod* m,
                                 uint16_t vreg,
                                 VRegKind kind,
-                                uint32_t* val) const
+                                uint32_t* val,
+                                bool need_full_register_list = false) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool GetVRegPairFromDebuggerShadowFrame(uint16_t vreg,
diff --git a/runtime/stack_map.h b/runtime/stack_map.h
index 430aaa3..514d30e 100644
--- a/runtime/stack_map.h
+++ b/runtime/stack_map.h
@@ -20,9 +20,12 @@
 #include <limits>
 
 #include "arch/instruction_set.h"
+#include "base/array_ref.h"
 #include "base/bit_memory_region.h"
 #include "base/bit_table.h"
 #include "base/bit_utils.h"
+#include "base/globals.h"
+#include "base/logging.h"
 #include "base/memory_region.h"
 #include "dex/dex_file_types.h"
 #include "dex_register_location.h"
@@ -360,15 +363,12 @@
     return GetMethodInfoOf(inline_info).GetMethodIndex();
   }
 
+  // Returns the dex registers for `stack_map`, ignoring any inlined dex registers.
   ALWAYS_INLINE DexRegisterMap GetDexRegisterMapOf(StackMap stack_map) const {
-    if (stack_map.HasDexRegisterMap()) {
-      DexRegisterMap map(number_of_dex_registers_, DexRegisterLocation::Invalid());
-      DecodeDexRegisterMap(stack_map.Row(), /* first_dex_register= */ 0, &map);
-      return map;
-    }
-    return DexRegisterMap(0, DexRegisterLocation::None());
+    return GetDexRegisterMapOf(stack_map, /* first= */ 0, number_of_dex_registers_);
   }
 
+  // Returns the dex register map of `inline_info`, and just those registers.
   ALWAYS_INLINE DexRegisterMap GetInlineDexRegisterMapOf(StackMap stack_map,
                                                          InlineInfo inline_info) const {
     if (stack_map.HasDexRegisterMap()) {
@@ -381,6 +381,17 @@
           ? number_of_dex_registers_
           : inline_infos_.GetRow(inline_info.Row() - 1).GetNumberOfDexRegisters();
       uint32_t last = inline_info.GetNumberOfDexRegisters();
+      return GetDexRegisterMapOf(stack_map, first, last);
+    }
+    return DexRegisterMap(0, DexRegisterLocation::None());
+  }
+
+  // Returns the dex register map of `stack_map` in the range the range [first, last).
+  ALWAYS_INLINE DexRegisterMap GetDexRegisterMapOf(StackMap stack_map,
+                                                   uint32_t first,
+                                                   uint32_t last) const {
+    if (stack_map.HasDexRegisterMap()) {
+      DCHECK_LE(first, last);
       DexRegisterMap map(last - first, DexRegisterLocation::Invalid());
       DecodeDexRegisterMap(stack_map.Row(), first, &map);
       return map;
@@ -409,12 +420,39 @@
     return stack_maps_.GetInvalidRow();
   }
 
-  // Searches the stack map list backwards because catch stack maps are stored at the end.
-  StackMap GetCatchStackMapForDexPc(uint32_t dex_pc) const {
+  StackMap GetCatchStackMapForDexPc(ArrayRef<const uint32_t> dex_pcs) const {
+    // Searches the stack map list backwards because catch stack maps are stored at the end.
     for (size_t i = GetNumberOfStackMaps(); i > 0; --i) {
       StackMap stack_map = GetStackMapAt(i - 1);
-      if (stack_map.GetDexPc() == dex_pc && stack_map.GetKind() == StackMap::Kind::Catch) {
-        return stack_map;
+      if (UNLIKELY(stack_map.GetKind() != StackMap::Kind::Catch)) {
+        // Early break since we should have catch stack maps only at the end.
+        if (kIsDebugBuild) {
+          for (size_t j = i - 1; j > 0; --j) {
+            DCHECK(GetStackMapAt(j - 1).GetKind() != StackMap::Kind::Catch);
+          }
+        }
+        break;
+      }
+
+      // Both the handler dex_pc and all of the inline dex_pcs have to match i.e. we want dex_pcs to
+      // be [stack_map_dex_pc, inline_dex_pc_1, ..., inline_dex_pc_n].
+      if (stack_map.GetDexPc() != dex_pcs.front()) {
+        continue;
+      }
+
+      const BitTableRange<InlineInfo>& inline_infos = GetInlineInfosOf(stack_map);
+      if (inline_infos.size() == dex_pcs.size() - 1) {
+        bool matching_dex_pcs = true;
+        for (size_t inline_info_index = 0; inline_info_index < inline_infos.size();
+             ++inline_info_index) {
+          if (inline_infos[inline_info_index].GetDexPc() != dex_pcs[inline_info_index + 1]) {
+            matching_dex_pcs = false;
+            break;
+          }
+        }
+        if (matching_dex_pcs) {
+          return stack_map;
+        }
       }
     }
     return stack_maps_.GetInvalidRow();
@@ -453,6 +491,10 @@
     return (*code_info_data & kIsDebuggable) != 0;
   }
 
+  uint32_t GetNumberOfDexRegisters() {
+    return number_of_dex_registers_;
+  }
+
  private:
   // Scan backward to determine dex register locations at given stack map.
   void DecodeDexRegisterMap(uint32_t stack_map_index,
diff --git a/test/004-ReferenceMap/stack_walk_refmap_jni.cc b/test/004-ReferenceMap/stack_walk_refmap_jni.cc
index 0659c0b..c84bd88 100644
--- a/test/004-ReferenceMap/stack_walk_refmap_jni.cc
+++ b/test/004-ReferenceMap/stack_walk_refmap_jni.cc
@@ -25,9 +25,8 @@
   int t_size = sizeof(t) / sizeof(*t);                                                        \
   const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();               \
   uintptr_t native_quick_pc = method_header->ToNativeQuickPc(GetMethod(),                     \
-                                                 dex_pc,                                      \
-                                                 /* is_catch_handler */ false,                \
-                                                 abort_if_not_found);                         \
+                                                             dex_pc,                          \
+                                                             abort_if_not_found);             \
   if (native_quick_pc != UINTPTR_MAX) {                                                       \
     CheckReferences(t,                                                                        \
                     t_size,                                                                   \