Merge "Only MADV_RANDOM for low RAM devices"
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index cf2bfee..ea8d501 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -1832,55 +1832,53 @@
mov r12, r0 @ r12 holds reference to code
ldr r0, [sp, #4] @ restore r0
RESTORE_SAVE_REFS_AND_ARGS_FRAME
+ adr lr, art_quick_instrumentation_exit + /* thumb mode */ 1
+ @ load art_quick_instrumentation_exit into lr in thumb mode
REFRESH_MARKING_REGISTER
- blx r12 @ call method with lr set to art_quick_instrumentation_exit
-@ Deliberate fall-through into art_quick_instrumentation_exit.
- .type art_quick_instrumentation_exit, #function
- .global art_quick_instrumentation_exit
-art_quick_instrumentation_exit:
- mov lr, #0 @ link register is to here, so clobber with 0 for later checks
- SETUP_SAVE_REFS_ONLY_FRAME r2 @ set up frame knowing r2 and r3 must be dead on exit
- mov r12, sp @ remember bottom of caller's frame
- push {r0-r1} @ save return value
- .cfi_adjust_cfa_offset 8
- .cfi_rel_offset r0, 0
- .cfi_rel_offset r1, 4
- mov r2, sp @ store gpr_res pointer.
- vpush {d0} @ save fp return value
- .cfi_adjust_cfa_offset 8
- mov r3, sp @ store fpr_res pointer
- mov r1, r12 @ pass SP
- mov r0, r9 @ pass Thread::Current
- blx artInstrumentationMethodExitFromCode @ (Thread*, SP, gpr_res*, fpr_res*)
-
- mov r2, r0 @ link register saved by instrumentation
- mov lr, r1 @ r1 is holding link register if we're to bounce to deoptimize
- vpop {d0} @ restore fp return value
- .cfi_adjust_cfa_offset -8
- pop {r0, r1} @ restore return value
- .cfi_adjust_cfa_offset -8
- .cfi_restore r0
- .cfi_restore r1
- RESTORE_SAVE_REFS_ONLY_FRAME
- REFRESH_MARKING_REGISTER
- cbz r2, .Ldo_deliver_instrumentation_exception
- @ Deliver exception if we got nullptr as function.
- bx r2 @ Otherwise, return
+ bx r12 @ call method with lr set to art_quick_instrumentation_exit
.Ldeliver_instrumentation_entry_exception:
@ Deliver exception for art_quick_instrumentation_entry placed after
@ art_quick_instrumentation_exit so that the fallthrough works.
RESTORE_SAVE_REFS_AND_ARGS_FRAME
-.Ldo_deliver_instrumentation_exception:
DELIVER_PENDING_EXCEPTION
END art_quick_instrumentation_entry
+ENTRY art_quick_instrumentation_exit
+ mov lr, #0 @ link register is to here, so clobber with 0 for later checks
+ SETUP_SAVE_EVERYTHING_FRAME r2
+
+ add r3, sp, #8 @ store fpr_res pointer, in kSaveEverything frame
+ add r2, sp, #136 @ store gpr_res pointer, in kSaveEverything frame
+ mov r1, sp @ pass SP
+ mov r0, r9 @ pass Thread::Current
+ blx artInstrumentationMethodExitFromCode @ (Thread*, SP, gpr_res*, fpr_res*)
+
+ cbz r0, .Ldo_deliver_instrumentation_exception
+ @ Deliver exception if we got nullptr as function.
+ cbnz r1, .Ldeoptimize
+ // Normal return.
+ str r0, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 4]
+ @ Set return pc.
+ RESTORE_SAVE_EVERYTHING_FRAME
+ REFRESH_MARKING_REGISTER
+ bx lr
+.Ldo_deliver_instrumentation_exception:
+ DELIVER_PENDING_EXCEPTION_FRAME_READY
+.Ldeoptimize:
+ str r1, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 4]
+ @ Set return pc.
+ RESTORE_SAVE_EVERYTHING_FRAME
+ // Jump to art_quick_deoptimize.
+ b art_quick_deoptimize
+END art_quick_instrumentation_exit
+
/*
* Instrumentation has requested that we deoptimize into the interpreter. The deoptimization
* will long jump to the upcall with a special exception of -1.
*/
.extern artDeoptimize
ENTRY art_quick_deoptimize
- SETUP_SAVE_ALL_CALLEE_SAVES_FRAME r0
+ SETUP_SAVE_EVERYTHING_FRAME r0
mov r0, r9 @ pass Thread::Current
blx artDeoptimize @ (Thread*)
END art_quick_deoptimize
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 3d8ca40..6c9ce93 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -2355,32 +2355,31 @@
.extern artInstrumentationMethodExitFromCode
ENTRY art_quick_instrumentation_exit
mov xLR, #0 // Clobber LR for later checks.
+ SETUP_SAVE_EVERYTHING_FRAME
- SETUP_SAVE_REFS_ONLY_FRAME
-
- str x0, [sp, #-16]! // Save integer result.
- .cfi_adjust_cfa_offset 16
- str d0, [sp, #8] // Save floating-point result.
-
- add x3, sp, #8 // Pass floating-point result pointer.
- mov x2, sp // Pass integer result pointer.
- add x1, sp, #16 // Pass SP.
+ add x3, sp, #8 // Pass floating-point result pointer, in kSaveEverything frame.
+ add x2, sp, #264 // Pass integer result pointer, in kSaveEverything frame.
+ mov x1, sp // Pass SP.
mov x0, xSELF // Pass Thread.
bl artInstrumentationMethodExitFromCode // (Thread*, SP, gpr_res*, fpr_res*)
- mov xIP0, x0 // Return address from instrumentation call.
- mov xLR, x1 // r1 is holding link register if we're to bounce to deoptimize
-
- ldr d0, [sp, #8] // Restore floating-point result.
- ldr x0, [sp], #16 // Restore integer result, and drop stack area.
- .cfi_adjust_cfa_offset -16
-
- RESTORE_SAVE_REFS_ONLY_FRAME
+ cbz x0, .Ldo_deliver_instrumentation_exception
+ // Handle error
+ cbnz x1, .Ldeoptimize
+ // Normal return.
+ str x0, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 8]
+ // Set return pc.
+ RESTORE_SAVE_EVERYTHING_FRAME
REFRESH_MARKING_REGISTER
- cbz xIP0, 1f // Handle error
- br xIP0 // Tail-call out.
-1:
- DELIVER_PENDING_EXCEPTION
+ br lr
+.Ldo_deliver_instrumentation_exception:
+ DELIVER_PENDING_EXCEPTION_FRAME_READY
+.Ldeoptimize:
+ str x1, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 8]
+ // Set return pc.
+ RESTORE_SAVE_EVERYTHING_FRAME
+ // Jump to art_quick_deoptimize.
+ b art_quick_deoptimize
END art_quick_instrumentation_exit
/*
@@ -2389,7 +2388,7 @@
*/
.extern artDeoptimize
ENTRY art_quick_deoptimize
- SETUP_SAVE_ALL_CALLEE_SAVES_FRAME
+ SETUP_SAVE_EVERYTHING_FRAME
mov x0, xSELF // Pass thread.
bl artDeoptimize // (Thread*)
brk 0
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index 4e5e93a..af82e08 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -2056,42 +2056,43 @@
DEFINE_FUNCTION_CUSTOM_CFA art_quick_instrumentation_exit, 0
pushl LITERAL(0) // Push a fake return PC as there will be none on the stack.
CFI_ADJUST_CFA_OFFSET(4)
- SETUP_SAVE_REFS_ONLY_FRAME ebx, ebx
- mov %esp, %ecx // Remember SP
- subl LITERAL(8), %esp // Save float return value.
+ SETUP_SAVE_EVERYTHING_FRAME ebx, ebx
+
+ movl %esp, %ecx // Remember SP
+ subl LITERAL(8), %esp // Align stack.
CFI_ADJUST_CFA_OFFSET(8)
- movq %xmm0, (%esp)
- PUSH edx // Save gpr return value.
+ PUSH edx // Save gpr return value. edx and eax need to be together,
+ // which isn't the case in kSaveEverything frame.
PUSH eax
- leal 8(%esp), %eax // Get pointer to fpr_result
+ leal 32(%esp), %eax // Get pointer to fpr_result, in kSaveEverything frame
movl %esp, %edx // Get pointer to gpr_result
PUSH eax // Pass fpr_result
PUSH edx // Pass gpr_result
- PUSH ecx // Pass SP.
+ PUSH ecx // Pass SP
pushl %fs:THREAD_SELF_OFFSET // Pass Thread::Current.
CFI_ADJUST_CFA_OFFSET(4)
+
call SYMBOL(artInstrumentationMethodExitFromCode) // (Thread*, SP, gpr_result*, fpr_result*)
- testl %eax, %eax // Check if we returned error.
- jz 1f
- mov %eax, %ecx // Move returned link register.
- addl LITERAL(16), %esp // Pop arguments.
- CFI_ADJUST_CFA_OFFSET(-16)
- movl %edx, %ebx // Move returned link register for deopt
- // (ebx is pretending to be our LR).
- POP eax // Restore gpr return value.
- POP edx
- movq (%esp), %xmm0 // Restore fpr return value.
- addl LITERAL(8), %esp
- CFI_ADJUST_CFA_OFFSET(-8)
- RESTORE_SAVE_REFS_ONLY_FRAME
- addl LITERAL(4), %esp // Remove fake return pc.
- CFI_ADJUST_CFA_OFFSET(-4)
- jmp *%ecx // Return.
-1:
- addl LITERAL(32), %esp
+ // Return result could have been changed if it's a reference.
+ movl 16(%esp), %ecx
+ movl %ecx, (80+32)(%esp)
+ addl LITERAL(32), %esp // Pop arguments and grp_result.
CFI_ADJUST_CFA_OFFSET(-32)
- RESTORE_SAVE_REFS_ONLY_FRAME
- DELIVER_PENDING_EXCEPTION
+
+ testl %eax, %eax // Check if we returned error.
+ jz .Ldo_deliver_instrumentation_exception
+ testl %edx, %edx
+ jnz .Ldeoptimize
+ // Normal return.
+ movl %eax, FRAME_SIZE_SAVE_EVERYTHING-4(%esp) // Set return pc.
+ RESTORE_SAVE_EVERYTHING_FRAME
+ ret
+.Ldeoptimize:
+ mov %edx, (FRAME_SIZE_SAVE_EVERYTHING-4)(%esp) // Set return pc.
+ RESTORE_SAVE_EVERYTHING_FRAME
+ jmp SYMBOL(art_quick_deoptimize)
+.Ldo_deliver_instrumentation_exception:
+ DELIVER_PENDING_EXCEPTION_FRAME_READY
END_FUNCTION art_quick_instrumentation_exit
/*
@@ -2099,8 +2100,7 @@
* will long jump to the upcall with a special exception of -1.
*/
DEFINE_FUNCTION art_quick_deoptimize
- PUSH ebx // Entry point for a jump. Fake that we were called.
- SETUP_SAVE_ALL_CALLEE_SAVES_FRAME ebx, ebx
+ SETUP_SAVE_EVERYTHING_FRAME ebx, ebx
subl LITERAL(12), %esp // Align stack.
CFI_ADJUST_CFA_OFFSET(12)
pushl %fs:THREAD_SELF_OFFSET // Pass Thread::Current().
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index 73e8610..6bf0828 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -2019,45 +2019,31 @@
pushq LITERAL(0) // Push a fake return PC as there will be none on the stack.
CFI_ADJUST_CFA_OFFSET(8)
- SETUP_SAVE_REFS_ONLY_FRAME
+ SETUP_SAVE_EVERYTHING_FRAME
- // We need to save rax and xmm0. We could use a callee-save from SETUP_REF_ONLY, but then
- // we would need to fully restore it. As there are a good number of callee-save registers, it
- // seems easier to have an extra small stack area. But this should be revisited.
-
- movq %rsp, %rsi // Pass SP.
-
- PUSH rax // Save integer result.
- movq %rsp, %rdx // Pass integer result pointer.
-
- subq LITERAL(8), %rsp // Save floating-point result.
- CFI_ADJUST_CFA_OFFSET(8)
- movq %xmm0, (%rsp)
- movq %rsp, %rcx // Pass floating-point result pointer.
-
- movq %gs:THREAD_SELF_OFFSET, %rdi // Pass Thread.
+ leaq 16(%rsp), %rcx // Pass floating-point result pointer, in kSaveEverything frame.
+ leaq 144(%rsp), %rdx // Pass integer result pointer, in kSaveEverything frame.
+ movq %rsp, %rsi // Pass SP.
+ movq %gs:THREAD_SELF_OFFSET, %rdi // Pass Thread.
call SYMBOL(artInstrumentationMethodExitFromCode) // (Thread*, SP, gpr_res*, fpr_res*)
- movq %rax, %rdi // Store return PC
- movq %rdx, %rsi // Store second return PC in hidden arg.
-
- movq (%rsp), %xmm0 // Restore floating-point result.
- addq LITERAL(8), %rsp
- CFI_ADJUST_CFA_OFFSET(-8)
- POP rax // Restore integer result.
-
- RESTORE_SAVE_REFS_ONLY_FRAME
-
- testq %rdi, %rdi // Check if we have a return-pc to go to. If we don't then there was
+ testq %rax, %rax // Check if we have a return-pc to go to. If we don't then there was
// an exception
- jz 1f
-
- addq LITERAL(8), %rsp // Drop fake return pc.
-
- jmp *%rdi // Return.
-1:
- DELIVER_PENDING_EXCEPTION
+ jz .Ldo_deliver_instrumentation_exception
+ testq %rdx, %rdx
+ jnz .Ldeoptimize
+ // Normal return.
+ movq %rax, FRAME_SIZE_SAVE_EVERYTHING-8(%rsp) // Set return pc.
+ RESTORE_SAVE_EVERYTHING_FRAME
+ ret
+.Ldeoptimize:
+ movq %rdx, FRAME_SIZE_SAVE_EVERYTHING-8(%rsp) // Set return pc.
+ RESTORE_SAVE_EVERYTHING_FRAME
+ // Jump to art_quick_deoptimize.
+ jmp SYMBOL(art_quick_deoptimize)
+.Ldo_deliver_instrumentation_exception:
+ DELIVER_PENDING_EXCEPTION_FRAME_READY
END_FUNCTION art_quick_instrumentation_exit
/*
@@ -2065,10 +2051,7 @@
* will long jump to the upcall with a special exception of -1.
*/
DEFINE_FUNCTION art_quick_deoptimize
- pushq %rsi // Entry point for a jump. Fake that we were called.
- // Use hidden arg.
- SETUP_SAVE_ALL_CALLEE_SAVES_FRAME
- // Stack should be aligned now.
+ SETUP_SAVE_EVERYTHING_FRAME // Stack should be aligned now.
movq %gs:THREAD_SELF_OFFSET, %rdi // Pass Thread.
call SYMBOL(artDeoptimize) // (Thread*)
UNREACHABLE
diff --git a/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc b/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc
index 53f0727..5f40711 100644
--- a/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc
@@ -73,7 +73,11 @@
// Before deoptimizing to interpreter, we must push the deoptimization context.
JValue return_value;
return_value.SetJ(0); // we never deoptimize from compiled code with an invoke result.
- self->PushDeoptimizationContext(return_value, false, /* from_code */ true, self->GetException());
+ self->PushDeoptimizationContext(return_value,
+ false /* is_reference */,
+ self->GetException(),
+ true /* from_code */,
+ DeoptimizationMethodType::kDefault);
artDeoptimizeImpl(self, kind, true);
}
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index e08319d..5f71326 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -744,7 +744,11 @@
ObjPtr<mirror::Throwable> pending_exception;
bool from_code = false;
- self->PopDeoptimizationContext(&result, &pending_exception, /* out */ &from_code);
+ DeoptimizationMethodType method_type;
+ self->PopDeoptimizationContext(/* out */ &result,
+ /* out */ &pending_exception,
+ /* out */ &from_code,
+ /* out */ &method_type);
// Push a transition back into managed code onto the linked list in thread.
self->PushManagedStackFragment(&fragment);
@@ -771,7 +775,11 @@
if (pending_exception != nullptr) {
self->SetException(pending_exception);
}
- interpreter::EnterInterpreterFromDeoptimize(self, deopt_frame, from_code, &result);
+ interpreter::EnterInterpreterFromDeoptimize(self,
+ deopt_frame,
+ &result,
+ from_code,
+ DeoptimizationMethodType::kDefault);
} else {
const char* old_cause = self->StartAssertNoThreadSuspension(
"Building interpreter shadow frame");
@@ -823,7 +831,11 @@
// Push the context of the deoptimization stack so we can restore the return value and the
// exception before executing the deoptimized frames.
self->PushDeoptimizationContext(
- result, shorty[0] == 'L', /* from_code */ false, self->GetException());
+ result,
+ shorty[0] == 'L' || shorty[0] == '[', /* class or array */
+ self->GetException(),
+ false /* from_code */,
+ DeoptimizationMethodType::kDefault);
// Set special exception to cause deoptimization.
self->SetException(Thread::GetDeoptimizationException());
@@ -1041,7 +1053,8 @@
CHECK(!self->IsExceptionPending()) << "Enter instrumentation exit stub with pending exception "
<< self->GetException()->Dump();
// Compute address of return PC and sanity check that it currently holds 0.
- size_t return_pc_offset = GetCalleeSaveReturnPcOffset(kRuntimeISA, CalleeSaveType::kSaveRefsOnly);
+ size_t return_pc_offset = GetCalleeSaveReturnPcOffset(kRuntimeISA,
+ CalleeSaveType::kSaveEverything);
uintptr_t* return_pc = reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(sp) +
return_pc_offset);
CHECK_EQ(*return_pc, 0U);
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 758c9e4..474e368 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -26,6 +26,7 @@
#include "class_linker.h"
#include "debugger.h"
#include "dex_file-inl.h"
+#include "dex_instruction-inl.h"
#include "entrypoints/quick/quick_alloc_entrypoints.h"
#include "entrypoints/quick/quick_entrypoints.h"
#include "entrypoints/runtime_asm_entrypoints.h"
@@ -227,39 +228,32 @@
return true; // Continue.
}
uintptr_t return_pc = GetReturnPc();
- if (m->IsRuntimeMethod()) {
- if (return_pc == instrumentation_exit_pc_) {
- if (kVerboseInstrumentation) {
- LOG(INFO) << " Handling quick to interpreter transition. Frame " << GetFrameId();
- }
- CHECK_LT(instrumentation_stack_depth_, instrumentation_stack_->size());
- const InstrumentationStackFrame& frame =
- instrumentation_stack_->at(instrumentation_stack_depth_);
- CHECK(frame.interpreter_entry_);
- // This is an interpreter frame so method enter event must have been reported. However we
- // need to push a DEX pc into the dex_pcs_ list to match size of instrumentation stack.
- // Since we won't report method entry here, we can safely push any DEX pc.
- dex_pcs_.push_back(0);
- last_return_pc_ = frame.return_pc_;
- ++instrumentation_stack_depth_;
- return true;
- } else {
- if (kVerboseInstrumentation) {
- LOG(INFO) << " Skipping runtime method. Frame " << GetFrameId();
- }
- last_return_pc_ = GetReturnPc();
- return true; // Ignore unresolved methods since they will be instrumented after resolution.
- }
- }
if (kVerboseInstrumentation) {
LOG(INFO) << " Installing exit stub in " << DescribeLocation();
}
if (return_pc == instrumentation_exit_pc_) {
+ CHECK_LT(instrumentation_stack_depth_, instrumentation_stack_->size());
+
+ if (m->IsRuntimeMethod()) {
+ const InstrumentationStackFrame& frame =
+ instrumentation_stack_->at(instrumentation_stack_depth_);
+ if (frame.interpreter_entry_) {
+ // This instrumentation frame is for an interpreter bridge and is
+ // pushed when executing the instrumented interpreter bridge. So method
+ // enter event must have been reported. However we need to push a DEX pc
+ // into the dex_pcs_ list to match size of instrumentation stack.
+ uint32_t dex_pc = DexFile::kDexNoIndex;
+ dex_pcs_.push_back(dex_pc);
+ last_return_pc_ = frame.return_pc_;
+ ++instrumentation_stack_depth_;
+ return true;
+ }
+ }
+
// We've reached a frame which has already been installed with instrumentation exit stub.
// We should have already installed instrumentation on previous frames.
reached_existing_instrumentation_frames_ = true;
- CHECK_LT(instrumentation_stack_depth_, instrumentation_stack_->size());
const InstrumentationStackFrame& frame =
instrumentation_stack_->at(instrumentation_stack_depth_);
CHECK_EQ(m, frame.method_) << "Expected " << ArtMethod::PrettyMethod(m)
@@ -271,8 +265,12 @@
} else {
CHECK_NE(return_pc, 0U);
CHECK(!reached_existing_instrumentation_frames_);
- InstrumentationStackFrame instrumentation_frame(GetThisObject(), m, return_pc, GetFrameId(),
- false);
+ InstrumentationStackFrame instrumentation_frame(
+ m->IsRuntimeMethod() ? nullptr : GetThisObject(),
+ m,
+ return_pc,
+ GetFrameId(), // A runtime method still gets a frame id.
+ false);
if (kVerboseInstrumentation) {
LOG(INFO) << "Pushing frame " << instrumentation_frame.Dump();
}
@@ -289,9 +287,12 @@
instrumentation_stack_->insert(it, instrumentation_frame);
SetReturnPc(instrumentation_exit_pc_);
}
- dex_pcs_.push_back((GetCurrentOatQuickMethodHeader() == nullptr)
- ? DexFile::kDexNoIndex
- : GetCurrentOatQuickMethodHeader()->ToDexPc(m, last_return_pc_));
+ uint32_t dex_pc = DexFile::kDexNoIndex;
+ if (last_return_pc_ != 0 &&
+ GetCurrentOatQuickMethodHeader() != nullptr) {
+ dex_pc = GetCurrentOatQuickMethodHeader()->ToDexPc(m, last_return_pc_);
+ }
+ dex_pcs_.push_back(dex_pc);
last_return_pc_ = return_pc;
++instrumentation_stack_depth_;
return true; // Continue.
@@ -389,7 +390,8 @@
CHECK(m == instrumentation_frame.method_) << ArtMethod::PrettyMethod(m);
}
SetReturnPc(instrumentation_frame.return_pc_);
- if (instrumentation_->ShouldNotifyMethodEnterExitEvents()) {
+ if (instrumentation_->ShouldNotifyMethodEnterExitEvents() &&
+ !m->IsRuntimeMethod()) {
// Create the method exit events. As the methods didn't really exit the result is 0.
// We only do this if no debugger is attached to prevent from posting events twice.
instrumentation_->MethodExitEvent(thread_, instrumentation_frame.this_object_, m,
@@ -947,6 +949,7 @@
ObjPtr<mirror::Object> this_object,
ArtMethod* method,
uint32_t dex_pc) const {
+ DCHECK(!method->IsRuntimeMethod());
if (HasMethodEntryListeners()) {
Thread* self = Thread::Current();
StackHandleScope<1> hs(self);
@@ -1151,6 +1154,54 @@
stack->push_front(instrumentation_frame);
}
+DeoptimizationMethodType Instrumentation::GetDeoptimizationMethodType(ArtMethod* method) {
+ if (method->IsRuntimeMethod()) {
+ // Certain methods have strict requirement on whether the dex instruction
+ // should be re-executed upon deoptimization.
+ if (method == Runtime::Current()->GetCalleeSaveMethod(
+ CalleeSaveType::kSaveEverythingForClinit)) {
+ return DeoptimizationMethodType::kKeepDexPc;
+ }
+ if (method == Runtime::Current()->GetCalleeSaveMethod(
+ CalleeSaveType::kSaveEverythingForSuspendCheck)) {
+ return DeoptimizationMethodType::kKeepDexPc;
+ }
+ }
+ return DeoptimizationMethodType::kDefault;
+}
+
+// Try to get the shorty of a runtime method if it's an invocation stub.
+struct RuntimeMethodShortyVisitor : public StackVisitor {
+ explicit RuntimeMethodShortyVisitor(Thread* thread)
+ : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ shorty('V') {}
+
+ bool VisitFrame() REQUIRES_SHARED(Locks::mutator_lock_) {
+ ArtMethod* m = GetMethod();
+ if (m != nullptr && !m->IsRuntimeMethod()) {
+ // The first Java method.
+ if (m->IsNative()) {
+ // Use JNI method's shorty for the jni stub.
+ shorty = m->GetShorty()[0];
+ return false;
+ }
+ const DexFile::CodeItem* code_item = m->GetCodeItem();
+ const Instruction* instr = Instruction::At(&code_item->insns_[GetDexPc()]);
+ if (instr->IsInvoke()) {
+ // If it's an invoke, use its shorty.
+ uint32_t method_idx = instr->VRegB();
+ shorty = m->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetDexFile()
+ ->GetMethodShorty(method_idx)[0];
+ }
+ // Stop stack walking since we've seen a Java frame.
+ return false;
+ }
+ return true;
+ }
+
+ char shorty;
+};
+
TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self,
uintptr_t* return_pc,
uint64_t* gpr_result,
@@ -1171,7 +1222,36 @@
ArtMethod* method = instrumentation_frame.method_;
uint32_t length;
const PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
- char return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0];
+ char return_shorty;
+
+ // Runtime method does not call into MethodExitEvent() so there should not be
+ // suspension point below.
+ ScopedAssertNoThreadSuspension ants(__FUNCTION__, method->IsRuntimeMethod());
+ if (method->IsRuntimeMethod()) {
+ if (method != Runtime::Current()->GetCalleeSaveMethod(
+ CalleeSaveType::kSaveEverythingForClinit)) {
+ // If the caller is at an invocation point and the runtime method is not
+ // for clinit, we need to pass return results to the caller.
+ // We need the correct shorty to decide whether we need to pass the return
+ // result for deoptimization below.
+ RuntimeMethodShortyVisitor visitor(self);
+ visitor.WalkStack();
+ return_shorty = visitor.shorty;
+ } else {
+ // Some runtime methods such as allocations, unresolved field getters, etc.
+ // have return value. We don't need to set return_value since MethodExitEvent()
+ // below isn't called for runtime methods. Deoptimization doesn't need the
+ // value either since the dex instruction will be re-executed by the
+ // interpreter, except these two cases:
+ // (1) For an invoke, which is handled above to get the correct shorty.
+ // (2) For MONITOR_ENTER/EXIT, which cannot be re-executed since it's not
+ // idempotent. However there is no return value for it anyway.
+ return_shorty = 'V';
+ }
+ } else {
+ return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0];
+ }
+
bool is_ref = return_shorty == '[' || return_shorty == 'L';
StackHandleScope<1> hs(self);
MutableHandle<mirror::Object> res(hs.NewHandle<mirror::Object>(nullptr));
@@ -1191,7 +1271,7 @@
// return_pc.
uint32_t dex_pc = DexFile::kDexNoIndex;
mirror::Object* this_object = instrumentation_frame.this_object_;
- if (!instrumentation_frame.interpreter_entry_) {
+ if (!method->IsRuntimeMethod() && !instrumentation_frame.interpreter_entry_) {
MethodExitEvent(self, this_object, instrumentation_frame.method_, dex_pc, return_value);
}
@@ -1217,10 +1297,12 @@
<< " in "
<< *self;
}
+ DeoptimizationMethodType deopt_method_type = GetDeoptimizationMethodType(method);
self->PushDeoptimizationContext(return_value,
- return_shorty == 'L',
+ return_shorty == 'L' || return_shorty == '[',
+ nullptr /* no pending exception */,
false /* from_code */,
- nullptr /* no pending exception */);
+ deopt_method_type);
return GetTwoWordSuccessValue(*return_pc,
reinterpret_cast<uintptr_t>(GetQuickDeoptimizationEntryPoint()));
} else {
@@ -1257,7 +1339,9 @@
// TODO: improve the dex pc information here, requires knowledge of current PC as opposed to
// return_pc.
uint32_t dex_pc = DexFile::kDexNoIndex;
- MethodUnwindEvent(self, instrumentation_frame.this_object_, method, dex_pc);
+ if (!method->IsRuntimeMethod()) {
+ MethodUnwindEvent(self, instrumentation_frame.this_object_, method, dex_pc);
+ }
}
// TODO: bring back CheckStackDepth(self, instrumentation_frame, 2);
CHECK_EQ(stack->size(), idx);
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index f6efb5f..de416c7 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -39,6 +39,7 @@
template <typename T> class Handle;
union JValue;
class Thread;
+enum class DeoptimizationMethodType;
namespace instrumentation {
@@ -435,6 +436,9 @@
bool interpreter_entry)
REQUIRES_SHARED(Locks::mutator_lock_);
+ DeoptimizationMethodType GetDeoptimizationMethodType(ArtMethod* method)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// Called when an instrumented method is exited. Removes the pushed instrumentation frame
// returning the intended link register. Generates method exit events. The gpr_result and
// fpr_result pointers are pointers to the locations where the integer/pointer and floating point
@@ -661,9 +665,15 @@
// An element in the instrumentation side stack maintained in art::Thread.
struct InstrumentationStackFrame {
- InstrumentationStackFrame(mirror::Object* this_object, ArtMethod* method,
- uintptr_t return_pc, size_t frame_id, bool interpreter_entry)
- : this_object_(this_object), method_(method), return_pc_(return_pc), frame_id_(frame_id),
+ InstrumentationStackFrame(mirror::Object* this_object,
+ ArtMethod* method,
+ uintptr_t return_pc,
+ size_t frame_id,
+ bool interpreter_entry)
+ : this_object_(this_object),
+ method_(method),
+ return_pc_(return_pc),
+ frame_id_(frame_id),
interpreter_entry_(interpreter_entry) {
}
diff --git a/runtime/instrumentation_test.cc b/runtime/instrumentation_test.cc
index 19ce299..5ec07e3 100644
--- a/runtime/instrumentation_test.cc
+++ b/runtime/instrumentation_test.cc
@@ -473,7 +473,23 @@
// Test instrumentation listeners for each event.
TEST_F(InstrumentationTest, MethodEntryEvent) {
- TestEvent(instrumentation::Instrumentation::kMethodEntered);
+ ScopedObjectAccess soa(Thread::Current());
+ jobject class_loader = LoadDex("Instrumentation");
+ Runtime* const runtime = Runtime::Current();
+ ClassLinker* class_linker = runtime->GetClassLinker();
+ StackHandleScope<1> hs(soa.Self());
+ Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
+ mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader);
+ ASSERT_TRUE(klass != nullptr);
+ ArtMethod* method =
+ klass->FindClassMethod("returnReference", "()Ljava/lang/Object;", kRuntimePointerSize);
+ ASSERT_TRUE(method != nullptr);
+ ASSERT_TRUE(method->IsDirect());
+ ASSERT_TRUE(method->GetDeclaringClass() == klass);
+ TestEvent(instrumentation::Instrumentation::kMethodEntered,
+ /*event_method*/ method,
+ /*event_field*/ nullptr,
+ /*with_object*/ true);
}
TEST_F(InstrumentationTest, MethodExitObjectEvent) {
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index 9cb74f7..34332d7 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -499,8 +499,9 @@
void EnterInterpreterFromDeoptimize(Thread* self,
ShadowFrame* shadow_frame,
+ JValue* ret_val,
bool from_code,
- JValue* ret_val)
+ DeoptimizationMethodType deopt_method_type)
REQUIRES_SHARED(Locks::mutator_lock_) {
JValue value;
// Set value to last known result in case the shadow frame chain is empty.
@@ -527,11 +528,27 @@
new_dex_pc = found_dex_pc; // the dex pc of a matching catch handler
// or DexFile::kDexNoIndex if there is none.
} else if (!from_code) {
- // For the debugger and full deoptimization stack, we must go past the invoke
- // instruction, as it already executed.
- // TODO: should be tested more once b/17586779 is fixed.
+ // Deoptimization is not called from code directly.
const Instruction* instr = Instruction::At(&code_item->insns_[dex_pc]);
- if (instr->IsInvoke()) {
+ if (deopt_method_type == DeoptimizationMethodType::kKeepDexPc) {
+ DCHECK(first);
+ // Need to re-execute the dex instruction.
+ // (1) An invocation might be split into class initialization and invoke.
+ // In this case, the invoke should not be skipped.
+ // (2) A suspend check should also execute the dex instruction at the
+ // corresponding dex pc.
+ DCHECK_EQ(new_dex_pc, dex_pc);
+ } else if (instr->Opcode() == Instruction::MONITOR_ENTER ||
+ instr->Opcode() == Instruction::MONITOR_EXIT) {
+ DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault);
+ DCHECK(first);
+ // Non-idempotent dex instruction should not be re-executed.
+ // On the other hand, if a MONITOR_ENTER is at the dex_pc of a suspend
+ // check, that MONITOR_ENTER should be executed. That case is handled
+ // above.
+ new_dex_pc = dex_pc + instr->SizeInCodeUnits();
+ } else if (instr->IsInvoke()) {
+ DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault);
if (IsStringInit(instr, shadow_frame->GetMethod())) {
uint16_t this_obj_vreg = GetReceiverRegisterForStringInit(instr);
// Move the StringFactory.newStringFromChars() result into the register representing
@@ -544,30 +561,27 @@
}
new_dex_pc = dex_pc + instr->SizeInCodeUnits();
} else if (instr->Opcode() == Instruction::NEW_INSTANCE) {
- // It's possible to deoptimize at a NEW_INSTANCE dex instruciton that's for a
- // java string, which is turned into a call into StringFactory.newEmptyString();
- // Move the StringFactory.newEmptyString() result into the destination register.
- DCHECK(value.GetL()->IsString());
- shadow_frame->SetVRegReference(instr->VRegA_21c(), value.GetL());
- // new-instance doesn't generate a result value.
- value.SetJ(0);
- // Skip the dex instruction since we essentially come back from an invocation.
- new_dex_pc = dex_pc + instr->SizeInCodeUnits();
- if (kIsDebugBuild) {
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- // This is a suspend point. But it's ok since value has been set into shadow_frame.
- ObjPtr<mirror::Class> klass = class_linker->ResolveType(
- dex::TypeIndex(instr->VRegB_21c()), shadow_frame->GetMethod());
- DCHECK(klass->IsStringClass());
- }
+ // A NEW_INSTANCE is simply re-executed, including
+ // "new-instance String" which is compiled into a call into
+ // StringFactory.newEmptyString().
+ DCHECK_EQ(new_dex_pc, dex_pc);
} else {
- CHECK(false) << "Unexpected instruction opcode " << instr->Opcode()
- << " at dex_pc " << dex_pc
- << " of method: " << ArtMethod::PrettyMethod(shadow_frame->GetMethod(), false);
+ DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault);
+ DCHECK(first);
+ // By default, we re-execute the dex instruction since if they are not
+ // an invoke, so that we don't have to decode the dex instruction to move
+ // result into the right vreg. All slow paths have been audited to be
+ // idempotent except monitor-enter/exit and invocation stubs.
+ // TODO: move result and advance dex pc. That also requires that we
+ // can tell the return type of a runtime method, possibly by decoding
+ // the dex instruction at the caller.
+ DCHECK_EQ(new_dex_pc, dex_pc);
}
} else {
// Nothing to do, the dex_pc is the one at which the code requested
// the deoptimization.
+ DCHECK(first);
+ DCHECK_EQ(new_dex_pc, dex_pc);
}
if (new_dex_pc != DexFile::kDexNoIndex) {
shadow_frame->SetDexPC(new_dex_pc);
@@ -576,8 +590,10 @@
ShadowFrame* old_frame = shadow_frame;
shadow_frame = shadow_frame->GetLink();
ShadowFrame::DeleteDeoptimizedFrame(old_frame);
- // Following deoptimizations of shadow frames must pass the invoke instruction.
+ // Following deoptimizations of shadow frames must be at invocation point
+ // and should advance dex pc past the invoke instruction.
from_code = false;
+ deopt_method_type = DeoptimizationMethodType::kDefault;
first = false;
}
ret_val->SetJ(value.GetJ());
diff --git a/runtime/interpreter/interpreter.h b/runtime/interpreter/interpreter.h
index 65cfade..df8568e 100644
--- a/runtime/interpreter/interpreter.h
+++ b/runtime/interpreter/interpreter.h
@@ -30,6 +30,7 @@
union JValue;
class ShadowFrame;
class Thread;
+enum class DeoptimizationMethodType;
namespace interpreter {
@@ -44,8 +45,11 @@
REQUIRES_SHARED(Locks::mutator_lock_);
// 'from_code' denotes whether the deoptimization was explicitly triggered by compiled code.
-extern void EnterInterpreterFromDeoptimize(Thread* self, ShadowFrame* shadow_frame, bool from_code,
- JValue* ret_val)
+extern void EnterInterpreterFromDeoptimize(Thread* self,
+ ShadowFrame* shadow_frame,
+ JValue* ret_val,
+ bool from_code,
+ DeoptimizationMethodType method_type)
REQUIRES_SHARED(Locks::mutator_lock_);
extern JValue EnterInterpreterFromEntryPoint(Thread* self, const DexFile::CodeItem* code_item,
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 3f23926..57b3a75 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -166,11 +166,13 @@
bool is_reference,
bool from_code,
ObjPtr<mirror::Throwable> pending_exception,
+ DeoptimizationMethodType method_type,
DeoptimizationContextRecord* link)
: ret_val_(ret_val),
is_reference_(is_reference),
from_code_(from_code),
pending_exception_(pending_exception.Ptr()),
+ deopt_method_type_(method_type),
link_(link) {}
JValue GetReturnValue() const { return ret_val_; }
@@ -185,6 +187,9 @@
mirror::Object** GetPendingExceptionAsGCRoot() {
return reinterpret_cast<mirror::Object**>(&pending_exception_);
}
+ DeoptimizationMethodType GetDeoptimizationMethodType() const {
+ return deopt_method_type_;
+ }
private:
// The value returned by the method at the top of the stack before deoptimization.
@@ -200,6 +205,9 @@
// exception).
mirror::Throwable* pending_exception_;
+ // Whether the context was created for an (idempotent) runtime method.
+ const DeoptimizationMethodType deopt_method_type_;
+
// A link to the previous DeoptimizationContextRecord.
DeoptimizationContextRecord* const link_;
@@ -229,26 +237,30 @@
void Thread::PushDeoptimizationContext(const JValue& return_value,
bool is_reference,
+ ObjPtr<mirror::Throwable> exception,
bool from_code,
- ObjPtr<mirror::Throwable> exception) {
+ DeoptimizationMethodType method_type) {
DeoptimizationContextRecord* record = new DeoptimizationContextRecord(
return_value,
is_reference,
from_code,
exception,
+ method_type,
tlsPtr_.deoptimization_context_stack);
tlsPtr_.deoptimization_context_stack = record;
}
void Thread::PopDeoptimizationContext(JValue* result,
ObjPtr<mirror::Throwable>* exception,
- bool* from_code) {
+ bool* from_code,
+ DeoptimizationMethodType* method_type) {
AssertHasDeoptimizationContext();
DeoptimizationContextRecord* record = tlsPtr_.deoptimization_context_stack;
tlsPtr_.deoptimization_context_stack = record->GetLink();
result->SetJ(record->GetReturnValue().GetJ());
*exception = record->GetPendingException();
*from_code = record->GetFromCode();
+ *method_type = record->GetDeoptimizationMethodType();
delete record;
}
@@ -3084,10 +3096,16 @@
NthCallerVisitor visitor(this, 0, false);
visitor.WalkStack();
if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.caller_pc)) {
+ // method_type shouldn't matter due to exception handling.
+ const DeoptimizationMethodType method_type = DeoptimizationMethodType::kDefault;
// Save the exception into the deoptimization context so it can be restored
// before entering the interpreter.
PushDeoptimizationContext(
- JValue(), /*is_reference */ false, /* from_code */ false, exception);
+ JValue(),
+ false /* is_reference */,
+ exception,
+ false /* from_code */,
+ method_type);
artDeoptimize(this);
UNREACHABLE();
} else {
@@ -3647,7 +3665,8 @@
PopStackedShadowFrame(StackedShadowFrameType::kDeoptimizationShadowFrame);
ObjPtr<mirror::Throwable> pending_exception;
bool from_code = false;
- PopDeoptimizationContext(result, &pending_exception, &from_code);
+ DeoptimizationMethodType method_type;
+ PopDeoptimizationContext(result, &pending_exception, &from_code, &method_type);
SetTopOfStack(nullptr);
SetTopOfShadowStack(shadow_frame);
@@ -3656,7 +3675,11 @@
if (pending_exception != nullptr) {
SetException(pending_exception);
}
- interpreter::EnterInterpreterFromDeoptimize(this, shadow_frame, from_code, result);
+ interpreter::EnterInterpreterFromDeoptimize(this,
+ shadow_frame,
+ result,
+ from_code,
+ method_type);
}
void Thread::SetException(ObjPtr<mirror::Throwable> new_exception) {
diff --git a/runtime/thread.h b/runtime/thread.h
index 7540fd2..ad4506e 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -117,6 +117,13 @@
kDeoptimizationShadowFrame,
};
+// The type of method that triggers deoptimization. It contains info on whether
+// the deoptimized method should advance dex_pc.
+enum class DeoptimizationMethodType {
+ kKeepDexPc, // dex pc is required to be kept upon deoptimization.
+ kDefault // dex pc may or may not advance depending on other conditions.
+};
+
// This should match RosAlloc::kNumThreadLocalSizeBrackets.
static constexpr size_t kNumRosAllocThreadLocalSizeBracketsInThread = 16;
@@ -960,14 +967,18 @@
// values on stacks.
// 'from_code' denotes whether the deoptimization was explicitly made from
// compiled code.
+ // 'method_type' contains info on whether deoptimization should advance
+ // dex_pc.
void PushDeoptimizationContext(const JValue& return_value,
bool is_reference,
+ ObjPtr<mirror::Throwable> exception,
bool from_code,
- ObjPtr<mirror::Throwable> exception)
+ DeoptimizationMethodType method_type)
REQUIRES_SHARED(Locks::mutator_lock_);
void PopDeoptimizationContext(JValue* result,
ObjPtr<mirror::Throwable>* exception,
- bool* from_code)
+ bool* from_code,
+ DeoptimizationMethodType* method_type)
REQUIRES_SHARED(Locks::mutator_lock_);
void AssertHasDeoptimizationContext()
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/test/597-deopt-busy-loop/expected.txt b/test/597-deopt-busy-loop/expected.txt
new file mode 100644
index 0000000..f993efc
--- /dev/null
+++ b/test/597-deopt-busy-loop/expected.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+Finishing
diff --git a/test/597-deopt-busy-loop/info.txt b/test/597-deopt-busy-loop/info.txt
new file mode 100644
index 0000000..2c50dbb
--- /dev/null
+++ b/test/597-deopt-busy-loop/info.txt
@@ -0,0 +1 @@
+Test deoptimizing when returning from suspend-check runtime method.
diff --git a/test/597-deopt-busy-loop/run b/test/597-deopt-busy-loop/run
new file mode 100644
index 0000000..bc04498
--- /dev/null
+++ b/test/597-deopt-busy-loop/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright (C) 2017 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.
+
+# We want to run in debuggable mode and compiled.
+exec ${RUN} --jit -Xcompiler-option --debuggable "${@}"
diff --git a/test/597-deopt-busy-loop/src/Main.java b/test/597-deopt-busy-loop/src/Main.java
new file mode 100644
index 0000000..46b6bbf
--- /dev/null
+++ b/test/597-deopt-busy-loop/src/Main.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class Main implements Runnable {
+ static final int numberOfThreads = 2;
+ volatile static boolean sExitFlag = false;
+ volatile static boolean sEntered = false;
+ int threadIndex;
+
+ private static native void deoptimizeAll();
+ private static native void assertIsInterpreted();
+ private static native void assertIsManaged();
+ private static native void ensureJitCompiled(Class<?> cls, String methodName);
+
+ Main(int index) {
+ threadIndex = index;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+
+ final Thread[] threads = new Thread[numberOfThreads];
+ for (int t = 0; t < threads.length; t++) {
+ threads[t] = new Thread(new Main(t));
+ threads[t].start();
+ }
+ for (Thread t : threads) {
+ t.join();
+ }
+ System.out.println("Finishing");
+ }
+
+ public void $noinline$busyLoop() {
+ assertIsManaged();
+ sEntered = true;
+ for (;;) {
+ if (sExitFlag) {
+ break;
+ }
+ }
+ assertIsInterpreted();
+ }
+
+ public void run() {
+ if (threadIndex == 0) {
+ while (!sEntered) {
+ Thread.yield();
+ }
+ deoptimizeAll();
+ sExitFlag = true;
+ } else {
+ ensureJitCompiled(Main.class, "$noinline$busyLoop");
+ $noinline$busyLoop();
+ }
+ }
+}
diff --git a/test/597-deopt-invoke-stub/expected.txt b/test/597-deopt-invoke-stub/expected.txt
new file mode 100644
index 0000000..f993efc
--- /dev/null
+++ b/test/597-deopt-invoke-stub/expected.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+Finishing
diff --git a/test/597-deopt-invoke-stub/info.txt b/test/597-deopt-invoke-stub/info.txt
new file mode 100644
index 0000000..31960a9
--- /dev/null
+++ b/test/597-deopt-invoke-stub/info.txt
@@ -0,0 +1 @@
+Test deoptimizing when returning from a quick-to-interpreter bridge.
diff --git a/test/597-deopt-invoke-stub/run b/test/597-deopt-invoke-stub/run
new file mode 100644
index 0000000..bc04498
--- /dev/null
+++ b/test/597-deopt-invoke-stub/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright (C) 2017 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.
+
+# We want to run in debuggable mode and compiled.
+exec ${RUN} --jit -Xcompiler-option --debuggable "${@}"
diff --git a/test/597-deopt-invoke-stub/src/Main.java b/test/597-deopt-invoke-stub/src/Main.java
new file mode 100644
index 0000000..0751783
--- /dev/null
+++ b/test/597-deopt-invoke-stub/src/Main.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class Main implements Runnable {
+ static final int numberOfThreads = 2;
+ volatile static boolean sExitFlag = false;
+ volatile static boolean sEntered = false;
+ int threadIndex;
+
+ private static native void deoptimizeAll();
+ private static native void assertIsInterpreted();
+ private static native void assertIsManaged();
+ private static native void ensureJitCompiled(Class<?> cls, String methodName);
+
+ Main(int index) {
+ threadIndex = index;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+
+ final Thread[] threads = new Thread[numberOfThreads];
+ for (int t = 0; t < threads.length; t++) {
+ threads[t] = new Thread(new Main(t));
+ threads[t].start();
+ }
+ for (Thread t : threads) {
+ t.join();
+ }
+ System.out.println("Finishing");
+ }
+
+ private static int $noinline$bar() {
+ // Should be entered via interpreter bridge.
+ assertIsInterpreted();
+ sEntered = true;
+ while (!sExitFlag) {}
+ assertIsInterpreted();
+ return 0x1234;
+ }
+
+ public void $noinline$foo() {
+ assertIsManaged();
+ if ($noinline$bar() != 0x1234) {
+ System.out.println("Bad return value");
+ }
+ assertIsInterpreted();
+ }
+
+ public void run() {
+ if (threadIndex == 0) {
+ while (!sEntered) {
+ Thread.yield();
+ }
+ deoptimizeAll();
+ sExitFlag = true;
+ } else {
+ ensureJitCompiled(Main.class, "$noinline$foo");
+ $noinline$foo();
+ }
+ }
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 20cfc34..d76103a 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -207,7 +207,9 @@
"variant": "trace | stream"
},
{
- "tests": ["604-hot-static-interface",
+ "tests": ["597-deopt-busy-loop",
+ "597-deopt-invoke-stub",
+ "604-hot-static-interface",
"612-jit-dex-cache",
"613-inlining-dex-cache",
"626-set-resolved-string"],