Introduce the notion of an nterp frame.

See comments in nterp_helpers.cc. An nterp frame follows the
calling conventions and exception handling of the compiler. There are
no ManagedStack transitions, and the compiler and interpreter can
just call each other directly.

For the stack walker, an nterp frame looks like a compiled frame.

This CL introduces an nterp frame, another CL will contain an
implementation for x64.

Bug: 119800099
Test: test.py
Change-Id: Ie9b691f58908b7f283b4cd63b84b651526155d27
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 01e2dc6..baa921d 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -210,6 +210,7 @@
         "native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc",
         "native/sun_misc_Unsafe.cc",
         "non_debuggable_classes.cc",
+        "nterp_helpers.cc",
         "oat.cc",
         "oat_file.cc",
         "oat_file_assistant.cc",
@@ -290,6 +291,7 @@
         arm: {
             srcs: [
                 "interpreter/mterp/mterp.cc",
+                "interpreter/mterp/nterp_stub.cc",
                 ":libart_mterp.arm",
                 "arch/arm/context_arm.cc",
                 "arch/arm/entrypoints_init_arm.cc",
@@ -305,6 +307,7 @@
         arm64: {
             srcs: [
                 "interpreter/mterp/mterp.cc",
+                "interpreter/mterp/nterp_stub.cc",
                 ":libart_mterp.arm64",
                 "arch/arm64/context_arm64.cc",
                 "arch/arm64/entrypoints_init_arm64.cc",
@@ -319,6 +322,7 @@
         x86: {
             srcs: [
                 "interpreter/mterp/mterp.cc",
+                "interpreter/mterp/nterp_stub.cc",
                 ":libart_mterp.x86",
                 "arch/x86/context_x86.cc",
                 "arch/x86/entrypoints_init_x86.cc",
@@ -340,6 +344,7 @@
                 // Note that the fault_handler_x86.cc is not a mistake.  This file is
                 // shared between the x86 and x86_64 architectures.
                 "interpreter/mterp/mterp.cc",
+                "interpreter/mterp/nterp_stub.cc",
                 ":libart_mterp.x86_64",
                 "arch/x86_64/context_x86_64.cc",
                 "arch/x86_64/entrypoints_init_x86_64.cc",
@@ -360,6 +365,7 @@
         mips: {
             srcs: [
                 "interpreter/mterp/mterp.cc",
+                "interpreter/mterp/nterp_stub.cc",
                 ":libart_mterp.mips",
                 "arch/mips/context_mips.cc",
                 "arch/mips/entrypoints_init_mips.cc",
@@ -373,6 +379,7 @@
         mips64: {
             srcs: [
                 "interpreter/mterp/mterp.cc",
+                "interpreter/mterp/nterp_stub.cc",
                 ":libart_mterp.mips64",
                 "arch/mips64/context_mips64.cc",
                 "arch/mips64/entrypoints_init_mips64.cc",
diff --git a/runtime/arch/context.h b/runtime/arch/context.h
index 5980b03..be7adc7 100644
--- a/runtime/arch/context.h
+++ b/runtime/arch/context.h
@@ -88,6 +88,12 @@
   // Smashes the caller save registers. If we're throwing, we don't want to return bogus values.
   virtual void SmashCallerSaves() = 0;
 
+  // Set `new_value` to the physical register containing the dex PC pointer in
+  // an nterp frame.
+  virtual void SetNterpDexPC(uintptr_t new_value ATTRIBUTE_UNUSED) {
+    abort();
+  }
+
   // Switches execution of the executing context to this context
   NO_RETURN virtual void DoLongJump() = 0;
 
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 646f73d..d975a30 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -612,6 +612,11 @@
     }
   }
 
+  if (OatQuickMethodHeader::NterpMethodHeader != nullptr &&
+      OatQuickMethodHeader::NterpMethodHeader->Contains(pc)) {
+    return OatQuickMethodHeader::NterpMethodHeader;
+  }
+
   // Check whether the pc is in the JIT code cache.
   jit::Jit* jit = runtime->GetJit();
   if (jit != nullptr) {
diff --git a/runtime/check_reference_map_visitor.h b/runtime/check_reference_map_visitor.h
index ebf9ea0..f4bda90 100644
--- a/runtime/check_reference_map_visitor.h
+++ b/runtime/check_reference_map_visitor.h
@@ -40,7 +40,12 @@
       CHECK_EQ(GetDexPc(), dex::kDexNoIndex);
     }
 
-    if (m == nullptr || m->IsNative() || m->IsRuntimeMethod() || IsShadowFrame()) {
+    // If the method is not compiled, continue the stack walk.
+    if (m == nullptr ||
+        m->IsNative() ||
+        m->IsRuntimeMethod() ||
+        IsShadowFrame() ||
+        !GetCurrentOatQuickMethodHeader()->IsOptimized()) {
       return true;
     }
 
diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc
index 17be7ec..849a967 100644
--- a/runtime/entrypoints/entrypoint_utils.cc
+++ b/runtime/entrypoints/entrypoint_utils.cc
@@ -202,8 +202,8 @@
     if (outer_method != nullptr) {
       const OatQuickMethodHeader* current_code = outer_method->GetOatQuickMethodHeader(caller_pc);
       DCHECK(current_code != nullptr);
-      DCHECK(current_code->IsOptimized());
-      if (CodeInfo::HasInlineInfo(current_code->GetOptimizedCodeInfoPtr())) {
+      if (current_code->IsOptimized() &&
+          CodeInfo::HasInlineInfo(current_code->GetOptimizedCodeInfoPtr())) {
         uintptr_t native_pc_offset = current_code->NativeQuickPcOffset(caller_pc);
         CodeInfo code_info = CodeInfo::DecodeInlineInfoOnly(current_code);
         StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset);
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index b5b01fe..b5e5238 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -703,6 +703,7 @@
 
 void CheckInterpreterAsmConstants() {
   CheckMterpAsmConstants();
+  CheckNterpAsmConstants();
 }
 
 void InitInterpreterTls(Thread* self) {
diff --git a/runtime/interpreter/interpreter_mterp_impl.h b/runtime/interpreter/interpreter_mterp_impl.h
index 177b0fd..892790b 100644
--- a/runtime/interpreter/interpreter_mterp_impl.h
+++ b/runtime/interpreter/interpreter_mterp_impl.h
@@ -36,6 +36,9 @@
                                  ShadowFrame* shadow_frame,
                                  JValue* result_register) REQUIRES_SHARED(Locks::mutator_lock_);
 
+// The entrypoint for nterp, which ArtMethods can directly point to.
+extern "C" void ExecuteNterpImpl() REQUIRES_SHARED(Locks::mutator_lock_);
+
 }  // namespace interpreter
 }  // namespace art
 
diff --git a/runtime/interpreter/mterp/mterp.h b/runtime/interpreter/mterp/mterp.h
index af52758..dfbba29 100644
--- a/runtime/interpreter/mterp/mterp.h
+++ b/runtime/interpreter/mterp/mterp.h
@@ -26,6 +26,9 @@
 extern "C" void* artMterpAsmInstructionStart[];
 extern "C" void* artMterpAsmInstructionEnd[];
 
+extern "C" void* artNterpAsmInstructionStart[];
+extern "C" void* artNterpAsmInstructionEnd[];
+
 namespace art {
 
 class Thread;
@@ -34,7 +37,10 @@
 
 void InitMterpTls(Thread* self);
 void CheckMterpAsmConstants();
+void CheckNterpAsmConstants();
 bool CanUseMterp();
+bool IsNterpSupported();
+const void* GetNterpEntryPoint();
 
 // Poison value for TestExportPC.  If we segfault with this value, it means that a mterp
 // handler for a recent opcode failed to export the Dalvik PC prior to a possible exit from
diff --git a/runtime/interpreter/mterp/nterp_stub.cc b/runtime/interpreter/mterp/nterp_stub.cc
new file mode 100644
index 0000000..e77f0e3
--- /dev/null
+++ b/runtime/interpreter/mterp/nterp_stub.cc
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "base/locks.h"
+
+/*
+ * Stub definitions for targets without nterp implementations.
+ */
+
+namespace art {
+namespace interpreter {
+
+bool IsNterpSupported() {
+  return false;
+}
+
+void CheckNterpAsmConstants() {
+}
+
+extern "C" void ExecuteNterpImpl() REQUIRES_SHARED(Locks::mutator_lock_) {
+  UNIMPLEMENTED(FATAL);
+}
+
+const void* GetNterpEntryPoint() {
+  return nullptr;
+}
+
+extern "C" void* artNterpAsmInstructionStart[] = { nullptr };
+extern "C" void* artNterpAsmInstructionEnd[] = { nullptr };
+
+}  // namespace interpreter
+}  // namespace art
diff --git a/runtime/nterp_helpers.cc b/runtime/nterp_helpers.cc
new file mode 100644
index 0000000..df0eb73
--- /dev/null
+++ b/runtime/nterp_helpers.cc
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "art_method-inl.h"
+#include "dex/code_item_accessors.h"
+#include "entrypoints/quick/callee_save_frame.h"
+#include "interpreter/interpreter_mterp_impl.h"
+#include "nterp_helpers.h"
+#include "oat_quick_method_header.h"
+#include "quick/quick_method_frame_info.h"
+
+namespace art {
+
+/**
+ * An nterp frame follows the optimizing compiler's ABI conventions, with
+ * int/long/reference parameters being passed in core registers / stack and
+ * float/double parameters being passed in floating point registers / stack.
+ *
+ * There are no ManagedStack transitions between compiler and nterp frames.
+ *
+ * On entry, nterp will copy its parameters to a dex register array allocated on
+ * the stack. There is a fast path when calling from nterp to nterp to not
+ * follow the ABI but just copy the parameters from the caller's dex registers
+ * to the callee's dex registers.
+ *
+ * The stack layout of an nterp frame is:
+ *    ----------------
+ *    |              |      All callee save registers of the platform
+ *    | callee-save  |      (core and floating point).
+ *    | registers    |      On x86 and x64 this includes the return address,
+ *    |              |      already spilled on entry.
+ *    ----------------
+ *    |              |      Contains `registers_size` entries (of size 4) from
+ *    |    dex       |      the code item information of the method.
+ *    |  registers   |
+ *    |              |
+ *    ----------------
+ *    |              |      A copy of the dex registers above, but only
+ *    |  reference   |      containing references, used for GC.
+ *    |  registers   |
+ *    |              |
+ *    ----------------
+ *    |  caller fp   |      Frame pointer of caller. Stored below the reference
+ *    ----------------      registers array for easy access from nterp when returning.
+ *    |  dex_pc_ptr  |      Pointer to the dex instruction being executed.
+ *    ----------------      Stored whenever nterp goes into the runtime.
+ *    |  alignment   |      Stack aligment of kStackAlignment. TODO: try to move
+ *    ----------------      this below the callee-save registers.
+ *    |              |      In case nterp calls compiled code, we reserve space
+ *    |     out      |      for out registers. This space will be used for
+ *    |   registers  |      arguments passed on stack.
+ *    |              |
+ *    ----------------
+ *    |  ArtMethod*  |      The method being currently executed.
+ *    ----------------
+ *
+ *    Exception handling:
+ *    Nterp follows the same convention than the compiler,
+ *    with the addition of:
+ *    - All catch handlers have the same landing pad.
+ *    - Before doing the longjmp for exception delivery, the register containing the
+ *      dex PC pointer must be updated.
+ *
+ *    Stack walking:
+ *    An nterp frame is walked like a compiled code frame. We add an
+ *    OatQuickMethodHeader prefix to the nterp entry point, which contains:
+ *    - vmap_table_offset=0 (nterp doesn't need one).
+ *    - code_size=NterpEnd-NterpStart
+ */
+
+static constexpr size_t kPointerSize = static_cast<size_t>(kRuntimePointerSize);
+
+static constexpr size_t NterpGetFrameEntrySize() {
+  uint32_t core_spills =
+      RuntimeCalleeSaveFrame::GetCoreSpills(CalleeSaveType::kSaveAllCalleeSaves);
+  uint32_t fp_spills =
+      RuntimeCalleeSaveFrame::GetFpSpills(CalleeSaveType::kSaveAllCalleeSaves);
+  // Note: the return address is considered part of the callee saves.
+  return (POPCOUNT(core_spills) + POPCOUNT(fp_spills)) * kPointerSize;
+}
+
+static size_t NterpGetFrameSize(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
+  CodeItemDataAccessor accessor(method->DexInstructionData());
+  const uint16_t num_regs = accessor.RegistersSize();
+  const uint16_t out_regs = accessor.OutsSize();
+
+  size_t frame_size =
+      NterpGetFrameEntrySize() +
+      (num_regs * kVRegSize) * 2 +  // dex registers and reference registers
+      kPointerSize +  // previous frame
+      kPointerSize +  // saved dex pc
+      (out_regs * kVRegSize) +  // out arguments
+      kPointerSize;  // method
+  return RoundUp(frame_size, kStackAlignment);
+}
+
+QuickMethodFrameInfo NterpFrameInfo(ArtMethod** frame) {
+  uint32_t core_spills =
+      RuntimeCalleeSaveFrame::GetCoreSpills(CalleeSaveType::kSaveAllCalleeSaves);
+  uint32_t fp_spills =
+      RuntimeCalleeSaveFrame::GetFpSpills(CalleeSaveType::kSaveAllCalleeSaves);
+  return QuickMethodFrameInfo(NterpGetFrameSize(*frame), core_spills, fp_spills);
+}
+
+uintptr_t NterpGetRegistersArray(ArtMethod** frame) {
+  CodeItemDataAccessor accessor((*frame)->DexInstructionData());
+  const uint16_t num_regs = accessor.RegistersSize();
+  // The registers array is just below the frame entry.
+  return reinterpret_cast<uintptr_t>(frame) + NterpGetFrameSize(*frame) -
+      NterpGetFrameEntrySize() -
+      (num_regs * kVRegSize);
+}
+
+uintptr_t NterpGetReferenceArray(ArtMethod** frame) {
+  CodeItemDataAccessor accessor((*frame)->DexInstructionData());
+  const uint16_t num_regs = accessor.RegistersSize();
+  // The references array is just below the registers array.
+  return NterpGetRegistersArray(frame) - (num_regs * kVRegSize);
+}
+
+uint32_t NterpGetDexPC(ArtMethod** frame) {
+  uintptr_t dex_pc_ptr = NterpGetReferenceArray(frame) -
+      kPointerSize -  // saved previous frame
+      kPointerSize;   // saved dex pc
+  CodeItemInstructionAccessor accessor((*frame)->DexInstructions());
+  return *reinterpret_cast<const uint16_t**>(dex_pc_ptr) - accessor.Insns();
+}
+
+uint32_t NterpGetVReg(ArtMethod** frame, uint16_t vreg) {
+  return reinterpret_cast<uint32_t*>(NterpGetRegistersArray(frame))[vreg];
+}
+
+uintptr_t NterpGetCatchHandler() {
+  // Nterp uses the same landing pad for all exceptions. The dex_pc_ptr set before
+  // longjmp will actually be used to jmp to the catch handler.
+  return reinterpret_cast<uintptr_t>(artNterpAsmInstructionEnd);
+}
+
+}  // namespace art
diff --git a/runtime/nterp_helpers.h b/runtime/nterp_helpers.h
new file mode 100644
index 0000000..758d1fd
--- /dev/null
+++ b/runtime/nterp_helpers.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#ifndef ART_RUNTIME_NTERP_HELPERS_H_
+#define ART_RUNTIME_NTERP_HELPERS_H_
+
+#include "quick/quick_method_frame_info.h"
+
+namespace art {
+
+class ArtMethod;
+
+/**
+ * Returns the QuickMethodFrameInfo of the given frame corresponding to the
+ * given method.
+ */
+QuickMethodFrameInfo NterpFrameInfo(ArtMethod** frame)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
+/**
+ * Returns the dex PC at which the given nterp frame is executing.
+ */
+uint32_t NterpGetDexPC(ArtMethod** frame)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
+/**
+ * Returns the reference array to be used by the GC to visit references in an
+ * nterp frame.
+ */
+uintptr_t NterpGetReferenceArray(ArtMethod** frame)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+/**
+ * Returns the dex register array to be used by the GC to update references in
+ * an nterp frame.
+ */
+uintptr_t NterpGetRegistersArray(ArtMethod** frame)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+/**
+ * Returns the nterp landing pad for catching an exception.
+ */
+uintptr_t NterpGetCatchHandler();
+
+/**
+ * Returns the value of dex register number `vreg` in the given frame.
+ */
+uint32_t NterpGetVReg(ArtMethod** frame, uint16_t vreg)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_NTERP_HELPERS_H_
diff --git a/runtime/oat_quick_method_header.cc b/runtime/oat_quick_method_header.cc
index 7a6ebf8..ebb868b 100644
--- a/runtime/oat_quick_method_header.cc
+++ b/runtime/oat_quick_method_header.cc
@@ -18,6 +18,9 @@
 
 #include "art_method.h"
 #include "dex/dex_file_types.h"
+#include "interpreter/interpreter_mterp_impl.h"
+#include "interpreter/mterp/mterp.h"
+#include "nterp_helpers.h"
 #include "scoped_thread_state_change-inl.h"
 #include "stack_map.h"
 #include "thread.h"
@@ -32,6 +35,8 @@
   uint32_t sought_offset = pc - reinterpret_cast<uintptr_t>(entry_point);
   if (method->IsNative()) {
     return dex::kDexNoIndex;
+  } else if (IsNterpMethodHeader()) {
+    return NterpGetDexPC(frame);
   } else {
     DCHECK(IsOptimized());
     CodeInfo code_info = CodeInfo::DecodeInlineInfoOnly(this);
@@ -41,7 +46,6 @@
     }
   }
   if (abort_on_failure) {
-    ScopedObjectAccess soa(Thread::Current());
     LOG(FATAL) << "Failed to find Dex offset for PC offset "
            << reinterpret_cast<void*>(sought_offset)
            << "(PC " << reinterpret_cast<void*>(pc) << ", entry_point=" << entry_point
@@ -57,6 +61,11 @@
                                                 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);
@@ -79,4 +88,15 @@
   return UINTPTR_MAX;
 }
 
+OatQuickMethodHeader* OatQuickMethodHeader::NterpMethodHeader =
+    (interpreter::IsNterpSupported()
+        ? reinterpret_cast<OatQuickMethodHeader*>(
+              reinterpret_cast<uintptr_t>(interpreter::GetNterpEntryPoint()) -
+                  sizeof(OatQuickMethodHeader))
+        : nullptr);
+
+bool OatQuickMethodHeader::IsNterpMethodHeader() const {
+  return interpreter::IsNterpSupported() ? (this == NterpMethodHeader) : false;
+}
+
 }  // namespace art
diff --git a/runtime/oat_quick_method_header.h b/runtime/oat_quick_method_header.h
index 0d08149..c8ee9b4 100644
--- a/runtime/oat_quick_method_header.h
+++ b/runtime/oat_quick_method_header.h
@@ -18,6 +18,7 @@
 #define ART_RUNTIME_OAT_QUICK_METHOD_HEADER_H_
 
 #include "arch/instruction_set.h"
+#include "base/locks.h"
 #include "base/macros.h"
 #include "base/utils.h"
 #include "quick/quick_method_frame_info.h"
@@ -37,6 +38,10 @@
         code_size_(code_size) {
   }
 
+  static OatQuickMethodHeader* NterpMethodHeader;
+
+  bool IsNterpMethodHeader() const;
+
   static OatQuickMethodHeader* FromCodePointer(const void* code_ptr) {
     uintptr_t code = reinterpret_cast<uintptr_t>(code_ptr);
     uintptr_t header = code - OFFSETOF_MEMBER(OatQuickMethodHeader, code_);
@@ -150,7 +155,8 @@
 
   uint32_t ToDexPc(ArtMethod** frame,
                    const uintptr_t pc,
-                   bool abort_on_failure = true) const;
+                   bool abort_on_failure = true) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   void SetHasShouldDeoptimizeFlag() {
     DCHECK_EQ(code_size_ & kShouldDeoptimizeMask, 0u);
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index 727bdf0..0e04b7b 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -33,6 +33,7 @@
 #include "mirror/class-inl.h"
 #include "mirror/class_loader.h"
 #include "mirror/throwable.h"
+#include "nterp_helpers.h"
 #include "oat_quick_method_header.h"
 #include "stack.h"
 #include "stack_map.h"
@@ -663,6 +664,12 @@
   if (smash_caller_saves) {
     context_->SmashCallerSaves();
   }
+  if (handler_method_ != nullptr &&
+      handler_method_header_ != nullptr &&
+      handler_method_header_->IsNterpMethodHeader()) {
+    context_->SetNterpDexPC(reinterpret_cast<uintptr_t>(
+        handler_method_->DexInstructions().Insns() + handler_dex_pc_));
+  }
   context_->DoLongJump();
   UNREACHABLE();
 }
diff --git a/runtime/stack.cc b/runtime/stack.cc
index e69bc1e..410e0fd 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -38,6 +38,7 @@
 #include "mirror/class-inl.h"
 #include "mirror/object-inl.h"
 #include "mirror/object_array-inl.h"
+#include "nterp_helpers.h"
 #include "oat_quick_method_header.h"
 #include "obj_ptr-inl.h"
 #include "quick/quick_method_frame_info.h"
@@ -122,13 +123,16 @@
       return current_inline_frames_.back().GetDexPc();
     } else if (cur_oat_quick_method_header_ == nullptr) {
       return dex::kDexNoIndex;
-    } else if (!(*GetCurrentQuickFrame())->IsNative()) {
+    } else if ((*GetCurrentQuickFrame())->IsNative()) {
+      return cur_oat_quick_method_header_->ToDexPc(
+          GetCurrentQuickFrame(), cur_quick_frame_pc_, abort_on_failure);
+    } else if (cur_oat_quick_method_header_->IsOptimized()) {
       StackMap* stack_map = GetCurrentStackMap();
       DCHECK(stack_map->IsValid());
       return stack_map->GetDexPc();
     } else {
-      return cur_oat_quick_method_header_->ToDexPc(
-          GetCurrentQuickFrame(), cur_quick_frame_pc_, abort_on_failure);
+      DCHECK(cur_oat_quick_method_header_->IsNterpMethodHeader());
+      return NterpGetDexPC(cur_quick_frame_);
     }
   } else {
     return 0;
@@ -214,6 +218,10 @@
     if (GetVRegFromDebuggerShadowFrame(vreg, kind, val)) {
       return true;
     }
+    if (cur_oat_quick_method_header_->IsNterpMethodHeader()) {
+      *val = NterpGetVReg(cur_quick_frame_, vreg);
+      return true;
+    }
     DCHECK(cur_oat_quick_method_header_->IsOptimized());
     if (location.has_value() && kind != kReferenceVReg) {
       uint32_t val2 = *val;
@@ -772,7 +780,12 @@
 
 QuickMethodFrameInfo StackVisitor::GetCurrentQuickFrameInfo() const {
   if (cur_oat_quick_method_header_ != nullptr) {
-    return cur_oat_quick_method_header_->GetFrameInfo();
+    if (cur_oat_quick_method_header_->IsOptimized()) {
+      return cur_oat_quick_method_header_->GetFrameInfo();
+    } else {
+      DCHECK(cur_oat_quick_method_header_->IsNterpMethodHeader());
+      return NterpFrameInfo(cur_quick_frame_);
+    }
   }
 
   ArtMethod* method = GetMethod();
diff --git a/runtime/thread.cc b/runtime/thread.cc
index a4bc8ee..6e57ec6 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -89,6 +89,7 @@
 #include "native_stack_dump.h"
 #include "nativehelper/scoped_local_ref.h"
 #include "nativehelper/scoped_utf_chars.h"
+#include "nterp_helpers.h"
 #include "nth_caller_visitor.h"
 #include "oat_quick_method_header.h"
 #include "obj_ptr-inl.h"
@@ -3713,6 +3714,8 @@
     ShadowFrame* shadow_frame = GetCurrentShadowFrame();
     if (shadow_frame != nullptr) {
       VisitShadowFrame(shadow_frame);
+    } else if (GetCurrentOatQuickMethodHeader()->IsNterpMethodHeader()) {
+      VisitNterpFrame();
     } else {
       VisitQuickFrame();
     }
@@ -3781,6 +3784,32 @@
     }
   }
 
+  void VisitNterpFrame() REQUIRES_SHARED(Locks::mutator_lock_) {
+    ArtMethod** cur_quick_frame = GetCurrentQuickFrame();
+    StackReference<mirror::Object>* vreg_ref_base =
+        reinterpret_cast<StackReference<mirror::Object>*>(NterpGetReferenceArray(cur_quick_frame));
+    StackReference<mirror::Object>* vreg_int_base =
+        reinterpret_cast<StackReference<mirror::Object>*>(NterpGetRegistersArray(cur_quick_frame));
+    CodeItemDataAccessor accessor((*cur_quick_frame)->DexInstructionData());
+    const uint16_t num_regs = accessor.RegistersSize();
+    // An nterp frame has two arrays: a dex register array and a reference array
+    // that shadows the dex register array but only containing references
+    // (non-reference dex registers have nulls). See nterp_helpers.cc.
+    for (size_t reg = 0; reg < num_regs; ++reg) {
+      StackReference<mirror::Object>* ref_addr = vreg_ref_base + reg;
+      mirror::Object* ref = ref_addr->AsMirrorPtr();
+      if (ref != nullptr) {
+        mirror::Object* new_ref = ref;
+        visitor_(&new_ref, reg, this);
+        if (new_ref != ref) {
+          ref_addr->Assign(new_ref);
+          StackReference<mirror::Object>* int_addr = vreg_int_base + reg;
+          int_addr->Assign(new_ref);
+        }
+      }
+    }
+  }
+
   template <typename T>
   ALWAYS_INLINE
   inline void VisitQuickFrameWithVregCallback() REQUIRES_SHARED(Locks::mutator_lock_) {