summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--openjdkjvmti/Android.bp1
-rw-r--r--openjdkjvmti/OpenjdkJvmTi.cc3
-rw-r--r--openjdkjvmti/art_jvmti.h4
-rw-r--r--openjdkjvmti/deopt_manager.cc322
-rw-r--r--openjdkjvmti/deopt_manager.h168
-rw-r--r--openjdkjvmti/events-inl.h4
-rw-r--r--openjdkjvmti/events.cc55
-rw-r--r--openjdkjvmti/events.h1
-rw-r--r--openjdkjvmti/ti_breakpoint.cc60
-rw-r--r--openjdkjvmti/ti_method.cc17
-rw-r--r--runtime/base/mutex.h2
-rw-r--r--runtime/debugger.cc4
-rw-r--r--runtime/debugger.h1
-rw-r--r--runtime/instrumentation.cc9
-rw-r--r--runtime/jit/jit.cc7
-rw-r--r--runtime/runtime_callbacks.cc11
-rw-r--r--runtime/runtime_callbacks.h10
-rw-r--r--test/993-breakpoints/breakpoints.cc51
-rw-r--r--test/993-breakpoints/expected.txt101
-rw-r--r--test/993-breakpoints/src/art/Test993.java178
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");