diff options
122 files changed, 4402 insertions, 998 deletions
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 115e722a75..6ee9cc6056 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -2593,10 +2593,6 @@ void ImageWriter::CopyAndFixupMethod(ArtMethod* orig, CopyReference(copy->GetDeclaringClassAddressWithoutBarrier(), orig->GetDeclaringClassUnchecked()); - mirror::MethodDexCacheType* orig_resolved_methods = - orig->GetDexCacheResolvedMethods(target_ptr_size_); - copy->SetDexCacheResolvedMethods(NativeLocationInImage(orig_resolved_methods), target_ptr_size_); - // OatWriter replaces the code_ with an offset value. Here we re-adjust to a pointer relative to // oat_begin_ diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index a20ec3c0db..3035e4657d 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -501,6 +501,20 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { StartAttributeStream("field_type") << iset->GetFieldType(); } + void VisitStaticFieldGet(HStaticFieldGet* sget) OVERRIDE { + StartAttributeStream("field_name") << + sget->GetFieldInfo().GetDexFile().PrettyField(sget->GetFieldInfo().GetFieldIndex(), + /* with type */ false); + StartAttributeStream("field_type") << sget->GetFieldType(); + } + + void VisitStaticFieldSet(HStaticFieldSet* sset) OVERRIDE { + StartAttributeStream("field_name") << + sset->GetFieldInfo().GetDexFile().PrettyField(sset->GetFieldInfo().GetFieldIndex(), + /* with type */ false); + StartAttributeStream("field_type") << sset->GetFieldType(); + } + void VisitUnresolvedInstanceFieldGet(HUnresolvedInstanceFieldGet* field_access) OVERRIDE { StartAttributeStream("field_type") << field_access->GetFieldType(); } diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc index f35aace3a9..089340e715 100644 --- a/compiler/optimizing/induction_var_range.cc +++ b/compiler/optimizing/induction_var_range.cc @@ -87,8 +87,10 @@ static bool IsGEZero(HInstruction* instruction) { IsGEZero(instruction->InputAt(1)); case Intrinsics::kMathAbsInt: case Intrinsics::kMathAbsLong: - // Instruction ABS(x) is >= 0. - return true; + // Instruction ABS(>=0) is >= 0. + // NOTE: ABS(minint) = minint prevents assuming + // >= 0 without looking at the argument. + return IsGEZero(instruction->InputAt(0)); default: break; } diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc index 76c1410340..d2dc88a73b 100644 --- a/compiler/optimizing/intrinsics_arm_vixl.cc +++ b/compiler/optimizing/intrinsics_arm_vixl.cc @@ -331,7 +331,7 @@ static void CreateIntToIntLocations(ArenaAllocator* arena, HInvoke* invoke) { locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap); } -static void CreateIntToIntLocationsWithOverlap(ArenaAllocator* arena, HInvoke* invoke) { +static void CreateLongToLongLocationsWithOverlap(ArenaAllocator* arena, HInvoke* invoke) { LocationSummary* locations = new (arena) LocationSummary(invoke, LocationSummary::kNoCall, kIntrinsified); @@ -383,11 +383,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerNumberOfLeadingZeros(HInvoke* in } void IntrinsicLocationsBuilderARMVIXL::VisitLongNumberOfLeadingZeros(HInvoke* invoke) { - LocationSummary* locations = new (arena_) LocationSummary(invoke, - LocationSummary::kNoCall, - kIntrinsified); - locations->SetInAt(0, Location::RequiresRegister()); - locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap); + CreateLongToLongLocationsWithOverlap(arena_, invoke); } void IntrinsicCodeGeneratorARMVIXL::VisitLongNumberOfLeadingZeros(HInvoke* invoke) { @@ -425,11 +421,7 @@ static void GenNumberOfTrailingZeros(HInvoke* invoke, } void IntrinsicLocationsBuilderARMVIXL::VisitIntegerNumberOfTrailingZeros(HInvoke* invoke) { - LocationSummary* locations = new (arena_) LocationSummary(invoke, - LocationSummary::kNoCall, - kIntrinsified); - locations->SetInAt(0, Location::RequiresRegister()); - locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap); + CreateIntToIntLocations(arena_, invoke); } void IntrinsicCodeGeneratorARMVIXL::VisitIntegerNumberOfTrailingZeros(HInvoke* invoke) { @@ -437,11 +429,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerNumberOfTrailingZeros(HInvoke* i } void IntrinsicLocationsBuilderARMVIXL::VisitLongNumberOfTrailingZeros(HInvoke* invoke) { - LocationSummary* locations = new (arena_) LocationSummary(invoke, - LocationSummary::kNoCall, - kIntrinsified); - locations->SetInAt(0, Location::RequiresRegister()); - locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap); + CreateLongToLongLocationsWithOverlap(arena_, invoke); } void IntrinsicCodeGeneratorARMVIXL::VisitLongNumberOfTrailingZeros(HInvoke* invoke) { @@ -2819,11 +2807,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerReverse(HInvoke* invoke) { } void IntrinsicLocationsBuilderARMVIXL::VisitLongReverse(HInvoke* invoke) { - LocationSummary* locations = new (arena_) LocationSummary(invoke, - LocationSummary::kNoCall, - kIntrinsified); - locations->SetInAt(0, Location::RequiresRegister()); - locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap); + CreateLongToLongLocationsWithOverlap(arena_, invoke); } void IntrinsicCodeGeneratorARMVIXL::VisitLongReverse(HInvoke* invoke) { @@ -2849,11 +2833,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerReverseBytes(HInvoke* invoke) { } void IntrinsicLocationsBuilderARMVIXL::VisitLongReverseBytes(HInvoke* invoke) { - LocationSummary* locations = new (arena_) LocationSummary(invoke, - LocationSummary::kNoCall, - kIntrinsified); - locations->SetInAt(0, Location::RequiresRegister()); - locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap); + CreateLongToLongLocationsWithOverlap(arena_, invoke); } void IntrinsicCodeGeneratorARMVIXL::VisitLongReverseBytes(HInvoke* invoke) { @@ -2982,7 +2962,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerHighestOneBit(HInvoke* invoke) { } void IntrinsicLocationsBuilderARMVIXL::VisitLongHighestOneBit(HInvoke* invoke) { - CreateIntToIntLocationsWithOverlap(arena_, invoke); + CreateLongToLongLocationsWithOverlap(arena_, invoke); } void IntrinsicCodeGeneratorARMVIXL::VisitLongHighestOneBit(HInvoke* invoke) { @@ -3047,7 +3027,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerLowestOneBit(HInvoke* invoke) { } void IntrinsicLocationsBuilderARMVIXL::VisitLongLowestOneBit(HInvoke* invoke) { - CreateIntToIntLocationsWithOverlap(arena_, invoke); + CreateLongToLongLocationsWithOverlap(arena_, invoke); } void IntrinsicCodeGeneratorARMVIXL::VisitLongLowestOneBit(HInvoke* invoke) { diff --git a/compiler/optimizing/load_store_analysis.h b/compiler/optimizing/load_store_analysis.h index a2c17944f2..02bc254729 100644 --- a/compiler/optimizing/load_store_analysis.h +++ b/compiler/optimizing/load_store_analysis.h @@ -461,49 +461,15 @@ class HeapLocationCollector : public HGraphVisitor { has_heap_stores_ = true; } - void VisitNewInstance(HNewInstance* new_instance) OVERRIDE { - // Any references appearing in the ref_info_array_ so far cannot alias with new_instance. - CreateReferenceInfoForReferenceType(new_instance); - } - - void VisitNewArray(HNewArray* new_array) OVERRIDE { - // Any references appearing in the ref_info_array_ so far cannot alias with new_array. - CreateReferenceInfoForReferenceType(new_array); - } - - void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* instruction) OVERRIDE { - CreateReferenceInfoForReferenceType(instruction); - } - - void VisitInvokeVirtual(HInvokeVirtual* instruction) OVERRIDE { - CreateReferenceInfoForReferenceType(instruction); - } - - void VisitInvokeInterface(HInvokeInterface* instruction) OVERRIDE { - CreateReferenceInfoForReferenceType(instruction); - } - - void VisitInvokeUnresolved(HInvokeUnresolved* instruction) OVERRIDE { - CreateReferenceInfoForReferenceType(instruction); - } - - void VisitInvokePolymorphic(HInvokePolymorphic* instruction) OVERRIDE { - CreateReferenceInfoForReferenceType(instruction); - } - - void VisitLoadString(HLoadString* instruction) OVERRIDE { - CreateReferenceInfoForReferenceType(instruction); - } - - void VisitPhi(HPhi* instruction) OVERRIDE { - CreateReferenceInfoForReferenceType(instruction); - } - - void VisitParameterValue(HParameterValue* instruction) OVERRIDE { - CreateReferenceInfoForReferenceType(instruction); - } - - void VisitSelect(HSelect* instruction) OVERRIDE { + void VisitInstruction(HInstruction* instruction) OVERRIDE { + // Any new-instance or new-array cannot alias with references that + // pre-exist the new-instance/new-array. We append entries into + // ref_info_array_ which keeps track of the order of creation + // of reference values since we visit the blocks in reverse post order. + // + // By default, VisitXXX() (including VisitPhi()) calls VisitInstruction(), + // unless VisitXXX() is overridden. VisitInstanceFieldGet() etc. above + // also call CreateReferenceInfoForReferenceType() explicitly. CreateReferenceInfoForReferenceType(instruction); } diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc index 65389252e2..5c097da16f 100644 --- a/compiler/verifier_deps_test.cc +++ b/compiler/verifier_deps_test.cc @@ -624,7 +624,7 @@ TEST_F(VerifierDepsTest, ConstClass_Resolved) { } TEST_F(VerifierDepsTest, ConstClass_Unresolved) { - ASSERT_TRUE(VerifyMethod("ConstClass_Unresolved")); + ASSERT_FALSE(VerifyMethod("ConstClass_Unresolved")); ASSERT_TRUE(HasClass("LUnresolvedClass;", false)); } @@ -634,7 +634,7 @@ TEST_F(VerifierDepsTest, CheckCast_Resolved) { } TEST_F(VerifierDepsTest, CheckCast_Unresolved) { - ASSERT_TRUE(VerifyMethod("CheckCast_Unresolved")); + ASSERT_FALSE(VerifyMethod("CheckCast_Unresolved")); ASSERT_TRUE(HasClass("LUnresolvedClass;", false)); } @@ -644,7 +644,7 @@ TEST_F(VerifierDepsTest, InstanceOf_Resolved) { } TEST_F(VerifierDepsTest, InstanceOf_Unresolved) { - ASSERT_TRUE(VerifyMethod("InstanceOf_Unresolved")); + ASSERT_FALSE(VerifyMethod("InstanceOf_Unresolved")); ASSERT_TRUE(HasClass("LUnresolvedClass;", false)); } @@ -654,12 +654,12 @@ TEST_F(VerifierDepsTest, NewInstance_Resolved) { } TEST_F(VerifierDepsTest, NewInstance_Unresolved) { - ASSERT_TRUE(VerifyMethod("NewInstance_Unresolved")); + ASSERT_FALSE(VerifyMethod("NewInstance_Unresolved")); ASSERT_TRUE(HasClass("LUnresolvedClass;", false)); } TEST_F(VerifierDepsTest, NewArray_Unresolved) { - ASSERT_TRUE(VerifyMethod("NewArray_Unresolved")); + ASSERT_FALSE(VerifyMethod("NewArray_Unresolved")); ASSERT_TRUE(HasClass("[LUnresolvedClass;", false)); } diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc index 1cca36ba2e..6c0d492be6 100644 --- a/openjdkjvmti/OpenjdkJvmTi.cc +++ b/openjdkjvmti/OpenjdkJvmTi.cc @@ -318,12 +318,10 @@ class JvmtiFunctions { return StackUtil::GetFrameLocation(env, thread, depth, method_ptr, location_ptr); } - static jvmtiError NotifyFramePop(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED) { + static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_generate_frame_pop_events); - return ERR(NOT_IMPLEMENTED); + return StackUtil::NotifyFramePop(env, thread, depth); } static jvmtiError ForceEarlyReturnObject(jvmtiEnv* env, diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h index 71a8d304fb..d74e25b807 100644 --- a/openjdkjvmti/art_jvmti.h +++ b/openjdkjvmti/art_jvmti.h @@ -52,6 +52,7 @@ namespace art { class ArtField; class ArtMethod; +class ShadowFrame; } // namespace art namespace openjdkjvmti { @@ -81,6 +82,7 @@ struct ArtJvmTiEnv : public jvmtiEnv { // Set of breakpoints is unique to each jvmtiEnv. std::unordered_set<Breakpoint> breakpoints; + std::unordered_set<const art::ShadowFrame*> notify_frames; ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler); @@ -235,7 +237,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_maintain_original_method_order = 1, .can_generate_single_step_events = 1, .can_generate_exception_events = 0, - .can_generate_frame_pop_events = 0, + .can_generate_frame_pop_events = 1, .can_generate_breakpoint_events = 1, .can_suspend = 1, .can_redefine_any_class = 0, diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h index 32dba3e3e1..31edc73ac6 100644 --- a/openjdkjvmti/events-inl.h +++ b/openjdkjvmti/events-inl.h @@ -244,6 +244,35 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kBreakpoint>(art::Thread* } } +// Need to give custom specializations for FramePop since it needs to filter out which particular +// agents get the event. This specialization gets an extra argument so we can determine which (if +// any) environments have the frame pop. +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFramePop>( + art::Thread* thread, + JNIEnv* jnienv, + jthread jni_thread, + jmethodID jmethod, + jboolean is_exception, + const art::ShadowFrame* frame) const { + for (ArtJvmTiEnv* env : envs) { + // Search for the frame. Do this before checking if we need to send the event so that we don't + // have to deal with use-after-free or the frames being reallocated later. + if (env != nullptr && env->notify_frames.erase(frame) != 0) { + if (ShouldDispatch<ArtJvmtiEvent::kFramePop>(env, thread)) { + // We temporarily clear any pending exceptions so the event can call back into java code. + ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); + jnienv->ExceptionClear(); + auto callback = impl::GetCallback<ArtJvmtiEvent::kFramePop>(env); + (*callback)(env, jnienv, jni_thread, jmethod, is_exception); + if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { + jnienv->Throw(thr.get()); + } + } + } + } +} + // Need to give custom specializations for FieldAccess and FieldModification since they need to // filter out which particular fields agents want to get notified on. // TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc index 73e688190d..acef682a13 100644 --- a/openjdkjvmti/events.cc +++ b/openjdkjvmti/events.cc @@ -538,6 +538,20 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat } } + void WatchedFramePop(art::Thread* self, const art::ShadowFrame& frame) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFramePop)) { + art::JNIEnvExt* jnienv = self->GetJniEnv(); + jboolean is_exception_pending = self->IsExceptionPending(); + RunEventCallback<ArtJvmtiEvent::kFramePop>( + self, + jnienv, + art::jni::EncodeArtMethod(frame.GetMethod()), + is_exception_pending, + &frame); + } + } + // Call-back when an exception is thrown. void ExceptionThrown(art::Thread* self ATTRIBUTE_UNUSED, art::Handle<art::mirror::Throwable> exception_object ATTRIBUTE_UNUSED) @@ -582,6 +596,8 @@ static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) { case ArtJvmtiEvent::kBreakpoint: case ArtJvmtiEvent::kSingleStep: return art::instrumentation::Instrumentation::kDexPcMoved; + case ArtJvmtiEvent::kFramePop: + return art::instrumentation::Instrumentation::kWatchedFramePop; default: LOG(FATAL) << "Unknown event "; return 0; @@ -648,6 +664,15 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { } return; } + // FramePop can never be disabled once it's been turned on since we would either need to deal + // with dangling pointers or have missed events. + case ArtJvmtiEvent::kFramePop: + if (!enable || (enable && frame_pop_enabled)) { + break; + } else { + SetupTraceListener(method_trace_listener_.get(), event, enable); + break; + } case ArtJvmtiEvent::kMethodEntry: case ArtJvmtiEvent::kMethodExit: case ArtJvmtiEvent::kFieldAccess: diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h index 3d05fa18c7..b49e80c46d 100644 --- a/openjdkjvmti/events.h +++ b/openjdkjvmti/events.h @@ -223,6 +223,11 @@ class EventHandler { std::unique_ptr<JvmtiAllocationListener> alloc_listener_; std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_; std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_; + + // True if frame pop has ever been enabled. Since we store pointers to stack frames we need to + // continue to listen to this event even if it has been disabled. + // TODO We could remove the listeners once all jvmtiEnvs have drained their shadow-frame vectors. + bool frame_pop_enabled; }; } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc index 35f2f0cb07..f99b16758c 100644 --- a/openjdkjvmti/ti_method.cc +++ b/openjdkjvmti/ti_method.cc @@ -51,6 +51,7 @@ #include "stack.h" #include "thread-current-inl.h" #include "thread_list.h" +#include "ti_stack.h" #include "ti_thread.h" #include "ti_phase.h" @@ -533,39 +534,6 @@ jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* i return IsMethodT(env, m, test, is_synthetic_ptr); } -struct FindFrameAtDepthVisitor : art::StackVisitor { - public: - FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth) - REQUIRES_SHARED(art::Locks::mutator_lock_) - : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames), - found_frame_(false), - cnt_(0), - depth_(static_cast<size_t>(depth)) { } - - bool FoundFrame() { - return found_frame_; - } - - bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS { - if (GetMethod()->IsRuntimeMethod()) { - return true; - } - if (cnt_ == depth_) { - // We found our frame, exit. - found_frame_ = true; - return false; - } else { - cnt_++; - return true; - } - } - - private: - bool found_frame_; - size_t cnt_; - size_t depth_; -}; - class CommonLocalVariableClosure : public art::Closure { public: CommonLocalVariableClosure(art::Thread* caller, diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc index c679d731fe..1b4e910062 100644 --- a/openjdkjvmti/ti_redefine.cc +++ b/openjdkjvmti/ti_redefine.cc @@ -1364,7 +1364,6 @@ jvmtiError Redefiner::Run() { } void Redefiner::ClassRedefinition::UpdateMethods(art::ObjPtr<art::mirror::Class> mclass, - art::ObjPtr<art::mirror::DexCache> new_dex_cache, const art::DexFile::ClassDef& class_def) { art::ClassLinker* linker = driver_->runtime_->GetClassLinker(); art::PointerSize image_pointer_size = linker->GetImagePointerSize(); @@ -1396,7 +1395,6 @@ void Redefiner::ClassRedefinition::UpdateMethods(art::ObjPtr<art::mirror::Class> method.SetDexMethodIndex(dex_method_idx); linker->SetEntryPointsToInterpreter(&method); method.SetCodeItemOffset(dex_file_->FindCodeItemOffset(class_def, dex_method_idx)); - method.SetDexCacheResolvedMethods(new_dex_cache->GetResolvedMethods(), image_pointer_size); // Clear all the intrinsics related flags. method.ClearAccessFlags(art::kAccIntrinsic | (~art::kAccFlagsNotUsedByIntrinsic)); // Notify the jit that this method is redefined. @@ -1433,7 +1431,7 @@ void Redefiner::ClassRedefinition::UpdateClass( art::ObjPtr<art::mirror::Object> original_dex_file) { DCHECK_EQ(dex_file_->NumClassDefs(), 1u); const art::DexFile::ClassDef& class_def = dex_file_->GetClassDef(0); - UpdateMethods(mclass, new_dex_cache, class_def); + UpdateMethods(mclass, class_def); UpdateFields(mclass); // Update the class fields. diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h index 984f922e25..528563bd07 100644 --- a/openjdkjvmti/ti_redefine.h +++ b/openjdkjvmti/ti_redefine.h @@ -184,7 +184,6 @@ class Redefiner { REQUIRES(art::Locks::mutator_lock_); void UpdateMethods(art::ObjPtr<art::mirror::Class> mclass, - art::ObjPtr<art::mirror::DexCache> new_dex_cache, const art::DexFile::ClassDef& class_def) REQUIRES(art::Locks::mutator_lock_); diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc index 20de9aa8fd..e165187348 100644 --- a/openjdkjvmti/ti_stack.cc +++ b/openjdkjvmti/ti_stack.cc @@ -37,6 +37,7 @@ #include <vector> #include "art_field-inl.h" +#include "art_method-inl.h" #include "art_jvmti.h" #include "art_method-inl.h" #include "barrier.h" @@ -54,6 +55,7 @@ #include "nativehelper/ScopedLocalRef.h" #include "scoped_thread_state_change-inl.h" #include "stack.h" +#include "ti_thread.h" #include "thread-current-inl.h" #include "thread_list.h" #include "thread_pool.h" @@ -991,4 +993,74 @@ jvmtiError StackUtil::GetOwnedMonitorInfo(jvmtiEnv* env, return GetOwnedMonitorInfoCommon(thread, handle_fun); } +jvmtiError StackUtil::NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) { + if (depth < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + ArtJvmTiEnv* tienv = ArtJvmTiEnv::AsArtJvmTiEnv(env); + art::Thread* self = art::Thread::Current(); + art::Thread* target; + do { + ThreadUtil::SuspendCheck(self); + art::MutexLock ucsl_mu(self, *art::Locks::user_code_suspension_lock_); + // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by a + // user-code suspension. We retry and do another SuspendCheck to clear this. + if (ThreadUtil::WouldSuspendForUserCodeLocked(self)) { + continue; + } + // From now on we know we cannot get suspended by user-code. + // NB This does a SuspendCheck (during thread state change) so we need to make sure we don't + // have the 'suspend_lock' locked here. + art::ScopedObjectAccess soa(self); + art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_); + target = ThreadUtil::GetNativeThread(thread, soa); + if (target == nullptr) { + return ERR(THREAD_NOT_ALIVE); + } else if (target != self) { + // TODO This is part of the spec but we could easily avoid needing to do it. We would just put + // all the logic into a sync-checkpoint. + art::MutexLock tscl_mu(self, *art::Locks::thread_suspend_count_lock_); + if (target->GetUserCodeSuspendCount() == 0) { + return ERR(THREAD_NOT_SUSPENDED); + } + } + // We hold the user_code_suspension_lock_ so the target thread is staying suspended until we are + // done (unless it's 'self' in which case we don't care since we aren't going to be returning). + // TODO We could implement this using a synchronous checkpoint and not bother with any of the + // suspension stuff. The spec does specifically say to return THREAD_NOT_SUSPENDED though. + // Find the requested stack frame. + std::unique_ptr<art::Context> context(art::Context::Create()); + FindFrameAtDepthVisitor visitor(target, context.get(), depth); + visitor.WalkStack(); + if (!visitor.FoundFrame()) { + return ERR(NO_MORE_FRAMES); + } + art::ArtMethod* method = visitor.GetMethod(); + if (method->IsNative()) { + return ERR(OPAQUE_FRAME); + } + // From here we are sure to succeed. + bool needs_instrument = false; + // Get/create a shadow frame + art::ShadowFrame* shadow_frame = visitor.GetCurrentShadowFrame(); + if (shadow_frame == nullptr) { + needs_instrument = true; + const size_t frame_id = visitor.GetFrameId(); + const uint16_t num_regs = method->GetCodeItem()->registers_size_; + shadow_frame = target->FindOrCreateDebuggerShadowFrame(frame_id, + num_regs, + method, + visitor.GetDexPc()); + } + // Mark shadow frame as needs_notify_pop_ + shadow_frame->SetNotifyPop(true); + tienv->notify_frames.insert(shadow_frame); + // Make sure can we will go to the interpreter and use the shadow frames. + if (needs_instrument) { + art::Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(target); + } + return OK; + } while (true); +} + } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_stack.h b/openjdkjvmti/ti_stack.h index 2f506d0064..b41fa4bf5a 100644 --- a/openjdkjvmti/ti_stack.h +++ b/openjdkjvmti/ti_stack.h @@ -35,7 +35,9 @@ #include "jni.h" #include "jvmti.h" +#include "art_method.h" #include "base/mutex.h" +#include "stack.h" namespace openjdkjvmti { @@ -77,6 +79,41 @@ class StackUtil { jthread thread, jint* owned_monitor_count_ptr, jobject** owned_monitors_ptr); + + static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth); +}; + +struct FindFrameAtDepthVisitor : art::StackVisitor { + public: + FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth) + REQUIRES_SHARED(art::Locks::mutator_lock_) + : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames), + found_frame_(false), + cnt_(0), + depth_(static_cast<size_t>(depth)) { } + + bool FoundFrame() { + return found_frame_; + } + + bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS { + if (GetMethod()->IsRuntimeMethod()) { + return true; + } + if (cnt_ == depth_) { + // We found our frame, exit. + found_frame_ = true; + return false; + } else { + cnt_++; + return true; + } + } + + private: + bool found_frame_; + size_t cnt_; + size_t depth_; }; } // namespace openjdkjvmti diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc index ed7623a465..efa2969eea 100644 --- a/patchoat/patchoat.cc +++ b/patchoat/patchoat.cc @@ -686,8 +686,6 @@ void PatchOat::FixupMethod(ArtMethod* object, ArtMethod* copy) { // Just update the entry points if it looks like we should. // TODO: sanity check all the pointers' values copy->SetDeclaringClass(RelocatedAddressOfPointer(object->GetDeclaringClass())); - copy->SetDexCacheResolvedMethods( - RelocatedAddressOfPointer(object->GetDexCacheResolvedMethods(pointer_size)), pointer_size); copy->SetEntryPointFromQuickCompiledCodePtrSize(RelocatedAddressOfPointer( object->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size)), pointer_size); // No special handling for IMT conflict table since all pointers are moved by the same offset. diff --git a/profman/profman.cc b/profman/profman.cc index fd3bd11c0e..d0c99e0201 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -116,9 +116,9 @@ NO_RETURN static void Usage(const char *fmt, ...) { UsageError(" --generate-test-profile=<filename>: generates a random profile file for testing."); UsageError(" --generate-test-profile-num-dex=<number>: number of dex files that should be"); UsageError(" included in the generated profile. Defaults to 20."); - UsageError(" --generate-test-profile-method-ratio=<number>: the percentage from the maximum"); + UsageError(" --generate-test-profile-method-percentage=<number>: the percentage from the maximum"); UsageError(" number of methods that should be generated. Defaults to 5."); - UsageError(" --generate-test-profile-class-ratio=<number>: the percentage from the maximum"); + UsageError(" --generate-test-profile-class-percentage=<number>: the percentage from the maximum"); UsageError(" number of classes that should be generated. Defaults to 5."); UsageError(" --generate-test-profile-seed=<number>: seed for random number generator used when"); UsageError(" generating random test profiles. Defaults to using NanoTime."); @@ -151,8 +151,8 @@ NO_RETURN static void Usage(const char *fmt, ...) { // Note: make sure you update the Usage if you change these values. static constexpr uint16_t kDefaultTestProfileNumDex = 20; -static constexpr uint16_t kDefaultTestProfileMethodRatio = 5; -static constexpr uint16_t kDefaultTestProfileClassRatio = 5; +static constexpr uint16_t kDefaultTestProfileMethodPercentage = 5; +static constexpr uint16_t kDefaultTestProfileClassPercentage = 5; // Separators used when parsing human friendly representation of profiles. static const std::string kMethodSep = "->"; @@ -178,8 +178,8 @@ class ProfMan FINAL { generate_boot_image_profile_(false), dump_output_to_fd_(kInvalidFd), test_profile_num_dex_(kDefaultTestProfileNumDex), - test_profile_method_ratio_(kDefaultTestProfileMethodRatio), - test_profile_class_ratio_(kDefaultTestProfileClassRatio), + test_profile_method_percerntage_(kDefaultTestProfileMethodPercentage), + test_profile_class_percentage_(kDefaultTestProfileClassPercentage), test_profile_seed_(NanoTime()), start_ns_(NanoTime()) {} @@ -253,15 +253,15 @@ class ProfMan FINAL { "--generate-test-profile-num-dex", &test_profile_num_dex_, Usage); - } else if (option.starts_with("--generate-test-profile-method-ratio")) { + } else if (option.starts_with("--generate-test-profile-method-percentage")) { ParseUintOption(option, - "--generate-test-profile-method-ratio", - &test_profile_method_ratio_, + "--generate-test-profile-method-percentage", + &test_profile_method_percerntage_, Usage); - } else if (option.starts_with("--generate-test-profile-class-ratio")) { + } else if (option.starts_with("--generate-test-profile-class-percentage")) { ParseUintOption(option, - "--generate-test-profile-class-ratio", - &test_profile_class_ratio_, + "--generate-test-profile-class-percentage", + &test_profile_class_percentage_, Usage); } else if (option.starts_with("--generate-test-profile-seed=")) { ParseUintOption(option, "--generate-test-profile-seed", &test_profile_seed_, Usage); @@ -1014,11 +1014,11 @@ class ProfMan FINAL { int GenerateTestProfile() { // Validate parameters for this command. - if (test_profile_method_ratio_ > 100) { - Usage("Invalid ratio for --generate-test-profile-method-ratio"); + if (test_profile_method_percerntage_ > 100) { + Usage("Invalid percentage for --generate-test-profile-method-percentage"); } - if (test_profile_class_ratio_ > 100) { - Usage("Invalid ratio for --generate-test-profile-class-ratio"); + if (test_profile_class_percentage_ > 100) { + Usage("Invalid percentage for --generate-test-profile-class-percentage"); } // If given APK files or DEX locations, check that they're ok. if (!apk_files_.empty() || !apks_fd_.empty() || !dex_locations_.empty()) { @@ -1039,8 +1039,8 @@ class ProfMan FINAL { if (apk_files_.empty() && apks_fd_.empty() && dex_locations_.empty()) { result = ProfileCompilationInfo::GenerateTestProfile(profile_test_fd, test_profile_num_dex_, - test_profile_method_ratio_, - test_profile_class_ratio_, + test_profile_method_percerntage_, + test_profile_class_percentage_, test_profile_seed_); } else { // Initialize MemMap for ZipArchive::OpenFromFd. @@ -1051,6 +1051,8 @@ class ProfMan FINAL { // Create a random profile file based on the set of dex files. result = ProfileCompilationInfo::GenerateTestProfile(profile_test_fd, dex_files, + test_profile_method_percerntage_, + test_profile_class_percentage_, test_profile_seed_); } close(profile_test_fd); // ignore close result. @@ -1101,8 +1103,8 @@ class ProfMan FINAL { std::string test_profile_; std::string create_profile_from_file_; uint16_t test_profile_num_dex_; - uint16_t test_profile_method_ratio_; - uint16_t test_profile_class_ratio_; + uint16_t test_profile_method_percerntage_; + uint16_t test_profile_class_percentage_; uint32_t test_profile_seed_; uint64_t start_ns_; }; diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index ea8d501939..69c615db9f 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -1596,10 +1596,21 @@ ENTRY art_quick_imt_conflict_trampoline .cfi_rel_offset r1, 0 .cfi_rel_offset r2, 4 ldr r4, [sp, #(2 * 4)] // Load referrer. + ldr r2, [r0, #ART_METHOD_JNI_OFFSET_32] // Load ImtConflictTable + // Load the declaring class (without read barrier) and access flags (for obsolete method check). + // The obsolete flag is set with suspended threads, so we do not need an acquire operation here. +#if ART_METHOD_ACCESS_FLAGS_OFFSET != ART_METHOD_DECLARING_CLASS_OFFSET + 4 +#error "Expecting declaring class and access flags to be consecutive for LDRD." +#endif + ldrd r0, r1, [r4, #ART_METHOD_DECLARING_CLASS_OFFSET] + // If the method is obsolete, just go through the dex cache miss slow path. + lsrs r1, #(ACC_OBSOLETE_METHOD_SHIFT + 1) + bcs .Limt_conflict_trampoline_dex_cache_miss + ldr r4, [r0, #MIRROR_CLASS_DEX_CACHE_OFFSET] // Load the DexCache (without read barrier). + UNPOISON_HEAP_REF r4 ubfx r1, r12, #0, #METHOD_DEX_CACHE_HASH_BITS // Calculate DexCache method slot index. - ldr r4, [r4, #ART_METHOD_DEX_CACHE_METHODS_OFFSET_32] // Load dex cache methods array + ldr r4, [r4, #MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET] // Load the resolved methods. add r4, r4, r1, lsl #(POINTER_SIZE_SHIFT + 1) // Load DexCache method slot address. - ldr r2, [r0, #ART_METHOD_JNI_OFFSET_32] // Load ImtConflictTable // FIXME: Configure the build to use the faster code when appropriate. // Currently we fall back to the slower version. @@ -1832,45 +1843,47 @@ 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 - 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 - DELIVER_PENDING_EXCEPTION -END art_quick_instrumentation_entry - -ENTRY art_quick_instrumentation_exit + 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_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 + 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*) - 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 + 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 - bx lr + cbz r2, .Ldo_deliver_instrumentation_exception + @ Deliver exception if we got nullptr as function. + bx r2 @ Otherwise, return +.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_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 + DELIVER_PENDING_EXCEPTION +END art_quick_instrumentation_entry /* * Instrumentation has requested that we deoptimize into the interpreter. The deoptimization @@ -1878,7 +1891,7 @@ END art_quick_instrumentation_exit */ .extern artDeoptimize ENTRY art_quick_deoptimize - SETUP_SAVE_EVERYTHING_FRAME r0 + SETUP_SAVE_ALL_CALLEE_SAVES_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 6c9ce93d2e..802cf5e711 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -2060,8 +2060,18 @@ END art_quick_proxy_invoke_handler .extern artLookupResolvedMethod ENTRY art_quick_imt_conflict_trampoline ldr xIP0, [sp, #0] // Load referrer + // Load the declaring class (without read barrier) and access flags (for obsolete method check). + // The obsolete flag is set with suspended threads, so we do not need an acquire operation here. +#if ART_METHOD_ACCESS_FLAGS_OFFSET != ART_METHOD_DECLARING_CLASS_OFFSET + 4 +#error "Expecting declaring class and access flags to be consecutive for LDP." +#endif + ldp wIP0, w15, [xIP0, #ART_METHOD_DECLARING_CLASS_OFFSET] + // If the method is obsolete, just go through the dex cache miss slow path. + tbnz x15, #ACC_OBSOLETE_METHOD_SHIFT, .Limt_conflict_trampoline_dex_cache_miss + ldr wIP0, [xIP0, #MIRROR_CLASS_DEX_CACHE_OFFSET] // Load the DexCache (without read barrier). + UNPOISON_HEAP_REF wIP0 ubfx x15, xIP1, #0, #METHOD_DEX_CACHE_HASH_BITS // Calculate DexCache method slot index. - ldr xIP0, [xIP0, #ART_METHOD_DEX_CACHE_METHODS_OFFSET_64] // Load dex cache methods array + ldr xIP0, [xIP0, #MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET] // Load the resolved methods. add xIP0, xIP0, x15, lsl #(POINTER_SIZE_SHIFT + 1) // Load DexCache method slot address. // Relaxed atomic load x14:x15 from the dex cache slot. @@ -2355,31 +2365,32 @@ END art_quick_instrumentation_entry .extern artInstrumentationMethodExitFromCode ENTRY art_quick_instrumentation_exit mov xLR, #0 // Clobber LR for later checks. - SETUP_SAVE_EVERYTHING_FRAME - 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. + 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. mov x0, xSELF // Pass Thread. bl artInstrumentationMethodExitFromCode // (Thread*, SP, gpr_res*, fpr_res*) - 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 + 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 REFRESH_MARKING_REGISTER - 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 + cbz xIP0, 1f // Handle error + br xIP0 // Tail-call out. +1: + DELIVER_PENDING_EXCEPTION END art_quick_instrumentation_exit /* @@ -2388,7 +2399,7 @@ END art_quick_instrumentation_exit */ .extern artDeoptimize ENTRY art_quick_deoptimize - SETUP_SAVE_EVERYTHING_FRAME + SETUP_SAVE_ALL_CALLEE_SAVES_FRAME mov x0, xSELF // Pass thread. bl artDeoptimize // (Thread*) brk 0 diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S index bb82d5819d..b876353183 100644 --- a/runtime/arch/mips/quick_entrypoints_mips.S +++ b/runtime/arch/mips/quick_entrypoints_mips.S @@ -2112,10 +2112,18 @@ ENTRY art_quick_imt_conflict_trampoline SETUP_SAVE_REFS_AND_ARGS_FRAME_REGISTERS_ONLY /* save_s4_thru_s8 */ 0 lw $t8, FRAME_SIZE_SAVE_REFS_AND_ARGS($sp) # $t8 = referrer. + // If the method is obsolete, just go through the dex cache miss slow path. + // The obsolete flag is set with suspended threads, so we do not need an acquire operation here. + lw $t9, ART_METHOD_ACCESS_FLAGS_OFFSET($t8) # $t9 = access flags. + sll $t9, $t9, 31 - ACC_OBSOLETE_METHOD_SHIFT # Move obsolete method bit to sign bit. + bltz $t9, .Limt_conflict_trampoline_dex_cache_miss + lw $t8, ART_METHOD_DECLARING_CLASS_OFFSET($t8) # $t8 = declaring class (no read barrier). + lw $t8, MIRROR_CLASS_DEX_CACHE_OFFSET($t8) # $t8 = dex cache (without read barrier). + UNPOISON_HEAP_REF $t8 la $t9, __atomic_load_8 addiu $sp, $sp, -ARG_SLOT_SIZE # Reserve argument slots on the stack. .cfi_adjust_cfa_offset ARG_SLOT_SIZE - lw $t8, ART_METHOD_DEX_CACHE_METHODS_OFFSET_32($t8) # $t8 = dex cache methods array. + lw $t8, MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET($t8) # $t8 = dex cache methods array. move $s2, $t7 # $s2 = method index (callee-saved). lw $s3, ART_METHOD_JNI_OFFSET_32($a0) # $s3 = ImtConflictTable (callee-saved). diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S index 7350c8521a..eeaae3c698 100644 --- a/runtime/arch/mips64/quick_entrypoints_mips64.S +++ b/runtime/arch/mips64/quick_entrypoints_mips64.S @@ -2024,8 +2024,16 @@ ENTRY art_quick_imt_conflict_trampoline SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL /* save_s4_thru_s8 */ 0 ld $t1, FRAME_SIZE_SAVE_REFS_AND_ARGS($sp) # $t1 = referrer. + // If the method is obsolete, just go through the dex cache miss slow path. + // The obsolete flag is set with suspended threads, so we do not need an acquire operation here. + lw $t9, ART_METHOD_ACCESS_FLAGS_OFFSET($t1) # $t9 = access flags. + sll $t9, $t9, 31 - ACC_OBSOLETE_METHOD_SHIFT # Move obsolete method bit to sign bit. + bltzc $t9, .Limt_conflict_trampoline_dex_cache_miss + lwu $t1, ART_METHOD_DECLARING_CLASS_OFFSET($t1) # $t1 = declaring class (no read barrier). + lwu $t1, MIRROR_CLASS_DEX_CACHE_OFFSET($t1) # $t1 = dex cache (without read barrier). + UNPOISON_HEAP_REF $t1 dla $t9, __atomic_load_16 - ld $t1, ART_METHOD_DEX_CACHE_METHODS_OFFSET_64($t1) # $t1 = dex cache methods array. + ld $t1, MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET($t1) # $t1 = dex cache methods array. dext $s2, $t0, 0, 32 # $s2 = zero-extended method index # (callee-saved). diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S index af82e08698..f08c7fedcc 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -1787,7 +1787,14 @@ DEFINE_FUNCTION art_quick_imt_conflict_trampoline PUSH ESI PUSH EDX movl 16(%esp), %edi // Load referrer. - movl ART_METHOD_DEX_CACHE_METHODS_OFFSET_32(%edi), %edi // Load dex cache methods array. + // If the method is obsolete, just go through the dex cache miss slow path. + // The obsolete flag is set with suspended threads, so we do not need an acquire operation here. + testl LITERAL(ACC_OBSOLETE_METHOD), ART_METHOD_ACCESS_FLAGS_OFFSET(%edi) + jnz .Limt_conflict_trampoline_dex_cache_miss + movl ART_METHOD_DECLARING_CLASS_OFFSET(%edi), %edi // Load declaring class (no read barrier). + movl MIRROR_CLASS_DEX_CACHE_OFFSET(%edi), %edi // Load the DexCache (without read barrier). + UNPOISON_HEAP_REF edi + movl MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET(%edi), %edi // Load the resolved methods. pushl ART_METHOD_JNI_OFFSET_32(%eax) // Push ImtConflictTable. CFI_ADJUST_CFA_OFFSET(4) movd %xmm7, %eax // Get target method index stored in xmm7. @@ -2056,43 +2063,42 @@ 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_EVERYTHING_FRAME ebx, ebx - - movl %esp, %ecx // Remember SP - subl LITERAL(8), %esp // Align stack. + SETUP_SAVE_REFS_ONLY_FRAME ebx, ebx + mov %esp, %ecx // Remember SP + subl LITERAL(8), %esp // Save float return value. CFI_ADJUST_CFA_OFFSET(8) - PUSH edx // Save gpr return value. edx and eax need to be together, - // which isn't the case in kSaveEverything frame. + movq %xmm0, (%esp) + PUSH edx // Save gpr return value. PUSH eax - leal 32(%esp), %eax // Get pointer to fpr_result, in kSaveEverything frame + leal 8(%esp), %eax // Get pointer to fpr_result 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*) - // 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) - 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 + 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 + CFI_ADJUST_CFA_OFFSET(-32) + RESTORE_SAVE_REFS_ONLY_FRAME + DELIVER_PENDING_EXCEPTION END_FUNCTION art_quick_instrumentation_exit /* @@ -2100,7 +2106,8 @@ END_FUNCTION art_quick_instrumentation_exit * will long jump to the upcall with a special exception of -1. */ DEFINE_FUNCTION art_quick_deoptimize - SETUP_SAVE_EVERYTHING_FRAME ebx, ebx + PUSH ebx // Entry point for a jump. Fake that we were called. + SETUP_SAVE_ALL_CALLEE_SAVES_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 6bf08289ee..b70abaa81e 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -1646,7 +1646,14 @@ DEFINE_FUNCTION art_quick_imt_conflict_trampoline int3 #else movq __SIZEOF_POINTER__(%rsp), %r10 // Load referrer. - movq ART_METHOD_DEX_CACHE_METHODS_OFFSET_64(%r10), %r10 // Load dex cache methods array. + // If the method is obsolete, just go through the dex cache miss slow path. + // The obsolete flag is set with suspended threads, so we do not need an acquire operation here. + testl LITERAL(ACC_OBSOLETE_METHOD), ART_METHOD_ACCESS_FLAGS_OFFSET(%r10) + jnz .Limt_conflict_trampoline_dex_cache_miss + movl ART_METHOD_DECLARING_CLASS_OFFSET(%r10), %r10d // Load declaring class (no read barrier). + movl MIRROR_CLASS_DEX_CACHE_OFFSET(%r10), %r10d // Load the DexCache (without read barrier). + UNPOISON_HEAP_REF r10d + movq MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET(%r10), %r10 // Load the resolved methods. mov %eax, %r11d // Remember method index in R11. andl LITERAL(METHOD_DEX_CACHE_SIZE_MINUS_ONE), %eax // Calculate DexCache method slot index. shll LITERAL(1), %eax // Multiply by 2 as entries have size 2 * __SIZEOF_POINTER__. @@ -2019,31 +2026,45 @@ 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_EVERYTHING_FRAME + SETUP_SAVE_REFS_ONLY_FRAME - 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. + // 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. call SYMBOL(artInstrumentationMethodExitFromCode) // (Thread*, SP, gpr_res*, fpr_res*) - testq %rax, %rax // Check if we have a return-pc to go to. If we don't then there was + 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 // an 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 + jz 1f + + addq LITERAL(8), %rsp // Drop fake return pc. + + jmp *%rdi // Return. +1: + DELIVER_PENDING_EXCEPTION END_FUNCTION art_quick_instrumentation_exit /* @@ -2051,7 +2072,10 @@ END_FUNCTION art_quick_instrumentation_exit * will long jump to the upcall with a special exception of -1. */ DEFINE_FUNCTION art_quick_deoptimize - SETUP_SAVE_EVERYTHING_FRAME // Stack should be aligned now. + 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. movq %gs:THREAD_SELF_OFFSET, %rdi // Pass Thread. call SYMBOL(artDeoptimize) // (Thread*) UNREACHABLE diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index 11f825353b..1588920e94 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -102,58 +102,6 @@ inline uint32_t ArtMethod::GetDexMethodIndex() { return GetDexMethodIndexUnchecked(); } -inline mirror::MethodDexCacheType* ArtMethod::GetDexCacheResolvedMethods(PointerSize pointer_size) { - return GetNativePointer<mirror::MethodDexCacheType*>(DexCacheResolvedMethodsOffset(pointer_size), - pointer_size); -} - -inline ArtMethod* ArtMethod::GetDexCacheResolvedMethod(uint16_t method_index, - PointerSize pointer_size) { - // NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here - // without accessing the DexCache and we don't want to do that in release build. - DCHECK_LT(method_index, GetInterfaceMethodIfProxy(pointer_size)->GetDexFile()->NumMethodIds()); - uint32_t slot_idx = method_index % mirror::DexCache::kDexCacheMethodCacheSize; - DCHECK_LT(slot_idx, GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods()); - mirror::MethodDexCachePair pair = mirror::DexCache::GetNativePairPtrSize( - GetDexCacheResolvedMethods(pointer_size), slot_idx, pointer_size); - ArtMethod* method = pair.GetObjectForIndex(method_index); - if (LIKELY(method != nullptr)) { - auto* declaring_class = method->GetDeclaringClass(); - if (LIKELY(declaring_class == nullptr || !declaring_class->IsErroneous())) { - return method; - } - } - return nullptr; -} - -inline void ArtMethod::SetDexCacheResolvedMethod(uint16_t method_index, - ArtMethod* new_method, - PointerSize pointer_size) { - // NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here - // without accessing the DexCache and we don't want to do that in release build. - DCHECK_LT(method_index, GetInterfaceMethodIfProxy(pointer_size)->GetDexFile()->NumMethodIds()); - DCHECK(new_method == nullptr || new_method->GetDeclaringClass() != nullptr); - uint32_t slot_idx = method_index % mirror::DexCache::kDexCacheMethodCacheSize; - DCHECK_LT(slot_idx, GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods()); - mirror::MethodDexCachePair pair(new_method, method_index); - mirror::DexCache::SetNativePairPtrSize( - GetDexCacheResolvedMethods(pointer_size), slot_idx, pair, pointer_size); -} - -inline bool ArtMethod::HasDexCacheResolvedMethods(PointerSize pointer_size) { - return GetDexCacheResolvedMethods(pointer_size) != nullptr; -} - -inline bool ArtMethod::HasSameDexCacheResolvedMethods(ArtMethod* other, PointerSize pointer_size) { - return GetDexCacheResolvedMethods(pointer_size) == - other->GetDexCacheResolvedMethods(pointer_size); -} - -inline bool ArtMethod::HasSameDexCacheResolvedMethods(mirror::MethodDexCacheType* other_cache, - PointerSize pointer_size) { - return GetDexCacheResolvedMethods(pointer_size) == other_cache; -} - inline ObjPtr<mirror::Class> ArtMethod::LookupResolvedClassFromTypeIndex(dex::TypeIndex type_idx) { ObjPtr<mirror::DexCache> dex_cache = GetDexCache(); ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(type_idx); @@ -403,13 +351,6 @@ inline ArtMethod* ArtMethod::GetInterfaceMethodIfProxy(PointerSize pointer_size) return interface_method; } -inline void ArtMethod::SetDexCacheResolvedMethods(mirror::MethodDexCacheType* new_dex_cache_methods, - PointerSize pointer_size) { - SetNativePointer(DexCacheResolvedMethodsOffset(pointer_size), - new_dex_cache_methods, - pointer_size); -} - inline dex::TypeIndex ArtMethod::GetReturnTypeIndex() { DCHECK(!IsProxyMethod()); const DexFile* dex_file = GetDexFile(); @@ -489,18 +430,12 @@ void ArtMethod::VisitRoots(RootVisitorType& visitor, PointerSize pointer_size) { } template <typename Visitor> -inline void ArtMethod::UpdateObjectsForImageRelocation(const Visitor& visitor, - PointerSize pointer_size) { +inline void ArtMethod::UpdateObjectsForImageRelocation(const Visitor& visitor) { mirror::Class* old_class = GetDeclaringClassUnchecked<kWithoutReadBarrier>(); mirror::Class* new_class = visitor(old_class); if (old_class != new_class) { SetDeclaringClass(new_class); } - mirror::MethodDexCacheType* old_methods = GetDexCacheResolvedMethods(pointer_size); - mirror::MethodDexCacheType* new_methods = visitor(old_methods); - if (old_methods != new_methods) { - SetDexCacheResolvedMethods(new_methods, pointer_size); - } } template <ReadBarrierOption kReadBarrierOption, typename Visitor> diff --git a/runtime/art_method.h b/runtime/art_method.h index 64988f2528..2d677617d9 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -137,6 +137,10 @@ class ArtMethod FINAL { } while (!access_flags_.compare_exchange_weak(old_access_flags, new_access_flags)); } + static MemberOffset AccessFlagsOffset() { + return MemberOffset(OFFSETOF_MEMBER(ArtMethod, access_flags_)); + } + // Approximate what kind of method call would be used for this method. InvokeType GetInvokeType() REQUIRES_SHARED(Locks::mutator_lock_); @@ -356,26 +360,6 @@ class ArtMethod FINAL { dex_method_index_ = new_idx; } - ALWAYS_INLINE mirror::MethodDexCacheType* GetDexCacheResolvedMethods(PointerSize pointer_size) - REQUIRES_SHARED(Locks::mutator_lock_); - ALWAYS_INLINE ArtMethod* GetDexCacheResolvedMethod(uint16_t method_index, - PointerSize pointer_size) - REQUIRES_SHARED(Locks::mutator_lock_); - - ALWAYS_INLINE void SetDexCacheResolvedMethod(uint16_t method_index, - ArtMethod* new_method, - PointerSize pointer_size) - REQUIRES_SHARED(Locks::mutator_lock_); - ALWAYS_INLINE void SetDexCacheResolvedMethods(mirror::MethodDexCacheType* new_dex_cache_methods, - PointerSize pointer_size) - REQUIRES_SHARED(Locks::mutator_lock_); - bool HasDexCacheResolvedMethods(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); - bool HasSameDexCacheResolvedMethods(ArtMethod* other, PointerSize pointer_size) - REQUIRES_SHARED(Locks::mutator_lock_); - bool HasSameDexCacheResolvedMethods(mirror::MethodDexCacheType* other_cache, - PointerSize pointer_size) - REQUIRES_SHARED(Locks::mutator_lock_); - // Lookup the Class* from the type index into this method's dex cache. ObjPtr<mirror::Class> LookupResolvedClassFromTypeIndex(dex::TypeIndex type_idx) REQUIRES_SHARED(Locks::mutator_lock_); @@ -427,12 +411,6 @@ class ArtMethod FINAL { void UnregisterNative() REQUIRES_SHARED(Locks::mutator_lock_); - static MemberOffset DexCacheResolvedMethodsOffset(PointerSize pointer_size) { - return MemberOffset(PtrSizedFieldsOffset(pointer_size) + OFFSETOF_MEMBER( - PtrSizedFields, dex_cache_resolved_methods_) / sizeof(void*) - * static_cast<size_t>(pointer_size)); - } - static MemberOffset DataOffset(PointerSize pointer_size) { return MemberOffset(PtrSizedFieldsOffset(pointer_size) + OFFSETOF_MEMBER( PtrSizedFields, data_) / sizeof(void*) * static_cast<size_t>(pointer_size)); @@ -686,8 +664,7 @@ class ArtMethod FINAL { // Update heap objects and non-entrypoint pointers by the passed in visitor for image relocation. // Does not use read barrier. template <typename Visitor> - ALWAYS_INLINE void UpdateObjectsForImageRelocation(const Visitor& visitor, - PointerSize pointer_size) + ALWAYS_INLINE void UpdateObjectsForImageRelocation(const Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_); // Update entry points by passing them through the visitor. @@ -728,9 +705,6 @@ class ArtMethod FINAL { // Must be the last fields in the method. struct PtrSizedFields { - // Short cuts to declaring_class_->dex_cache_ member for fast compiled code access. - mirror::MethodDexCacheType* dex_cache_resolved_methods_; - // Depending on the method type, the data is // - native method: pointer to the JNI function registered to this method // or a function to resolve the JNI function, diff --git a/runtime/asm_support.h b/runtime/asm_support.h index 44c0661e3f..f4830e2db5 100644 --- a/runtime/asm_support.h +++ b/runtime/asm_support.h @@ -141,7 +141,7 @@ ADD_TEST_EQ(SHADOWFRAME_CACHED_HOTNESS_COUNTDOWN_OFFSET, #define SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET (SHADOWFRAME_NUMBER_OF_VREGS_OFFSET + 10) ADD_TEST_EQ(SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET, static_cast<int32_t>(art::ShadowFrame::HotnessCountdownOffset())) -#define SHADOWFRAME_VREGS_OFFSET (SHADOWFRAME_NUMBER_OF_VREGS_OFFSET + 12) +#define SHADOWFRAME_VREGS_OFFSET (SHADOWFRAME_NUMBER_OF_VREGS_OFFSET + 16) ADD_TEST_EQ(SHADOWFRAME_VREGS_OFFSET, static_cast<int32_t>(art::ShadowFrame::VRegsOffset())) diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h index 439ecaf28e..d6f003027b 100644 --- a/runtime/class_linker-inl.h +++ b/runtime/class_linker-inl.h @@ -186,7 +186,8 @@ inline ArtMethod* ClassLinker::GetResolvedMethod(uint32_t method_idx, ArtMethod* // lookup in the context of the original method from where it steals the code. // However, we delay the GetInterfaceMethodIfProxy() until needed. DCHECK(!referrer->IsProxyMethod() || referrer->IsConstructor()); - ArtMethod* resolved_method = referrer->GetDexCacheResolvedMethod(method_idx, image_pointer_size_); + ArtMethod* resolved_method = referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedMethod( + method_idx, image_pointer_size_); if (resolved_method == nullptr) { return nullptr; } @@ -226,7 +227,8 @@ inline ArtMethod* ClassLinker::ResolveMethod(Thread* self, // However, we delay the GetInterfaceMethodIfProxy() until needed. DCHECK(!referrer->IsProxyMethod() || referrer->IsConstructor()); Thread::PoisonObjectPointersIfDebug(); - ArtMethod* resolved_method = referrer->GetDexCacheResolvedMethod(method_idx, image_pointer_size_); + ArtMethod* resolved_method = referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedMethod( + method_idx, image_pointer_size_); DCHECK(resolved_method == nullptr || !resolved_method->IsRuntimeMethod()); if (UNLIKELY(resolved_method == nullptr)) { referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_); diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 051c0c2938..1beb7837d4 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -1115,35 +1115,6 @@ static bool FlattenPathClassLoader(ObjPtr<mirror::ClassLoader> class_loader, return true; } -class FixupArtMethodArrayVisitor : public ArtMethodVisitor { - public: - explicit FixupArtMethodArrayVisitor(const ImageHeader& header) : header_(header) {} - - virtual void Visit(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) { - const bool is_copied = method->IsCopied(); - mirror::MethodDexCacheType* resolved_methods = - method->GetDexCacheResolvedMethods(kRuntimePointerSize); - if (resolved_methods != nullptr) { - bool in_image_space = false; - if (kIsDebugBuild || is_copied) { - in_image_space = header_.GetImageSection(ImageHeader::kSectionDexCacheArrays).Contains( - reinterpret_cast<const uint8_t*>(resolved_methods) - header_.GetImageBegin()); - } - // Must be in image space for non-miranda method. - DCHECK(is_copied || in_image_space) - << resolved_methods << " is not in image starting at " - << reinterpret_cast<void*>(header_.GetImageBegin()); - if (!is_copied || in_image_space) { - method->SetDexCacheResolvedMethods(method->GetDexCache()->GetResolvedMethods(), - kRuntimePointerSize); - } - } - } - - private: - const ImageHeader& header_; -}; - class VerifyDeclaringClassVisitor : public ArtMethodVisitor { public: VerifyDeclaringClassVisitor() REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_) @@ -1492,12 +1463,6 @@ bool AppImageClassLoadersAndDexCachesHelper::Update( FixupInternVisitor fixup_intern_visitor; bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_intern_visitor); } - if (*out_forward_dex_cache_array) { - ScopedTrace timing("Fixup ArtMethod dex cache arrays"); - FixupArtMethodArrayVisitor visitor(header); - header.VisitPackedArtMethods(&visitor, space->Begin(), kRuntimePointerSize); - Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader.Get()); - } if (kVerifyArtMethodDeclaringClasses) { ScopedTrace timing("Verify declaring classes"); ReaderMutexLock rmu(self, *Locks::heap_bitmap_lock_); @@ -3444,8 +3409,6 @@ void ClassLinker::LoadMethod(const DexFile& dex_file, dst->SetDeclaringClass(klass.Get()); dst->SetCodeItemOffset(it.GetMethodCodeItemOffset()); - dst->SetDexCacheResolvedMethods(klass->GetDexCache()->GetResolvedMethods(), image_pointer_size_); - uint32_t access_flags = it.GetMethodAccessFlags(); if (UNLIKELY(strcmp("finalize", method_name) == 0)) { @@ -4729,7 +4692,6 @@ void ClassLinker::CheckProxyMethod(ArtMethod* method, ArtMethod* prototype) cons // The proxy method doesn't have its own dex cache or dex file and so it steals those of its // interface prototype. The exception to this are Constructors and the Class of the Proxy itself. - CHECK(prototype->HasSameDexCacheResolvedMethods(method, image_pointer_size_)); auto* np = method->GetInterfaceMethodIfProxy(image_pointer_size_); CHECK_EQ(prototype->GetDeclaringClass()->GetDexCache(), np->GetDexCache()); CHECK_EQ(prototype->GetDexMethodIndex(), method->GetDexMethodIndex()); diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc index 5e9707c062..f887b8ed42 100644 --- a/runtime/class_linker_test.cc +++ b/runtime/class_linker_test.cc @@ -245,11 +245,6 @@ class ClassLinkerTest : public CommonRuntimeTest { EXPECT_TRUE(method->GetDeclaringClass() != nullptr); EXPECT_TRUE(method->GetName() != nullptr); EXPECT_TRUE(method->GetSignature() != Signature::NoSignature()); - - EXPECT_TRUE(method->HasDexCacheResolvedMethods(kRuntimePointerSize)); - EXPECT_TRUE(method->HasSameDexCacheResolvedMethods( - method->GetDeclaringClass()->GetDexCache()->GetResolvedMethods(), - kRuntimePointerSize)); } void AssertField(ObjPtr<mirror::Class> klass, ArtField* field) diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index be3e4f811a..8253739427 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -83,7 +83,7 @@ inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method, ObjPtr<mirror::DexCache> dex_cache = caller->GetDexCache(); const DexFile* dex_file = dex_cache->GetDexFile(); const DexFile::MethodId& method_id = dex_file->GetMethodId(method_index); - ArtMethod* inlined_method = caller->GetDexCacheResolvedMethod(method_index, kRuntimePointerSize); + ArtMethod* inlined_method = dex_cache->GetResolvedMethod(method_index, kRuntimePointerSize); if (inlined_method != nullptr) { DCHECK(!inlined_method->IsRuntimeMethod()); return inlined_method; @@ -106,7 +106,7 @@ inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method, << dex_file->GetMethodSignature(method_id) << " declared. " << "This must be due to duplicate classes or playing wrongly with class loaders"; } - caller->SetDexCacheResolvedMethod(method_index, inlined_method, kRuntimePointerSize); + dex_cache->SetResolvedMethod(method_index, inlined_method, kRuntimePointerSize); return inlined_method; } diff --git a/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc b/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc index 5f40711753..53f0727a5f 100644 --- a/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc @@ -73,11 +73,7 @@ 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 /* is_reference */, - self->GetException(), - true /* from_code */, - DeoptimizationMethodType::kDefault); + self->PushDeoptimizationContext(return_value, false, /* from_code */ true, self->GetException()); artDeoptimizeImpl(self, kind, true); } diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 5f713265df..c6abd2882a 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -744,11 +744,7 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, ObjPtr<mirror::Throwable> pending_exception; bool from_code = false; - DeoptimizationMethodType method_type; - self->PopDeoptimizationContext(/* out */ &result, - /* out */ &pending_exception, - /* out */ &from_code, - /* out */ &method_type); + self->PopDeoptimizationContext(&result, &pending_exception, /* out */ &from_code); // Push a transition back into managed code onto the linked list in thread. self->PushManagedStackFragment(&fragment); @@ -775,11 +771,7 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, if (pending_exception != nullptr) { self->SetException(pending_exception); } - interpreter::EnterInterpreterFromDeoptimize(self, - deopt_frame, - &result, - from_code, - DeoptimizationMethodType::kDefault); + interpreter::EnterInterpreterFromDeoptimize(self, deopt_frame, from_code, &result); } else { const char* old_cause = self->StartAssertNoThreadSuspension( "Building interpreter shadow frame"); @@ -831,11 +823,7 @@ 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' || shorty[0] == '[', /* class or array */ - self->GetException(), - false /* from_code */, - DeoptimizationMethodType::kDefault); + result, shorty[0] == 'L', /* from_code */ false, self->GetException()); // Set special exception to cause deoptimization. self->SetException(Thread::GetDeoptimizationException()); @@ -1053,8 +1041,7 @@ 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::kSaveEverything); + size_t return_pc_offset = GetCalleeSaveReturnPcOffset(kRuntimeISA, CalleeSaveType::kSaveRefsOnly); uintptr_t* return_pc = reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(sp) + return_pc_offset); CHECK_EQ(*return_pc, 0U); @@ -1271,7 +1258,7 @@ extern "C" const void* artQuickResolutionTrampoline( // FindVirtualMethodFor... This is ok for FindDexMethodIndexInOtherDexFile that only cares // about the name and signature. uint32_t update_dex_cache_method_index = called->GetDexMethodIndex(); - if (!called->HasSameDexCacheResolvedMethods(caller, kRuntimePointerSize)) { + if (called->GetDexFile() != caller->GetDexFile()) { // Calling from one dex file to another, need to compute the method index appropriate to // the caller's dex file. Since we get here only if the original called was a runtime // method, we've got the correct dex_file and a dex_method_idx from above. @@ -1283,12 +1270,16 @@ extern "C" const void* artQuickResolutionTrampoline( called->FindDexMethodIndexInOtherDexFile(*caller_dex_file, caller_method_name_and_sig_index); } - if ((update_dex_cache_method_index != DexFile::kDexNoIndex) && - (caller->GetDexCacheResolvedMethod( - update_dex_cache_method_index, kRuntimePointerSize) != called)) { - caller->SetDexCacheResolvedMethod(update_dex_cache_method_index, - called, - kRuntimePointerSize); + if (update_dex_cache_method_index != DexFile::kDexNoIndex) { + // Note: We do not need the read barrier for the dex cache as the SetResolvedMethod() + // operates on native (non-moveable) data and constants (num_resolved_methods_). + ObjPtr<mirror::DexCache> caller_dex_cache = caller->GetDexCache<kWithoutReadBarrier>(); + if (caller_dex_cache->GetResolvedMethod( + update_dex_cache_method_index, kRuntimePointerSize) != called) { + caller_dex_cache->SetResolvedMethod(update_dex_cache_method_index, + called, + kRuntimePointerSize); + } } } else if (invoke_type == kStatic) { const auto called_dex_method_idx = called->GetDexMethodIndex(); diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index 14e017abd9..1a48b46020 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -1128,7 +1128,7 @@ class ImageSpaceLoader { } } else { if (fixup_heap_objects_) { - method->UpdateObjectsForImageRelocation(ForwardObjectAdapter(this), pointer_size_); + method->UpdateObjectsForImageRelocation(ForwardObjectAdapter(this)); } method->UpdateEntrypoints<kWithoutReadBarrier>(ForwardCodeAdapter(this), pointer_size_); } diff --git a/runtime/generated/asm_support_gen.h b/runtime/generated/asm_support_gen.h index 314c45e117..071d1aedb7 100644 --- a/runtime/generated/asm_support_gen.h +++ b/runtime/generated/asm_support_gen.h @@ -48,6 +48,10 @@ DEFINE_CHECK_EQ(static_cast<int32_t>(THREAD_IS_GC_MARKING_OFFSET), (static_cast< DEFINE_CHECK_EQ(static_cast<int32_t>(THREAD_CARD_TABLE_OFFSET), (static_cast<int32_t>(art::Thread:: CardTableOffset<art::kRuntimePointerSize>().Int32Value()))) #define CODEITEM_INSNS_OFFSET 16 DEFINE_CHECK_EQ(static_cast<int32_t>(CODEITEM_INSNS_OFFSET), (static_cast<int32_t>(__builtin_offsetof(art::DexFile::CodeItem, insns_)))) +#define MIRROR_CLASS_DEX_CACHE_OFFSET 16 +DEFINE_CHECK_EQ(static_cast<int32_t>(MIRROR_CLASS_DEX_CACHE_OFFSET), (static_cast<int32_t>(art::mirror::Class:: DexCacheOffset().Int32Value()))) +#define MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET 48 +DEFINE_CHECK_EQ(static_cast<int32_t>(MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET), (static_cast<int32_t>(art::mirror::DexCache:: ResolvedMethodsOffset().Int32Value()))) #define MIRROR_OBJECT_CLASS_OFFSET 0 DEFINE_CHECK_EQ(static_cast<int32_t>(MIRROR_OBJECT_CLASS_OFFSET), (static_cast<int32_t>(art::mirror::Object:: ClassOffset().Int32Value()))) #define MIRROR_OBJECT_LOCK_WORD_OFFSET 4 @@ -60,20 +64,18 @@ DEFINE_CHECK_EQ(static_cast<uint32_t>(ACCESS_FLAGS_CLASS_IS_FINALIZABLE), (stati DEFINE_CHECK_EQ(static_cast<uint32_t>(ACCESS_FLAGS_CLASS_IS_INTERFACE), (static_cast<uint32_t>((art::kAccInterface)))) #define ACCESS_FLAGS_CLASS_IS_FINALIZABLE_BIT 0x1f DEFINE_CHECK_EQ(static_cast<uint32_t>(ACCESS_FLAGS_CLASS_IS_FINALIZABLE_BIT), (static_cast<uint32_t>((art::MostSignificantBit(art::kAccClassIsFinalizable))))) -#define ART_METHOD_DEX_CACHE_METHODS_OFFSET_32 20 -DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_DEX_CACHE_METHODS_OFFSET_32), (static_cast<int32_t>(art::ArtMethod:: DexCacheResolvedMethodsOffset(art::PointerSize::k32).Int32Value()))) -#define ART_METHOD_DEX_CACHE_METHODS_OFFSET_64 24 -DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_DEX_CACHE_METHODS_OFFSET_64), (static_cast<int32_t>(art::ArtMethod:: DexCacheResolvedMethodsOffset(art::PointerSize::k64).Int32Value()))) -#define ART_METHOD_JNI_OFFSET_32 24 +#define ART_METHOD_JNI_OFFSET_32 20 DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_JNI_OFFSET_32), (static_cast<int32_t>(art::ArtMethod:: EntryPointFromJniOffset(art::PointerSize::k32).Int32Value()))) -#define ART_METHOD_JNI_OFFSET_64 32 +#define ART_METHOD_JNI_OFFSET_64 24 DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_JNI_OFFSET_64), (static_cast<int32_t>(art::ArtMethod:: EntryPointFromJniOffset(art::PointerSize::k64).Int32Value()))) -#define ART_METHOD_QUICK_CODE_OFFSET_32 28 +#define ART_METHOD_QUICK_CODE_OFFSET_32 24 DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_QUICK_CODE_OFFSET_32), (static_cast<int32_t>(art::ArtMethod:: EntryPointFromQuickCompiledCodeOffset(art::PointerSize::k32).Int32Value()))) -#define ART_METHOD_QUICK_CODE_OFFSET_64 40 +#define ART_METHOD_QUICK_CODE_OFFSET_64 32 DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_QUICK_CODE_OFFSET_64), (static_cast<int32_t>(art::ArtMethod:: EntryPointFromQuickCompiledCodeOffset(art::PointerSize::k64).Int32Value()))) #define ART_METHOD_DECLARING_CLASS_OFFSET 0 DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_DECLARING_CLASS_OFFSET), (static_cast<int32_t>(art::ArtMethod:: DeclaringClassOffset().Int32Value()))) +#define ART_METHOD_ACCESS_FLAGS_OFFSET 4 +DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_ACCESS_FLAGS_OFFSET), (static_cast<int32_t>(art::ArtMethod:: AccessFlagsOffset().Int32Value()))) #define STRING_DEX_CACHE_ELEMENT_SIZE_SHIFT 3 DEFINE_CHECK_EQ(static_cast<int32_t>(STRING_DEX_CACHE_ELEMENT_SIZE_SHIFT), (static_cast<int32_t>(art::WhichPowerOf2(sizeof(art::mirror::StringDexCachePair))))) #define STRING_DEX_CACHE_SIZE_MINUS_ONE 1023 @@ -126,6 +128,10 @@ DEFINE_CHECK_EQ(static_cast<size_t>(OBJECT_ALIGNMENT_MASK), (static_cast<size_t> DEFINE_CHECK_EQ(static_cast<uint32_t>(OBJECT_ALIGNMENT_MASK_TOGGLED), (static_cast<uint32_t>(~static_cast<uint32_t>(art::kObjectAlignment - 1)))) #define OBJECT_ALIGNMENT_MASK_TOGGLED64 0xfffffffffffffff8 DEFINE_CHECK_EQ(static_cast<uint64_t>(OBJECT_ALIGNMENT_MASK_TOGGLED64), (static_cast<uint64_t>(~static_cast<uint64_t>(art::kObjectAlignment - 1)))) +#define ACC_OBSOLETE_METHOD 262144 +DEFINE_CHECK_EQ(static_cast<int32_t>(ACC_OBSOLETE_METHOD), (static_cast<int32_t>(art::kAccObsoleteMethod))) +#define ACC_OBSOLETE_METHOD_SHIFT 18 +DEFINE_CHECK_EQ(static_cast<int32_t>(ACC_OBSOLETE_METHOD_SHIFT), (static_cast<int32_t>(art::WhichPowerOf2(art::kAccObsoleteMethod)))) #define ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE 128 DEFINE_CHECK_EQ(static_cast<int32_t>(ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE), (static_cast<int32_t>((art::gc::allocator::RosAlloc::kMaxThreadLocalBracketSize)))) #define ROSALLOC_BRACKET_QUANTUM_SIZE_SHIFT 3 diff --git a/runtime/image.cc b/runtime/image.cc index 950ac5dcbf..1f7e0f31b5 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -26,7 +26,7 @@ namespace art { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -const uint8_t ImageHeader::kImageVersion[] = { '0', '4', '6', '\0' }; // Hash-based methods array. +const uint8_t ImageHeader::kImageVersion[] = { '0', '4', '7', '\0' }; // Smaller ArtMethod. ImageHeader::ImageHeader(uint32_t image_begin, uint32_t image_size, diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 474e3684f6..05384b417c 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -26,7 +26,6 @@ #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" @@ -106,6 +105,7 @@ Instrumentation::Instrumentation() have_field_read_listeners_(false), have_field_write_listeners_(false), have_exception_thrown_listeners_(false), + have_watched_frame_pop_listeners_(false), have_branch_listeners_(false), have_invoke_virtual_or_interface_listeners_(false), deoptimized_methods_lock_("deoptimized methods lock", kDeoptimizedMethodsLock), @@ -228,32 +228,39 @@ static void InstrumentationInstallStack(Thread* thread, void* arg) return true; // Continue. } uintptr_t return_pc = GetReturnPc(); - 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()) { + 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_); - 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; + 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_) { // 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) @@ -265,12 +272,8 @@ static void InstrumentationInstallStack(Thread* thread, void* arg) } else { CHECK_NE(return_pc, 0U); CHECK(!reached_existing_instrumentation_frames_); - InstrumentationStackFrame instrumentation_frame( - m->IsRuntimeMethod() ? nullptr : GetThisObject(), - m, - return_pc, - GetFrameId(), // A runtime method still gets a frame id. - false); + InstrumentationStackFrame instrumentation_frame(GetThisObject(), m, return_pc, GetFrameId(), + false); if (kVerboseInstrumentation) { LOG(INFO) << "Pushing frame " << instrumentation_frame.Dump(); } @@ -287,12 +290,9 @@ static void InstrumentationInstallStack(Thread* thread, void* arg) instrumentation_stack_->insert(it, instrumentation_frame); SetReturnPc(instrumentation_exit_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); + dex_pcs_.push_back((GetCurrentOatQuickMethodHeader() == nullptr) + ? DexFile::kDexNoIndex + : GetCurrentOatQuickMethodHeader()->ToDexPc(m, last_return_pc_)); last_return_pc_ = return_pc; ++instrumentation_stack_depth_; return true; // Continue. @@ -390,8 +390,7 @@ static void InstrumentationRestoreStack(Thread* thread, void* arg) CHECK(m == instrumentation_frame.method_) << ArtMethod::PrettyMethod(m); } SetReturnPc(instrumentation_frame.return_pc_); - if (instrumentation_->ShouldNotifyMethodEnterExitEvents() && - !m->IsRuntimeMethod()) { + if (instrumentation_->ShouldNotifyMethodEnterExitEvents()) { // 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, @@ -506,6 +505,11 @@ void Instrumentation::AddListener(InstrumentationListener* listener, uint32_t ev exception_thrown_listeners_, listener, &have_exception_thrown_listeners_); + PotentiallyAddListenerTo(kWatchedFramePop, + events, + watched_frame_pop_listeners_, + listener, + &have_watched_frame_pop_listeners_); UpdateInterpreterHandlerTable(); } @@ -583,6 +587,11 @@ void Instrumentation::RemoveListener(InstrumentationListener* listener, uint32_t exception_thrown_listeners_, listener, &have_exception_thrown_listeners_); + PotentiallyRemoveListenerFrom(kWatchedFramePop, + events, + watched_frame_pop_listeners_, + listener, + &have_watched_frame_pop_listeners_); UpdateInterpreterHandlerTable(); } @@ -949,7 +958,6 @@ 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); @@ -1045,6 +1053,14 @@ void Instrumentation::InvokeVirtualOrInterfaceImpl(Thread* thread, } } +void Instrumentation::WatchedFramePopImpl(Thread* thread, const ShadowFrame& frame) const { + for (InstrumentationListener* listener : watched_frame_pop_listeners_) { + if (listener != nullptr) { + listener->WatchedFramePop(thread, frame); + } + } +} + void Instrumentation::FieldReadEventImpl(Thread* thread, ObjPtr<mirror::Object> this_object, ArtMethod* method, @@ -1154,54 +1170,6 @@ 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, @@ -1222,36 +1190,7 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, ArtMethod* method = instrumentation_frame.method_; uint32_t length; const PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); - 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]; - } - + char 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)); @@ -1271,7 +1210,7 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, // return_pc. uint32_t dex_pc = DexFile::kDexNoIndex; mirror::Object* this_object = instrumentation_frame.this_object_; - if (!method->IsRuntimeMethod() && !instrumentation_frame.interpreter_entry_) { + if (!instrumentation_frame.interpreter_entry_) { MethodExitEvent(self, this_object, instrumentation_frame.method_, dex_pc, return_value); } @@ -1297,12 +1236,10 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, << " in " << *self; } - DeoptimizationMethodType deopt_method_type = GetDeoptimizationMethodType(method); self->PushDeoptimizationContext(return_value, - return_shorty == 'L' || return_shorty == '[', - nullptr /* no pending exception */, + return_shorty == 'L', false /* from_code */, - deopt_method_type); + nullptr /* no pending exception */); return GetTwoWordSuccessValue(*return_pc, reinterpret_cast<uintptr_t>(GetQuickDeoptimizationEntryPoint())); } else { @@ -1339,9 +1276,7 @@ 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; - if (!method->IsRuntimeMethod()) { - MethodUnwindEvent(self, instrumentation_frame.this_object_, method, dex_pc); - } + 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 de416c77e8..114db7682d 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -38,8 +38,8 @@ class ArtField; class ArtMethod; template <typename T> class Handle; union JValue; +class ShadowFrame; class Thread; -enum class DeoptimizationMethodType; namespace instrumentation { @@ -144,6 +144,15 @@ struct InstrumentationListener { uint32_t dex_pc, ArtMethod* callee) REQUIRES_SHARED(Locks::mutator_lock_) = 0; + + // Call-back when a shadow_frame with the needs_notify_pop_ boolean set is popped off the stack by + // either return or exceptions. Normally instrumentation listeners should ensure that there are + // shadow-frames by deoptimizing stacks. + virtual void WatchedFramePop(Thread* thread ATTRIBUTE_UNUSED, + const ShadowFrame& frame ATTRIBUTE_UNUSED) + REQUIRES_SHARED(Locks::mutator_lock_) { + return; + } }; // Instrumentation is a catch-all for when extra information is required from the runtime. The @@ -162,6 +171,7 @@ class Instrumentation { kExceptionThrown = 0x40, kBranch = 0x80, kInvokeVirtualOrInterface = 0x100, + kWatchedFramePop = 0x200, }; enum class InstrumentationLevel { @@ -335,11 +345,16 @@ class Instrumentation { return have_invoke_virtual_or_interface_listeners_; } + bool HasWatchedFramePopListeners() const REQUIRES_SHARED(Locks::mutator_lock_) { + return have_watched_frame_pop_listeners_; + } + bool IsActive() const REQUIRES_SHARED(Locks::mutator_lock_) { return have_dex_pc_listeners_ || have_method_entry_listeners_ || have_method_exit_listeners_ || have_field_read_listeners_ || have_field_write_listeners_ || have_exception_thrown_listeners_ || have_method_unwind_listeners_ || - have_branch_listeners_ || have_invoke_virtual_or_interface_listeners_; + have_branch_listeners_ || have_invoke_virtual_or_interface_listeners_ || + have_watched_frame_pop_listeners_; } // Any instrumentation *other* than what is needed for Jit profiling active? @@ -347,7 +362,7 @@ class Instrumentation { return have_dex_pc_listeners_ || have_method_exit_listeners_ || have_field_read_listeners_ || have_field_write_listeners_ || have_exception_thrown_listeners_ || have_method_unwind_listeners_ || - have_branch_listeners_; + have_branch_listeners_ || have_watched_frame_pop_listeners_; } // Inform listeners that a method has been entered. A dex PC is provided as we may install @@ -425,6 +440,14 @@ class Instrumentation { } } + // Inform listeners that a branch has been taken (only supported by the interpreter). + void WatchedFramePopped(Thread* thread, const ShadowFrame& frame) const + REQUIRES_SHARED(Locks::mutator_lock_) { + if (UNLIKELY(HasWatchedFramePopListeners())) { + WatchedFramePopImpl(thread, frame); + } + } + // Inform listeners that an exception was thrown. void ExceptionThrownEvent(Thread* thread, mirror::Throwable* exception_object) const REQUIRES_SHARED(Locks::mutator_lock_); @@ -436,9 +459,6 @@ 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 @@ -535,6 +555,8 @@ class Instrumentation { uint32_t dex_pc, ArtMethod* callee) const REQUIRES_SHARED(Locks::mutator_lock_); + void WatchedFramePopImpl(Thread* thread, const ShadowFrame& frame) const + REQUIRES_SHARED(Locks::mutator_lock_); void FieldReadEventImpl(Thread* thread, ObjPtr<mirror::Object> this_object, ArtMethod* method, @@ -606,6 +628,9 @@ class Instrumentation { // Do we have any exception thrown listeners? Short-cut to avoid taking the instrumentation_lock_. bool have_exception_thrown_listeners_ GUARDED_BY(Locks::mutator_lock_); + // Do we have any frame pop listeners? Short-cut to avoid taking the instrumentation_lock_. + bool have_watched_frame_pop_listeners_ GUARDED_BY(Locks::mutator_lock_); + // Do we have any branch listeners? Short-cut to avoid taking the instrumentation_lock_. bool have_branch_listeners_ GUARDED_BY(Locks::mutator_lock_); @@ -637,6 +662,7 @@ class Instrumentation { std::list<InstrumentationListener*> field_read_listeners_ GUARDED_BY(Locks::mutator_lock_); std::list<InstrumentationListener*> field_write_listeners_ GUARDED_BY(Locks::mutator_lock_); std::list<InstrumentationListener*> exception_thrown_listeners_ GUARDED_BY(Locks::mutator_lock_); + std::list<InstrumentationListener*> watched_frame_pop_listeners_ GUARDED_BY(Locks::mutator_lock_); // The set of methods being deoptimized (by the debugger) which must be executed with interpreter // only. @@ -665,15 +691,9 @@ 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 5ec07e3508..7390f4fc97 100644 --- a/runtime/instrumentation_test.cc +++ b/runtime/instrumentation_test.cc @@ -28,6 +28,7 @@ #include "jvalue.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" +#include "interpreter/shadow_frame.h" #include "thread-inl.h" #include "thread_list.h" #include "well_known_classes.h" @@ -48,7 +49,8 @@ class TestInstrumentationListener FINAL : public instrumentation::Instrumentatio received_field_written_object_event(false), received_exception_thrown_event(false), received_branch_event(false), - received_invoke_virtual_or_interface_event(false) {} + received_invoke_virtual_or_interface_event(false), + received_watched_frame_pop(false) {} virtual ~TestInstrumentationListener() {} @@ -146,6 +148,11 @@ class TestInstrumentationListener FINAL : public instrumentation::Instrumentatio received_invoke_virtual_or_interface_event = true; } + void WatchedFramePop(Thread* thread ATTRIBUTE_UNUSED, const ShadowFrame& frame ATTRIBUTE_UNUSED) + OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { + received_watched_frame_pop = true; + } + void Reset() { received_method_enter_event = false; received_method_exit_event = false; @@ -158,6 +165,7 @@ class TestInstrumentationListener FINAL : public instrumentation::Instrumentatio received_exception_thrown_event = false; received_branch_event = false; received_invoke_virtual_or_interface_event = false; + received_watched_frame_pop = false; } bool received_method_enter_event; @@ -171,6 +179,7 @@ class TestInstrumentationListener FINAL : public instrumentation::Instrumentatio bool received_exception_thrown_event; bool received_branch_event; bool received_invoke_virtual_or_interface_event; + bool received_watched_frame_pop; private: DISALLOW_COPY_AND_ASSIGN(TestInstrumentationListener); @@ -221,6 +230,7 @@ class InstrumentationTest : public CommonRuntimeTest { mirror::Object* const event_obj = nullptr; const uint32_t event_dex_pc = 0; + ShadowFrameAllocaUniquePtr test_frame = CREATE_SHADOW_FRAME(0, nullptr, event_method, 0); // Check the listener is registered and is notified of the event. EXPECT_TRUE(HasEventListener(instr, instrumentation_event)); @@ -231,7 +241,8 @@ class InstrumentationTest : public CommonRuntimeTest { event_method, event_obj, event_field, - event_dex_pc); + event_dex_pc, + *test_frame); EXPECT_TRUE(DidListenerReceiveEvent(listener, instrumentation_event, with_object)); listener.Reset(); @@ -250,7 +261,8 @@ class InstrumentationTest : public CommonRuntimeTest { event_method, event_obj, event_field, - event_dex_pc); + event_dex_pc, + *test_frame); EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event, with_object)); } @@ -361,6 +373,8 @@ class InstrumentationTest : public CommonRuntimeTest { return instr->HasBranchListeners(); case instrumentation::Instrumentation::kInvokeVirtualOrInterface: return instr->HasInvokeVirtualOrInterfaceListeners(); + case instrumentation::Instrumentation::kWatchedFramePop: + return instr->HasWatchedFramePopListeners(); default: LOG(FATAL) << "Unknown instrumentation event " << event_type; UNREACHABLE(); @@ -373,7 +387,8 @@ class InstrumentationTest : public CommonRuntimeTest { ArtMethod* method, mirror::Object* obj, ArtField* field, - uint32_t dex_pc) + uint32_t dex_pc, + const ShadowFrame& frame) REQUIRES_SHARED(Locks::mutator_lock_) { switch (event_type) { case instrumentation::Instrumentation::kMethodEntered: @@ -411,6 +426,9 @@ class InstrumentationTest : public CommonRuntimeTest { case instrumentation::Instrumentation::kInvokeVirtualOrInterface: instr->InvokeVirtualOrInterface(self, obj, method, dex_pc, method); break; + case instrumentation::Instrumentation::kWatchedFramePop: + instr->WatchedFramePopped(self, frame); + break; default: LOG(FATAL) << "Unknown instrumentation event " << event_type; UNREACHABLE(); @@ -441,6 +459,8 @@ class InstrumentationTest : public CommonRuntimeTest { return listener.received_branch_event; case instrumentation::Instrumentation::kInvokeVirtualOrInterface: return listener.received_invoke_virtual_or_interface_event; + case instrumentation::Instrumentation::kWatchedFramePop: + return listener.received_watched_frame_pop; default: LOG(FATAL) << "Unknown instrumentation event " << event_type; UNREACHABLE(); @@ -473,23 +493,7 @@ TEST_F(InstrumentationTest, NoInstrumentation) { // Test instrumentation listeners for each event. TEST_F(InstrumentationTest, MethodEntryEvent) { - 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); + TestEvent(instrumentation::Instrumentation::kMethodEntered); } TEST_F(InstrumentationTest, MethodExitObjectEvent) { @@ -543,6 +547,10 @@ TEST_F(InstrumentationTest, FieldReadEvent) { TestEvent(instrumentation::Instrumentation::kFieldRead); } +TEST_F(InstrumentationTest, WatchedFramePop) { + TestEvent(instrumentation::Instrumentation::kWatchedFramePop); +} + TEST_F(InstrumentationTest, FieldWriteObjectEvent) { ScopedObjectAccess soa(Thread::Current()); jobject class_loader = LoadDex("Instrumentation"); diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index 34332d7df7..9cb74f7c36 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -499,9 +499,8 @@ static int16_t GetReceiverRegisterForStringInit(const Instruction* instr) { void EnterInterpreterFromDeoptimize(Thread* self, ShadowFrame* shadow_frame, - JValue* ret_val, bool from_code, - DeoptimizationMethodType deopt_method_type) + JValue* ret_val) REQUIRES_SHARED(Locks::mutator_lock_) { JValue value; // Set value to last known result in case the shadow frame chain is empty. @@ -528,27 +527,11 @@ 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) { - // Deoptimization is not called from code directly. + // 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. const Instruction* instr = Instruction::At(&code_item->insns_[dex_pc]); - 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 (instr->IsInvoke()) { if (IsStringInit(instr, shadow_frame->GetMethod())) { uint16_t this_obj_vreg = GetReceiverRegisterForStringInit(instr); // Move the StringFactory.newStringFromChars() result into the register representing @@ -561,27 +544,30 @@ void EnterInterpreterFromDeoptimize(Thread* self, } new_dex_pc = dex_pc + instr->SizeInCodeUnits(); } else if (instr->Opcode() == Instruction::NEW_INSTANCE) { - // 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); + // 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()); + } } else { - 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); + CHECK(false) << "Unexpected instruction opcode " << instr->Opcode() + << " at dex_pc " << dex_pc + << " of method: " << ArtMethod::PrettyMethod(shadow_frame->GetMethod(), false); } } 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); @@ -590,10 +576,8 @@ void EnterInterpreterFromDeoptimize(Thread* self, ShadowFrame* old_frame = shadow_frame; shadow_frame = shadow_frame->GetLink(); ShadowFrame::DeleteDeoptimizedFrame(old_frame); - // Following deoptimizations of shadow frames must be at invocation point - // and should advance dex pc past the invoke instruction. + // Following deoptimizations of shadow frames must pass 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 df8568edcd..65cfade09a 100644 --- a/runtime/interpreter/interpreter.h +++ b/runtime/interpreter/interpreter.h @@ -30,7 +30,6 @@ class ArtMethod; union JValue; class ShadowFrame; class Thread; -enum class DeoptimizationMethodType; namespace interpreter { @@ -45,11 +44,8 @@ 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, - JValue* ret_val, - bool from_code, - DeoptimizationMethodType method_type) +extern void EnterInterpreterFromDeoptimize(Thread* self, ShadowFrame* shadow_frame, bool from_code, + JValue* ret_val) REQUIRES_SHARED(Locks::mutator_lock_); extern JValue EnterInterpreterFromEntryPoint(Thread* self, const DexFile::CodeItem* code_item, diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 8c825f3f97..ae461fd987 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -431,6 +431,9 @@ uint32_t FindNextInstructionFollowingException( uint32_t found_dex_pc = shadow_frame.GetMethod()->FindCatchBlock( hs.NewHandle(exception->GetClass()), dex_pc, &clear_exception); if (found_dex_pc == DexFile::kDexNoIndex && instrumentation != nullptr) { + if (shadow_frame.NeedsNotifyPop()) { + instrumentation->WatchedFramePopped(self, shadow_frame); + } // Exception is not caught by the current method. We will unwind to the // caller. Notify any instrumentation listener. instrumentation->MethodUnwindEvent(self, shadow_frame.GetThisObject(), diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index 0c5a45faf0..f352960204 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -150,6 +150,37 @@ NO_INLINE static bool DoDexPcMoveEvent(Thread* self, } } +static bool NeedsMethodExitEvent(const instrumentation::Instrumentation* ins) + REQUIRES_SHARED(Locks::mutator_lock_) { + return ins->HasMethodExitListeners() || ins->HasWatchedFramePopListeners(); +} + +// Sends the normal method exit event. Returns true if the events succeeded and false if there is a +// pending exception. +NO_INLINE static bool SendMethodExitEvents(Thread* self, + const instrumentation::Instrumentation* instrumentation, + const ShadowFrame& frame, + ObjPtr<mirror::Object> thiz, + ArtMethod* method, + uint32_t dex_pc, + const JValue& result) + REQUIRES_SHARED(Locks::mutator_lock_) { + bool had_event = false; + if (UNLIKELY(instrumentation->HasMethodExitListeners())) { + had_event = true; + instrumentation->MethodExitEvent(self, thiz.Ptr(), method, dex_pc, result); + } + if (UNLIKELY(frame.NeedsNotifyPop() && instrumentation->HasWatchedFramePopListeners())) { + had_event = true; + instrumentation->WatchedFramePopped(self, frame); + } + if (UNLIKELY(had_event)) { + return !self->IsExceptionPending(); + } else { + return true; + } +} + template<bool do_access_check, bool transaction_active> JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowFrame& shadow_frame, JValue result_register, @@ -262,14 +293,15 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, JValue result; self->AllowThreadSuspension(); HANDLE_MONITOR_CHECKS(); - if (UNLIKELY(instrumentation->HasMethodExitListeners())) { - instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), - shadow_frame.GetMethod(), inst->GetDexPc(insns), - result); - if (UNLIKELY(self->IsExceptionPending())) { - // Don't send another method exit event. - HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); - } + if (UNLIKELY(NeedsMethodExitEvent(instrumentation) && + !SendMethodExitEvents(self, + instrumentation, + shadow_frame, + shadow_frame.GetThisObject(code_item->ins_size_), + shadow_frame.GetMethod(), + inst->GetDexPc(insns), + result))) { + HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); } if (interpret_one_instruction) { /* Signal mterp to return to caller */ @@ -283,14 +315,15 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, JValue result; self->AllowThreadSuspension(); HANDLE_MONITOR_CHECKS(); - if (UNLIKELY(instrumentation->HasMethodExitListeners())) { - instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), - shadow_frame.GetMethod(), inst->GetDexPc(insns), - result); - if (UNLIKELY(self->IsExceptionPending())) { - // Don't send another method exit event. - HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); - } + if (UNLIKELY(NeedsMethodExitEvent(instrumentation) && + !SendMethodExitEvents(self, + instrumentation, + shadow_frame, + shadow_frame.GetThisObject(code_item->ins_size_), + shadow_frame.GetMethod(), + inst->GetDexPc(insns), + result))) { + HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); } if (interpret_one_instruction) { /* Signal mterp to return to caller */ @@ -305,14 +338,15 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, result.SetI(shadow_frame.GetVReg(inst->VRegA_11x(inst_data))); self->AllowThreadSuspension(); HANDLE_MONITOR_CHECKS(); - if (UNLIKELY(instrumentation->HasMethodExitListeners())) { - instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), - shadow_frame.GetMethod(), inst->GetDexPc(insns), - result); - if (UNLIKELY(self->IsExceptionPending())) { - // Don't send another method exit event. - HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); - } + if (UNLIKELY(NeedsMethodExitEvent(instrumentation) && + !SendMethodExitEvents(self, + instrumentation, + shadow_frame, + shadow_frame.GetThisObject(code_item->ins_size_), + shadow_frame.GetMethod(), + inst->GetDexPc(insns), + result))) { + HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); } if (interpret_one_instruction) { /* Signal mterp to return to caller */ @@ -326,14 +360,15 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, result.SetJ(shadow_frame.GetVRegLong(inst->VRegA_11x(inst_data))); self->AllowThreadSuspension(); HANDLE_MONITOR_CHECKS(); - if (UNLIKELY(instrumentation->HasMethodExitListeners())) { - instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), - shadow_frame.GetMethod(), inst->GetDexPc(insns), - result); - if (UNLIKELY(self->IsExceptionPending())) { - // Don't send another method exit event. - HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); - } + if (UNLIKELY(NeedsMethodExitEvent(instrumentation) && + !SendMethodExitEvents(self, + instrumentation, + shadow_frame, + shadow_frame.GetThisObject(code_item->ins_size_), + shadow_frame.GetMethod(), + inst->GetDexPc(insns), + result))) { + HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); } if (interpret_one_instruction) { /* Signal mterp to return to caller */ @@ -367,17 +402,18 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, } } result.SetL(obj_result); - if (UNLIKELY(instrumentation->HasMethodExitListeners())) { - instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), - shadow_frame.GetMethod(), inst->GetDexPc(insns), - result); - if (UNLIKELY(self->IsExceptionPending())) { - // Don't send another method exit event. - HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); - } - // Re-load since it might have moved during the MethodExitEvent. - result.SetL(shadow_frame.GetVRegReference(ref_idx)); - } + if (UNLIKELY(NeedsMethodExitEvent(instrumentation) && + !SendMethodExitEvents(self, + instrumentation, + shadow_frame, + shadow_frame.GetThisObject(code_item->ins_size_), + shadow_frame.GetMethod(), + inst->GetDexPc(insns), + result))) { + HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); + } + // Re-load since it might have moved during the MethodExitEvent. + result.SetL(shadow_frame.GetVRegReference(ref_idx)); if (interpret_one_instruction) { /* Signal mterp to return to caller */ shadow_frame.SetDexPC(DexFile::kDexNoIndex); diff --git a/runtime/interpreter/shadow_frame.h b/runtime/interpreter/shadow_frame.h index 05768cd6d3..6903af284f 100644 --- a/runtime/interpreter/shadow_frame.h +++ b/runtime/interpreter/shadow_frame.h @@ -361,6 +361,14 @@ class ShadowFrame { return result_register_; } + bool NeedsNotifyPop() const { + return needs_notify_pop_; + } + + void SetNotifyPop(bool notify) { + needs_notify_pop_ = notify; + } + private: ShadowFrame(uint32_t num_vregs, ShadowFrame* link, ArtMethod* method, uint32_t dex_pc, bool has_reference_array) @@ -372,7 +380,8 @@ class ShadowFrame { number_of_vregs_(num_vregs), dex_pc_(dex_pc), cached_hotness_countdown_(0), - hotness_countdown_(0) { + hotness_countdown_(0), + needs_notify_pop_(0) { // TODO(iam): Remove this parameter, it's an an artifact of portable removal DCHECK(has_reference_array); if (has_reference_array) { @@ -404,6 +413,9 @@ class ShadowFrame { uint32_t dex_pc_; int16_t cached_hotness_countdown_; int16_t hotness_countdown_; + // TODO Might be worth it to try to bit-pack this into some other field to reduce stack usage. + // NB alignment requires that this field takes 4 bytes. Only 1 bit is actually ever used. + bool needs_notify_pop_; // This is a two-part array: // - [0..number_of_vregs) holds the raw virtual registers, and each element here is always 4 diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc index c9bfc9c991..4b0e1071d4 100644 --- a/runtime/jit/profile_compilation_info.cc +++ b/runtime/jit/profile_compilation_info.cc @@ -28,6 +28,7 @@ #include <cstdlib> #include <string> #include <vector> +#include <iostream> #include "android-base/file.h" @@ -47,8 +48,10 @@ namespace art { const uint8_t ProfileCompilationInfo::kProfileMagic[] = { 'p', 'r', 'o', '\0' }; -// Last profile version: update the multidex separator. -const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '9', '\0' }; +// Last profile version: merge profiles directly from the file without creating +// profile_compilation_info object. All the profile line headers are now placed together +// before corresponding method_encodings and class_ids. +const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '1', '0', '\0' }; static constexpr uint16_t kMaxDexFileKeyLength = PATH_MAX; @@ -177,9 +180,36 @@ bool ProfileCompilationInfo::AddClasses(const std::set<DexCacheResolvedClasses>& return true; } +bool ProfileCompilationInfo::MergeWith(const std::string& filename) { + std::string error; + int flags = O_RDONLY | O_NOFOLLOW | O_CLOEXEC; + ScopedFlock profile_file = LockedFile::Open(filename.c_str(), flags, + /*block*/false, &error); + + if (profile_file.get() == nullptr) { + LOG(WARNING) << "Couldn't lock the profile file " << filename << ": " << error; + return false; + } + + int fd = profile_file->Fd(); + + ProfileLoadSatus status = LoadInternal(fd, &error); + if (status == kProfileLoadSuccess) { + return true; + } + + LOG(WARNING) << "Could not load profile data from file " << filename << ": " << error; + return false; +} + bool ProfileCompilationInfo::Load(const std::string& filename, bool clear_if_invalid) { ScopedTrace trace(__PRETTY_FUNCTION__); std::string error; + + if (!IsEmpty()) { + return kProfileLoadWouldOverwiteData; + } + int flags = O_RDWR | O_NOFOLLOW | O_CLOEXEC; // There's no need to fsync profile data right away. We get many chances // to write it again in case something goes wrong. We can rely on a simple @@ -288,15 +318,14 @@ static constexpr size_t kLineHeaderSize = /** * Serialization format: - * magic,version,number_of_dex_files,uncompressed_size_of_zipped_data,compressed_data_size, - * zipped[dex_location1,number_of_classes1,methods_region_size,dex_location_checksum1 - * num_method_ids, - * method_encoding_11,method_encoding_12...,class_id1,class_id2... - * startup/post startup bitmap, - * dex_location2,number_of_classes2,methods_region_size,dex_location_checksum2, num_method_ids, - * method_encoding_21,method_encoding_22...,,class_id1,class_id2... - * startup/post startup bitmap, - * .....] + * [profile_header, zipped[[profile_line_header1, profile_line_header2...],[profile_line_data1, + * profile_line_data2...]]] + * profile_header: + * magic,version,number_of_dex_files,uncompressed_size_of_zipped_data,compressed_data_size + * profile_line_header: + * dex_location,number_of_classes,methods_region_size,dex_location_checksum,num_method_ids + * profile_line_data: + * method_encoding_1,method_encoding_2...,class_id1,class_id2...,startup/post startup bitmap * The method_encoding is: * method_id,number_of_inline_caches,inline_cache1,inline_cache2... * The inline_cache is: @@ -343,10 +372,6 @@ bool ProfileCompilationInfo::Save(int fd) { << " bytes. Profile will not be written to disk."; return false; } - if (required_capacity > kProfileSizeWarningThresholdInBytes) { - LOG(WARNING) << "Profile data size exceeds " - << std::to_string(kProfileSizeWarningThresholdInBytes); - } AddUintToBuffer(&buffer, required_capacity); if (!WriteBuffer(fd, buffer.data(), buffer.size())) { return false; @@ -358,12 +383,10 @@ bool ProfileCompilationInfo::Save(int fd) { // Dex files must be written in the order of their profile index. This // avoids writing the index in the output file and simplifies the parsing logic. + // Write profile line headers. for (const DexFileData* dex_data_ptr : info_) { const DexFileData& dex_data = *dex_data_ptr; - // Note that we allow dex files without any methods or classes, so that - // inline caches can refer valid dex files. - if (dex_data.profile_key.size() >= kMaxDexFileKeyLength) { LOG(WARNING) << "DexFileKey exceeds allocated limit"; return false; @@ -381,6 +404,13 @@ bool ProfileCompilationInfo::Save(int fd) { AddUintToBuffer(&buffer, dex_data.num_method_ids); // uint32_t AddStringToBuffer(&buffer, dex_data.profile_key); + } + + for (const DexFileData* dex_data_ptr : info_) { + const DexFileData& dex_data = *dex_data_ptr; + + // Note that we allow dex files without any methods or classes, so that + // inline caches can refer valid dex files. uint16_t last_method_index = 0; for (const auto& method_it : dex_data.method_map) { @@ -413,6 +443,11 @@ bool ProfileCompilationInfo::Save(int fd) { required_capacity, &output_size); + if (output_size > kProfileSizeWarningThresholdInBytes) { + LOG(WARNING) << "Profile data size exceeds " + << std::to_string(kProfileSizeWarningThresholdInBytes); + } + buffer.clear(); AddUintToBuffer(&buffer, output_size); @@ -690,10 +725,12 @@ bool ProfileCompilationInfo::AddClassIndex(const std::string& dex_location, } \ while (false) -bool ProfileCompilationInfo::ReadInlineCache(SafeBuffer& buffer, - uint8_t number_of_dex_files, - /*out*/ InlineCacheMap* inline_cache, - /*out*/ std::string* error) { +bool ProfileCompilationInfo::ReadInlineCache( + SafeBuffer& buffer, + uint8_t number_of_dex_files, + const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap, + /*out*/ InlineCacheMap* inline_cache, + /*out*/ std::string* error) { uint16_t inline_cache_size; READ_UINT(uint16_t, buffer, inline_cache_size, error); for (; inline_cache_size > 0; inline_cache_size--) { @@ -723,7 +760,8 @@ bool ProfileCompilationInfo::ReadInlineCache(SafeBuffer& buffer, for (; dex_classes_size > 0; dex_classes_size--) { uint16_t type_index; READ_UINT(uint16_t, buffer, type_index, error); - dex_pc_data->AddClass(dex_profile_index, dex::TypeIndex(type_index)); + dex_pc_data->AddClass(dex_profile_index_remap.Get(dex_profile_index), + dex::TypeIndex(type_index)); } } } @@ -733,6 +771,7 @@ bool ProfileCompilationInfo::ReadInlineCache(SafeBuffer& buffer, bool ProfileCompilationInfo::ReadMethods(SafeBuffer& buffer, uint8_t number_of_dex_files, const ProfileLineHeader& line_header, + const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap, /*out*/std::string* error) { uint32_t unread_bytes_before_operation = buffer.CountUnreadBytes(); if (unread_bytes_before_operation < line_header.method_region_size_bytes) { @@ -751,7 +790,11 @@ bool ProfileCompilationInfo::ReadMethods(SafeBuffer& buffer, uint16_t method_index = last_method_index + diff_with_last_method_index; last_method_index = method_index; InlineCacheMap* inline_cache = data->FindOrAddMethod(method_index); - if (!ReadInlineCache(buffer, number_of_dex_files, inline_cache, error)) { + if (!ReadInlineCache(buffer, + number_of_dex_files, + dex_profile_index_remap, + inline_cache, + error)) { return false; } } @@ -954,6 +997,8 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine SafeBuffer& buffer, uint8_t number_of_dex_files, const ProfileLineHeader& line_header, + const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap, + bool merge_classes, /*out*/std::string* error) { DexFileData* data = GetOrAddDexFileData(line_header.dex_location, line_header.checksum, @@ -964,12 +1009,14 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine return kProfileLoadBadData; } - if (!ReadMethods(buffer, number_of_dex_files, line_header, error)) { + if (!ReadMethods(buffer, number_of_dex_files, line_header, dex_profile_index_remap, error)) { return kProfileLoadBadData; } - if (!ReadClasses(buffer, line_header, error)) { - return kProfileLoadBadData; + if (merge_classes) { + if (!ReadClasses(buffer, line_header, error)) { + return kProfileLoadBadData; + } } const size_t bytes = data->bitmap_storage.size(); @@ -986,9 +1033,10 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine // TODO(calin): Fix this API. ProfileCompilationInfo::Load should be static and // return a unique pointer to a ProfileCompilationInfo upon success. -bool ProfileCompilationInfo::Load(int fd) { +bool ProfileCompilationInfo::Load(int fd, bool merge_classes) { std::string error; - ProfileLoadSatus status = LoadInternal(fd, &error); + + ProfileLoadSatus status = LoadInternal(fd, &error, merge_classes); if (status == kProfileLoadSuccess) { return true; @@ -1000,14 +1048,10 @@ bool ProfileCompilationInfo::Load(int fd) { // TODO(calin): fail fast if the dex checksums don't match. ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal( - int fd, std::string* error) { + int fd, std::string* error, bool merge_classes) { ScopedTrace trace(__PRETTY_FUNCTION__); DCHECK_GE(fd, 0); - if (!IsEmpty()) { - return kProfileLoadWouldOverwiteData; - } - struct stat stat_buffer; if (fstat(fd, &stat_buffer) != 0) { return kProfileLoadIOError; @@ -1071,6 +1115,8 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal( return kProfileLoadBadData; } + std::vector<ProfileLineHeader> profile_line_headers; + // Read profile line headers. for (uint8_t k = 0; k < number_of_dex_files; k++) { ProfileLineHeader line_header; @@ -1079,9 +1125,22 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal( if (status != kProfileLoadSuccess) { return status; } + profile_line_headers.push_back(line_header); + } + SafeMap<uint8_t, uint8_t> dex_profile_index_remap; + if (!RemapProfileIndex(profile_line_headers, &dex_profile_index_remap)) { + return kProfileLoadBadData; + } + + for (uint8_t k = 0; k < number_of_dex_files; k++) { // Now read the actual profile line. - status = ReadProfileLine(uncompressed_data, number_of_dex_files, line_header, error); + status = ReadProfileLine(uncompressed_data, + number_of_dex_files, + profile_line_headers[k], + dex_profile_index_remap, + merge_classes, + error); if (status != kProfileLoadSuccess) { return status; } @@ -1096,6 +1155,37 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal( } } +bool ProfileCompilationInfo::RemapProfileIndex( + const std::vector<ProfileLineHeader>& profile_line_headers, + /*out*/SafeMap<uint8_t, uint8_t>* dex_profile_index_remap) { + // First verify that all checksums match. This will avoid adding garbage to + // the current profile info. + // Note that the number of elements should be very small, so this should not + // be a performance issue. + for (const ProfileLineHeader other_profile_line_header : profile_line_headers) { + // verify_checksum is false because we want to differentiate between a missing dex data and + // a mismatched checksum. + const DexFileData* dex_data = FindDexData(other_profile_line_header.dex_location, + 0u, + false /* verify_checksum */); + if ((dex_data != nullptr) && (dex_data->checksum != other_profile_line_header.checksum)) { + LOG(WARNING) << "Checksum mismatch for dex " << other_profile_line_header.dex_location; + return false; + } + } + // All checksums match. Import the data. + uint32_t num_dex_files = static_cast<uint32_t>(profile_line_headers.size()); + for (uint32_t i = 0; i < num_dex_files; i++) { + const DexFileData* dex_data = GetOrAddDexFileData(profile_line_headers[i].dex_location, + profile_line_headers[i].checksum, + profile_line_headers[i].num_method_ids); + if (dex_data == nullptr) { + return false; // Could happen if we exceed the number of allowed dex files. + } + dex_profile_index_remap->Put(i, dex_data->profile_index); + } + return true; +} std::unique_ptr<uint8_t[]> ProfileCompilationInfo::DeflateBuffer(const uint8_t* in_buffer, uint32_t in_size, uint32_t* compressed_data_size) { @@ -1488,16 +1578,16 @@ std::set<DexCacheResolvedClasses> ProfileCompilationInfo::GetResolvedClasses( // Naive implementation to generate a random profile file suitable for testing. bool ProfileCompilationInfo::GenerateTestProfile(int fd, uint16_t number_of_dex_files, - uint16_t method_ratio, - uint16_t class_ratio, + uint16_t method_percentage, + uint16_t class_percentage, uint32_t random_seed) { const std::string base_dex_location = "base.apk"; ProfileCompilationInfo info; // The limits are defined by the dex specification. const uint16_t max_method = std::numeric_limits<uint16_t>::max(); const uint16_t max_classes = std::numeric_limits<uint16_t>::max(); - uint16_t number_of_methods = max_method * method_ratio / 100; - uint16_t number_of_classes = max_classes * class_ratio / 100; + uint16_t number_of_methods = max_method * method_percentage / 100; + uint16_t number_of_classes = max_classes * class_percentage / 100; std::srand(random_seed); @@ -1534,28 +1624,47 @@ bool ProfileCompilationInfo::GenerateTestProfile(int fd, } // Naive implementation to generate a random profile file suitable for testing. +// Description of random selection: +// * Select a random starting point S. +// * For every index i, add (S+i) % (N - total number of methods/classes) to profile with the +// probably of 1/(N - i - number of methods/classes needed to add in profile). bool ProfileCompilationInfo::GenerateTestProfile( int fd, std::vector<std::unique_ptr<const DexFile>>& dex_files, + uint16_t method_percentage, + uint16_t class_percentage, uint32_t random_seed) { std::srand(random_seed); ProfileCompilationInfo info; for (std::unique_ptr<const DexFile>& dex_file : dex_files) { const std::string& location = dex_file->GetLocation(); uint32_t checksum = dex_file->GetLocationChecksum(); - for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) { - // Randomly add a class from the dex file (with 50% chance). - if (std::rand() % 2 != 0) { + + uint32_t number_of_classes = dex_file->NumClassDefs(); + uint32_t classes_required_in_profile = (number_of_classes * class_percentage) / 100; + uint32_t class_start_index = rand() % number_of_classes; + for (uint32_t i = 0; i < number_of_classes && classes_required_in_profile; ++i) { + if (number_of_classes - i == classes_required_in_profile || + std::rand() % (number_of_classes - i - classes_required_in_profile) == 0) { + uint32_t class_index = (i + class_start_index) % number_of_classes; info.AddClassIndex(location, checksum, - dex_file->GetClassDef(i).class_idx_, + dex_file->GetClassDef(class_index).class_idx_, dex_file->NumMethodIds()); + classes_required_in_profile--; } } - for (uint32_t i = 0; i < dex_file->NumMethodIds(); ++i) { - // Randomly add a method from the dex file (with 50% chance). - if (std::rand() % 2 != 0) { - info.AddMethodIndex(MethodHotness::kFlagHot, MethodReference(dex_file.get(), i)); + + uint32_t number_of_methods = dex_file->NumMethodIds(); + uint32_t methods_required_in_profile = (number_of_methods * method_percentage) / 100; + uint32_t method_start_index = rand() % number_of_methods; + for (uint32_t i = 0; i < number_of_methods && methods_required_in_profile; ++i) { + if (number_of_methods - i == methods_required_in_profile || + std::rand() % (number_of_methods - i - methods_required_in_profile) == 0) { + uint32_t method_index = (method_start_index + i) % number_of_methods; + info.AddMethodIndex(MethodHotness::kFlagHot, MethodReference(dex_file.get(), + method_index)); + methods_required_in_profile--; } } } diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h index ffb67ae2be..7fd7a2d049 100644 --- a/runtime/jit/profile_compilation_info.h +++ b/runtime/jit/profile_compilation_info.h @@ -288,9 +288,10 @@ class ProfileCompilationInfo { // Add hotness flags for a simple method. bool AddMethodHotness(const MethodReference& method_ref, const MethodHotness& hotness); - // Load profile information from the given file descriptor. + // Load or Merge profile information from the given file descriptor. // If the current profile is non-empty the load will fail. - bool Load(int fd); + // If merge_classes is set to false, classes will not be merged/loaded. + bool Load(int fd, bool merge_classes = true); // Load profile information from the given file // If the current profile is non-empty the load will fail. @@ -303,6 +304,9 @@ class ProfileCompilationInfo { // we don't want all of the classes to be image classes. bool MergeWith(const ProfileCompilationInfo& info, bool merge_classes = true); + // Merge profile information from the given file descriptor. + bool MergeWith(const std::string& filename); + // Save the profile data to the given file descriptor. bool Save(int fd); @@ -372,6 +376,8 @@ class ProfileCompilationInfo { // the provided list of dex files. static bool GenerateTestProfile(int fd, std::vector<std::unique_ptr<const DexFile>>& dex_files, + uint16_t method_percentage, + uint16_t class_percentage, uint32_t random_seed); // Check that the given profile method info contain the same data. @@ -594,7 +600,7 @@ class ProfileCompilationInfo { }; // Entry point for profile loding functionality. - ProfileLoadSatus LoadInternal(int fd, std::string* error); + ProfileLoadSatus LoadInternal(int fd, std::string* error, bool merge_classes = true); // Read the profile header from the given fd and store the number of profile // lines into number_of_dex_files. @@ -619,6 +625,8 @@ class ProfileCompilationInfo { ProfileLoadSatus ReadProfileLine(SafeBuffer& buffer, uint8_t number_of_dex_files, const ProfileLineHeader& line_header, + const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap, + bool merge_classes, /*out*/std::string* error); // Read all the classes from the buffer into the profile `info_` structure. @@ -630,11 +638,18 @@ class ProfileCompilationInfo { bool ReadMethods(SafeBuffer& buffer, uint8_t number_of_dex_files, const ProfileLineHeader& line_header, + const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap, /*out*/std::string* error); + // The method generates mapping of profile indices while merging a new profile + // data into current data. It returns true, if the mapping was successful. + bool RemapProfileIndex(const std::vector<ProfileLineHeader>& profile_line_headers, + /*out*/SafeMap<uint8_t, uint8_t>* dex_profile_index_remap); + // Read the inline cache encoding from line_bufer into inline_cache. bool ReadInlineCache(SafeBuffer& buffer, uint8_t number_of_dex_files, + const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap, /*out*/InlineCacheMap* inline_cache, /*out*/std::string* error); diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc index 6010bce2e5..40d303fc13 100644 --- a/runtime/jit/profile_compilation_info_test.cc +++ b/runtime/jit/profile_compilation_info_test.cc @@ -387,6 +387,23 @@ TEST_F(ProfileCompilationInfoTest, MergeFail) { ASSERT_FALSE(info1.MergeWith(info2)); } + +TEST_F(ProfileCompilationInfoTest, MergeFdFail) { + ScratchFile profile; + + ProfileCompilationInfo info1; + ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 1, /* method_idx */ 1, &info1)); + // Use the same file, change the checksum. + ProfileCompilationInfo info2; + ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 2, /* method_idx */ 2, &info2)); + + ASSERT_TRUE(info1.Save(profile.GetFd())); + ASSERT_EQ(0, profile.GetFile()->Flush()); + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + + ASSERT_FALSE(info2.Load(profile.GetFd())); +} + TEST_F(ProfileCompilationInfoTest, SaveMaxMethods) { ScratchFile profile; @@ -833,29 +850,6 @@ TEST_F(ProfileCompilationInfoTest, MissingTypesInlineCachesMerge) { ASSERT_TRUE(info_no_inline_cache.Save(GetFd(profile))); } -TEST_F(ProfileCompilationInfoTest, LoadShouldClearExistingDataFromProfiles) { - ScratchFile profile; - - ProfileCompilationInfo saved_info; - // Save a few methods. - for (uint16_t i = 0; i < 10; i++) { - ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info)); - } - ASSERT_TRUE(saved_info.Save(GetFd(profile))); - ASSERT_EQ(0, profile.GetFile()->Flush()); - ASSERT_TRUE(profile.GetFile()->ResetOffset()); - - // Add a bunch of methods to test_info. - ProfileCompilationInfo test_info; - for (uint16_t i = 0; i < 10; i++) { - ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, /* method_idx */ i, &test_info)); - } - - // Attempt to load the saved profile into test_info. - // This should fail since the test_info already contains data and the load would overwrite it. - ASSERT_FALSE(test_info.Load(GetFd(profile))); -} - TEST_F(ProfileCompilationInfoTest, SampledMethodsTest) { ProfileCompilationInfo test_info; static constexpr size_t kNumMethods = 1000; diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index 2d8d6ba147..d9cfa533ca 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -75,7 +75,7 @@ static constexpr bool kUseDlopenOnHost = true; static constexpr bool kPrintDlOpenErrorMessage = false; // If true, we advise the kernel about dex file mem map accesses. -static constexpr bool kMadviseDexFileAccesses = false; +static constexpr bool kMadviseDexFileAccesses = true; // Note for OatFileBase and descendents: // @@ -1498,11 +1498,13 @@ const DexFile::ClassDef* OatFile::OatDexFile::FindClassDef(const DexFile& dex_fi // Madvise the dex file based on the state we are moving to. void OatDexFile::MadviseDexFile(const DexFile& dex_file, MadviseState state) { - if (!kMadviseDexFileAccesses) { + const bool low_ram = Runtime::Current()->GetHeap()->IsLowMemoryMode(); + // TODO: Also do madvise hints for non low ram devices. + if (!kMadviseDexFileAccesses || !low_ram) { return; } if (state == MadviseState::kMadviseStateAtLoad) { - if (Runtime::Current()->GetHeap()->IsLowMemoryMode()) { + if (low_ram) { // Default every dex file to MADV_RANDOM when its loaded by default for low ram devices. // Other devices have enough page cache to get performance benefits from loading more pages // into the page cache. diff --git a/runtime/thread.cc b/runtime/thread.cc index 57b3a75352..3f23926b29 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -166,13 +166,11 @@ 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_; } @@ -187,9 +185,6 @@ 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. @@ -205,9 +200,6 @@ 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_; @@ -237,30 +229,26 @@ class StackedShadowFrameRecord { void Thread::PushDeoptimizationContext(const JValue& return_value, bool is_reference, - ObjPtr<mirror::Throwable> exception, bool from_code, - DeoptimizationMethodType method_type) { + ObjPtr<mirror::Throwable> exception) { 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, - DeoptimizationMethodType* method_type) { + bool* from_code) { 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; } @@ -3096,16 +3084,10 @@ 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(), - false /* is_reference */, - exception, - false /* from_code */, - method_type); + JValue(), /*is_reference */ false, /* from_code */ false, exception); artDeoptimize(this); UNREACHABLE(); } else { @@ -3665,8 +3647,7 @@ void Thread::DeoptimizeWithDeoptimizationException(JValue* result) { PopStackedShadowFrame(StackedShadowFrameType::kDeoptimizationShadowFrame); ObjPtr<mirror::Throwable> pending_exception; bool from_code = false; - DeoptimizationMethodType method_type; - PopDeoptimizationContext(result, &pending_exception, &from_code, &method_type); + PopDeoptimizationContext(result, &pending_exception, &from_code); SetTopOfStack(nullptr); SetTopOfShadowStack(shadow_frame); @@ -3675,11 +3656,7 @@ void Thread::DeoptimizeWithDeoptimizationException(JValue* result) { if (pending_exception != nullptr) { SetException(pending_exception); } - interpreter::EnterInterpreterFromDeoptimize(this, - shadow_frame, - result, - from_code, - method_type); + interpreter::EnterInterpreterFromDeoptimize(this, shadow_frame, from_code, result); } void Thread::SetException(ObjPtr<mirror::Throwable> new_exception) { diff --git a/runtime/thread.h b/runtime/thread.h index ad4506e309..7540fd2563 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -117,13 +117,6 @@ 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; @@ -967,18 +960,14 @@ 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, - DeoptimizationMethodType method_type) + ObjPtr<mirror::Throwable> exception) REQUIRES_SHARED(Locks::mutator_lock_); void PopDeoptimizationContext(JValue* result, ObjPtr<mirror::Throwable>* exception, - bool* from_code, - DeoptimizationMethodType* method_type) + bool* from_code) REQUIRES_SHARED(Locks::mutator_lock_); void AssertHasDeoptimizationContext() REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/runtime/verifier/method_verifier-inl.h b/runtime/verifier/method_verifier-inl.h index 6c832e3492..9bb875c033 100644 --- a/runtime/verifier/method_verifier-inl.h +++ b/runtime/verifier/method_verifier-inl.h @@ -77,7 +77,7 @@ inline bool MethodVerifier::HasFailures() const { inline const RegType& MethodVerifier::ResolveCheckedClass(dex::TypeIndex class_idx) { DCHECK(!HasFailures()); - const RegType& result = ResolveClassAndCheckAccess(class_idx); + const RegType& result = ResolveClass<CheckAccess::kYes>(class_idx); DCHECK(!HasFailures()); return result; } diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 312d8dfc18..9a876077d4 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -1765,7 +1765,9 @@ bool MethodVerifier::SetTypesFromSignature() { // it's effectively considered initialized the instant we reach here (in the sense that we // can return without doing anything or call virtual methods). { - const RegType& reg_type = ResolveClassAndCheckAccess(iterator.GetTypeIdx()); + // Note: don't check access. No error would be thrown for declaring or passing an + // inaccessible class. Only actual accesses to fields or methods will. + const RegType& reg_type = ResolveClass<CheckAccess::kNo>(iterator.GetTypeIdx()); if (!reg_type.IsNonZeroReferenceTypes()) { DCHECK(HasFailures()); return false; @@ -2322,7 +2324,7 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { case Instruction::CONST_CLASS: { // Get type from instruction if unresolved then we need an access check // TODO: check Compiler::CanAccessTypeWithoutChecks returns false when res_type is unresolved - const RegType& res_type = ResolveClassAndCheckAccess(dex::TypeIndex(inst->VRegB_21c())); + const RegType& res_type = ResolveClass<CheckAccess::kYes>(dex::TypeIndex(inst->VRegB_21c())); // Register holds class, ie its type is class, on error it will hold Conflict. work_line_->SetRegisterType<LockOp::kClear>( this, inst->VRegA_21c(), res_type.IsConflict() ? res_type @@ -2393,7 +2395,7 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { */ const bool is_checkcast = (inst->Opcode() == Instruction::CHECK_CAST); const dex::TypeIndex type_idx((is_checkcast) ? inst->VRegB_21c() : inst->VRegC_22c()); - const RegType& res_type = ResolveClassAndCheckAccess(type_idx); + const RegType& res_type = ResolveClass<CheckAccess::kYes>(type_idx); if (res_type.IsConflict()) { // If this is a primitive type, fail HARD. ObjPtr<mirror::Class> klass = @@ -2463,7 +2465,7 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { break; } case Instruction::NEW_INSTANCE: { - const RegType& res_type = ResolveClassAndCheckAccess(dex::TypeIndex(inst->VRegB_21c())); + const RegType& res_type = ResolveClass<CheckAccess::kYes>(dex::TypeIndex(inst->VRegB_21c())); if (res_type.IsConflict()) { DCHECK_NE(failures_.size(), 0U); break; // bad class @@ -2675,7 +2677,7 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { // ensure that subsequent merges don't lose type information - such as becoming an // interface from a class that would lose information relevant to field checks. const RegType& orig_type = work_line_->GetRegisterType(this, instance_of_inst->VRegB_22c()); - const RegType& cast_type = ResolveClassAndCheckAccess( + const RegType& cast_type = ResolveClass<CheckAccess::kYes>( dex::TypeIndex(instance_of_inst->VRegC_22c())); if (!orig_type.Equals(cast_type) && @@ -3723,7 +3725,8 @@ inline bool MethodVerifier::IsInstantiableOrPrimitive(mirror::Class* klass) { return klass->IsInstantiable() || klass->IsPrimitive(); } -const RegType& MethodVerifier::ResolveClassAndCheckAccess(dex::TypeIndex class_idx) { +template <MethodVerifier::CheckAccess C> +const RegType& MethodVerifier::ResolveClass(dex::TypeIndex class_idx) { mirror::Class* klass = can_load_classes_ ? Runtime::Current()->GetClassLinker()->ResolveType( *dex_file_, class_idx, dex_cache_, class_loader_) @@ -3760,13 +3763,15 @@ const RegType& MethodVerifier::ResolveClassAndCheckAccess(dex::TypeIndex class_i // Record result of class resolution attempt. VerifierDeps::MaybeRecordClassResolution(*dex_file_, class_idx, klass); - // Check if access is allowed. Unresolved types use xxxWithAccessCheck to - // check at runtime if access is allowed and so pass here. If result is - // primitive, skip the access check. - if (result->IsNonZeroReferenceTypes() && !result->IsUnresolvedTypes()) { + // If requested, check if access is allowed. Unresolved types are included in this check, as the + // interpreter only tests whether access is allowed when a class is not pre-verified and runs in + // the access-checks interpreter. If result is primitive, skip the access check. + // + // Note: we do this for unresolved classes to trigger re-verification at runtime. + if (C == CheckAccess::kYes && result->IsNonZeroReferenceTypes()) { const RegType& referrer = GetDeclaringClass(); - if (!referrer.IsUnresolvedTypes() && !referrer.CanAccess(*result)) { - Fail(VERIFY_ERROR_ACCESS_CLASS) << "illegal class access: '" + if (!referrer.CanAccess(*result)) { + Fail(VERIFY_ERROR_ACCESS_CLASS) << "(possibly) illegal class access: '" << referrer << "' -> '" << *result << "'"; } } @@ -3785,7 +3790,8 @@ const RegType& MethodVerifier::GetCaughtExceptionType() { if (!iterator.GetHandlerTypeIndex().IsValid()) { common_super = ®_types_.JavaLangThrowable(false); } else { - const RegType& exception = ResolveClassAndCheckAccess(iterator.GetHandlerTypeIndex()); + const RegType& exception = + ResolveClass<CheckAccess::kYes>(iterator.GetHandlerTypeIndex()); if (!reg_types_.JavaLangThrowable(false).IsAssignableFrom(exception, this)) { DCHECK(!exception.IsUninitializedTypes()); // Comes from dex, shouldn't be uninit. if (exception.IsUnresolvedTypes()) { @@ -3827,7 +3833,7 @@ const RegType& MethodVerifier::GetCaughtExceptionType() { ArtMethod* MethodVerifier::ResolveMethodAndCheckAccess( uint32_t dex_method_idx, MethodType method_type) { const DexFile::MethodId& method_id = dex_file_->GetMethodId(dex_method_idx); - const RegType& klass_type = ResolveClassAndCheckAccess(method_id.class_idx_); + const RegType& klass_type = ResolveClass<CheckAccess::kYes>(method_id.class_idx_); if (klass_type.IsConflict()) { std::string append(" in attempt to access method "); append += dex_file_->GetMethodName(method_id); @@ -4598,7 +4604,7 @@ void MethodVerifier::VerifyNewArray(const Instruction* inst, bool is_filled, boo DCHECK_EQ(inst->Opcode(), Instruction::FILLED_NEW_ARRAY_RANGE); type_idx = dex::TypeIndex(inst->VRegB_3rc()); } - const RegType& res_type = ResolveClassAndCheckAccess(type_idx); + const RegType& res_type = ResolveClass<CheckAccess::kYes>(type_idx); if (res_type.IsConflict()) { // bad class DCHECK_NE(failures_.size(), 0U); } else { @@ -4812,7 +4818,7 @@ void MethodVerifier::VerifyAPut(const Instruction* inst, ArtField* MethodVerifier::GetStaticField(int field_idx) { const DexFile::FieldId& field_id = dex_file_->GetFieldId(field_idx); // Check access to class - const RegType& klass_type = ResolveClassAndCheckAccess(field_id.class_idx_); + const RegType& klass_type = ResolveClass<CheckAccess::kYes>(field_id.class_idx_); if (klass_type.IsConflict()) { // bad class AppendToLastFailMessage(StringPrintf(" in attempt to access static field %d (%s) in %s", field_idx, dex_file_->GetFieldName(field_id), @@ -4820,6 +4826,9 @@ ArtField* MethodVerifier::GetStaticField(int field_idx) { return nullptr; } if (klass_type.IsUnresolvedTypes()) { + // Accessibility checks depend on resolved fields. + DCHECK(klass_type.Equals(GetDeclaringClass()) || !failures_.empty()); + return nullptr; // Can't resolve Class so no more to do here, will do checking at runtime. } ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); @@ -4850,7 +4859,7 @@ ArtField* MethodVerifier::GetStaticField(int field_idx) { ArtField* MethodVerifier::GetInstanceField(const RegType& obj_type, int field_idx) { const DexFile::FieldId& field_id = dex_file_->GetFieldId(field_idx); // Check access to class. - const RegType& klass_type = ResolveClassAndCheckAccess(field_id.class_idx_); + const RegType& klass_type = ResolveClass<CheckAccess::kYes>(field_id.class_idx_); if (klass_type.IsConflict()) { AppendToLastFailMessage(StringPrintf(" in attempt to access instance field %d (%s) in %s", field_idx, dex_file_->GetFieldName(field_id), @@ -4858,6 +4867,9 @@ ArtField* MethodVerifier::GetInstanceField(const RegType& obj_type, int field_id return nullptr; } if (klass_type.IsUnresolvedTypes()) { + // Accessibility checks depend on resolved fields. + DCHECK(klass_type.Equals(GetDeclaringClass()) || !failures_.empty()); + return nullptr; // Can't resolve Class so no more to do here } ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); @@ -4994,6 +5006,31 @@ void MethodVerifier::VerifyISFieldAccess(const Instruction* inst, const RegType& DCHECK(!can_load_classes_ || self_->IsExceptionPending()); self_->ClearException(); } + } else { + // If we don't have the field (it seems we failed resolution) and this is a PUT, we need to + // redo verification at runtime as the field may be final, unless the field id shows it's in + // the same class. + // + // For simplicity, it is OK to not distinguish compile-time vs runtime, and post this an + // ACCESS_FIELD failure at runtime. This has the same effect as NO_FIELD - punting the class + // to the access-checks interpreter. + // + // Note: see b/34966607. This and above may be changed in the future. + if (kAccType == FieldAccessType::kAccPut) { + const DexFile::FieldId& field_id = dex_file_->GetFieldId(field_idx); + const char* field_class_descriptor = dex_file_->GetFieldDeclaringClassDescriptor(field_id); + const RegType* field_class_type = ®_types_.FromDescriptor(GetClassLoader(), + field_class_descriptor, + false); + if (!field_class_type->Equals(GetDeclaringClass())) { + Fail(VERIFY_ERROR_ACCESS_FIELD) << "could not check field put for final field modify of " + << field_class_descriptor + << "." + << dex_file_->GetFieldName(field_id) + << " from other class " + << GetDeclaringClass(); + } + } } if (field_type == nullptr) { const DexFile::FieldId& field_id = dex_file_->GetFieldId(field_idx); diff --git a/runtime/verifier/method_verifier.h b/runtime/verifier/method_verifier.h index ea8729cb3e..da4102a786 100644 --- a/runtime/verifier/method_verifier.h +++ b/runtime/verifier/method_verifier.h @@ -576,9 +576,14 @@ class MethodVerifier { void VerifyQuickFieldAccess(const Instruction* inst, const RegType& insn_type, bool is_primitive) REQUIRES_SHARED(Locks::mutator_lock_); - // Resolves a class based on an index and performs access checks to ensure the referrer can - // access the resolved class. - const RegType& ResolveClassAndCheckAccess(dex::TypeIndex class_idx) + enum class CheckAccess { // private. + kYes, + kNo, + }; + // Resolves a class based on an index and, if C is kYes, performs access checks to ensure + // the referrer can access the resolved class. + template <CheckAccess C> + const RegType& ResolveClass(dex::TypeIndex class_idx) REQUIRES_SHARED(Locks::mutator_lock_); /* diff --git a/test/004-JniTest/build b/test/004-JniTest/build index c8440fcb73..e563d734c2 100755 --- a/test/004-JniTest/build +++ b/test/004-JniTest/build @@ -38,4 +38,30 @@ function javac_wrapper { export -f javac_wrapper export JAVAC=javac_wrapper +###################################################################### + +# Use the original dx with no extra magic or pessimizing flags. +# This ensures that any default optimizations that dx do would not break JNI. + +export ORIGINAL_DX="$DX" + +# Filter out --debug flag from dx. +function dx_wrapper { + local args=("$@") + local args_filtered=() + for i in "${args[@]}"; do + case "$i" in + --debug) + ;; + *) + args_filtered+=("$i") + ;; + esac + done + "$ORIGINAL_DX" "${args_filtered[@]}" +} + +export -f dx_wrapper +export DX=dx_wrapper + ./default-build "$@" diff --git a/test/004-JniTest/expected.txt b/test/004-JniTest/expected.txt index f7e404d30b..7e85ab1041 100644 --- a/test/004-JniTest/expected.txt +++ b/test/004-JniTest/expected.txt @@ -58,3 +58,5 @@ EXCEPTION OCCURED: java.lang.IncompatibleClassChangeError: Conflicting default m hi-lambda: λ hi-default δλ hi-default δλ +Clinit Lookup: ClassWithoutClinit: <NSME Exception> +Clinit Lookup: ClassWithClinit: Main$ClassWithClinit()(Class: class java.lang.reflect.Constructor) diff --git a/test/004-JniTest/jni_test.cc b/test/004-JniTest/jni_test.cc index a34d633590..bc5a0a64e8 100644 --- a/test/004-JniTest/jni_test.cc +++ b/test/004-JniTest/jni_test.cc @@ -776,6 +776,18 @@ static jint Java_Main_intCriticalNativeMethod(jint a, jint b, jint c) { return a + b + c; } +extern "C" JNIEXPORT jobject JNICALL Java_Main_lookupClinit(JNIEnv* env, jclass, jclass kls) { + jmethodID clinit_id = env->GetStaticMethodID(kls, "<clinit>", "()V"); + + if (clinit_id != nullptr) { + jobject obj = env->ToReflectedMethod(kls, clinit_id, /*isStatic*/ true); + CHECK(obj != nullptr); + return obj; + } else { + return nullptr; + } +} + extern "C" JNIEXPORT jboolean JNICALL Java_Main_isSlowDebug(JNIEnv*, jclass) { // Return whether slow-debug is on. Only relevant for debug builds. if (kIsDebugBuild) { diff --git a/test/004-JniTest/src/Main.java b/test/004-JniTest/src/Main.java index 0c4ed89f81..fe5f4e3dc6 100644 --- a/test/004-JniTest/src/Main.java +++ b/test/004-JniTest/src/Main.java @@ -56,6 +56,8 @@ public class Main { registerNativesJniTest(); testFastNativeMethods(); testCriticalNativeMethods(); + + testClinitMethodLookup(); } private static native boolean registerNativesJniTest(); @@ -314,6 +316,36 @@ public class Main { } private static native boolean isSlowDebug(); + + private static void testClinitMethodLookup() { + // Expect this to print <NSME Exception> + try { + System.out.println("Clinit Lookup: ClassWithoutClinit: " + methodString(lookupClinit(ClassWithoutClinit.class))); + } catch (NoSuchMethodError e) { + System.out.println("Clinit Lookup: ClassWithoutClinit: <NSME Exception>"); + } + // Expect this to print <clinit> + try { + System.out.println("Clinit Lookup: ClassWithClinit: " + methodString(lookupClinit(ClassWithClinit.class))); + } catch (NoSuchMethodError e) { + System.out.println("Clinit Lookup: ClassWithClinit: <NSME Exception>"); + } + } + + private static String methodString(java.lang.reflect.Executable method) { + if (method == null) { + return "<<null>>"; + } else { + return method.toString() + "(Class: " + method.getClass().toString() + ")"; + } + } + private static native java.lang.reflect.Executable lookupClinit(Class kls); + + private static class ClassWithoutClinit { + } + private static class ClassWithClinit { + static {} + } } @FunctionalInterface diff --git a/test/1923-frame-pop/expected.txt b/test/1923-frame-pop/expected.txt new file mode 100644 index 0000000000..7bc001da6c --- /dev/null +++ b/test/1923-frame-pop/expected.txt @@ -0,0 +1,8 @@ +public static void art.Test1923.recurTimesA(int,java.lang.Runnable) pop. Line=44 exception:false +Ran recurTimes(10) without errors! +public static void art.Test1923.recurTimesF(int,java.lang.Runnable) pop. Line=84 exception:false +Ran recurTimes(10) without errors! +public static void art.Test1923.recurTimesK(int,java.lang.Runnable) pop. Line=124 exception:false +Ran recurTimes(10) without errors! +public static void art.Test1923.recurTimesF(int,java.lang.Runnable) pop. Line=83 exception:true +Caught exception art.Test1923$RecursionError: Unable recur further. Still 90 outstanding! while running recurTimes(100) diff --git a/test/1923-frame-pop/info.txt b/test/1923-frame-pop/info.txt new file mode 100644 index 0000000000..b4984d9e4f --- /dev/null +++ b/test/1923-frame-pop/info.txt @@ -0,0 +1,3 @@ +Tests notify frame pop JVMTI functionality. + +This tests the normal use case. diff --git a/test/597-deopt-invoke-stub/run b/test/1923-frame-pop/run index bc04498bfe..51875a7e86 100644..100755 --- a/test/597-deopt-invoke-stub/run +++ b/test/1923-frame-pop/run @@ -1,12 +1,12 @@ #!/bin/bash # -# Copyright (C) 2017 The Android Open Source Project +# Copyright 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 +# 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, @@ -14,5 +14,5 @@ # 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 "${@}" +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1923-frame-pop/src/Main.java b/test/1923-frame-pop/src/Main.java new file mode 100644 index 0000000000..8881483ae4 --- /dev/null +++ b/test/1923-frame-pop/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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 { + public static void main(String[] args) throws Exception { + art.Test1923.run(); + } +} diff --git a/test/1923-frame-pop/src/art/Breakpoint.java b/test/1923-frame-pop/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1923-frame-pop/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1923-frame-pop/src/art/FramePop.java b/test/1923-frame-pop/src/art/FramePop.java new file mode 100644 index 0000000000..86bf226b31 --- /dev/null +++ b/test/1923-frame-pop/src/art/FramePop.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Method; + +public class FramePop { + public static native void enableFramePopEvent(Class klass, Method method, Thread thr) + throws Exception; + public static native void notifyFramePop(Thread target, int depth) throws Exception; +} diff --git a/test/1923-frame-pop/src/art/Locals.java b/test/1923-frame-pop/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1923-frame-pop/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1923-frame-pop/src/art/StackTrace.java b/test/1923-frame-pop/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1923-frame-pop/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1923-frame-pop/src/art/Suspension.java b/test/1923-frame-pop/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1923-frame-pop/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1923-frame-pop/src/art/Test1923.java b/test/1923-frame-pop/src/art/Test1923.java new file mode 100644 index 0000000000..b5265b7e9d --- /dev/null +++ b/test/1923-frame-pop/src/art/Test1923.java @@ -0,0 +1,209 @@ +/* + * 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. + */ + +package art; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.function.IntUnaryOperator; +import java.util.function.Function; + +public class Test1923 { + public static void handleFramePop(Executable m, boolean exception, long location) { + System.out.println( + m + " pop. Line=" + Breakpoint.locationToLine(m, location) + " exception:" + exception); + } + + public static void recurTimesA(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesB(times - 1, safepoint); + } + + public static void recurTimesB(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesC(times - 1, safepoint); + } + + public static void recurTimesC(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesD(times - 1, safepoint); + } + + public static void recurTimesD(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesE(times - 1, safepoint); + } + + public static void recurTimesE(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesF(times - 1, safepoint); + } + + public static void recurTimesF(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesG(times - 1, safepoint); + } + + public static void recurTimesG(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesH(times - 1, safepoint); + } + + public static void recurTimesH(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesI(times - 1, safepoint); + } + + public static void recurTimesI(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesJ(times - 1, safepoint); + } + + public static void recurTimesJ(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesK(times - 1, safepoint); + } + + public static class RecursionError extends Error { + public RecursionError(String s) { super(s); } + } + public static void recurTimesK(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + safepoint.run(); + throw new RecursionError("Unable recur further. Still " + times + " outstanding!"); + } + + public static class ThreadPauser implements Runnable { + public final Semaphore sem_wakeup_main; + public final Semaphore sem_wait; + + public ThreadPauser() { + sem_wakeup_main = new Semaphore(0); + sem_wait = new Semaphore(0); + } + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public static void doRecurTestWith(final int times, int watch_frame) throws Exception { + final String target_method_name_start = "recurTimes"; + final ThreadPauser safepoint = new ThreadPauser(); + Thread target = new Thread(() -> { + try { + recurTimesA(times, safepoint); + System.out.println("Ran recurTimes(" + times + ") without errors!"); + } catch (RecursionError e) { + System.out.println("Caught exception " + e + " while running recurTimes(" + times + ")"); + } + }); + target.start(); + safepoint.waitForOtherThreadToPause(); + Suspension.suspend(target); + // Safe block + int cnt = 0; + StackTrace.StackFrameData target_frame = null; + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(target)) { + if (frame.method.getName().startsWith(target_method_name_start)) { + if (times - cnt == watch_frame) { + target_frame = frame; + break; + } else { + cnt++; + } + } + } + if (target_frame != null) { + FramePop.notifyFramePop(target, target_frame.depth); + } else { + System.out.println( + "Unable to find stack frame for " + watch_frame + " depth of " + + target_method_name_start); + } + Suspension.resume(target); + safepoint.wakeupOtherThread(); + target.join(); + } + + public static void run() throws Exception { + // TODO Investigate what thread argument means for FramePop event enable. + // Listen for events on all threads. + FramePop.enableFramePopEvent( + Test1923.class, + Test1923.class.getDeclaredMethod( + "handleFramePop", Executable.class, Boolean.TYPE, Long.TYPE), + null); + doRecurTestWith(10, 0); + doRecurTestWith(10, 5); + doRecurTestWith(10, 10); + doRecurTestWith(100, 95); + } +} diff --git a/test/1923-frame-pop/src/art/Trace.java b/test/1923-frame-pop/src/art/Trace.java new file mode 100644 index 0000000000..ba3d397b0b --- /dev/null +++ b/test/1923-frame-pop/src/art/Trace.java @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class Trace { + public static native void enableTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Method fieldAccess, + Method fieldModify, + Method singleStep, + Thread thr); + public static native void disableTracing(Thread thr); + + public static void enableFieldTracing(Class<?> methodClass, + Method fieldAccess, + Method fieldModify, + Thread thr) { + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); + } + + public static void enableMethodTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Thread thr) { + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); + } + + public static native void watchFieldAccess(Field f); + public static native void watchFieldModification(Field f); + public static native void watchAllFieldAccesses(); + public static native void watchAllFieldModifications(); +} diff --git a/test/1924-frame-pop-toggle/expected.txt b/test/1924-frame-pop-toggle/expected.txt new file mode 100644 index 0000000000..64ca9db74b --- /dev/null +++ b/test/1924-frame-pop-toggle/expected.txt @@ -0,0 +1,8 @@ +public static void art.Test1924.recurTimesA(int,java.lang.Runnable) pop. Line=44 exception:false +Ran recurTimes(10) without errors! +public static void art.Test1924.recurTimesF(int,java.lang.Runnable) pop. Line=84 exception:false +Ran recurTimes(10) without errors! +public static void art.Test1924.recurTimesK(int,java.lang.Runnable) pop. Line=124 exception:false +Ran recurTimes(10) without errors! +public static void art.Test1924.recurTimesF(int,java.lang.Runnable) pop. Line=83 exception:true +Caught exception art.Test1924$RecursionError: Unable recur further. Still 90 outstanding! while running recurTimes(100) diff --git a/test/1924-frame-pop-toggle/frame_pop_toggle.cc b/test/1924-frame-pop-toggle/frame_pop_toggle.cc new file mode 100644 index 0000000000..1fb7a1f72b --- /dev/null +++ b/test/1924-frame-pop-toggle/frame_pop_toggle.cc @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#include <pthread.h> + +#include <cstdio> +#include <iostream> +#include <vector> + +#include "android-base/logging.h" +#include "jni.h" +#include "jvmti.h" + +#include "scoped_local_ref.h" +#include "scoped_primitive_array.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test1924FramePop { + +extern "C" JNIEXPORT void JNICALL Java_art_Test1924_toggleFramePop( + JNIEnv* env, jclass, jthread thr) { + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode( + JVMTI_DISABLE, JVMTI_EVENT_FRAME_POP, thr)); + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode( + JVMTI_ENABLE, JVMTI_EVENT_FRAME_POP, thr)); +} + +} // namespace Test1924FramePop +} // namespace art + diff --git a/test/1924-frame-pop-toggle/info.txt b/test/1924-frame-pop-toggle/info.txt new file mode 100644 index 0000000000..1f7480f969 --- /dev/null +++ b/test/1924-frame-pop-toggle/info.txt @@ -0,0 +1,3 @@ +Tests notify frame pop JVMTI functionality. + +This tests toggling frame pop off and on. diff --git a/test/597-deopt-busy-loop/run b/test/1924-frame-pop-toggle/run index bc04498bfe..51875a7e86 100644..100755 --- a/test/597-deopt-busy-loop/run +++ b/test/1924-frame-pop-toggle/run @@ -1,12 +1,12 @@ #!/bin/bash # -# Copyright (C) 2017 The Android Open Source Project +# Copyright 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 +# 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, @@ -14,5 +14,5 @@ # 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 "${@}" +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1924-frame-pop-toggle/src/Main.java b/test/1924-frame-pop-toggle/src/Main.java new file mode 100644 index 0000000000..f48fc2d308 --- /dev/null +++ b/test/1924-frame-pop-toggle/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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 { + public static void main(String[] args) throws Exception { + art.Test1924.run(); + } +} diff --git a/test/1924-frame-pop-toggle/src/art/Breakpoint.java b/test/1924-frame-pop-toggle/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1924-frame-pop-toggle/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1924-frame-pop-toggle/src/art/FramePop.java b/test/1924-frame-pop-toggle/src/art/FramePop.java new file mode 100644 index 0000000000..86bf226b31 --- /dev/null +++ b/test/1924-frame-pop-toggle/src/art/FramePop.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Method; + +public class FramePop { + public static native void enableFramePopEvent(Class klass, Method method, Thread thr) + throws Exception; + public static native void notifyFramePop(Thread target, int depth) throws Exception; +} diff --git a/test/1924-frame-pop-toggle/src/art/Locals.java b/test/1924-frame-pop-toggle/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1924-frame-pop-toggle/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1924-frame-pop-toggle/src/art/StackTrace.java b/test/1924-frame-pop-toggle/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1924-frame-pop-toggle/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1924-frame-pop-toggle/src/art/Suspension.java b/test/1924-frame-pop-toggle/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1924-frame-pop-toggle/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1924-frame-pop-toggle/src/art/Test1924.java b/test/1924-frame-pop-toggle/src/art/Test1924.java new file mode 100644 index 0000000000..0ffbfc621c --- /dev/null +++ b/test/1924-frame-pop-toggle/src/art/Test1924.java @@ -0,0 +1,212 @@ +/* + * 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. + */ + +package art; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.function.IntUnaryOperator; +import java.util.function.Function; + +public class Test1924 { + public static void handleFramePop(Executable m, boolean exception, long location) { + System.out.println( + m + " pop. Line=" + Breakpoint.locationToLine(m, location) + " exception:" + exception); + } + + public static void recurTimesA(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesB(times - 1, safepoint); + } + + public static void recurTimesB(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesC(times - 1, safepoint); + } + + public static void recurTimesC(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesD(times - 1, safepoint); + } + + public static void recurTimesD(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesE(times - 1, safepoint); + } + + public static void recurTimesE(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesF(times - 1, safepoint); + } + + public static void recurTimesF(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesG(times - 1, safepoint); + } + + public static void recurTimesG(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesH(times - 1, safepoint); + } + + public static void recurTimesH(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesI(times - 1, safepoint); + } + + public static void recurTimesI(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesJ(times - 1, safepoint); + } + + public static void recurTimesJ(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesK(times - 1, safepoint); + } + + public static class RecursionError extends Error { + public RecursionError(String s) { super(s); } + } + public static void recurTimesK(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + safepoint.run(); + throw new RecursionError("Unable recur further. Still " + times + " outstanding!"); + } + + public static class ThreadPauser implements Runnable { + public final Semaphore sem_wakeup_main; + public final Semaphore sem_wait; + + public ThreadPauser() { + sem_wakeup_main = new Semaphore(0); + sem_wait = new Semaphore(0); + } + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public static void doRecurTestWith(final int times, int watch_frame) throws Exception { + final String target_method_name_start = "recurTimes"; + final ThreadPauser safepoint = new ThreadPauser(); + Thread target = new Thread(() -> { + try { + recurTimesA(times, safepoint); + System.out.println("Ran recurTimes(" + times + ") without errors!"); + } catch (RecursionError e) { + System.out.println("Caught exception " + e + " while running recurTimes(" + times + ")"); + } + }); + target.start(); + safepoint.waitForOtherThreadToPause(); + Suspension.suspend(target); + // Safe block + int cnt = 0; + StackTrace.StackFrameData target_frame = null; + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(target)) { + if (frame.method.getName().startsWith(target_method_name_start)) { + if (times - cnt == watch_frame) { + target_frame = frame; + break; + } else { + cnt++; + } + } + } + if (target_frame != null) { + FramePop.notifyFramePop(target, target_frame.depth); + } else { + System.out.println( + "Unable to find stack frame for " + watch_frame + " depth of " + + target_method_name_start); + } + Suspension.resume(target); + toggleFramePop(null); + safepoint.wakeupOtherThread(); + target.join(); + } + + public static void run() throws Exception { + // TODO Investigate what thread argument means for FramePop event enable. + // Listen for events on all threads. + FramePop.enableFramePopEvent( + Test1924.class, + Test1924.class.getDeclaredMethod( + "handleFramePop", Executable.class, Boolean.TYPE, Long.TYPE), + null); + doRecurTestWith(10, 0); + doRecurTestWith(10, 5); + doRecurTestWith(10, 10); + doRecurTestWith(100, 95); + } + + public static native void toggleFramePop(Thread thr); +} diff --git a/test/1924-frame-pop-toggle/src/art/Trace.java b/test/1924-frame-pop-toggle/src/art/Trace.java new file mode 100644 index 0000000000..ba3d397b0b --- /dev/null +++ b/test/1924-frame-pop-toggle/src/art/Trace.java @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class Trace { + public static native void enableTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Method fieldAccess, + Method fieldModify, + Method singleStep, + Thread thr); + public static native void disableTracing(Thread thr); + + public static void enableFieldTracing(Class<?> methodClass, + Method fieldAccess, + Method fieldModify, + Thread thr) { + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); + } + + public static void enableMethodTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Thread thr) { + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); + } + + public static native void watchFieldAccess(Field f); + public static native void watchFieldModification(Field f); + public static native void watchAllFieldAccesses(); + public static native void watchAllFieldModifications(); +} diff --git a/test/1925-self-frame-pop/expected.txt b/test/1925-self-frame-pop/expected.txt new file mode 100644 index 0000000000..f154205a0f --- /dev/null +++ b/test/1925-self-frame-pop/expected.txt @@ -0,0 +1,4 @@ +public static void art.Test1925.recurTimesE(int,java.lang.Runnable) pop. Line=76 exception:false +Ran recurTimes(10) without errors! +public static void art.Test1925.recurTimesE(int,java.lang.Runnable) pop. Line=75 exception:true +Caught exception art.Test1925$RecursionError: Unable recur further. Still 90 outstanding! while running recurTimes(100) diff --git a/test/1925-self-frame-pop/info.txt b/test/1925-self-frame-pop/info.txt new file mode 100644 index 0000000000..ccca498fc5 --- /dev/null +++ b/test/1925-self-frame-pop/info.txt @@ -0,0 +1,3 @@ +Tests notify frame pop JVMTI functionality. + +This tests setting frame-pop on the current thread diff --git a/test/1925-self-frame-pop/run b/test/1925-self-frame-pop/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1925-self-frame-pop/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1925-self-frame-pop/src/Main.java b/test/1925-self-frame-pop/src/Main.java new file mode 100644 index 0000000000..2761aefa85 --- /dev/null +++ b/test/1925-self-frame-pop/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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 { + public static void main(String[] args) throws Exception { + art.Test1925.run(); + } +} diff --git a/test/1925-self-frame-pop/src/art/Breakpoint.java b/test/1925-self-frame-pop/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1925-self-frame-pop/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1925-self-frame-pop/src/art/FramePop.java b/test/1925-self-frame-pop/src/art/FramePop.java new file mode 100644 index 0000000000..86bf226b31 --- /dev/null +++ b/test/1925-self-frame-pop/src/art/FramePop.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Method; + +public class FramePop { + public static native void enableFramePopEvent(Class klass, Method method, Thread thr) + throws Exception; + public static native void notifyFramePop(Thread target, int depth) throws Exception; +} diff --git a/test/1925-self-frame-pop/src/art/Locals.java b/test/1925-self-frame-pop/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1925-self-frame-pop/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1925-self-frame-pop/src/art/StackTrace.java b/test/1925-self-frame-pop/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1925-self-frame-pop/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1925-self-frame-pop/src/art/Suspension.java b/test/1925-self-frame-pop/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1925-self-frame-pop/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1925-self-frame-pop/src/art/Test1925.java b/test/1925-self-frame-pop/src/art/Test1925.java new file mode 100644 index 0000000000..b413d06545 --- /dev/null +++ b/test/1925-self-frame-pop/src/art/Test1925.java @@ -0,0 +1,168 @@ +/* + * 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. + */ + +package art; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.function.IntUnaryOperator; +import java.util.function.Function; + +public class Test1925 { + public static void handleFramePop(Executable m, boolean exception, long location) { + System.out.println( + m + " pop. Line=" + Breakpoint.locationToLine(m, location) + " exception:" + exception); + } + + public static void recurTimesA(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesB(times - 1, safepoint); + } + + public static void recurTimesB(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesC(times - 1, safepoint); + } + + public static void recurTimesC(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesD(times - 1, safepoint); + } + + public static void recurTimesD(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesE(times - 1, safepoint); + } + + public static void recurTimesE(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesF(times - 1, safepoint); + } + + public static void recurTimesF(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesG(times - 1, safepoint); + } + + public static void recurTimesG(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesH(times - 1, safepoint); + } + + public static void recurTimesH(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesI(times - 1, safepoint); + } + + public static void recurTimesI(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesJ(times - 1, safepoint); + } + + public static void recurTimesJ(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesK(times - 1, safepoint); + } + + public static class RecursionError extends Error { + public RecursionError(String s) { super(s); } + } + public static void recurTimesK(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + safepoint.run(); + throw new RecursionError("Unable recur further. Still " + times + " outstanding!"); + } + + public static void doRecurTestWith(final int times, int watch_frame) throws Exception { + final String target_method_name_start = "recurTimes"; + final Runnable safepoint = () -> { + StackTrace.StackFrameData target_frame = null; + int cnt = 0; + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(Thread.currentThread())) { + if (frame.method.getName().startsWith(target_method_name_start)) { + if (times - cnt == watch_frame) { + target_frame = frame; + break; + } else { + cnt++; + } + } + } + try { + FramePop.notifyFramePop(null, target_frame.depth); + } catch (Exception e) { + throw new Error("Unexpected error in notifyFramePop!", e); + } + }; + try { + recurTimesA(times, safepoint); + System.out.println("Ran recurTimes(" + times + ") without errors!"); + } catch (Throwable e) { + System.out.println("Caught exception " + e + " while running recurTimes(" + times + ")"); + } + } + + public static void run() throws Exception { + FramePop.enableFramePopEvent( + Test1925.class, + Test1925.class.getDeclaredMethod( + "handleFramePop", Executable.class, Boolean.TYPE, Long.TYPE), + null); + doRecurTestWith(10, 5); + doRecurTestWith(100, 95); + } +} diff --git a/test/1925-self-frame-pop/src/art/Trace.java b/test/1925-self-frame-pop/src/art/Trace.java new file mode 100644 index 0000000000..ba3d397b0b --- /dev/null +++ b/test/1925-self-frame-pop/src/art/Trace.java @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class Trace { + public static native void enableTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Method fieldAccess, + Method fieldModify, + Method singleStep, + Thread thr); + public static native void disableTracing(Thread thr); + + public static void enableFieldTracing(Class<?> methodClass, + Method fieldAccess, + Method fieldModify, + Thread thr) { + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); + } + + public static void enableMethodTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Thread thr) { + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); + } + + public static native void watchFieldAccess(Field f); + public static native void watchFieldModification(Field f); + public static native void watchAllFieldAccesses(); + public static native void watchAllFieldModifications(); +} diff --git a/test/1926-missed-frame-pop/expected.txt b/test/1926-missed-frame-pop/expected.txt new file mode 100644 index 0000000000..20e723f3ee --- /dev/null +++ b/test/1926-missed-frame-pop/expected.txt @@ -0,0 +1,9 @@ +Ran recurTimes(10) without errors after disabling frame pop event! +renabling frame pop event with similar stack. +Ran recurTimes(10) without errors! +Ran recurTimes(10) without errors after disabling frame pop event! +renabling frame pop event with similar stack. +Ran recurTimes(10) without errors! +Ran recurTimes(10) without errors after disabling frame pop event! +renabling frame pop event with similar stack. +Ran recurTimes(10) without errors! diff --git a/test/1926-missed-frame-pop/frame_pop_missed.cc b/test/1926-missed-frame-pop/frame_pop_missed.cc new file mode 100644 index 0000000000..c5104de1fa --- /dev/null +++ b/test/1926-missed-frame-pop/frame_pop_missed.cc @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#include <pthread.h> + +#include <cstdio> +#include <iostream> +#include <vector> + +#include "android-base/logging.h" +#include "jni.h" +#include "jvmti.h" + +#include "scoped_local_ref.h" +#include "scoped_primitive_array.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test1926FramePopMissed { + +extern "C" JNIEXPORT void JNICALL Java_art_Test1926_disableFramePop( + JNIEnv* env, jclass, jthread thr) { + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode( + JVMTI_DISABLE, JVMTI_EVENT_FRAME_POP, thr)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test1926_reenableFramePop( + JNIEnv* env, jclass, jthread thr) { + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode( + JVMTI_ENABLE, JVMTI_EVENT_FRAME_POP, thr)); +} + +} // namespace Test1926FramePopMissed +} // namespace art + diff --git a/test/1926-missed-frame-pop/info.txt b/test/1926-missed-frame-pop/info.txt new file mode 100644 index 0000000000..b4984d9e4f --- /dev/null +++ b/test/1926-missed-frame-pop/info.txt @@ -0,0 +1,3 @@ +Tests notify frame pop JVMTI functionality. + +This tests the normal use case. diff --git a/test/1926-missed-frame-pop/run b/test/1926-missed-frame-pop/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1926-missed-frame-pop/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1926-missed-frame-pop/src/Main.java b/test/1926-missed-frame-pop/src/Main.java new file mode 100644 index 0000000000..f924079f2d --- /dev/null +++ b/test/1926-missed-frame-pop/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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 { + public static void main(String[] args) throws Exception { + art.Test1926.run(); + } +} diff --git a/test/1926-missed-frame-pop/src/art/Breakpoint.java b/test/1926-missed-frame-pop/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1926-missed-frame-pop/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1926-missed-frame-pop/src/art/FramePop.java b/test/1926-missed-frame-pop/src/art/FramePop.java new file mode 100644 index 0000000000..86bf226b31 --- /dev/null +++ b/test/1926-missed-frame-pop/src/art/FramePop.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Method; + +public class FramePop { + public static native void enableFramePopEvent(Class klass, Method method, Thread thr) + throws Exception; + public static native void notifyFramePop(Thread target, int depth) throws Exception; +} diff --git a/test/1926-missed-frame-pop/src/art/Locals.java b/test/1926-missed-frame-pop/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1926-missed-frame-pop/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1926-missed-frame-pop/src/art/StackTrace.java b/test/1926-missed-frame-pop/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1926-missed-frame-pop/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1926-missed-frame-pop/src/art/Suspension.java b/test/1926-missed-frame-pop/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1926-missed-frame-pop/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1926-missed-frame-pop/src/art/Test1926.java b/test/1926-missed-frame-pop/src/art/Test1926.java new file mode 100644 index 0000000000..cb210729a9 --- /dev/null +++ b/test/1926-missed-frame-pop/src/art/Test1926.java @@ -0,0 +1,222 @@ +/* + * 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. + */ + +package art; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.function.IntUnaryOperator; +import java.util.function.Function; + +public class Test1926 { + public static void handleFramePop(Executable m, boolean exception, long location) { + System.out.println( + m + " pop. Line=" + Breakpoint.locationToLine(m, location) + " exception:" + exception); + } + + public static void recurTimesA(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesB(times - 1, safepoint); + } + + public static void recurTimesB(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesC(times - 1, safepoint); + } + + public static void recurTimesC(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesD(times - 1, safepoint); + } + + public static void recurTimesD(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesE(times - 1, safepoint); + } + + public static void recurTimesE(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesF(times - 1, safepoint); + } + + public static void recurTimesF(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesG(times - 1, safepoint); + } + + public static void recurTimesG(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesH(times - 1, safepoint); + } + + public static void recurTimesH(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesI(times - 1, safepoint); + } + + public static void recurTimesI(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesJ(times - 1, safepoint); + } + + public static void recurTimesJ(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesK(times - 1, safepoint); + } + + public static void recurTimesK(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + recurTimesL(times - 1, safepoint); + } + + public static class RecursionError extends Error { + public RecursionError(String s) { super(s); } + } + + public static void recurTimesL(int times, Runnable safepoint) { + if (times == 0) { + safepoint.run(); + return; + } + safepoint.run(); + throw new RecursionError("Unable recur further. Still " + times + " outstanding!"); + } + + public static class ThreadPauser implements Runnable { + public final Semaphore sem_wakeup_main; + public final Semaphore sem_wait; + + public ThreadPauser() { + sem_wakeup_main = new Semaphore(0); + sem_wait = new Semaphore(0); + } + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public static void doRecurTestWith(final int times, int watch_frame) throws Exception { + final String target_method_name_start = "recurTimes"; + final ThreadPauser safepoint = new ThreadPauser(); + Thread target = new Thread(() -> { + recurTimesA(times, () -> { + safepoint.run(); + disableFramePop(null); + }); + System.out.println("Ran recurTimes(" + times + ") without errors after disabling " + + "frame pop event!"); + System.out.println("renabling frame pop event with similar stack."); + recurTimesB(times, () -> { reenableFramePop(null); }); + System.out.println("Ran recurTimes(" + times + ") without errors!"); + }); + target.start(); + safepoint.waitForOtherThreadToPause(); + Suspension.suspend(target); + // Safe block + int cnt = 0; + StackTrace.StackFrameData target_frame = null; + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(target)) { + if (frame.method.getName().startsWith(target_method_name_start)) { + if (times - cnt == watch_frame) { + target_frame = frame; + break; + } else { + cnt++; + } + } + } + if (target_frame != null) { + FramePop.notifyFramePop(target, target_frame.depth); + } else { + System.out.println( + "Unable to find stack frame for " + watch_frame + " depth of " + + target_method_name_start); + } + Suspension.resume(target); + safepoint.wakeupOtherThread(); + target.join(); + } + + public static void run() throws Exception { + // Listen for events on all threads. + FramePop.enableFramePopEvent( + Test1926.class, + Test1926.class.getDeclaredMethod( + "handleFramePop", Executable.class, Boolean.TYPE, Long.TYPE), + null); + doRecurTestWith(10, 0); + doRecurTestWith(10, 5); + doRecurTestWith(10, 10); + } + + public static native void disableFramePop(Thread thr); + public static native void reenableFramePop(Thread thr); +} diff --git a/test/1926-missed-frame-pop/src/art/Trace.java b/test/1926-missed-frame-pop/src/art/Trace.java new file mode 100644 index 0000000000..ba3d397b0b --- /dev/null +++ b/test/1926-missed-frame-pop/src/art/Trace.java @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class Trace { + public static native void enableTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Method fieldAccess, + Method fieldModify, + Method singleStep, + Thread thr); + public static native void disableTracing(Thread thr); + + public static void enableFieldTracing(Class<?> methodClass, + Method fieldAccess, + Method fieldModify, + Thread thr) { + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); + } + + public static void enableMethodTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Thread thr) { + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); + } + + public static native void watchFieldAccess(Field f); + public static native void watchFieldModification(Field f); + public static native void watchAllFieldAccesses(); + public static native void watchAllFieldModifications(); +} diff --git a/test/597-deopt-busy-loop/expected.txt b/test/597-deopt-busy-loop/expected.txt deleted file mode 100644 index f993efcdad..0000000000 --- a/test/597-deopt-busy-loop/expected.txt +++ /dev/null @@ -1,2 +0,0 @@ -JNI_OnLoad called -Finishing diff --git a/test/597-deopt-busy-loop/info.txt b/test/597-deopt-busy-loop/info.txt deleted file mode 100644 index 2c50dbbe79..0000000000 --- a/test/597-deopt-busy-loop/info.txt +++ /dev/null @@ -1 +0,0 @@ -Test deoptimizing when returning from suspend-check runtime method. diff --git a/test/597-deopt-busy-loop/src/Main.java b/test/597-deopt-busy-loop/src/Main.java deleted file mode 100644 index 46b6bbf4f3..0000000000 --- a/test/597-deopt-busy-loop/src/Main.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 deleted file mode 100644 index f993efcdad..0000000000 --- a/test/597-deopt-invoke-stub/expected.txt +++ /dev/null @@ -1,2 +0,0 @@ -JNI_OnLoad called -Finishing diff --git a/test/597-deopt-invoke-stub/info.txt b/test/597-deopt-invoke-stub/info.txt deleted file mode 100644 index 31960a988b..0000000000 --- a/test/597-deopt-invoke-stub/info.txt +++ /dev/null @@ -1 +0,0 @@ -Test deoptimizing when returning from a quick-to-interpreter bridge. diff --git a/test/597-deopt-invoke-stub/src/Main.java b/test/597-deopt-invoke-stub/src/Main.java deleted file mode 100644 index 075178361b..0000000000 --- a/test/597-deopt-invoke-stub/src/Main.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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/620-checker-bce-intrinsics/src/Main.java b/test/620-checker-bce-intrinsics/src/Main.java index afc3c656bc..e0b5986bc3 100644 --- a/test/620-checker-bce-intrinsics/src/Main.java +++ b/test/620-checker-bce-intrinsics/src/Main.java @@ -34,15 +34,15 @@ public class Main { return x; } - /// CHECK-START: int Main.oneArrayAbs(int[], int) BCE (before) + /// CHECK-START: int Main.oneArrayAbs(int[], int[]) BCE (before) /// CHECK-DAG: BoundsCheck loop:<<Loop:B\d+>> outer_loop:none // - /// CHECK-START: int Main.oneArrayAbs(int[], int) BCE (after) + /// CHECK-START: int Main.oneArrayAbs(int[], int[]) BCE (after) /// CHECK-NOT: BoundsCheck /// CHECK-NOT: Deoptimize - static int oneArrayAbs(int[] a, int lo) { + static int oneArrayAbs(int[] a, int[] b) { int x = 0; - for (int i = Math.abs(lo); i < a.length; i++) { + for (int i = Math.abs(b.length); i < a.length; i++) { x += a[i]; } return x; @@ -221,15 +221,15 @@ public class Main { int[] b = { 6, 7, 8, 9, 4, 2 }; int[] c = { 1, 2, 3 }; int[] d = { 8, 5, 3, 2 }; + int[] e = { }; expectEquals(15, oneArray(a)); expectEquals(36, oneArray(b)); expectEquals(6, oneArray(c)); expectEquals(18, oneArray(d)); - expectEquals(5, oneArrayAbs(a, -4)); - expectEquals(15, oneArrayAbs(a, 0)); - expectEquals(5, oneArrayAbs(a, 4)); + expectEquals(15, oneArrayAbs(a, e)); + expectEquals(5, oneArrayAbs(a, d)); expectEquals(30, twoArrays(a, a)); expectEquals(49, twoArrays(a, b)); diff --git a/test/623-checker-loop-regressions/src/Main.java b/test/623-checker-loop-regressions/src/Main.java index 0e8561299c..056ed914a8 100644 --- a/test/623-checker-loop-regressions/src/Main.java +++ b/test/623-checker-loop-regressions/src/Main.java @@ -464,6 +464,15 @@ public class Main { return r; } + static int absCanBeNegative(int x) { + int a[] = { 1, 2, 3 }; + int y = 0; + for (int i = Math.abs(x); i < a.length; i++) { + y += a[i]; + } + return y; + } + public static void main(String[] args) { expectEquals(10, earlyExitFirst(-1)); for (int i = 0; i <= 10; i++) { @@ -586,6 +595,24 @@ public class Main { expectEquals(11, f[i]); } + expectEquals(0, absCanBeNegative(-3)); + expectEquals(3, absCanBeNegative(-2)); + expectEquals(5, absCanBeNegative(-1)); + expectEquals(6, absCanBeNegative(0)); + expectEquals(5, absCanBeNegative(1)); + expectEquals(3, absCanBeNegative(2)); + expectEquals(0, absCanBeNegative(3)); + expectEquals(0, absCanBeNegative(Integer.MAX_VALUE)); + // Abs(min_int) = min_int. + int verify = 0; + try { + absCanBeNegative(Integer.MIN_VALUE); + verify = 1; + } catch (ArrayIndexOutOfBoundsException e) { + verify = 2; + } + expectEquals(2, verify); + System.out.println("passed"); } diff --git a/test/954-invoke-polymorphic-verifier/smali/Unresolved.smali b/test/954-invoke-polymorphic-verifier/smali/Unresolved.smali index 882f0e9256..26044726ad 100644 --- a/test/954-invoke-polymorphic-verifier/smali/Unresolved.smali +++ b/test/954-invoke-polymorphic-verifier/smali/Unresolved.smali @@ -23,7 +23,7 @@ .line 23 invoke-direct {p0}, Ljava/lang/Object;-><init>()V # Get an unresolvable instance (abstract class) - invoke-static {}, LAbstract;->getUnresolvedInstance()Lother/thing/Foo; + invoke-static {}, LUnresolved;->getUnresolvedInstance()Lother/thing/Foo; move-result-object v0 const-string v1, "1" const-string v2, "2" diff --git a/test/Android.bp b/test/Android.bp index d0c05652b1..2aed50c455 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -248,6 +248,7 @@ art_cc_defaults { "ti-agent/test_env.cc", "ti-agent/breakpoint_helper.cc", "ti-agent/common_helper.cc", + "ti-agent/frame_pop_helper.cc", "ti-agent/locals_helper.cc", "ti-agent/redefinition_helper.cc", "ti-agent/suspension_helper.cc", @@ -295,6 +296,8 @@ art_cc_defaults { "1920-suspend-native-monitor/native_suspend_monitor.cc", "1921-suspend-native-recursive-monitor/native_suspend_recursive_monitor.cc", "1922-owned-monitors-info/owned_monitors.cc", + "1924-frame-pop-toggle/frame_pop_toggle.cc", + "1926-missed-frame-pop/frame_pop_missed.cc", ], shared_libs: [ "libbase", diff --git a/test/knownfailures.json b/test/knownfailures.json index d76103a226..df7544e6e0 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -207,9 +207,7 @@ "variant": "trace | stream" }, { - "tests": ["597-deopt-busy-loop", - "597-deopt-invoke-stub", - "604-hot-static-interface", + "tests": ["604-hot-static-interface", "612-jit-dex-cache", "613-inlining-dex-cache", "626-set-resolved-string"], @@ -683,6 +681,16 @@ "env_vars": {"SANITIZE_HOST": "address"} }, { + "tests": [ + "1923-frame-pop", + "1924-frame-pop-toggle" + ], + "description": "ASAN seems to make these tests overflow their stacks.", + "variant": "64", + "bug": "b/65189092", + "env_vars": {"SANITIZE_HOST": "address"} + }, + { "tests": ["988-method-trace"], "variant": "redefine-stress | jvmti-stress", "description": "Test disabled due to redefine-stress disabling intrinsics which changes the trace output slightly." diff --git a/test/run-test b/test/run-test index e6196a0213..9996986a92 100755 --- a/test/run-test +++ b/test/run-test @@ -407,6 +407,10 @@ while true; do elif [ "x$1" = "x--random-profile" ]; then run_args="${run_args} --random-profile" shift + elif [ "x$1" = "x--dex2oat-jobs" ]; then + shift + run_args="${run_args} -Xcompiler-option -j$1" + shift elif expr "x$1" : "x--" >/dev/null 2>&1; then echo "unknown $0 option: $1" 1>&2 usage="yes" @@ -702,6 +706,7 @@ if [ "$usage" = "yes" ]; then echo " --bisection-search Perform bisection bug search." echo " --vdex Test using vdex as in input to dex2oat. Only works with --prebuild." echo " --suspend-timeout Change thread suspend timeout ms (default 500000)." + echo " --dex2oat-jobs Number of dex2oat jobs." ) 1>&2 # Direct to stderr so usage is not printed if --quiet is set. exit 1 fi diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py index 68e1856adb..f425097b57 100755 --- a/test/testrunner/testrunner.py +++ b/test/testrunner/testrunner.py @@ -127,6 +127,7 @@ build = False gdb = False gdb_arg = '' stop_testrunner = False +dex2oat_jobs = -1 # -1 corresponds to default threads for dex2oat def gather_test_info(): """The method gathers test information about the test to be run which includes @@ -341,6 +342,9 @@ def run_tests(tests): if gdb_arg: options_all += ' --gdb-arg ' + gdb_arg + if dex2oat_jobs != -1: + options_all += ' --dex2oat-jobs ' + str(dex2oat_jobs) + config = itertools.product(tests, TARGET_TYPES, RUN_TYPES, PREBUILD_TYPES, COMPILER_TYPES, RELOCATE_TYPES, TRACE_TYPES, GC_TYPES, JNI_TYPES, IMAGE_TYPES, PICTEST_TYPES, @@ -860,6 +864,7 @@ def parse_option(): global gdb global gdb_arg global timeout + global dex2oat_jobs parser = argparse.ArgumentParser(description="Runs all or a subset of the ART test suite.") parser.add_argument('-t', '--test', dest='test', help='name of the test') @@ -887,6 +892,8 @@ def parse_option(): parser.set_defaults(build = env.ART_TEST_RUN_TEST_BUILD) parser.add_argument('--gdb', action='store_true', dest='gdb') parser.add_argument('--gdb-arg', dest='gdb_arg') + parser.add_argument('--dex2oat-jobs', type=int, dest='dex2oat_jobs', + help='Number of dex2oat jobs') options = vars(parser.parse_args()) if options['build_target']: @@ -987,6 +994,8 @@ def parse_option(): if options['gdb_arg']: gdb_arg = options['gdb_arg'] timeout = options['timeout'] + if options['dex2oat_jobs']: + dex2oat_jobs = options['dex2oat_jobs'] return test diff --git a/test/ti-agent/frame_pop_helper.cc b/test/ti-agent/frame_pop_helper.cc new file mode 100644 index 0000000000..4571032ce6 --- /dev/null +++ b/test/ti-agent/frame_pop_helper.cc @@ -0,0 +1,130 @@ +/* + * 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. + */ + +#include "common_helper.h" + +#include "jni.h" +#include "jvmti.h" + +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { +namespace common_frame_pop { + +struct FramePopData { + jclass test_klass; + jmethodID pop_method; +}; + +static void framePopCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr, + jmethodID method ATTRIBUTE_UNUSED, + jboolean was_popped_by_exception) { + FramePopData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + jlong location; + jmethodID frame_method; + if (JvmtiErrorToException(jnienv, + jvmti, + jvmti->GetFrameLocation(thr, 0, &frame_method, &location))) { + return; + } + CHECK(data->pop_method != nullptr); + jobject method_arg = GetJavaMethod(jvmti, jnienv, frame_method); + jnienv->CallStaticVoidMethod(data->test_klass, + data->pop_method, + method_arg, + was_popped_by_exception, + location); + jnienv->DeleteLocalRef(method_arg); +} + +extern "C" JNIEXPORT void JNICALL Java_art_FramePop_enableFramePopEvent( + JNIEnv* env, jclass, jclass klass, jobject notify_method, jthread thr) { + FramePopData* data = nullptr; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->Allocate(sizeof(FramePopData), + reinterpret_cast<unsigned char**>(&data)))) { + return; + } + memset(data, 0, sizeof(FramePopData)); + data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(klass)); + data->pop_method = env->FromReflectedMethod(notify_method); + if (env->ExceptionCheck()) { + return; + } + void* old_data = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) { + return; + } else if (old_data != nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Environment already has local storage set!"); + return; + } + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { + return; + } + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_generate_frame_pop_events = 1; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) { + return; + } + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.FramePop = framePopCB; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + return; + } + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_FRAME_POP, + thr)); +} + +extern "C" JNIEXPORT jlong JNICALL Java_art_FramePop_makeJvmtiEnvForFramePop(JNIEnv* env, jclass) { + JavaVM* vm; + jvmtiEnv* out_jvmti_env = nullptr; + if (env->GetJavaVM(&vm) != JNI_OK || + vm->GetEnv(reinterpret_cast<void**>(&out_jvmti_env), JVMTI_VERSION_1_0) != JNI_OK) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + if (rt_exception.get() == nullptr) { + // CNFE should be pending. + return 0L; + } + env->ThrowNew(rt_exception.get(), "Unable to create new jvmti_env"); + return 0L; + } + SetAllCapabilities(out_jvmti_env); + return static_cast<jlong>(reinterpret_cast<intptr_t>(out_jvmti_env)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_FramePop_notifyFramePop( + JNIEnv* env, jclass, jthread thr, jint depth) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->NotifyFramePop(thr, depth)); +} + +} // namespace common_frame_pop +} // namespace art + diff --git a/tools/cpp-define-generator/constant_globals.def b/tools/cpp-define-generator/constant_globals.def index dbaf33cdef..5018f52937 100644 --- a/tools/cpp-define-generator/constant_globals.def +++ b/tools/cpp-define-generator/constant_globals.def @@ -19,6 +19,7 @@ #if defined(DEFINE_INCLUDE_DEPENDENCIES) #include <atomic> // std::memory_order_relaxed #include "globals.h" // art::kObjectAlignment +#include "modifiers.h" #endif DEFINE_EXPR(STD_MEMORY_ORDER_RELAXED, int32_t, std::memory_order_relaxed) @@ -30,5 +31,8 @@ DEFINE_OBJECT_EXPR(ALIGNMENT_MASK, size_t, art::kObjectAlignment - 1) DEFINE_OBJECT_EXPR(ALIGNMENT_MASK_TOGGLED, uint32_t, ~static_cast<uint32_t>(art::kObjectAlignment - 1)) DEFINE_OBJECT_EXPR(ALIGNMENT_MASK_TOGGLED64, uint64_t, ~static_cast<uint64_t>(art::kObjectAlignment - 1)) +DEFINE_EXPR(ACC_OBSOLETE_METHOD, int32_t, art::kAccObsoleteMethod) +DEFINE_EXPR(ACC_OBSOLETE_METHOD_SHIFT, int32_t, art::WhichPowerOf2(art::kAccObsoleteMethod)) + #undef DEFINE_OBJECT_EXPR diff --git a/tools/cpp-define-generator/offset_dexcache.def b/tools/cpp-define-generator/offset_art_method.def index 43f94344cc..e6a0907759 100644 --- a/tools/cpp-define-generator/offset_dexcache.def +++ b/tools/cpp-define-generator/offset_art_method.def @@ -33,10 +33,10 @@ DEFINE_EXPR(DECLARING_CLASS_ ## field_name ## _OFFSET, int32_t, art::mirror::Class::method_name##Offset().Int32Value()) // New macro suffix Method Name (of the Offset method) -DEFINE_ART_METHOD_OFFSET_SIZED(DEX_CACHE_METHODS, DexCacheResolvedMethods) DEFINE_ART_METHOD_OFFSET_SIZED(JNI, EntryPointFromJni) DEFINE_ART_METHOD_OFFSET_SIZED(QUICK_CODE, EntryPointFromQuickCompiledCode) DEFINE_ART_METHOD_OFFSET(DECLARING_CLASS, DeclaringClass) +DEFINE_ART_METHOD_OFFSET(ACCESS_FLAGS, AccessFlags) #undef DEFINE_ART_METHOD_OFFSET #undef DEFINE_ART_METHOD_OFFSET_32 diff --git a/tools/cpp-define-generator/offset_mirror_class.def b/tools/cpp-define-generator/offset_mirror_class.def new file mode 100644 index 0000000000..9b7bfce0c0 --- /dev/null +++ b/tools/cpp-define-generator/offset_mirror_class.def @@ -0,0 +1,32 @@ +/* + * 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. + */ + +// Offsets within java.lang.Class (mirror::Class). + +#if defined(DEFINE_INCLUDE_DEPENDENCIES) +#include "mirror/class.h" // art::mirror::Object +#endif + +#include "common.def" // DEFINE_OFFSET_EXPR + +#define DEFINE_MIRROR_CLASS_OFFSET(field_name, method_name) \ + DEFINE_OFFSET_EXPR(MIRROR_CLASS, field_name, int32_t, art::mirror::Class::method_name##Offset().Int32Value()) + +// New macro suffix Method Name (of the Offset method) +DEFINE_MIRROR_CLASS_OFFSET(DEX_CACHE, DexCache) + +#undef DEFINE_MIRROR_CLASS_OFFSET +#include "common_undef.def" // undef DEFINE_OFFSET_EXPR diff --git a/tools/cpp-define-generator/offset_mirror_dex_cache.def b/tools/cpp-define-generator/offset_mirror_dex_cache.def new file mode 100644 index 0000000000..8f008bb631 --- /dev/null +++ b/tools/cpp-define-generator/offset_mirror_dex_cache.def @@ -0,0 +1,32 @@ +/* + * 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. + */ + +// Offsets within java.lang.DexCache (mirror::DexCache). + +#if defined(DEFINE_INCLUDE_DEPENDENCIES) +#include "mirror/class.h" // art::mirror::Object +#endif + +#include "common.def" // DEFINE_OFFSET_EXPR + +#define DEFINE_MIRROR_DEX_CACHE_OFFSET(field_name, method_name) \ + DEFINE_OFFSET_EXPR(MIRROR_DEX_CACHE, field_name, int32_t, art::mirror::DexCache::method_name##Offset().Int32Value()) + +// New macro suffix Method Name (of the Offset method) +DEFINE_MIRROR_DEX_CACHE_OFFSET(RESOLVED_METHODS, ResolvedMethods) + +#undef DEFINE_MIRROR_CLASS_OFFSET +#include "common_undef.def" // undef DEFINE_OFFSET_EXPR diff --git a/tools/cpp-define-generator/offsets_all.def b/tools/cpp-define-generator/offsets_all.def index b8947de2dc..c2e8c9728c 100644 --- a/tools/cpp-define-generator/offsets_all.def +++ b/tools/cpp-define-generator/offsets_all.def @@ -42,12 +42,13 @@ // #include "offset_shadow_frame.def" #include "offset_codeitem.def" // TODO: MIRROR_OBJECT_HEADER_SIZE (depends on #ifdef read barrier) -// TODO: MIRROR_CLASS offsets (see above) +#include "offset_mirror_class.def" +#include "offset_mirror_dex_cache.def" #include "offset_mirror_object.def" #include "constant_class.def" // TODO: MIRROR_*_ARRAY offsets (depends on header size) // TODO: MIRROR_STRING offsets (depends on header size) -#include "offset_dexcache.def" +#include "offset_art_method.def" #include "constant_dexcache.def" #include "constant_card_table.def" #include "constant_heap.def" |