diff options
| -rw-r--r-- | openjdkjvmti/Android.bp | 1 | ||||
| -rw-r--r-- | openjdkjvmti/OpenjdkJvmTi.cc | 3 | ||||
| -rw-r--r-- | openjdkjvmti/art_jvmti.h | 4 | ||||
| -rw-r--r-- | openjdkjvmti/deopt_manager.cc | 322 | ||||
| -rw-r--r-- | openjdkjvmti/deopt_manager.h | 168 | ||||
| -rw-r--r-- | openjdkjvmti/events-inl.h | 4 | ||||
| -rw-r--r-- | openjdkjvmti/events.cc | 55 | ||||
| -rw-r--r-- | openjdkjvmti/events.h | 1 | ||||
| -rw-r--r-- | openjdkjvmti/ti_breakpoint.cc | 60 | ||||
| -rw-r--r-- | openjdkjvmti/ti_method.cc | 17 | ||||
| -rw-r--r-- | runtime/base/mutex.h | 2 | ||||
| -rw-r--r-- | runtime/debugger.cc | 4 | ||||
| -rw-r--r-- | runtime/debugger.h | 1 | ||||
| -rw-r--r-- | runtime/instrumentation.cc | 9 | ||||
| -rw-r--r-- | runtime/jit/jit.cc | 7 | ||||
| -rw-r--r-- | runtime/runtime_callbacks.cc | 11 | ||||
| -rw-r--r-- | runtime/runtime_callbacks.h | 10 | ||||
| -rw-r--r-- | test/993-breakpoints/breakpoints.cc | 51 | ||||
| -rw-r--r-- | test/993-breakpoints/expected.txt | 101 | ||||
| -rw-r--r-- | test/993-breakpoints/src/art/Test993.java | 178 |
20 files changed, 944 insertions, 65 deletions
diff --git a/openjdkjvmti/Android.bp b/openjdkjvmti/Android.bp index eebfec48ab..c6090ef9fc 100644 --- a/openjdkjvmti/Android.bp +++ b/openjdkjvmti/Android.bp @@ -24,6 +24,7 @@ cc_defaults { defaults: ["art_defaults"], host_supported: true, srcs: [ + "deopt_manager.cc", "events.cc", "fixed_up_dex_file.cc", "object_tagging.cc", diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc index c2584e6944..5f726b16e0 100644 --- a/openjdkjvmti/OpenjdkJvmTi.cc +++ b/openjdkjvmti/OpenjdkJvmTi.cc @@ -71,6 +71,7 @@ namespace openjdkjvmti { EventHandler gEventHandler; +DeoptManager gDeoptManager; #define ENSURE_NON_NULL(n) \ do { \ @@ -1711,6 +1712,7 @@ static jint GetEnvHandler(art::JavaVMExt* vm, /*out*/void** env, jint version) { extern "C" bool ArtPlugin_Initialize() { art::Runtime* runtime = art::Runtime::Current(); + gDeoptManager.Setup(); if (runtime->IsStarted()) { PhaseUtil::SetToLive(); } else { @@ -1731,6 +1733,7 @@ extern "C" bool ArtPlugin_Initialize() { extern "C" bool ArtPlugin_Deinitialize() { gEventHandler.Shutdown(); + gDeoptManager.Shutdown(); PhaseUtil::Unregister(); ThreadUtil::Unregister(); ClassUtil::Unregister(); diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h index 3edefaf412..126346088c 100644 --- a/openjdkjvmti/art_jvmti.h +++ b/openjdkjvmti/art_jvmti.h @@ -39,6 +39,7 @@ #include <jni.h> +#include "deopt_manager.h" #include "base/casts.h" #include "base/logging.h" #include "base/macros.h" @@ -60,9 +61,6 @@ namespace openjdkjvmti { class ObjectTagTable; -// Make sure that the DEFAULT_MUTEX_ACQUIRED_AFTER macro works. -using art::Locks; - // A structure that is a jvmtiEnv with additional information for the runtime. struct ArtJvmTiEnv : public jvmtiEnv { art::JavaVMExt* art_vm; diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc new file mode 100644 index 0000000000..f843054681 --- /dev/null +++ b/openjdkjvmti/deopt_manager.cc @@ -0,0 +1,322 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include <functional> + +#include "deopt_manager.h" + +#include "art_jvmti.h" +#include "art_method-inl.h" +#include "base/enums.h" +#include "base/mutex-inl.h" +#include "dex_file_annotations.h" +#include "events-inl.h" +#include "jni_internal.h" +#include "mirror/class-inl.h" +#include "mirror/object_array-inl.h" +#include "modifiers.h" +#include "nativehelper/scoped_local_ref.h" +#include "runtime_callbacks.h" +#include "scoped_thread_state_change-inl.h" +#include "thread-current-inl.h" +#include "thread_list.h" +#include "ti_phase.h" + +namespace openjdkjvmti { + +// TODO We should make this much more selective in the future so we only return true when we +// actually care about the method (i.e. had locals changed, have breakpoints, etc.). For now though +// we can just assume that we care we are loaded at all. +// +// Even if we don't keep track of this at the method level we might want to keep track of it at the +// level of enabled capabilities. +bool JvmtiMethodInspectionCallback::IsMethodBeingInspected( + art::ArtMethod* method ATTRIBUTE_UNUSED) { + return true; +} + +bool JvmtiMethodInspectionCallback::IsMethodSafeToJit(art::ArtMethod* method) { + return !manager_->MethodHasBreakpoints(method); +} + +DeoptManager::DeoptManager() + : deoptimization_status_lock_("JVMTI_DeoptimizationStatusLock"), + deoptimization_condition_("JVMTI_DeoptimizationCondition", deoptimization_status_lock_), + performing_deoptimization_(false), + global_deopt_count_(0), + deopter_count_(0), + inspection_callback_(this) { } + +void DeoptManager::Setup() { + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Add method Inspection Callback"); + art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks(); + callbacks->AddMethodInspectionCallback(&inspection_callback_); +} + +void DeoptManager::Shutdown() { + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("remove method Inspection Callback"); + art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks(); + callbacks->RemoveMethodInspectionCallback(&inspection_callback_); +} + +bool DeoptManager::MethodHasBreakpoints(art::ArtMethod* method) { + art::MutexLock lk(art::Thread::Current(), deoptimization_status_lock_); + return MethodHasBreakpointsLocked(method); +} + +bool DeoptManager::MethodHasBreakpointsLocked(art::ArtMethod* method) { + if (deopter_count_ == 0) { + return false; + } + auto elem = breakpoint_status_.find(method); + return elem != breakpoint_status_.end() && elem->second != 0; +} + +void DeoptManager::RemoveDeoptimizeAllMethods() { + art::Thread* self = art::Thread::Current(); + art::ScopedThreadSuspension sts(self, art::kSuspended); + deoptimization_status_lock_.ExclusiveLock(self); + RemoveDeoptimizeAllMethodsLocked(self); +} + +void DeoptManager::AddDeoptimizeAllMethods() { + art::Thread* self = art::Thread::Current(); + art::ScopedThreadSuspension sts(self, art::kSuspended); + deoptimization_status_lock_.ExclusiveLock(self); + AddDeoptimizeAllMethodsLocked(self); +} + +void DeoptManager::AddMethodBreakpoint(art::ArtMethod* method) { + DCHECK(method->IsInvokable()); + DCHECK(!method->IsProxyMethod()) << method->PrettyMethod(); + DCHECK(!method->IsNative()) << method->PrettyMethod(); + + art::Thread* self = art::Thread::Current(); + method = method->GetCanonicalMethod(); + bool is_default = method->IsDefault(); + + art::ScopedThreadSuspension sts(self, art::kSuspended); + deoptimization_status_lock_.ExclusiveLock(self); + + DCHECK_GT(deopter_count_, 0u) << "unexpected deotpimization request"; + + if (MethodHasBreakpointsLocked(method)) { + // Don't need to do anything extra. + breakpoint_status_[method]++; + // Another thread might be deoptimizing the very method we just added new breakpoints for. Wait + // for any deopts to finish before moving on. + WaitForDeoptimizationToFinish(self); + return; + } + breakpoint_status_[method] = 1; + auto instrumentation = art::Runtime::Current()->GetInstrumentation(); + if (instrumentation->IsForcedInterpretOnly()) { + // We are already interpreting everything so no need to do anything. + deoptimization_status_lock_.ExclusiveUnlock(self); + return; + } else if (is_default) { + AddDeoptimizeAllMethodsLocked(self); + } else { + PerformLimitedDeoptimization(self, method); + } +} + +void DeoptManager::RemoveMethodBreakpoint(art::ArtMethod* method) { + DCHECK(method->IsInvokable()) << method->PrettyMethod(); + DCHECK(!method->IsProxyMethod()) << method->PrettyMethod(); + DCHECK(!method->IsNative()) << method->PrettyMethod(); + + art::Thread* self = art::Thread::Current(); + method = method->GetCanonicalMethod(); + bool is_default = method->IsDefault(); + + art::ScopedThreadSuspension sts(self, art::kSuspended); + // Ideally we should do a ScopedSuspendAll right here to get the full mutator_lock_ that we might + // need but since that is very heavy we will instead just use a condition variable to make sure we + // don't race with ourselves. + deoptimization_status_lock_.ExclusiveLock(self); + + DCHECK_GT(deopter_count_, 0u) << "unexpected deotpimization request"; + DCHECK(MethodHasBreakpointsLocked(method)) << "Breakpoint on a method was removed without " + << "breakpoints present!"; + auto instrumentation = art::Runtime::Current()->GetInstrumentation(); + breakpoint_status_[method] -= 1; + if (UNLIKELY(instrumentation->IsForcedInterpretOnly())) { + // We don't need to do anything since we are interpreting everything anyway. + deoptimization_status_lock_.ExclusiveUnlock(self); + return; + } else if (breakpoint_status_[method] == 0) { + if (UNLIKELY(is_default)) { + RemoveDeoptimizeAllMethodsLocked(self); + } else { + PerformLimitedUndeoptimization(self, method); + } + } else { + // Another thread might be deoptimizing the very methods we just removed breakpoints from. Wait + // for any deopts to finish before moving on. + WaitForDeoptimizationToFinish(self); + } +} + +void DeoptManager::WaitForDeoptimizationToFinishLocked(art::Thread* self) { + while (performing_deoptimization_) { + deoptimization_condition_.Wait(self); + } +} + +void DeoptManager::WaitForDeoptimizationToFinish(art::Thread* self) { + WaitForDeoptimizationToFinishLocked(self); + deoptimization_status_lock_.ExclusiveUnlock(self); +} + +class ScopedDeoptimizationContext : public art::ValueObject { + public: + ScopedDeoptimizationContext(art::Thread* self, DeoptManager* deopt) + RELEASE(deopt->deoptimization_status_lock_) + ACQUIRE(art::Locks::mutator_lock_) + ACQUIRE(art::Roles::uninterruptible_) + : self_(self), deopt_(deopt), uninterruptible_cause_(nullptr) { + deopt_->WaitForDeoptimizationToFinishLocked(self_); + DCHECK(!deopt->performing_deoptimization_) + << "Already performing deoptimization on another thread!"; + // Use performing_deoptimization_ to keep track of the lock. + deopt_->performing_deoptimization_ = true; + deopt_->deoptimization_status_lock_.Unlock(self_); + art::Runtime::Current()->GetThreadList()->SuspendAll("JMVTI Deoptimizing methods", + /*long_suspend*/ false); + uninterruptible_cause_ = self_->StartAssertNoThreadSuspension("JVMTI deoptimizing methods"); + } + + ~ScopedDeoptimizationContext() + RELEASE(art::Locks::mutator_lock_) + RELEASE(art::Roles::uninterruptible_) { + // Can be suspended again. + self_->EndAssertNoThreadSuspension(uninterruptible_cause_); + // Release the mutator lock. + art::Runtime::Current()->GetThreadList()->ResumeAll(); + // Let other threads know it's fine to proceed. + art::MutexLock lk(self_, deopt_->deoptimization_status_lock_); + deopt_->performing_deoptimization_ = false; + deopt_->deoptimization_condition_.Broadcast(self_); + } + + private: + art::Thread* self_; + DeoptManager* deopt_; + const char* uninterruptible_cause_; +}; + +void DeoptManager::AddDeoptimizeAllMethodsLocked(art::Thread* self) { + global_deopt_count_++; + if (global_deopt_count_ == 1) { + PerformGlobalDeoptimization(self); + } else { + WaitForDeoptimizationToFinish(self); + } +} + +void DeoptManager::RemoveDeoptimizeAllMethodsLocked(art::Thread* self) { + DCHECK_GT(global_deopt_count_, 0u) << "Request to remove non-existant global deoptimization!"; + global_deopt_count_--; + if (global_deopt_count_ == 0) { + PerformGlobalUndeoptimization(self); + } else { + WaitForDeoptimizationToFinish(self); + } +} + +void DeoptManager::PerformLimitedDeoptimization(art::Thread* self, art::ArtMethod* method) { + ScopedDeoptimizationContext sdc(self, this); + art::Runtime::Current()->GetInstrumentation()->Deoptimize(method); +} + +void DeoptManager::PerformLimitedUndeoptimization(art::Thread* self, art::ArtMethod* method) { + ScopedDeoptimizationContext sdc(self, this); + art::Runtime::Current()->GetInstrumentation()->Undeoptimize(method); +} + +void DeoptManager::PerformGlobalDeoptimization(art::Thread* self) { + ScopedDeoptimizationContext sdc(self, this); + art::Runtime::Current()->GetInstrumentation()->DeoptimizeEverything( + kDeoptManagerInstrumentationKey); +} + +void DeoptManager::PerformGlobalUndeoptimization(art::Thread* self) { + ScopedDeoptimizationContext sdc(self, this); + art::Runtime::Current()->GetInstrumentation()->UndeoptimizeEverything( + kDeoptManagerInstrumentationKey); +} + + +void DeoptManager::RemoveDeoptimizationRequester() { + art::Thread* self = art::Thread::Current(); + art::ScopedThreadStateChange sts(self, art::kSuspended); + deoptimization_status_lock_.ExclusiveLock(self); + DCHECK_GT(deopter_count_, 0u) << "Removing deoptimization requester without any being present"; + deopter_count_--; + if (deopter_count_ == 0) { + ScopedDeoptimizationContext sdc(self, this); + // TODO Give this a real key. + art::Runtime::Current()->GetInstrumentation()->DisableDeoptimization(""); + return; + } else { + deoptimization_status_lock_.ExclusiveUnlock(self); + } +} + +void DeoptManager::AddDeoptimizationRequester() { + art::Thread* self = art::Thread::Current(); + art::ScopedThreadStateChange stsc(self, art::kSuspended); + deoptimization_status_lock_.ExclusiveLock(self); + deopter_count_++; + if (deopter_count_ == 1) { + ScopedDeoptimizationContext sdc(self, this); + art::Runtime::Current()->GetInstrumentation()->EnableDeoptimization(); + return; + } else { + deoptimization_status_lock_.ExclusiveUnlock(self); + } +} + +void DeoptManager::DeoptimizeThread(art::Thread* target) { + art::Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(target); +} + +extern DeoptManager gDeoptManager; +DeoptManager* DeoptManager::Get() { + return &gDeoptManager; +} + +} // namespace openjdkjvmti diff --git a/openjdkjvmti/deopt_manager.h b/openjdkjvmti/deopt_manager.h new file mode 100644 index 0000000000..b265fa8ec2 --- /dev/null +++ b/openjdkjvmti/deopt_manager.h @@ -0,0 +1,168 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_OPENJDKJVMTI_DEOPT_MANAGER_H_ +#define ART_OPENJDKJVMTI_DEOPT_MANAGER_H_ + +#include <unordered_map> + +#include "jni.h" +#include "jvmti.h" + +#include "base/mutex.h" +#include "runtime_callbacks.h" +#include "ti_breakpoint.h" + +namespace art { +class ArtMethod; +namespace mirror { +class Class; +} // namespace mirror +} // namespace art + +namespace openjdkjvmti { + +class DeoptManager; + +struct JvmtiMethodInspectionCallback : public art::MethodInspectionCallback { + public: + explicit JvmtiMethodInspectionCallback(DeoptManager* manager) : manager_(manager) {} + + bool IsMethodBeingInspected(art::ArtMethod* method) + OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_); + + bool IsMethodSafeToJit(art::ArtMethod* method) + OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_); + + private: + DeoptManager* manager_; +}; + +class ScopedDeoptimizationContext; + +class DeoptManager { + public: + DeoptManager(); + + void Setup(); + void Shutdown(); + + void RemoveDeoptimizationRequester() REQUIRES(!deoptimization_status_lock_, + !art::Roles::uninterruptible_); + void AddDeoptimizationRequester() REQUIRES(!deoptimization_status_lock_, + !art::Roles::uninterruptible_); + bool MethodHasBreakpoints(art::ArtMethod* method) + REQUIRES(!deoptimization_status_lock_); + + void RemoveMethodBreakpoint(art::ArtMethod* method) + REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + void AddMethodBreakpoint(art::ArtMethod* method) + REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + void AddDeoptimizeAllMethods() + REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + void RemoveDeoptimizeAllMethods() + REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + void DeoptimizeThread(art::Thread* target) REQUIRES_SHARED(art::Locks::mutator_lock_); + void DeoptimizeAllThreads() REQUIRES_SHARED(art::Locks::mutator_lock_); + + static DeoptManager* Get(); + + private: + bool MethodHasBreakpointsLocked(art::ArtMethod* method) + REQUIRES(deoptimization_status_lock_); + + // Wait until nothing is currently in the middle of deoptimizing/undeoptimizing something. This is + // needed to ensure that everything is synchronized since threads need to drop the + // deoptimization_status_lock_ while deoptimizing methods. + void WaitForDeoptimizationToFinish(art::Thread* self) + RELEASE(deoptimization_status_lock_) REQUIRES(!art::Locks::mutator_lock_); + + void WaitForDeoptimizationToFinishLocked(art::Thread* self) + REQUIRES(deoptimization_status_lock_, !art::Locks::mutator_lock_); + + void AddDeoptimizeAllMethodsLocked(art::Thread* self) + RELEASE(deoptimization_status_lock_) + REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_); + + void RemoveDeoptimizeAllMethodsLocked(art::Thread* self) + RELEASE(deoptimization_status_lock_) + REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_); + + void PerformGlobalDeoptimization(art::Thread* self) + RELEASE(deoptimization_status_lock_) + REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_); + + void PerformGlobalUndeoptimization(art::Thread* self) + RELEASE(deoptimization_status_lock_) + REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_); + + void PerformLimitedDeoptimization(art::Thread* self, art::ArtMethod* method) + RELEASE(deoptimization_status_lock_) + REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_); + + void PerformLimitedUndeoptimization(art::Thread* self, art::ArtMethod* method) + RELEASE(deoptimization_status_lock_) + REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_); + + static constexpr const char* kDeoptManagerInstrumentationKey = "JVMTI_DeoptManager"; + // static constexpr const char* kDeoptManagerThreadName = "JVMTI_DeoptManagerWorkerThread"; + + art::Mutex deoptimization_status_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + art::ConditionVariable deoptimization_condition_ GUARDED_BY(deoptimization_status_lock_); + bool performing_deoptimization_ GUARDED_BY(deoptimization_status_lock_); + + // Number of times we have gotten requests to deopt everything. + uint32_t global_deopt_count_ GUARDED_BY(deoptimization_status_lock_); + + // Number of users of deoptimization there currently are. + uint32_t deopter_count_ GUARDED_BY(deoptimization_status_lock_); + + // A map from methods to the number of breakpoints in them from all envs. + std::unordered_map<art::ArtMethod*, uint32_t> breakpoint_status_ + GUARDED_BY(deoptimization_status_lock_); + + // The MethodInspectionCallback we use to tell the runtime if we care about particular methods. + JvmtiMethodInspectionCallback inspection_callback_; + + // Helper for setting up/tearing-down for deoptimization. + friend class ScopedDeoptimizationContext; +}; + +} // namespace openjdkjvmti +#endif // ART_OPENJDKJVMTI_DEOPT_MANAGER_H_ diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h index d97916fa0a..7f77f90862 100644 --- a/openjdkjvmti/events-inl.h +++ b/openjdkjvmti/events-inl.h @@ -480,6 +480,7 @@ inline bool EventHandler::NeedsEventUpdate(ArtJvmTiEnv* env, ArtJvmtiEvent event = added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable : ArtJvmtiEvent::kClassFileLoadHookRetransformable; return (added && caps.can_access_local_variables == 1) || + caps.can_generate_breakpoint_events == 1 || (caps.can_retransform_classes == 1 && IsEventEnabledAnywhere(event) && env->event_masks.IsEnabledAnywhere(event)); @@ -497,6 +498,9 @@ inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env, if (added && caps.can_access_local_variables == 1) { HandleLocalAccessCapabilityAdded(); } + if (caps.can_generate_breakpoint_events == 1) { + HandleBreakpointEventsChanged(added); + } } } diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc index 381dc1f036..6a64441a4a 100644 --- a/openjdkjvmti/events.cc +++ b/openjdkjvmti/events.cc @@ -37,6 +37,7 @@ #include "art_jvmti.h" #include "art_method-inl.h" #include "base/logging.h" +#include "deopt_manager.h" #include "dex_file_types.h" #include "gc/allocation_listener.h" #include "gc/gc_pause_listener.h" @@ -810,9 +811,49 @@ static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) { } } +static bool EventNeedsFullDeopt(ArtJvmtiEvent event) { + switch (event) { + case ArtJvmtiEvent::kBreakpoint: + case ArtJvmtiEvent::kException: + return false; + // TODO We should support more of these or at least do something to make them discriminate by + // thread. + case ArtJvmtiEvent::kMethodEntry: + case ArtJvmtiEvent::kExceptionCatch: + case ArtJvmtiEvent::kMethodExit: + case ArtJvmtiEvent::kFieldModification: + case ArtJvmtiEvent::kFieldAccess: + case ArtJvmtiEvent::kSingleStep: + case ArtJvmtiEvent::kFramePop: + return true; + default: + LOG(FATAL) << "Unexpected event type!"; + UNREACHABLE(); + } +} + static void SetupTraceListener(JvmtiMethodTraceListener* listener, ArtJvmtiEvent event, bool enable) { + bool needs_full_deopt = EventNeedsFullDeopt(event); + // Make sure we can deopt. + { + art::ScopedObjectAccess soa(art::Thread::Current()); + DeoptManager* deopt_manager = DeoptManager::Get(); + if (enable) { + deopt_manager->AddDeoptimizationRequester(); + if (needs_full_deopt) { + deopt_manager->AddDeoptimizeAllMethods(); + } + } else { + if (needs_full_deopt) { + deopt_manager->RemoveDeoptimizeAllMethods(); + } + deopt_manager->RemoveDeoptimizationRequester(); + } + } + + // Add the actual listeners. art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative); uint32_t new_events = GetInstrumentationEventsFor(event); art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation(); @@ -821,11 +862,6 @@ static void SetupTraceListener(JvmtiMethodTraceListener* listener, art::gc::kCollectorTypeInstrumentation); art::ScopedSuspendAll ssa("jvmti method tracing installation"); if (enable) { - // TODO Depending on the features being used we should be able to avoid deoptimizing everything - // like we do here. - if (!instr->AreAllMethodsDeoptimized()) { - instr->EnableMethodTracing("jvmti-tracing", /*needs_interpreter*/true); - } instr->AddListener(listener, new_events); } else { instr->RemoveListener(listener, new_events); @@ -910,6 +946,7 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { } // 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. + // TODO We really need to make this not the case anymore. case ArtJvmtiEvent::kFramePop: if (!enable || (enable && frame_pop_enabled)) { break; @@ -1046,6 +1083,14 @@ jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, return ERR(NONE); } +void EventHandler::HandleBreakpointEventsChanged(bool added) { + if (added) { + DeoptManager::Get()->AddDeoptimizationRequester(); + } else { + DeoptManager::Get()->RemoveDeoptimizationRequester(); + } +} + void EventHandler::Shutdown() { // Need to remove the method_trace_listener_ if it's there. art::Thread* self = art::Thread::Current(); diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h index a062e1589e..aed24e59f3 100644 --- a/openjdkjvmti/events.h +++ b/openjdkjvmti/events.h @@ -232,6 +232,7 @@ class EventHandler { void HandleEventType(ArtJvmtiEvent event, bool enable); void HandleLocalAccessCapabilityAdded(); + void HandleBreakpointEventsChanged(bool enable); bool OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event); diff --git a/openjdkjvmti/ti_breakpoint.cc b/openjdkjvmti/ti_breakpoint.cc index 9c3687fffd..8e5b56e9bf 100644 --- a/openjdkjvmti/ti_breakpoint.cc +++ b/openjdkjvmti/ti_breakpoint.cc @@ -37,6 +37,7 @@ #include "art_method-inl.h" #include "base/enums.h" #include "base/mutex-inl.h" +#include "deopt_manager.h" #include "dex_file_annotations.h" #include "events-inl.h" #include "jni_internal.h" @@ -63,18 +64,30 @@ Breakpoint::Breakpoint(art::ArtMethod* m, jlocation loc) : method_(m), location_ } void BreakpointUtil::RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass) { - art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_); std::vector<Breakpoint> to_remove; - for (const Breakpoint& b : env->breakpoints) { - if (b.GetMethod()->GetDeclaringClass() == klass) { - to_remove.push_back(b); + { + art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_); + for (const Breakpoint& b : env->breakpoints) { + if (b.GetMethod()->GetDeclaringClass() == klass) { + to_remove.push_back(b); + } + } + for (const Breakpoint& b : to_remove) { + auto it = env->breakpoints.find(b); + DCHECK(it != env->breakpoints.end()); + env->breakpoints.erase(it); } } - for (const Breakpoint& b : to_remove) { - auto it = env->breakpoints.find(b); - DCHECK(it != env->breakpoints.end()); - env->breakpoints.erase(it); + if (!to_remove.empty()) { + LOG(WARNING) << "Methods with breakpoints potentially not being un-deoptimized."; } + // TODO Figure out how to do this. + // DeoptManager* deopt = DeoptManager::Get(); + // for (const Breakpoint& b : to_remove) { + // // TODO It might be good to send these all at once instead. + // // deopt->RemoveMethodBreakpointSuspended(b.GetMethod()); + // LOG(WARNING) << "not un-deopting methods! :-0"; + // } } jvmtiError BreakpointUtil::SetBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) { @@ -82,20 +95,23 @@ jvmtiError BreakpointUtil::SetBreakpoint(jvmtiEnv* jenv, jmethodID method, jloca if (method == nullptr) { return ERR(INVALID_METHODID); } - // Need to get mutator_lock_ so we can find the interface version of any default methods. art::ScopedObjectAccess soa(art::Thread::Current()); art::ArtMethod* art_method = art::jni::DecodeArtMethod(method)->GetCanonicalMethod(); - art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_); if (location < 0 || static_cast<uint32_t>(location) >= art_method->GetCodeItem()->insns_size_in_code_units_) { return ERR(INVALID_LOCATION); } - auto res_pair = env->breakpoints.insert(/* Breakpoint */ {art_method, location}); - if (!res_pair.second) { - // Didn't get inserted because it's already present! - return ERR(DUPLICATE); + DeoptManager::Get()->AddMethodBreakpoint(art_method); + { + art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_); + auto res_pair = env->breakpoints.insert(/* Breakpoint */ {art_method, location}); + if (LIKELY(res_pair.second)) { + return OK; + } } - return OK; + // Didn't get inserted because it's already present! + DeoptManager::Get()->RemoveMethodBreakpoint(art_method); + return ERR(DUPLICATE); } jvmtiError BreakpointUtil::ClearBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) { @@ -103,15 +119,17 @@ jvmtiError BreakpointUtil::ClearBreakpoint(jvmtiEnv* jenv, jmethodID method, jlo if (method == nullptr) { return ERR(INVALID_METHODID); } - // Need to get mutator_lock_ so we can find the interface version of any default methods. art::ScopedObjectAccess soa(art::Thread::Current()); art::ArtMethod* art_method = art::jni::DecodeArtMethod(method)->GetCanonicalMethod(); - art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_); - auto pos = env->breakpoints.find(/* Breakpoint */ {art_method, location}); - if (pos == env->breakpoints.end()) { - return ERR(NOT_FOUND); + { + art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_); + auto pos = env->breakpoints.find(/* Breakpoint */ {art_method, location}); + if (pos == env->breakpoints.end()) { + return ERR(NOT_FOUND); + } + env->breakpoints.erase(pos); } - env->breakpoints.erase(pos); + DeoptManager::Get()->RemoveMethodBreakpoint(art_method); return OK; } diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc index 41679daaa1..5d63285825 100644 --- a/openjdkjvmti/ti_method.cc +++ b/openjdkjvmti/ti_method.cc @@ -86,21 +86,6 @@ struct TiMethodCallback : public art::MethodCallback { TiMethodCallback gMethodCallback; -// TODO We should make this much more selective in the future so we only return true when we -// actually care about the method (i.e. had locals changed, have breakpoints, etc.). For now though -// we can just assume that we care we are loaded at all. -// -// Even if we don't keep track of this at the method level we might want to keep track of it at the -// level of enabled capabilities. -struct TiMethodInspectionCallback : public art::MethodInspectionCallback { - bool IsMethodBeingInspected(art::ArtMethod* method ATTRIBUTE_UNUSED) - OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { - return true; - } -}; - -TiMethodInspectionCallback gMethodInspectionCallback; - void MethodUtil::Register(EventHandler* handler) { gMethodCallback.event_handler = handler; art::ScopedThreadStateChange stsc(art::Thread::Current(), @@ -108,7 +93,6 @@ void MethodUtil::Register(EventHandler* handler) { art::ScopedSuspendAll ssa("Add method callback"); art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks(); callbacks->AddMethodCallback(&gMethodCallback); - callbacks->AddMethodInspectionCallback(&gMethodInspectionCallback); } void MethodUtil::Unregister() { @@ -117,7 +101,6 @@ void MethodUtil::Unregister() { art::ScopedSuspendAll ssa("Remove method callback"); art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks(); callbacks->RemoveMethodCallback(&gMethodCallback); - callbacks->AddMethodInspectionCallback(&gMethodInspectionCallback); } jvmtiError MethodUtil::GetBytecodes(jvmtiEnv* env, diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h index 189c0d0030..4b56d3b30c 100644 --- a/runtime/base/mutex.h +++ b/runtime/base/mutex.h @@ -664,7 +664,7 @@ class Locks { // When declaring any Mutex add DEFAULT_MUTEX_ACQUIRED_AFTER to use annotalysis to check the code // doesn't try to hold a higher level Mutex. - #define DEFAULT_MUTEX_ACQUIRED_AFTER ACQUIRED_AFTER(Locks::classlinker_classes_lock_) + #define DEFAULT_MUTEX_ACQUIRED_AFTER ACQUIRED_AFTER(art::Locks::classlinker_classes_lock_) static Mutex* allocated_monitor_ids_lock_ ACQUIRED_AFTER(classlinker_classes_lock_); diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 85df14a576..8898afe116 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -346,6 +346,10 @@ bool DebuggerActiveMethodInspectionCallback::IsMethodBeingInspected(ArtMethod* m return Dbg::IsDebuggerActive(); } +bool DebuggerActiveMethodInspectionCallback::IsMethodSafeToJit(ArtMethod* m) { + return !Dbg::MethodHasAnyBreakpoints(m); +} + // Breakpoints. static std::vector<Breakpoint> gBreakpoints GUARDED_BY(Locks::breakpoint_lock_); diff --git a/runtime/debugger.h b/runtime/debugger.h index 18126b1eed..ec37833f6d 100644 --- a/runtime/debugger.h +++ b/runtime/debugger.h @@ -55,6 +55,7 @@ class Thread; struct DebuggerActiveMethodInspectionCallback : public MethodInspectionCallback { bool IsMethodBeingInspected(ArtMethod* m ATTRIBUTE_UNUSED) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); + bool IsMethodSafeToJit(ArtMethod* m) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); }; diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 2c82cb1acd..49f202182d 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -137,11 +137,12 @@ static void UpdateEntrypoints(ArtMethod* method, const void* quick_code) method->SetEntryPointFromQuickCompiledCode(quick_code); } -bool Instrumentation::NeedDebugVersionFor(ArtMethod* method) const REQUIRES_SHARED(Locks::mutator_lock_) { - return Dbg::IsDebuggerActive() && - Runtime::Current()->IsJavaDebuggable() && +bool Instrumentation::NeedDebugVersionFor(ArtMethod* method) const + REQUIRES_SHARED(Locks::mutator_lock_) { + return Runtime::Current()->IsJavaDebuggable() && !method->IsNative() && - !method->IsProxyMethod(); + !method->IsProxyMethod() && + Runtime::Current()->GetRuntimeCallbacks()->IsMethodBeingInspected(method); } void Instrumentation::InstallStubsForMethod(ArtMethod* method) { diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index 97a3b717e2..72b5a942fe 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -272,9 +272,12 @@ bool Jit::CompileMethod(ArtMethod* method, Thread* self, bool osr) { DCHECK(Runtime::Current()->UseJitCompilation()); DCHECK(!method->IsRuntimeMethod()); + RuntimeCallbacks* cb = Runtime::Current()->GetRuntimeCallbacks(); // Don't compile the method if it has breakpoints. - if (Dbg::IsDebuggerActive() && Dbg::MethodHasAnyBreakpoints(method)) { - VLOG(jit) << "JIT not compiling " << method->PrettyMethod() << " due to breakpoint"; + if (cb->IsMethodBeingInspected(method) && !cb->IsMethodSafeToJit(method)) { + VLOG(jit) << "JIT not compiling " << method->PrettyMethod() + << " due to not being safe to jit according to runtime-callbacks. For example, there" + << " could be breakpoints in this method."; return false; } diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc index f164f7c8ec..339fe822fd 100644 --- a/runtime/runtime_callbacks.cc +++ b/runtime/runtime_callbacks.cc @@ -43,6 +43,17 @@ void RuntimeCallbacks::RemoveMethodInspectionCallback(MethodInspectionCallback* Remove(cb, &method_inspection_callbacks_); } +bool RuntimeCallbacks::IsMethodSafeToJit(ArtMethod* m) { + for (MethodInspectionCallback* cb : method_inspection_callbacks_) { + if (!cb->IsMethodSafeToJit(m)) { + DCHECK(cb->IsMethodBeingInspected(m)) + << "Contract requires that !IsMethodSafeToJit(m) -> IsMethodBeingInspected(m)"; + return false; + } + } + return true; +} + bool RuntimeCallbacks::IsMethodBeingInspected(ArtMethod* m) { for (MethodInspectionCallback* cb : method_inspection_callbacks_) { if (cb->IsMethodBeingInspected(m)) { diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h index c9360491bb..c1ba9643a7 100644 --- a/runtime/runtime_callbacks.h +++ b/runtime/runtime_callbacks.h @@ -104,6 +104,11 @@ class MethodInspectionCallback { // Returns true if the method is being inspected currently and the runtime should not modify it in // potentially dangerous ways (i.e. replace with compiled version, JIT it, etc). virtual bool IsMethodBeingInspected(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) = 0; + + // Returns true if the method is safe to Jit, false otherwise. + // Note that '!IsMethodSafeToJit(m) implies IsMethodBeingInspected(m)'. That is that if this + // method returns false IsMethodBeingInspected must return true. + virtual bool IsMethodSafeToJit(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) = 0; }; class RuntimeCallbacks { @@ -167,6 +172,11 @@ class RuntimeCallbacks { // on by some code. bool IsMethodBeingInspected(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_); + // Returns false if some MethodInspectionCallback indicates the method cannot be safetly jitted + // (which implies that it is being Inspected). Returns true otherwise. If it returns false the + // entrypoint should not be changed to JITed code. + bool IsMethodSafeToJit(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_); + void AddMethodInspectionCallback(MethodInspectionCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_); void RemoveMethodInspectionCallback(MethodInspectionCallback* cb) diff --git a/test/993-breakpoints/breakpoints.cc b/test/993-breakpoints/breakpoints.cc index 3734ce8634..e9cf3b32c6 100644 --- a/test/993-breakpoints/breakpoints.cc +++ b/test/993-breakpoints/breakpoints.cc @@ -49,6 +49,57 @@ jobject JNICALL Java_art_Test993_constructNative(JNIEnv* env, } extern "C" JNIEXPORT +void JNICALL Java_art_Test993_invokeNativeObject(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject target, + jclass clazz, + jobject thizz) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return; + } + if (thizz == nullptr) { + env->CallStaticObjectMethod(clazz, method); + } else { + env->CallObjectMethod(thizz, method); + } +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test993_invokeNativeBool(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject target, + jclass clazz, + jobject thizz) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return; + } + if (thizz == nullptr) { + env->CallStaticBooleanMethod(clazz, method); + } else { + env->CallBooleanMethod(thizz, method); + } +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test993_invokeNativeLong(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject target, + jclass clazz, + jobject thizz) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return; + } + if (thizz == nullptr) { + env->CallStaticLongMethod(clazz, method); + } else { + env->CallLongMethod(thizz, method); + } +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test993_invokeNative(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject target, diff --git a/test/993-breakpoints/expected.txt b/test/993-breakpoints/expected.txt index 962154734b..1749a77e9d 100644 --- a/test/993-breakpoints/expected.txt +++ b/test/993-breakpoints/expected.txt @@ -552,6 +552,107 @@ Running private instance invoke Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118 Invoking "new TestClass4().callPrivateMethod()" Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118 +Running Vector constructor + Breaking on [] + Native constructor: public java.util.Vector(), type: class java.util.Vector + Created: [] + Reflective constructor: public java.util.Vector() + Created: [] + Constructing: new Vector() + Created: [] + Breaking on [public java.util.Vector() @ <NON-DETERMINISTIC>] + Native constructor: public java.util.Vector(), type: class java.util.Vector + Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC> + Created: [] + Reflective constructor: public java.util.Vector() + Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC> + Created: [] + Constructing: new Vector() + Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC> + Created: [] +Running Stack constructor + Breaking on [] + Native constructor: public java.util.Stack(), type: class java.util.Stack + Created: [] + Reflective constructor: public java.util.Stack() + Created: [] + Constructing: new Stack() + Created: [] + Breaking on [public java.util.Stack() @ <NON-DETERMINISTIC>] + Native constructor: public java.util.Stack(), type: class java.util.Stack + Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC> + Created: [] + Reflective constructor: public java.util.Stack() + Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC> + Created: [] + Constructing: new Stack() + Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC> + Created: [] + Breaking on [public java.util.Vector() @ <NON-DETERMINISTIC>] + Native constructor: public java.util.Stack(), type: class java.util.Stack + Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC> + Created: [] + Reflective constructor: public java.util.Stack() + Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC> + Created: [] + Constructing: new Stack() + Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC> + Created: [] + Breaking on [public java.util.Stack() @ <NON-DETERMINISTIC>, public java.util.Vector() @ <NON-DETERMINISTIC>] + Native constructor: public java.util.Stack(), type: class java.util.Stack + Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC> + Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC> + Created: [] + Reflective constructor: public java.util.Stack() + Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC> + Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC> + Created: [] + Constructing: new Stack() + Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC> + Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC> + Created: [] +Running bcp static invoke + Breaking on [] + Native invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null] + Reflective invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null] + Invoking "Optional::empty" + Breaking on [public static java.util.Optional java.util.Optional.empty() @ <NON-DETERMINISTIC>] + Native invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null] + Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC> + Reflective invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null] + Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC> + Invoking "Optional::empty" + Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC> +Running bcp private static invoke + Breaking on [] + Native invoking: private static long java.util.Random.seedUniquifier() args: [this: null] + Invoking "Random::seedUniquifier" + Breaking on [private static long java.util.Random.seedUniquifier() @ <NON-DETERMINISTIC>] + Native invoking: private static long java.util.Random.seedUniquifier() args: [this: null] + Breakpoint: private static long java.util.Random.seedUniquifier() @ line=<NON-DETERMINISTIC> + Invoking "Random::seedUniquifier" + Breakpoint: private static long java.util.Random.seedUniquifier() @ line=<NON-DETERMINISTIC> +Running bcp private invoke + Breaking on [] + Native invoking: private java.math.BigDecimal java.time.Duration.toSeconds() args: [this: PT336H] + Invoking "Duration::toSeconds" + Breaking on [private java.math.BigDecimal java.time.Duration.toSeconds() @ <NON-DETERMINISTIC>] + Native invoking: private java.math.BigDecimal java.time.Duration.toSeconds() args: [this: PT336H] + Breakpoint: private java.math.BigDecimal java.time.Duration.toSeconds() @ line=<NON-DETERMINISTIC> + Invoking "Duration::toSeconds" + Breakpoint: private java.math.BigDecimal java.time.Duration.toSeconds() @ line=<NON-DETERMINISTIC> +Running bcp invoke + Breaking on [] + Native invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test]] + Reflective invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test2]] + Invoking "Optional::isPresent" + Breaking on [public boolean java.util.Optional.isPresent() @ <NON-DETERMINISTIC>] + Native invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test]] + Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC> + Reflective invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test2]] + Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC> + Invoking "Optional::isPresent" + Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC> Running TestClass1 constructor Breaking on [] Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1 diff --git a/test/993-breakpoints/src/art/Test993.java b/test/993-breakpoints/src/art/Test993.java index 781ebffc0f..d6a6a676cd 100644 --- a/test/993-breakpoints/src/art/Test993.java +++ b/test/993-breakpoints/src/art/Test993.java @@ -16,20 +16,20 @@ package art; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Arrays; import java.lang.reflect.Executable; import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.util.List; -import java.util.Set; -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.Collection; + +import java.time.Duration; + import java.util.ArrayList; -import java.util.HashSet; -import java.util.function.IntUnaryOperator; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.Stack; +import java.util.Vector; + import java.util.function.Supplier; public class Test993 { @@ -120,7 +120,13 @@ public class Test993 { } public static void notifyBreakpointReached(Thread thr, Executable e, long loc) { - System.out.println("\t\t\tBreakpoint: " + e + " @ line=" + Breakpoint.locationToLine(e, loc)); + String line; + if (e.getDeclaringClass().getPackage().equals(Test993.class.getPackage())) { + line = Integer.valueOf(Breakpoint.locationToLine(e, loc)).toString(); + } else { + line = "<NON-DETERMINISTIC>"; + } + System.out.println("\t\t\tBreakpoint: " + e + " @ line=" + line); } public static interface ThrowRunnable extends Runnable { @@ -180,6 +186,57 @@ public class Test993 { public static native void invokeNative(Method m, Class<?> clazz, Object thizz); + public static class InvokeNativeBool implements Runnable { + Method m; + Object this_arg; + public InvokeNativeBool(Method m, Object this_arg) { + this.m = m; + this.this_arg = this_arg; + } + + @Override + public void run() { + System.out.println("\t\tNative invoking: " + m + " args: [this: " + this_arg + "]"); + invokeNativeBool(m, m.getDeclaringClass(), this_arg); + } + } + + public static native void invokeNativeBool(Method m, Class<?> clazz, Object thizz); + + public static class InvokeNativeObject implements Runnable { + Method m; + Object this_arg; + public InvokeNativeObject(Method m, Object this_arg) { + this.m = m; + this.this_arg = this_arg; + } + + @Override + public void run() { + System.out.println("\t\tNative invoking: " + m + " args: [this: " + this_arg + "]"); + invokeNativeObject(m, m.getDeclaringClass(), this_arg); + } + } + + public static native void invokeNativeObject(Method m, Class<?> clazz, Object thizz); + + public static class InvokeNativeLong implements Runnable { + Method m; + Object this_arg; + public InvokeNativeLong(Method m, Object this_arg) { + this.m = m; + this.this_arg = this_arg; + } + + @Override + public void run() { + System.out.println("\t\tNative invoking: " + m + " args: [this: " + this_arg + "]"); + invokeNativeLong(m, m.getDeclaringClass(), this_arg); + } + } + + public static native void invokeNativeLong(Method m, Class<?> clazz, Object thizz); + public static class ConstructDirect implements Runnable { String msg; Supplier<Object> s; @@ -258,7 +315,15 @@ public class Test993 { } private static Breakpoint.Manager.BP BP(Executable m) { - return new Breakpoint.Manager.BP(m); + return new Breakpoint.Manager.BP(m) { + public String toString() { + if (method.getDeclaringClass().getPackage().equals(Test993.class.getPackage())) { + return super.toString(); + } else { + return method.toString() + " @ <NON-DETERMINISTIC>"; + } + } + }; } public static void run() throws Exception { @@ -271,6 +336,7 @@ public class Test993 { Thread.currentThread()); runMethodTests(); + runBCPMethodTests(); runConstructorTests(); Breakpoint.stopBreakpointWatch(Thread.currentThread()); @@ -302,6 +368,94 @@ public class Test993 { runTestGroups("TestClass1ext constructor", tc1ext_constructors, tc1ext_bps); } + // These test to make sure we are able to break on functions that might have been quickened or + // inlined from the boot-image. These were all chosen for being in the bootclasspath, not being + // long enough to prevent inlining, and not being used for the testing framework. + public static void runBCPMethodTests() throws Exception { + // The methods we will be breaking on. + Method bcp_private_method = Duration.class.getDeclaredMethod("toSeconds"); + Method bcp_virtual_method = Optional.class.getDeclaredMethod("isPresent"); + Method bcp_static_method = Optional.class.getDeclaredMethod("empty"); + Method bcp_private_static_method = Random.class.getDeclaredMethod("seedUniquifier"); + + // Some constructors we will break on. + Constructor<?> bcp_stack_constructor = Stack.class.getConstructor(); + Constructor<?> bcp_vector_constructor = Vector.class.getConstructor(); + if (!(Vector.class.isAssignableFrom(Stack.class))) { + throw new Error("Expected Stack to extend Vector!"); + } + + // BCP constructors. + Runnable[] vector_constructors = new Runnable[] { + new ConstructNative(bcp_vector_constructor), + new ConstructReflect(bcp_vector_constructor), + new ConstructDirect("new Vector()", Vector::new), + }; + Breakpoint.Manager.BP[] vector_breakpoints = new Breakpoint.Manager.BP[] { + BP(bcp_vector_constructor), + }; + runTestGroups("Vector constructor", vector_constructors, vector_breakpoints); + + Runnable[] stack_constructors = new Runnable[] { + new ConstructNative(bcp_stack_constructor), + new ConstructReflect(bcp_stack_constructor), + new ConstructDirect("new Stack()", Stack::new), + }; + Breakpoint.Manager.BP[] stack_breakpoints = new Breakpoint.Manager.BP[] { + BP(bcp_stack_constructor), BP(bcp_vector_constructor), + }; + runTestGroups("Stack constructor", stack_constructors, stack_breakpoints); + + // Static function + Runnable[] static_invokes = new Runnable[] { + new InvokeNativeObject(bcp_static_method, null), + + new InvokeReflect(bcp_static_method, null), + + new InvokeDirect("Optional::empty", () -> { Optional.empty(); }), + }; + Breakpoint.Manager.BP[] static_breakpoints = new Breakpoint.Manager.BP[] { + BP(bcp_static_method) + }; + runTestGroups("bcp static invoke", static_invokes, static_breakpoints); + + // Static private class function + Runnable[] private_static_invokes = new Runnable[] { + new InvokeNativeLong(bcp_private_static_method, null), + + new InvokeDirect("Random::seedUniquifier", () -> { new Random(); }), + }; + Breakpoint.Manager.BP[] private_static_breakpoints = new Breakpoint.Manager.BP[] { + BP(bcp_private_static_method) + }; + runTestGroups("bcp private static invoke", private_static_invokes, private_static_breakpoints); + + // private class method + Duration test_duration = Duration.ofDays(14); + Runnable[] private_invokes = new Runnable[] { + new InvokeNativeObject(bcp_private_method, test_duration), + + new InvokeDirect("Duration::toSeconds", () -> { test_duration.multipliedBy(2); }), + }; + Breakpoint.Manager.BP[] private_breakpoints = new Breakpoint.Manager.BP[] { + BP(bcp_private_method) + }; + runTestGroups("bcp private invoke", private_invokes, private_breakpoints); + + // class method + Runnable[] public_invokes = new Runnable[] { + new InvokeNativeBool(bcp_virtual_method, Optional.of("test")), + + new InvokeReflect(bcp_virtual_method, Optional.of("test2")), + + new InvokeDirect("Optional::isPresent", () -> { Optional.of("test3").isPresent(); }), + }; + Breakpoint.Manager.BP[] public_breakpoints = new Breakpoint.Manager.BP[] { + BP(bcp_virtual_method) + }; + runTestGroups("bcp invoke", public_invokes, public_breakpoints); + } + public static void runMethodTests() throws Exception { // The methods we will be breaking on. Method breakpoint_method = Test993.class.getDeclaredMethod("breakpoint"); |