diff options
22 files changed, 553 insertions, 204 deletions
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index cf2bfeeeef..ea8d501939 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -1832,55 +1832,53 @@ ENTRY art_quick_instrumentation_entry 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 3d8ca402cf..6c9ce93d2e 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -2355,32 +2355,31 @@ END art_quick_instrumentation_entry .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 @@ END art_quick_instrumentation_exit */ .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 4e5e93ac5a..af82e08698 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -2056,42 +2056,43 @@ END_FUNCTION art_quick_instrumentation_entry 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 @@ END_FUNCTION art_quick_instrumentation_exit * 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 73e86100f6..6bf08289ee 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 @@ DEFINE_FUNCTION_CUSTOM_CFA art_quick_instrumentation_exit, 0 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 - - // 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. + SETUP_SAVE_EVERYTHING_FRAME - 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 @@ END_FUNCTION art_quick_instrumentation_exit * 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 53f0727a5f..5f40711753 100644 --- a/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc @@ -73,7 +73,11 @@ extern "C" NO_RETURN void artDeoptimizeFromCompiledCode(DeoptimizationKind kind, // 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 e08319d509..5f713265df 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -744,7 +744,11 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, 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 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, 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 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, // 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 @@ extern "C" TwoWordReturn artInstrumentationMethodExitFromCode(Thread* self, 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 758c9e4dc4..474e3684f6 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 @@ static void InstrumentationInstallStack(Thread* thread, void* arg) 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 @@ static void InstrumentationInstallStack(Thread* thread, void* arg) } 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 @@ static void InstrumentationInstallStack(Thread* thread, void* arg) 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 @@ static void InstrumentationRestoreStack(Thread* thread, void* arg) 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 @@ void Instrumentation::MethodEnterEventImpl(Thread* thread, 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 @@ void Instrumentation::PushInstrumentationStackFrame(Thread* self, mirror::Object 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 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, 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 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, // 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 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, << " 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 @@ uintptr_t Instrumentation::PopMethodForUnwind(Thread* self, bool is_deoptimizati // 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 f6efb5fedf..de416c77e8 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -39,6 +39,7 @@ class ArtMethod; template <typename T> class Handle; union JValue; class Thread; +enum class DeoptimizationMethodType; namespace instrumentation { @@ -435,6 +436,9 @@ class Instrumentation { 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 @@ std::ostream& operator<<(std::ostream& os, const Instrumentation::Instrumentatio // 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 19ce299de6..5ec07e3508 100644 --- a/runtime/instrumentation_test.cc +++ b/runtime/instrumentation_test.cc @@ -473,7 +473,23 @@ TEST_F(InstrumentationTest, NoInstrumentation) { // 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 9cb74f7c36..34332d7df7 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -499,8 +499,9 @@ static int16_t GetReceiverRegisterForStringInit(const Instruction* instr) { 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 @@ void EnterInterpreterFromDeoptimize(Thread* self, 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 @@ void EnterInterpreterFromDeoptimize(Thread* self, } 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 @@ void EnterInterpreterFromDeoptimize(Thread* self, 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 65cfade09a..df8568edcd 100644 --- a/runtime/interpreter/interpreter.h +++ b/runtime/interpreter/interpreter.h @@ -30,6 +30,7 @@ class ArtMethod; union JValue; class ShadowFrame; class Thread; +enum class DeoptimizationMethodType; namespace interpreter { @@ -44,8 +45,11 @@ extern void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method, 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 3f23926b29..57b3a75352 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -166,11 +166,13 @@ class DeoptimizationContextRecord { 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 @@ class DeoptimizationContextRecord { 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 @@ class DeoptimizationContextRecord { // 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 @@ class StackedShadowFrameRecord { 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 @@ void Thread::QuickDeliverException() { 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 @@ void Thread::DeoptimizeWithDeoptimizationException(JValue* result) { 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 @@ void Thread::DeoptimizeWithDeoptimizationException(JValue* result) { 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 7540fd2563..ad4506e309 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -117,6 +117,13 @@ enum class StackedShadowFrameType { 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 @@ class Thread { // 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 0000000000..f993efcdad --- /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 0000000000..2c50dbbe79 --- /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 0000000000..bc04498bfe --- /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 0000000000..46b6bbf4f3 --- /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 0000000000..f993efcdad --- /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 0000000000..31960a988b --- /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 0000000000..bc04498bfe --- /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 0000000000..075178361b --- /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 20cfc34e43..d76103a226 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"], |