Fix deoptimization with pending exception
When deoptimizing the stack, we set a fake exception in the current
Thread* (see method Thread::GetDeoptimizationException). On the next
exception check, the QuickExceptionHandler will deoptimize the stack.
The issue is when we deoptimize while an exception is already pending
in the current Thread*: setting the fake exception will clobber the
pending exception which is not correct. This happens in the
artQuickToInterpreterBridge when returning from the interpreter and
we want to deoptimize the stack for debugging (like single-stepping).
This CL saves the pending exception before asking for deoptimization.
Then the exception is restored just before executing the deoptimized
frames with the interpreter.
Also cleans up the way we save deoptimization context (return value
and pending exception).
Bug: 23371176
Bug: 19944235
Change-Id: I7f4c8347b328817c452beda3399e210eba3a88a4
diff --git a/runtime/thread.cc b/runtime/thread.cc
index a33e150..63534b1 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -162,27 +162,41 @@
ResetQuickAllocEntryPoints(&tlsPtr_.quick_entrypoints);
}
-class DeoptimizationReturnValueRecord {
+class DeoptimizationContextRecord {
public:
- DeoptimizationReturnValueRecord(const JValue& ret_val,
- bool is_reference,
- DeoptimizationReturnValueRecord* link)
- : ret_val_(ret_val), is_reference_(is_reference), link_(link) {}
+ DeoptimizationContextRecord(const JValue& ret_val, bool is_reference,
+ mirror::Throwable* pending_exception,
+ DeoptimizationContextRecord* link)
+ : ret_val_(ret_val), is_reference_(is_reference), pending_exception_(pending_exception),
+ link_(link) {}
JValue GetReturnValue() const { return ret_val_; }
bool IsReference() const { return is_reference_; }
- DeoptimizationReturnValueRecord* GetLink() const { return link_; }
- mirror::Object** GetGCRoot() {
+ mirror::Throwable* GetPendingException() const { return pending_exception_; }
+ DeoptimizationContextRecord* GetLink() const { return link_; }
+ mirror::Object** GetReturnValueAsGCRoot() {
DCHECK(is_reference_);
return ret_val_.GetGCRoot();
}
+ mirror::Object** GetPendingExceptionAsGCRoot() {
+ return reinterpret_cast<mirror::Object**>(&pending_exception_);
+ }
private:
+ // The value returned by the method at the top of the stack before deoptimization.
JValue ret_val_;
- const bool is_reference_;
- DeoptimizationReturnValueRecord* const link_;
- DISALLOW_COPY_AND_ASSIGN(DeoptimizationReturnValueRecord);
+ // Indicates whether the returned value is a reference. If so, the GC will visit it.
+ const bool is_reference_;
+
+ // The exception that was pending before deoptimization (or null if there was no pending
+ // exception).
+ mirror::Throwable* pending_exception_;
+
+ // A link to the previous DeoptimizationContextRecord.
+ DeoptimizationContextRecord* const link_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeoptimizationContextRecord);
};
class StackedShadowFrameRecord {
@@ -206,22 +220,28 @@
DISALLOW_COPY_AND_ASSIGN(StackedShadowFrameRecord);
};
-void Thread::PushAndClearDeoptimizationReturnValue() {
- DeoptimizationReturnValueRecord* record = new DeoptimizationReturnValueRecord(
- tls64_.deoptimization_return_value,
- tls32_.deoptimization_return_value_is_reference,
- tlsPtr_.deoptimization_return_value_stack);
- tlsPtr_.deoptimization_return_value_stack = record;
- ClearDeoptimizationReturnValue();
+void Thread::PushDeoptimizationContext(const JValue& return_value, bool is_reference,
+ mirror::Throwable* exception) {
+ DeoptimizationContextRecord* record = new DeoptimizationContextRecord(
+ return_value,
+ is_reference,
+ exception,
+ tlsPtr_.deoptimization_context_stack);
+ tlsPtr_.deoptimization_context_stack = record;
}
-JValue Thread::PopDeoptimizationReturnValue() {
- DeoptimizationReturnValueRecord* record = tlsPtr_.deoptimization_return_value_stack;
- DCHECK(record != nullptr);
- tlsPtr_.deoptimization_return_value_stack = record->GetLink();
- JValue ret_val(record->GetReturnValue());
+void Thread::PopDeoptimizationContext(JValue* result, mirror::Throwable** exception) {
+ AssertHasDeoptimizationContext();
+ DeoptimizationContextRecord* record = tlsPtr_.deoptimization_context_stack;
+ tlsPtr_.deoptimization_context_stack = record->GetLink();
+ result->SetJ(record->GetReturnValue().GetJ());
+ *exception = record->GetPendingException();
delete record;
- return ret_val;
+}
+
+void Thread::AssertHasDeoptimizationContext() {
+ CHECK(tlsPtr_.deoptimization_context_stack != nullptr)
+ << "No deoptimization context for thread " << *this;
}
void Thread::PushStackedShadowFrame(ShadowFrame* sf, StackedShadowFrameType type) {
@@ -1575,6 +1595,9 @@
CHECK(tlsPtr_.flip_function == nullptr);
CHECK_EQ(tls32_.suspended_at_suspend_check, false);
+ // Make sure we processed all deoptimization requests.
+ CHECK(tlsPtr_.deoptimization_context_stack == nullptr) << "Missed deoptimization";
+
// We may be deleting a still born thread.
SetStateUnsafe(kTerminated);
@@ -2593,7 +2616,7 @@
visitor->VisitRootIfNonNull(&tlsPtr_.opeer, RootInfo(kRootThreadObject, thread_id));
if (tlsPtr_.exception != nullptr && tlsPtr_.exception != GetDeoptimizationException()) {
visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&tlsPtr_.exception),
- RootInfo(kRootNativeStack, thread_id));
+ RootInfo(kRootNativeStack, thread_id));
}
visitor->VisitRootIfNonNull(&tlsPtr_.monitor_enter_object, RootInfo(kRootNativeStack, thread_id));
tlsPtr_.jni_env->locals.VisitRoots(visitor, RootInfo(kRootJNILocal, thread_id));
@@ -2602,6 +2625,7 @@
if (tlsPtr_.debug_invoke_req != nullptr) {
tlsPtr_.debug_invoke_req->VisitRoots(visitor, RootInfo(kRootDebugger, thread_id));
}
+ // Visit roots for deoptimization.
if (tlsPtr_.stacked_shadow_frame_record != nullptr) {
RootCallbackVisitor visitor_to_callback(visitor, thread_id);
ReferenceMapVisitor<RootCallbackVisitor> mapper(this, nullptr, visitor_to_callback);
@@ -2615,14 +2639,16 @@
}
}
}
- if (tlsPtr_.deoptimization_return_value_stack != nullptr) {
- for (DeoptimizationReturnValueRecord* record = tlsPtr_.deoptimization_return_value_stack;
+ if (tlsPtr_.deoptimization_context_stack != nullptr) {
+ for (DeoptimizationContextRecord* record = tlsPtr_.deoptimization_context_stack;
record != nullptr;
record = record->GetLink()) {
if (record->IsReference()) {
- visitor->VisitRootIfNonNull(record->GetGCRoot(),
- RootInfo(kRootThreadObject, thread_id));
+ visitor->VisitRootIfNonNull(record->GetReturnValueAsGCRoot(),
+ RootInfo(kRootThreadObject, thread_id));
}
+ visitor->VisitRootIfNonNull(record->GetPendingExceptionAsGCRoot(),
+ RootInfo(kRootThreadObject, thread_id));
}
}
for (auto* verifier = tlsPtr_.method_verifier; verifier != nullptr; verifier = verifier->link_) {