diff options
| author | 2015-09-11 15:40:48 +0000 | |
|---|---|---|
| committer | 2015-09-11 15:40:48 +0000 | |
| commit | e2cb7f297dfeb1a6e19c3b8d45b742427054fa56 (patch) | |
| tree | b5501cd32e859351b5f058da56e62accb03a3821 | |
| parent | 15982a940dd0032e225f8d2829609d7a35a3d842 (diff) | |
| parent | 520633bebd2bf4d70884d30f179dbde9f275aac6 (diff) | |
Merge "Support deoptimization on exception"
| -rw-r--r-- | runtime/debugger.cc | 56 | ||||
| -rw-r--r-- | runtime/debugger.h | 16 | ||||
| -rw-r--r-- | runtime/interpreter/interpreter.cc | 8 | ||||
| -rw-r--r-- | runtime/interpreter/interpreter_common.cc | 5 | ||||
| -rw-r--r-- | runtime/quick_exception_handler.cc | 22 | ||||
| -rw-r--r-- | runtime/quick_exception_handler.h | 12 | ||||
| -rw-r--r-- | runtime/thread.cc | 23 |
7 files changed, 124 insertions, 18 deletions
diff --git a/runtime/debugger.cc b/runtime/debugger.cc index c3bd5754fd..72226af76d 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -3501,6 +3501,62 @@ bool Dbg::IsForcedInterpreterNeededForUpcallImpl(Thread* thread, ArtMethod* m) { return instrumentation->IsDeoptimized(m); } +struct NeedsDeoptimizationVisitor : public StackVisitor { + public: + explicit NeedsDeoptimizationVisitor(Thread* self) + SHARED_REQUIRES(Locks::mutator_lock_) + : StackVisitor(self, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + needs_deoptimization_(false) {} + + bool VisitFrame() OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { + // The visitor is meant to be used when handling exception from compiled code only. + CHECK(!IsShadowFrame()) << "We only expect to visit compiled frame: " << PrettyMethod(GetMethod()); + ArtMethod* method = GetMethod(); + if (method == nullptr) { + // We reach an upcall and don't need to deoptimize this part of the stack (ManagedFragment) + // so we can stop the visit. + DCHECK(!needs_deoptimization_); + return false; + } + if (Runtime::Current()->GetInstrumentation()->InterpretOnly()) { + // We found a compiled frame in the stack but instrumentation is set to interpret + // everything: we need to deoptimize. + needs_deoptimization_ = true; + return false; + } + if (Runtime::Current()->GetInstrumentation()->IsDeoptimized(method)) { + // We found a deoptimized method in the stack. + needs_deoptimization_ = true; + return false; + } + return true; + } + + bool NeedsDeoptimization() const { + return needs_deoptimization_; + } + + private: + // Do we need to deoptimize the stack? + bool needs_deoptimization_; + + DISALLOW_COPY_AND_ASSIGN(NeedsDeoptimizationVisitor); +}; + +// Do we need to deoptimize the stack to handle an exception? +bool Dbg::IsForcedInterpreterNeededForExceptionImpl(Thread* thread) { + const SingleStepControl* const ssc = thread->GetSingleStepControl(); + if (ssc != nullptr) { + // We deopt to step into the catch handler. + return true; + } + // Deoptimization is required if at least one method in the stack needs it. However we + // skip frames that will be unwound (thus not executed). + NeedsDeoptimizationVisitor visitor(thread); + visitor.WalkStack(true); // includes upcall. + return visitor.NeedsDeoptimization(); +} + // Scoped utility class to suspend a thread so that we may do tasks such as walk its stack. Doesn't // cause suspension if the thread is the current thread. class ScopedDebuggerThreadSuspension { diff --git a/runtime/debugger.h b/runtime/debugger.h index a9fa6ce8cb..8278fc6e9e 100644 --- a/runtime/debugger.h +++ b/runtime/debugger.h @@ -576,6 +576,19 @@ class Dbg { return IsForcedInterpreterNeededForUpcallImpl(thread, m); } + // Indicates whether we need to force the use of interpreter when handling an + // exception. This allows to deoptimize the stack and continue execution with + // the interpreter. + // Note: the interpreter will start by handling the exception when executing + // the deoptimized frames. + static bool IsForcedInterpreterNeededForException(Thread* thread) + SHARED_REQUIRES(Locks::mutator_lock_) { + if (!IsDebuggerActive()) { + return false; + } + return IsForcedInterpreterNeededForExceptionImpl(thread); + } + // Single-stepping. static JDWP::JdwpError ConfigureStep(JDWP::ObjectId thread_id, JDWP::JdwpStepSize size, JDWP::JdwpStepDepth depth) @@ -734,6 +747,9 @@ class Dbg { static bool IsForcedInterpreterNeededForUpcallImpl(Thread* thread, ArtMethod* m) SHARED_REQUIRES(Locks::mutator_lock_); + static bool IsForcedInterpreterNeededForExceptionImpl(Thread* thread) + SHARED_REQUIRES(Locks::mutator_lock_); + // Indicates whether the debugger is making requests. static bool gDebuggerActive; diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index 6c6232c437..3ac80c6642 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -399,14 +399,19 @@ void EnterInterpreterFromDeoptimize(Thread* self, ShadowFrame* shadow_frame, JVa JValue value; // Set value to last known result in case the shadow frame chain is empty. value.SetJ(ret_val->GetJ()); + // Are we executing the first shadow frame? + bool first = true; while (shadow_frame != nullptr) { self->SetTopOfShadowStack(shadow_frame); const DexFile::CodeItem* code_item = shadow_frame->GetMethod()->GetCodeItem(); const uint32_t dex_pc = shadow_frame->GetDexPC(); uint32_t new_dex_pc; if (UNLIKELY(self->IsExceptionPending())) { + // If we deoptimize from the QuickExceptionHandler, we already reported the exception to + // the instrumentation. To prevent from reporting it a second time, we simply pass a + // null Instrumentation*. const instrumentation::Instrumentation* const instrumentation = - Runtime::Current()->GetInstrumentation(); + first ? nullptr : Runtime::Current()->GetInstrumentation(); uint32_t found_dex_pc = FindNextInstructionFollowingException(self, *shadow_frame, dex_pc, instrumentation); new_dex_pc = found_dex_pc; // the dex pc of a matching catch handler @@ -424,6 +429,7 @@ void EnterInterpreterFromDeoptimize(Thread* self, ShadowFrame* shadow_frame, JVa ShadowFrame* old_frame = shadow_frame; shadow_frame = shadow_frame->GetLink(); ShadowFrame::DeleteDeoptimizedFrame(old_frame); + first = false; } ret_val->SetJ(value.GetJ()); } diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index af67379375..6602840ed0 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -414,20 +414,21 @@ EXPLICIT_DO_IPUT_QUICK_ALL_TEMPLATE_DECL(Primitive::kPrimNot) // iput-objec #undef EXPLICIT_DO_IPUT_QUICK_ALL_TEMPLATE_DECL #undef EXPLICIT_DO_IPUT_QUICK_TEMPLATE_DECL +// We accept a null Instrumentation* meaning we must not report anything to the instrumentation. uint32_t FindNextInstructionFollowingException( Thread* self, ShadowFrame& shadow_frame, uint32_t dex_pc, const instrumentation::Instrumentation* instrumentation) { self->VerifyStack(); StackHandleScope<2> hs(self); Handle<mirror::Throwable> exception(hs.NewHandle(self->GetException())); - if (instrumentation->HasExceptionCaughtListeners() + if (instrumentation != nullptr && instrumentation->HasExceptionCaughtListeners() && self->IsExceptionThrownByCurrentMethod(exception.Get())) { instrumentation->ExceptionCaughtEvent(self, exception.Get()); } bool clear_exception = false; uint32_t found_dex_pc = shadow_frame.GetMethod()->FindCatchBlock( hs.NewHandle(exception->GetClass()), dex_pc, &clear_exception); - if (found_dex_pc == DexFile::kDexNoIndex) { + if (found_dex_pc == DexFile::kDexNoIndex && instrumentation != nullptr) { // Exception is not caught by the current method. We will unwind to the // caller. Notify any instrumentation listener. instrumentation->MethodUnwindEvent(self, shadow_frame.GetThisObject(), diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc index 9d5ce9f385..60defbaaa3 100644 --- a/runtime/quick_exception_handler.cc +++ b/runtime/quick_exception_handler.cc @@ -40,7 +40,7 @@ QuickExceptionHandler::QuickExceptionHandler(Thread* self, bool is_deoptimizatio handler_dex_pc_(0), clear_exception_(false), handler_frame_depth_(kInvalidFrameDepth) { } -// Finds catch handler or prepares for deoptimization. +// Finds catch handler. class CatchBlockStackVisitor FINAL : public StackVisitor { public: CatchBlockStackVisitor(Thread* self, Context* context, Handle<mirror::Throwable>* exception, @@ -125,7 +125,7 @@ void QuickExceptionHandler::FindCatch(mirror::Throwable* exception) { StackHandleScope<1> hs(self_); Handle<mirror::Throwable> exception_ref(hs.NewHandle(exception)); - // Walk the stack to find catch handler or prepare for deoptimization. + // Walk the stack to find catch handler. CatchBlockStackVisitor visitor(self_, context_, &exception_ref, this); visitor.WalkStack(true); @@ -146,16 +146,6 @@ void QuickExceptionHandler::FindCatch(mirror::Throwable* exception) { // Put exception back in root set with clear throw location. self_->SetException(exception_ref.Get()); } - // The debugger may suspend this thread and walk its stack. Let's do this before popping - // instrumentation frames. - instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); - if (instrumentation->HasExceptionCaughtListeners() - && self_->IsExceptionThrownByCurrentMethod(exception)) { - instrumentation->ExceptionCaughtEvent(self_, exception_ref.Get()); - // Instrumentation may have been updated. - method_tracing_active_ = is_deoptimization_ || - Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled(); - } } // Prepares deoptimization. @@ -189,6 +179,12 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { // Ignore callee save method. DCHECK(method->IsCalleeSaveMethod()); return true; + } else if (method->IsNative()) { + // If we return from JNI with a pending exception and want to deoptimize, we need to skip + // the native method. + // The top method is a runtime method, the native method comes next. + CHECK_EQ(GetFrameDepth(), 1U); + return true; } else { return HandleDeoptimization(method); } @@ -201,7 +197,7 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { bool HandleDeoptimization(ArtMethod* m) SHARED_REQUIRES(Locks::mutator_lock_) { const DexFile::CodeItem* code_item = m->GetCodeItem(); - CHECK(code_item != nullptr); + CHECK(code_item != nullptr) << "No code item for " << PrettyMethod(m); uint16_t num_regs = code_item->registers_size_; uint32_t dex_pc = GetDexPc(); StackHandleScope<2> hs(self_); // Dex cache, class loader and method. diff --git a/runtime/quick_exception_handler.h b/runtime/quick_exception_handler.h index e934834e3c..4db95a87ec 100644 --- a/runtime/quick_exception_handler.h +++ b/runtime/quick_exception_handler.h @@ -43,9 +43,18 @@ class QuickExceptionHandler { UNREACHABLE(); } + // Find the catch handler for the given exception. void FindCatch(mirror::Throwable* exception) SHARED_REQUIRES(Locks::mutator_lock_); + + // Deoptimize the stack to the upcall. For every compiled frame, we create a "copy" + // shadow frame that will be executed with the interpreter. void DeoptimizeStack() SHARED_REQUIRES(Locks::mutator_lock_); + + // Update the instrumentation stack by removing all methods that will be unwound + // by the exception being thrown. void UpdateInstrumentationStack() SHARED_REQUIRES(Locks::mutator_lock_); + + // Long jump either to a catch handler or to the upcall. NO_RETURN void DoLongJump() SHARED_REQUIRES(Locks::mutator_lock_); void SetHandlerQuickFrame(ArtMethod** handler_quick_frame) { @@ -83,9 +92,10 @@ class QuickExceptionHandler { private: Thread* const self_; Context* const context_; + // Should we deoptimize the stack? const bool is_deoptimization_; // Is method tracing active? - bool method_tracing_active_; + const bool method_tracing_active_; // Quick frame with found handler or last frame if no handler found. ArtMethod** handler_quick_frame_; // PC to branch to for the handler. diff --git a/runtime/thread.cc b/runtime/thread.cc index af5830aafb..86ac1407da 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -2344,10 +2344,31 @@ void Thread::QuickDeliverException() { // Get exception from thread. mirror::Throwable* exception = GetException(); CHECK(exception != nullptr); + bool is_deoptimization = (exception == GetDeoptimizationException()); + if (!is_deoptimization) { + // This is a real exception: let the instrumentation know about it. + instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); + if (instrumentation->HasExceptionCaughtListeners() && + IsExceptionThrownByCurrentMethod(exception)) { + // Instrumentation may cause GC so keep the exception object safe. + StackHandleScope<1> hs(this); + HandleWrapper<mirror::Throwable> h_exception(hs.NewHandleWrapper(&exception)); + instrumentation->ExceptionCaughtEvent(this, exception); + } + // Does instrumentation need to deoptimize the stack? + // Note: we do this *after* reporting the exception to instrumentation in case it + // now requires deoptimization. It may happen if a debugger is attached and requests + // new events (single-step, breakpoint, ...) when the exception is reported. + is_deoptimization = Dbg::IsForcedInterpreterNeededForException(this); + if (is_deoptimization) { + // Save the exception into the deoptimization context so it can be restored + // before entering the interpreter. + PushDeoptimizationContext(JValue(), false, exception); + } + } // Don't leave exception visible while we try to find the handler, which may cause class // resolution. ClearException(); - bool is_deoptimization = (exception == GetDeoptimizationException()); QuickExceptionHandler exception_handler(this, is_deoptimization); if (is_deoptimization) { exception_handler.DeoptimizeStack(); |