OSDN Git Service

Deoptimization support in optimizing compiler for setting local values
authorMingyao Yang <mingyao@google.com>
Mon, 6 Jul 2015 18:10:37 +0000 (11:10 -0700)
committerSebastien Hertz <shertz@google.com>
Thu, 17 Sep 2015 15:07:22 +0000 (17:07 +0200)
Due to compiler optimizations, we may not always be able to update
the value of a local variable in a compiled frame (like a variable
seen as constant by the compiler). To avoid that situation, we simply
deoptimize compiled frames updated by the debugger so they are
executed by the interpreter with the updated value.

When the debugger attempts to set a local variable (actually a DEX
register or a pair of registers) in a compiled frame, we allocate a
ShadowFrame associated to that frame (using its frame id) and set the
new value in that ShadowFrame. When we know we are about to continue
the execution of the compiled frame, we deoptimize the stack using
the preallocated ShadowFrame (instead of creating a new one). We
initialize it with the current value of all DEX registers except
the ones that have been set by the debugger. Therefore, the
ShadowFrame represent the runtime context modified by the debugger.

Bumps oat version to force recompilation.

Bug: 19944235
Change-Id: I0ebe6241264f7a3be0f14ee4516c1f7436e04da6

12 files changed:
runtime/asm_support.h
runtime/debugger.cc
runtime/debugger.h
runtime/entrypoints_order_test.cc
runtime/instrumentation.cc
runtime/instrumentation.h
runtime/oat.h
runtime/quick_exception_handler.cc
runtime/stack.cc
runtime/stack.h
runtime/thread.cc
runtime/thread.h

index 04ff120..1b569fe 100644 (file)
@@ -109,7 +109,7 @@ ADD_TEST_EQ(THREAD_SELF_OFFSET,
             art::Thread::SelfOffset<__SIZEOF_POINTER__>().Int32Value())
 
 // Offset of field Thread::tlsPtr_.thread_local_pos.
-#define THREAD_LOCAL_POS_OFFSET (THREAD_CARD_TABLE_OFFSET + 151 * __SIZEOF_POINTER__)
+#define THREAD_LOCAL_POS_OFFSET (THREAD_CARD_TABLE_OFFSET + 152 * __SIZEOF_POINTER__)
 ADD_TEST_EQ(THREAD_LOCAL_POS_OFFSET,
             art::Thread::ThreadLocalPosOffset<__SIZEOF_POINTER__>().Int32Value())
 // Offset of field Thread::tlsPtr_.thread_local_end.
index ad69676..450031a 100644 (file)
@@ -2648,7 +2648,7 @@ JDWP::JdwpError Dbg::SetLocalValues(JDWP::Request* request) {
     uint64_t value = request->ReadValue(width);
 
     VLOG(jdwp) << "    --> slot " << slot << " " << sigByte << " " << value;
-    error = Dbg::SetLocalValue(visitor, slot, sigByte, value, width);
+    error = Dbg::SetLocalValue(thread, visitor, slot, sigByte, value, width);
     if (error != JDWP::ERR_NONE) {
       return error;
     }
@@ -2666,8 +2666,8 @@ static JDWP::JdwpError FailSetLocalValue(const StackVisitor& visitor, uint16_t v
   return kStackFrameLocalAccessError;
 }
 
-JDWP::JdwpError Dbg::SetLocalValue(StackVisitor& visitor, int slot, JDWP::JdwpTag tag,
-                                   uint64_t value, size_t width) {
+JDWP::JdwpError Dbg::SetLocalValue(Thread* thread, StackVisitor& visitor, int slot,
+                                   JDWP::JdwpTag tag, uint64_t value, size_t width) {
   ArtMethod* m = visitor.GetMethod();
   JDWP::JdwpError error = JDWP::ERR_NONE;
   uint16_t vreg = DemangleSlot(slot, m, &error);
@@ -2679,26 +2679,26 @@ JDWP::JdwpError Dbg::SetLocalValue(StackVisitor& visitor, int slot, JDWP::JdwpTa
     case JDWP::JT_BOOLEAN:
     case JDWP::JT_BYTE:
       CHECK_EQ(width, 1U);
-      if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
+      if (!visitor.SetVRegFromDebugger(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
         return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
       }
       break;
     case JDWP::JT_SHORT:
     case JDWP::JT_CHAR:
       CHECK_EQ(width, 2U);
-      if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
+      if (!visitor.SetVRegFromDebugger(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
         return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
       }
       break;
     case JDWP::JT_INT:
       CHECK_EQ(width, 4U);
-      if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
+      if (!visitor.SetVRegFromDebugger(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
         return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
       }
       break;
     case JDWP::JT_FLOAT:
       CHECK_EQ(width, 4U);
-      if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kFloatVReg)) {
+      if (!visitor.SetVRegFromDebugger(m, vreg, static_cast<uint32_t>(value), kFloatVReg)) {
         return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
       }
       break;
@@ -2716,7 +2716,7 @@ JDWP::JdwpError Dbg::SetLocalValue(StackVisitor& visitor, int slot, JDWP::JdwpTa
         VLOG(jdwp) << tag << " object " << o << " is an invalid object";
         return JDWP::ERR_INVALID_OBJECT;
       }
-      if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(reinterpret_cast<uintptr_t>(o)),
+      if (!visitor.SetVRegFromDebugger(m, vreg, static_cast<uint32_t>(reinterpret_cast<uintptr_t>(o)),
                                  kReferenceVReg)) {
         return FailSetLocalValue(visitor, vreg, tag, reinterpret_cast<uintptr_t>(o));
       }
@@ -2724,14 +2724,14 @@ JDWP::JdwpError Dbg::SetLocalValue(StackVisitor& visitor, int slot, JDWP::JdwpTa
     }
     case JDWP::JT_DOUBLE: {
       CHECK_EQ(width, 8U);
-      if (!visitor.SetVRegPair(m, vreg, value, kDoubleLoVReg, kDoubleHiVReg)) {
+      if (!visitor.SetVRegPairFromDebugger(m, vreg, value, kDoubleLoVReg, kDoubleHiVReg)) {
         return FailSetLocalValue(visitor, vreg, tag, value);
       }
       break;
     }
     case JDWP::JT_LONG: {
       CHECK_EQ(width, 8U);
-      if (!visitor.SetVRegPair(m, vreg, value, kLongLoVReg, kLongHiVReg)) {
+      if (!visitor.SetVRegPairFromDebugger(m, vreg, value, kLongLoVReg, kLongHiVReg)) {
         return FailSetLocalValue(visitor, vreg, tag, value);
       }
       break;
@@ -2740,6 +2740,15 @@ JDWP::JdwpError Dbg::SetLocalValue(StackVisitor& visitor, int slot, JDWP::JdwpTa
       LOG(FATAL) << "Unknown tag " << tag;
       UNREACHABLE();
   }
+
+  // If we set the local variable in a compiled frame, we need to trigger a deoptimization of
+  // the stack so we continue execution with the interpreter using the new value(s) of the updated
+  // local variable(s). To achieve this, we install instrumentation exit stub on each method of the
+  // thread's stack. The stub will cause the deoptimization to happen.
+  if (!visitor.IsShadowFrame() && thread->HasDebuggerShadowFrames()) {
+    Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(thread);
+  }
+
   return JDWP::ERR_NONE;
 }
 
@@ -3492,11 +3501,17 @@ bool Dbg::IsForcedInterpreterNeededForUpcallImpl(Thread* thread, ArtMethod* m) {
       return true;
     }
   }
+  if (thread->HasDebuggerShadowFrames()) {
+    // We need to deoptimize the stack for the exception handling flow so that
+    // we don't miss any deoptimization that should be done when there are
+    // debugger shadow frames.
+    return true;
+  }
   // We have to require stack deoptimization if the upcall is deoptimized.
   return instrumentation->IsDeoptimized(m);
 }
 
-struct NeedsDeoptimizationVisitor : public StackVisitor {
+class NeedsDeoptimizationVisitor : public StackVisitor {
  public:
   explicit NeedsDeoptimizationVisitor(Thread* self)
       SHARED_REQUIRES(Locks::mutator_lock_)
@@ -3524,6 +3539,13 @@ struct NeedsDeoptimizationVisitor : public StackVisitor {
       needs_deoptimization_ = true;
       return false;
     }
+    ShadowFrame* frame = GetThread()->FindDebuggerShadowFrame(GetFrameId());
+    if (frame != nullptr) {
+      // The debugger allocated a ShadowFrame to update a variable in the stack: we need to
+      // deoptimize the stack to execute (and deallocate) this frame.
+      needs_deoptimization_ = true;
+      return false;
+    }
     return true;
   }
 
index 8278fc6..b4d42de 100644 (file)
@@ -31,6 +31,7 @@
 #include "jdwp/jdwp.h"
 #include "jni.h"
 #include "jvalue.h"
+#include "thread.h"
 #include "thread_state.h"
 
 namespace art {
@@ -570,7 +571,7 @@ class Dbg {
   // execution with interpreter for debugging.
   static bool IsForcedInterpreterNeededForUpcall(Thread* thread, ArtMethod* m)
       SHARED_REQUIRES(Locks::mutator_lock_) {
-    if (!IsDebuggerActive()) {
+    if (!IsDebuggerActive() && !thread->HasDebuggerShadowFrames()) {
       return false;
     }
     return IsForcedInterpreterNeededForUpcallImpl(thread, m);
@@ -583,7 +584,7 @@ class Dbg {
   // the deoptimized frames.
   static bool IsForcedInterpreterNeededForException(Thread* thread)
       SHARED_REQUIRES(Locks::mutator_lock_) {
-    if (!IsDebuggerActive()) {
+    if (!IsDebuggerActive() && !thread->HasDebuggerShadowFrames()) {
       return false;
     }
     return IsForcedInterpreterNeededForExceptionImpl(thread);
@@ -716,8 +717,8 @@ class Dbg {
                                        ScopedObjectAccessUnchecked& soa, int slot,
                                        JDWP::JdwpTag tag, uint8_t* buf, size_t width)
       REQUIRES(!Locks::thread_list_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
-  static JDWP::JdwpError SetLocalValue(StackVisitor& visitor, int slot, JDWP::JdwpTag tag,
-                                       uint64_t value, size_t width)
+  static JDWP::JdwpError SetLocalValue(Thread* thread, StackVisitor& visitor, int slot,
+                                       JDWP::JdwpTag tag, uint64_t value, size_t width)
       REQUIRES(!Locks::thread_list_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
 
   static void DdmBroadcast(bool connect) SHARED_REQUIRES(Locks::mutator_lock_);
index 7db8888..c37d159 100644 (file)
@@ -106,7 +106,9 @@ class EntrypointsOrderTest : public CommonRuntimeTest {
                         sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, stacked_shadow_frame_record,
                         deoptimization_context_stack, sizeof(void*));
-    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, deoptimization_context_stack, name, sizeof(void*));
+    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, deoptimization_context_stack,
+                        frame_id_to_shadow_frame, sizeof(void*));
+    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, frame_id_to_shadow_frame, name, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, name, pthread_self, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, pthread_self, last_no_thread_suspension_cause,
                         sizeof(void*));
index 7e2a84d..deada4c 100644 (file)
@@ -297,6 +297,11 @@ static void InstrumentationInstallStack(Thread* thread, void* arg)
   thread->VerifyStack();
 }
 
+void Instrumentation::InstrumentThreadStack(Thread* thread) {
+  instrumentation_stubs_installed_ = true;
+  InstrumentationInstallStack(thread, this);
+}
+
 // Removes the instrumentation exit pc as the return PC for every quick frame.
 static void InstrumentationRestoreStack(Thread* thread, void* arg)
     SHARED_REQUIRES(Locks::mutator_lock_) {
@@ -1001,7 +1006,8 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, uintpt
 
   ArtMethod* method = instrumentation_frame.method_;
   uint32_t length;
-  char return_shorty = method->GetShorty(&length)[0];
+  const size_t pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+  char return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0];
   JValue return_value;
   if (return_shorty == 'V') {
     return_value.SetJ(0);
index 6711ac3..8046056 100644 (file)
@@ -377,6 +377,13 @@ class Instrumentation {
   void InstallStubsForMethod(ArtMethod* method)
       SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_);
 
+  // Install instrumentation exit stub on every method of the stack of the given thread.
+  // This is used by the debugger to cause a deoptimization of the thread's stack after updating
+  // local variable(s).
+  void InstrumentThreadStack(Thread* thread)
+      SHARED_REQUIRES(Locks::mutator_lock_)
+      REQUIRES(!Locks::thread_list_lock_);
+
  private:
   InstrumentationLevel GetCurrentInstrumentationLevel() const;
 
index b8b8d30..24acbc8 100644 (file)
@@ -32,7 +32,7 @@ class InstructionSetFeatures;
 class PACKED(4) OatHeader {
  public:
   static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
-  static constexpr uint8_t kOatVersion[] = { '0', '7', '0', '\0' };
+  static constexpr uint8_t kOatVersion[] = { '0', '7', '1', '\0' };
 
   static constexpr const char* kImageLocationKey = "image-location";
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
index d797d2a..5c13e13 100644 (file)
@@ -100,6 +100,15 @@ class CatchBlockStackVisitor FINAL : public StackVisitor {
             method->ToNativeQuickPc(found_dex_pc, /* is_catch_handler */ true));
         exception_handler_->SetHandlerQuickFrame(GetCurrentQuickFrame());
         return false;  // End stack walk.
+      } else if (UNLIKELY(GetThread()->HasDebuggerShadowFrames())) {
+        // We are going to unwind this frame. Did we prepare a shadow frame for debugging?
+        size_t frame_id = GetFrameId();
+        ShadowFrame* frame = GetThread()->FindDebuggerShadowFrame(frame_id);
+        if (frame != nullptr) {
+          // We will not execute this shadow frame so we can safely deallocate it.
+          GetThread()->RemoveDebuggerShadowFrameMapping(frame_id);
+          ShadowFrame::DeleteDeoptimizedFrame(frame);
+        }
       }
     }
     return true;  // Continue stack walk.
@@ -310,7 +319,17 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor {
                                       true, true);
     bool verifier_success = verifier.Verify();
     CHECK(verifier_success) << PrettyMethod(m);
-    ShadowFrame* new_frame = ShadowFrame::CreateDeoptimizedFrame(num_regs, nullptr, m, dex_pc);
+    // Check if a shadow frame already exists for debugger's set-local-value purpose.
+    const size_t frame_id = GetFrameId();
+    ShadowFrame* new_frame = GetThread()->FindDebuggerShadowFrame(frame_id);
+    const bool* updated_vregs;
+    if (new_frame == nullptr) {
+      new_frame = ShadowFrame::CreateDeoptimizedFrame(num_regs, nullptr, m, dex_pc);
+      updated_vregs = nullptr;
+    } else {
+      updated_vregs = GetThread()->GetUpdatedVRegFlags(frame_id);
+      DCHECK(updated_vregs != nullptr);
+    }
     {
       ScopedStackedShadowFramePusher pusher(GetThread(), new_frame,
                                             StackedShadowFrameType::kShadowFrameUnderConstruction);
@@ -322,6 +341,10 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor {
       static constexpr uint32_t kDeadValue = 0xEBADDE09;
       static constexpr uint64_t kLongDeadValue = 0xEBADDE09EBADDE09;
       for (uint16_t reg = 0; reg < num_regs; ++reg) {
+        if (updated_vregs != nullptr && updated_vregs[reg]) {
+          // Keep the value set by debugger.
+          continue;
+        }
         VRegKind kind = GetVRegKind(reg, kinds);
         switch (kind) {
           case kUndefined:
@@ -413,6 +436,12 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor {
         }
       }
     }
+    if (updated_vregs != nullptr) {
+      // Calling Thread::RemoveDebuggerShadowFrameMapping will also delete the updated_vregs
+      // array so this must come after we processed the frame.
+      GetThread()->RemoveDebuggerShadowFrameMapping(frame_id);
+      DCHECK(GetThread()->FindDebuggerShadowFrame(frame_id) == nullptr);
+    }
     if (prev_shadow_frame_ != nullptr) {
       prev_shadow_frame_->SetLink(new_frame);
     } else {
index d956f0e..d739743 100644 (file)
@@ -220,10 +220,37 @@ bool StackVisitor::IsReferenceVReg(ArtMethod* m, uint16_t vreg) {
   return vreg < num_regs && TestBitmap(vreg, reg_bitmap);
 }
 
+bool StackVisitor::GetVRegFromDebuggerShadowFrame(uint16_t vreg,
+                                                  VRegKind kind,
+                                                  uint32_t* val) const {
+  size_t frame_id = const_cast<StackVisitor*>(this)->GetFrameId();
+  ShadowFrame* shadow_frame = thread_->FindDebuggerShadowFrame(frame_id);
+  if (shadow_frame != nullptr) {
+    bool* updated_vreg_flags = thread_->GetUpdatedVRegFlags(frame_id);
+    DCHECK(updated_vreg_flags != nullptr);
+    if (updated_vreg_flags[vreg]) {
+      // Value is set by the debugger.
+      if (kind == kReferenceVReg) {
+        *val = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(
+            shadow_frame->GetVRegReference(vreg)));
+      } else {
+        *val = shadow_frame->GetVReg(vreg);
+      }
+      return true;
+    }
+  }
+  // No value is set by the debugger.
+  return false;
+}
+
 bool StackVisitor::GetVReg(ArtMethod* m, uint16_t vreg, VRegKind kind, uint32_t* val) const {
   if (cur_quick_frame_ != nullptr) {
     DCHECK(context_ != nullptr);  // You can't reliably read registers without a context.
     DCHECK(m == GetMethod());
+    // Check if there is value set by the debugger.
+    if (GetVRegFromDebuggerShadowFrame(vreg, kind, val)) {
+      return true;
+    }
     if (m->IsOptimized(sizeof(void*))) {
       return GetVRegFromOptimizedCode(m, vreg, kind, val);
     } else {
@@ -267,7 +294,6 @@ bool StackVisitor::GetVRegFromOptimizedCode(ArtMethod* m, uint16_t vreg, VRegKin
                                                     // its instructions?
   uint16_t number_of_dex_registers = code_item->registers_size_;
   DCHECK_LT(vreg, code_item->registers_size_);
-
   ArtMethod* outer_method = *GetCurrentQuickFrame();
   const void* code_pointer = outer_method->GetQuickOatCodePointer(sizeof(void*));
   DCHECK(code_pointer != nullptr);
@@ -348,6 +374,20 @@ bool StackVisitor::GetRegisterIfAccessible(uint32_t reg, VRegKind kind, uint32_t
   return true;
 }
 
+bool StackVisitor::GetVRegPairFromDebuggerShadowFrame(uint16_t vreg,
+                                                      VRegKind kind_lo,
+                                                      VRegKind kind_hi,
+                                                      uint64_t* val) const {
+  uint32_t low_32bits;
+  uint32_t high_32bits;
+  bool success = GetVRegFromDebuggerShadowFrame(vreg, kind_lo, &low_32bits);
+  success &= GetVRegFromDebuggerShadowFrame(vreg + 1, kind_hi, &high_32bits);
+  if (success) {
+    *val = (static_cast<uint64_t>(high_32bits) << 32) | static_cast<uint64_t>(low_32bits);
+  }
+  return success;
+}
+
 bool StackVisitor::GetVRegPair(ArtMethod* m, uint16_t vreg, VRegKind kind_lo,
                                VRegKind kind_hi, uint64_t* val) const {
   if (kind_lo == kLongLoVReg) {
@@ -358,6 +398,10 @@ bool StackVisitor::GetVRegPair(ArtMethod* m, uint16_t vreg, VRegKind kind_lo,
     LOG(FATAL) << "Expected long or double: kind_lo=" << kind_lo << ", kind_hi=" << kind_hi;
     UNREACHABLE();
   }
+  // Check if there is value set by the debugger.
+  if (GetVRegPairFromDebuggerShadowFrame(vreg, kind_lo, kind_hi, val)) {
+    return true;
+  }
   if (cur_quick_frame_ != nullptr) {
     DCHECK(context_ != nullptr);  // You can't reliably read registers without a context.
     DCHECK(m == GetMethod());
@@ -435,17 +479,17 @@ bool StackVisitor::GetRegisterPairIfAccessible(uint32_t reg_lo, uint32_t reg_hi,
 bool StackVisitor::SetVReg(ArtMethod* m, uint16_t vreg, uint32_t new_value,
                            VRegKind kind) {
   if (cur_quick_frame_ != nullptr) {
-      DCHECK(context_ != nullptr);  // You can't reliably write registers without a context.
-      DCHECK(m == GetMethod());
-      if (m->IsOptimized(sizeof(void*))) {
-        return false;
-      } else {
-        return SetVRegFromQuickCode(m, vreg, new_value, kind);
-      }
+    DCHECK(context_ != nullptr);  // You can't reliably write registers without a context.
+    DCHECK(m == GetMethod());
+    if (m->IsOptimized(sizeof(void*))) {
+      return false;
     } else {
-      cur_shadow_frame_->SetVReg(vreg, new_value);
-      return true;
+      return SetVRegFromQuickCode(m, vreg, new_value, kind);
     }
+  } else {
+    cur_shadow_frame_->SetVReg(vreg, new_value);
+    return true;
+  }
 }
 
 bool StackVisitor::SetVRegFromQuickCode(ArtMethod* m, uint16_t vreg, uint32_t new_value,
@@ -475,6 +519,34 @@ bool StackVisitor::SetVRegFromQuickCode(ArtMethod* m, uint16_t vreg, uint32_t ne
   }
 }
 
+bool StackVisitor::SetVRegFromDebugger(ArtMethod* m,
+                                       uint16_t vreg,
+                                       uint32_t new_value,
+                                       VRegKind kind) {
+  const DexFile::CodeItem* code_item = m->GetCodeItem();
+  if (code_item == nullptr) {
+    return false;
+  }
+  ShadowFrame* shadow_frame = GetCurrentShadowFrame();
+  if (shadow_frame == nullptr) {
+    // This is a compiled frame: we must prepare and update a shadow frame that will
+    // be executed by the interpreter after deoptimization of the stack.
+    const size_t frame_id = GetFrameId();
+    const uint16_t num_regs = code_item->registers_size_;
+    shadow_frame = thread_->FindOrCreateDebuggerShadowFrame(frame_id, num_regs, m, GetDexPc());
+    CHECK(shadow_frame != nullptr);
+    // Remember the vreg has been set for debugging and must not be overwritten by the
+    // original value during deoptimization of the stack.
+    thread_->GetUpdatedVRegFlags(frame_id)[vreg] = true;
+  }
+  if (kind == kReferenceVReg) {
+    shadow_frame->SetVRegReference(vreg, reinterpret_cast<mirror::Object*>(new_value));
+  } else {
+    shadow_frame->SetVReg(vreg, new_value);
+  }
+  return true;
+}
+
 bool StackVisitor::SetRegisterIfAccessible(uint32_t reg, uint32_t new_value, VRegKind kind) {
   const bool is_float = (kind == kFloatVReg) || (kind == kDoubleLoVReg) || (kind == kDoubleHiVReg);
   if (!IsAccessibleRegister(reg, is_float)) {
@@ -557,6 +629,39 @@ bool StackVisitor::SetVRegPairFromQuickCode(
   }
 }
 
+bool StackVisitor::SetVRegPairFromDebugger(ArtMethod* m,
+                                           uint16_t vreg,
+                                           uint64_t new_value,
+                                           VRegKind kind_lo,
+                                           VRegKind kind_hi) {
+  if (kind_lo == kLongLoVReg) {
+    DCHECK_EQ(kind_hi, kLongHiVReg);
+  } else if (kind_lo == kDoubleLoVReg) {
+    DCHECK_EQ(kind_hi, kDoubleHiVReg);
+  } else {
+    LOG(FATAL) << "Expected long or double: kind_lo=" << kind_lo << ", kind_hi=" << kind_hi;
+    UNREACHABLE();
+  }
+  const DexFile::CodeItem* code_item = m->GetCodeItem();
+  if (code_item == nullptr) {
+    return false;
+  }
+  ShadowFrame* shadow_frame = GetCurrentShadowFrame();
+  if (shadow_frame == nullptr) {
+    // This is a compiled frame: we must prepare for deoptimization (see SetVRegFromDebugger).
+    const size_t frame_id = GetFrameId();
+    const uint16_t num_regs = code_item->registers_size_;
+    shadow_frame = thread_->FindOrCreateDebuggerShadowFrame(frame_id, num_regs, m, GetDexPc());
+    CHECK(shadow_frame != nullptr);
+    // Remember the vreg pair has been set for debugging and must not be overwritten by the
+    // original value during deoptimization of the stack.
+    thread_->GetUpdatedVRegFlags(frame_id)[vreg] = true;
+    thread_->GetUpdatedVRegFlags(frame_id)[vreg + 1] = true;
+  }
+  shadow_frame->SetVRegLong(vreg, new_value);
+  return true;
+}
+
 bool StackVisitor::SetRegisterPairIfAccessible(uint32_t reg_lo, uint32_t reg_hi,
                                                uint64_t new_value, bool is_float) {
   if (!IsAccessibleRegister(reg_lo, is_float) || !IsAccessibleRegister(reg_hi, is_float)) {
index 5bbf003..b805239 100644 (file)
@@ -507,10 +507,21 @@ class StackVisitor {
   bool SetVReg(ArtMethod* m, uint16_t vreg, uint32_t new_value, VRegKind kind)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
+  // Values will be set in debugger shadow frames. Debugger will make sure deoptimization
+  // is triggered to make the values effective.
+  bool SetVRegFromDebugger(ArtMethod* m, uint16_t vreg, uint32_t new_value, VRegKind kind)
+      SHARED_REQUIRES(Locks::mutator_lock_);
+
   bool SetVRegPair(ArtMethod* m, uint16_t vreg, uint64_t new_value,
                    VRegKind kind_lo, VRegKind kind_hi)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
+  // Values will be set in debugger shadow frames. Debugger will make sure deoptimization
+  // is triggered to make the values effective.
+  bool SetVRegPairFromDebugger(ArtMethod* m, uint16_t vreg, uint64_t new_value,
+                               VRegKind kind_lo, VRegKind kind_hi)
+      SHARED_REQUIRES(Locks::mutator_lock_);
+
   uintptr_t* GetGPRAddress(uint32_t reg) const;
 
   // This is a fast-path for getting/setting values in a quick frame.
@@ -645,6 +656,8 @@ class StackVisitor {
   uintptr_t GetFPR(uint32_t reg) const;
   void SetFPR(uint32_t reg, uintptr_t value);
 
+  bool GetVRegFromDebuggerShadowFrame(uint16_t vreg, VRegKind kind, uint32_t* val) const
+      SHARED_REQUIRES(Locks::mutator_lock_);
   bool GetVRegFromQuickCode(ArtMethod* m, uint16_t vreg, VRegKind kind,
                             uint32_t* val) const
       SHARED_REQUIRES(Locks::mutator_lock_);
@@ -654,6 +667,9 @@ class StackVisitor {
   bool GetRegisterIfAccessible(uint32_t reg, VRegKind kind, uint32_t* val) const
       SHARED_REQUIRES(Locks::mutator_lock_);
 
+  bool GetVRegPairFromDebuggerShadowFrame(uint16_t vreg, VRegKind kind_lo, VRegKind kind_hi,
+                                          uint64_t* val) const
+      SHARED_REQUIRES(Locks::mutator_lock_);
   bool GetVRegPairFromQuickCode(ArtMethod* m, uint16_t vreg, VRegKind kind_lo,
                                 VRegKind kind_hi, uint64_t* val) const
       SHARED_REQUIRES(Locks::mutator_lock_);
index 6e10368..5bf895e 100644 (file)
@@ -260,6 +260,125 @@ ShadowFrame* Thread::PopStackedShadowFrame(StackedShadowFrameType type) {
   return shadow_frame;
 }
 
+class FrameIdToShadowFrame {
+ public:
+  static FrameIdToShadowFrame* Create(size_t frame_id,
+                                      ShadowFrame* shadow_frame,
+                                      FrameIdToShadowFrame* next,
+                                      size_t num_vregs) {
+    // Append a bool array at the end to keep track of what vregs are updated by the debugger.
+    uint8_t* memory = new uint8_t[sizeof(FrameIdToShadowFrame) + sizeof(bool) * num_vregs];
+    return new (memory) FrameIdToShadowFrame(frame_id, shadow_frame, next);
+  }
+
+  static void Delete(FrameIdToShadowFrame* f) {
+    uint8_t* memory = reinterpret_cast<uint8_t*>(f);
+    delete[] memory;
+  }
+
+  size_t GetFrameId() const { return frame_id_; }
+  ShadowFrame* GetShadowFrame() const { return shadow_frame_; }
+  FrameIdToShadowFrame* GetNext() const { return next_; }
+  void SetNext(FrameIdToShadowFrame* next) { next_ = next; }
+  bool* GetUpdatedVRegFlags() {
+    return updated_vreg_flags_;
+  }
+
+ private:
+  FrameIdToShadowFrame(size_t frame_id,
+                       ShadowFrame* shadow_frame,
+                       FrameIdToShadowFrame* next)
+      : frame_id_(frame_id),
+        shadow_frame_(shadow_frame),
+        next_(next) {}
+
+  const size_t frame_id_;
+  ShadowFrame* const shadow_frame_;
+  FrameIdToShadowFrame* next_;
+  bool updated_vreg_flags_[0];
+
+  DISALLOW_COPY_AND_ASSIGN(FrameIdToShadowFrame);
+};
+
+static FrameIdToShadowFrame* FindFrameIdToShadowFrame(FrameIdToShadowFrame* head,
+                                                      size_t frame_id) {
+  FrameIdToShadowFrame* found = nullptr;
+  for (FrameIdToShadowFrame* record = head; record != nullptr; record = record->GetNext()) {
+    if (record->GetFrameId() == frame_id) {
+      if (kIsDebugBuild) {
+        // Sanity check we have at most one record for this frame.
+        CHECK(found == nullptr) << "Multiple records for the frame " << frame_id;
+        found = record;
+      } else {
+        return record;
+      }
+    }
+  }
+  return found;
+}
+
+ShadowFrame* Thread::FindDebuggerShadowFrame(size_t frame_id) {
+  FrameIdToShadowFrame* record = FindFrameIdToShadowFrame(
+      tlsPtr_.frame_id_to_shadow_frame, frame_id);
+  if (record != nullptr) {
+    return record->GetShadowFrame();
+  }
+  return nullptr;
+}
+
+// Must only be called when FindDebuggerShadowFrame(frame_id) returns non-nullptr.
+bool* Thread::GetUpdatedVRegFlags(size_t frame_id) {
+  FrameIdToShadowFrame* record = FindFrameIdToShadowFrame(
+      tlsPtr_.frame_id_to_shadow_frame, frame_id);
+  CHECK(record != nullptr);
+  return record->GetUpdatedVRegFlags();
+}
+
+ShadowFrame* Thread::FindOrCreateDebuggerShadowFrame(size_t frame_id,
+                                                     uint32_t num_vregs,
+                                                     ArtMethod* method,
+                                                     uint32_t dex_pc) {
+  ShadowFrame* shadow_frame = FindDebuggerShadowFrame(frame_id);
+  if (shadow_frame != nullptr) {
+    return shadow_frame;
+  }
+  VLOG(deopt) << "Create pre-deopted ShadowFrame for " << PrettyMethod(method);
+  shadow_frame = ShadowFrame::CreateDeoptimizedFrame(num_vregs, nullptr, method, dex_pc);
+  FrameIdToShadowFrame* record = FrameIdToShadowFrame::Create(frame_id,
+                                                              shadow_frame,
+                                                              tlsPtr_.frame_id_to_shadow_frame,
+                                                              num_vregs);
+  for (uint32_t i = 0; i < num_vregs; i++) {
+    // Do this to clear all references for root visitors.
+    shadow_frame->SetVRegReference(i, nullptr);
+    // This flag will be changed to true if the debugger modifies the value.
+    record->GetUpdatedVRegFlags()[i] = false;
+  }
+  tlsPtr_.frame_id_to_shadow_frame = record;
+  return shadow_frame;
+}
+
+void Thread::RemoveDebuggerShadowFrameMapping(size_t frame_id) {
+  FrameIdToShadowFrame* head = tlsPtr_.frame_id_to_shadow_frame;
+  if (head->GetFrameId() == frame_id) {
+    tlsPtr_.frame_id_to_shadow_frame = head->GetNext();
+    FrameIdToShadowFrame::Delete(head);
+    return;
+  }
+  FrameIdToShadowFrame* prev = head;
+  for (FrameIdToShadowFrame* record = head->GetNext();
+       record != nullptr;
+       prev = record, record = record->GetNext()) {
+    if (record->GetFrameId() == frame_id) {
+      prev->SetNext(record->GetNext());
+      FrameIdToShadowFrame::Delete(record);
+      return;
+    }
+  }
+  LOG(FATAL) << "No shadow frame for frame " << frame_id;
+  UNREACHABLE();
+}
+
 void Thread::InitTid() {
   tls32_.tid = ::art::GetTid();
 }
@@ -1594,6 +1713,8 @@ Thread::~Thread() {
 
   // Make sure we processed all deoptimization requests.
   CHECK(tlsPtr_.deoptimization_context_stack == nullptr) << "Missed deoptimization";
+  CHECK(tlsPtr_.frame_id_to_shadow_frame == nullptr) <<
+      "Not all deoptimized frames have been consumed by the debugger.";
 
   // We may be deleting a still born thread.
   SetStateUnsafe(kTerminated);
@@ -2661,17 +2782,24 @@ void Thread::VisitRoots(RootVisitor* visitor) {
       }
     }
   }
-  if (tlsPtr_.deoptimization_context_stack != nullptr) {
-    for (DeoptimizationContextRecord* record = tlsPtr_.deoptimization_context_stack;
-         record != nullptr;
-         record = record->GetLink()) {
-      if (record->IsReference()) {
-        visitor->VisitRootIfNonNull(record->GetReturnValueAsGCRoot(),
-                                    RootInfo(kRootThreadObject, thread_id));
-      }
-      visitor->VisitRootIfNonNull(record->GetPendingExceptionAsGCRoot(),
+  for (DeoptimizationContextRecord* record = tlsPtr_.deoptimization_context_stack;
+       record != nullptr;
+       record = record->GetLink()) {
+    if (record->IsReference()) {
+      visitor->VisitRootIfNonNull(record->GetReturnValueAsGCRoot(),
                                   RootInfo(kRootThreadObject, thread_id));
     }
+    visitor->VisitRootIfNonNull(record->GetPendingExceptionAsGCRoot(),
+                                RootInfo(kRootThreadObject, thread_id));
+  }
+  if (tlsPtr_.frame_id_to_shadow_frame != nullptr) {
+    RootCallbackVisitor visitor_to_callback(visitor, thread_id);
+    ReferenceMapVisitor<RootCallbackVisitor> mapper(this, nullptr, visitor_to_callback);
+    for (FrameIdToShadowFrame* record = tlsPtr_.frame_id_to_shadow_frame;
+         record != nullptr;
+         record = record->GetNext()) {
+      mapper.VisitShadowFrame(record->GetShadowFrame());
+    }
   }
   for (auto* verifier = tlsPtr_.method_verifier; verifier != nullptr; verifier = verifier->link_) {
     verifier->VisitRoots(visitor, RootInfo(kRootNativeStack, thread_id));
index 2d450f5..11f2e28 100644 (file)
@@ -79,6 +79,7 @@ class Context;
 struct DebugInvokeReq;
 class DeoptimizationContextRecord;
 class DexFile;
+class FrameIdToShadowFrame;
 class JavaVMExt;
 struct JNIEnvExt;
 class Monitor;
@@ -516,6 +517,10 @@ class Thread {
       jobjectArray output_array = nullptr, int* stack_depth = nullptr)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
+  bool HasDebuggerShadowFrames() const {
+    return tlsPtr_.frame_id_to_shadow_frame != nullptr;
+  }
+
   void VisitRoots(RootVisitor* visitor) SHARED_REQUIRES(Locks::mutator_lock_);
 
   ALWAYS_INLINE void VerifyStack() SHARED_REQUIRES(Locks::mutator_lock_);
@@ -840,6 +845,25 @@ class Thread {
   void PushStackedShadowFrame(ShadowFrame* sf, StackedShadowFrameType type);
   ShadowFrame* PopStackedShadowFrame(StackedShadowFrameType type);
 
+  // For debugger, find the shadow frame that corresponds to a frame id.
+  // Or return null if there is none.
+  ShadowFrame* FindDebuggerShadowFrame(size_t frame_id)
+      SHARED_REQUIRES(Locks::mutator_lock_);
+  // For debugger, find the bool array that keeps track of the updated vreg set
+  // for a frame id.
+  bool* GetUpdatedVRegFlags(size_t frame_id) SHARED_REQUIRES(Locks::mutator_lock_);
+  // For debugger, find the shadow frame that corresponds to a frame id. If
+  // one doesn't exist yet, create one and track it in frame_id_to_shadow_frame.
+  ShadowFrame* FindOrCreateDebuggerShadowFrame(size_t frame_id,
+                                               uint32_t num_vregs,
+                                               ArtMethod* method,
+                                               uint32_t dex_pc)
+      SHARED_REQUIRES(Locks::mutator_lock_);
+
+  // Delete the entry that maps from frame_id to shadow_frame.
+  void RemoveDebuggerShadowFrameMapping(size_t frame_id)
+      SHARED_REQUIRES(Locks::mutator_lock_);
+
   std::deque<instrumentation::InstrumentationStackFrame>* GetInstrumentationStack() {
     return tlsPtr_.instrumentation_stack;
   }
@@ -1184,7 +1208,7 @@ class Thread {
       top_handle_scope(nullptr), class_loader_override(nullptr), long_jump_context(nullptr),
       instrumentation_stack(nullptr), debug_invoke_req(nullptr), single_step_control(nullptr),
       stacked_shadow_frame_record(nullptr), deoptimization_context_stack(nullptr),
-      name(nullptr), pthread_self(0),
+      frame_id_to_shadow_frame(nullptr), name(nullptr), pthread_self(0),
       last_no_thread_suspension_cause(nullptr), thread_local_start(nullptr),
       thread_local_pos(nullptr), thread_local_end(nullptr), thread_local_objects(0),
       thread_local_alloc_stack_top(nullptr), thread_local_alloc_stack_end(nullptr),
@@ -1270,6 +1294,11 @@ class Thread {
     // Deoptimization return value record stack.
     DeoptimizationContextRecord* deoptimization_context_stack;
 
+    // For debugger, a linked list that keeps the mapping from frame_id to shadow frame.
+    // Shadow frames may be created before deoptimization happens so that the debugger can
+    // set local values there first.
+    FrameIdToShadowFrame* frame_id_to_shadow_frame;
+
     // A cached copy of the java.lang.Thread's name.
     std::string* name;