mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-12 00:43:14 +00:00
378 lines
14 KiB
C++
378 lines
14 KiB
C++
/*
|
|
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
|
|
* Copyright (c) 2020 SAP SE. 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.
|
|
*
|
|
* 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 "precompiled.hpp"
|
|
#include "code/scopeDesc.hpp"
|
|
#include "memory/allocation.hpp"
|
|
#include "prims/jvmtiDeferredUpdates.hpp"
|
|
#include "runtime/deoptimization.hpp"
|
|
#include "runtime/escapeBarrier.hpp"
|
|
#include "runtime/frame.inline.hpp"
|
|
#include "runtime/handles.hpp"
|
|
#include "runtime/handshake.hpp"
|
|
#include "runtime/interfaceSupport.inline.hpp"
|
|
#include "runtime/javaThread.inline.hpp"
|
|
#include "runtime/keepStackGCProcessed.hpp"
|
|
#include "runtime/mutexLocker.hpp"
|
|
#include "runtime/registerMap.hpp"
|
|
#include "runtime/stackFrameStream.inline.hpp"
|
|
#include "runtime/stackValue.hpp"
|
|
#include "runtime/stackValueCollection.hpp"
|
|
#include "runtime/threadSMR.hpp"
|
|
#include "runtime/vframe.hpp"
|
|
#include "runtime/vframe_hp.hpp"
|
|
#include "utilities/debug.hpp"
|
|
#include "utilities/globalDefinitions.hpp"
|
|
#include "utilities/macros.hpp"
|
|
|
|
#if COMPILER2_OR_JVMCI
|
|
|
|
// Returns true iff objects were reallocated and relocked because of access through JVMTI
|
|
bool EscapeBarrier::objs_are_deoptimized(JavaThread* thread, intptr_t* fr_id) {
|
|
// first/oldest update holds the flag
|
|
GrowableArrayView<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread);
|
|
bool result = false;
|
|
if (list != nullptr) {
|
|
for (int i = 0; i < list->length(); i++) {
|
|
if (list->at(i)->matches(fr_id)) {
|
|
result = list->at(i)->objects_are_deoptimized();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Deoptimize objects of frames of the target thread at depth >= d1 and depth <= d2.
|
|
// Deoptimize objects of caller frames if they passed references to ArgEscape objects as arguments.
|
|
// Return false in the case of a reallocation failure and true otherwise.
|
|
bool EscapeBarrier::deoptimize_objects(int d1, int d2) {
|
|
if (!barrier_active()) return true;
|
|
if (d1 < deoptee_thread()->frames_to_pop_failed_realloc()) {
|
|
// The deoptee thread has frames with reallocation failures on top of its stack.
|
|
// These frames are about to be removed. We must not interfere with that and signal failure.
|
|
return false;
|
|
}
|
|
if (deoptee_thread()->has_last_Java_frame() &&
|
|
deoptee_thread()->last_continuation() == nullptr) {
|
|
assert(calling_thread() == Thread::current(), "should be");
|
|
KeepStackGCProcessedMark ksgcpm(deoptee_thread());
|
|
ResourceMark rm(calling_thread());
|
|
HandleMark hm(calling_thread());
|
|
RegisterMap reg_map(deoptee_thread(),
|
|
RegisterMap::UpdateMap::skip,
|
|
RegisterMap::ProcessFrames::skip,
|
|
RegisterMap::WalkContinuation::skip);
|
|
vframe* vf = deoptee_thread()->last_java_vframe(®_map);
|
|
int cur_depth = 0;
|
|
|
|
// Skip frames at depth < d1
|
|
while (vf != nullptr && cur_depth < d1) {
|
|
cur_depth++;
|
|
vf = vf->sender();
|
|
}
|
|
|
|
while (vf != nullptr && ((cur_depth <= d2) || !vf->is_entry_frame())) {
|
|
if (vf->is_compiled_frame()) {
|
|
compiledVFrame* cvf = compiledVFrame::cast(vf);
|
|
// Deoptimize frame and local objects if any exist.
|
|
// If cvf is deeper than depth, then we deoptimize iff local objects are passed as args.
|
|
bool should_deopt = cur_depth <= d2 ? cvf->has_ea_local_in_scope() : cvf->arg_escape();
|
|
if (should_deopt && !deoptimize_objects(cvf->fr().id())) {
|
|
// reallocation of scalar replaced objects failed because heap is exhausted
|
|
return false;
|
|
}
|
|
|
|
// move to top frame
|
|
while(!vf->is_top()) {
|
|
cur_depth++;
|
|
vf = vf->sender();
|
|
}
|
|
}
|
|
|
|
// move to next physical frame
|
|
cur_depth++;
|
|
vf = vf->sender();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EscapeBarrier::deoptimize_objects_all_threads() {
|
|
if (!barrier_active()) return true;
|
|
ResourceMark rm(calling_thread());
|
|
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
|
|
// Skip thread with mounted continuation
|
|
if (jt->last_continuation() != nullptr) {
|
|
continue;
|
|
}
|
|
if (jt->frames_to_pop_failed_realloc() > 0) {
|
|
// The deoptee thread jt has frames with reallocation failures on top of its stack.
|
|
// These frames are about to be removed. We must not interfere with that and signal failure.
|
|
return false;
|
|
}
|
|
if (jt->has_last_Java_frame()) {
|
|
KeepStackGCProcessedMark ksgcpm(jt);
|
|
RegisterMap reg_map(jt,
|
|
RegisterMap::UpdateMap::skip,
|
|
RegisterMap::ProcessFrames::skip,
|
|
RegisterMap::WalkContinuation::skip);
|
|
vframe* vf = jt->last_java_vframe(®_map);
|
|
assert(jt->frame_anchor()->walkable(),
|
|
"The stack of JavaThread " PTR_FORMAT " is not walkable. Thread state is %d",
|
|
p2i(jt), jt->thread_state());
|
|
while (vf != nullptr) {
|
|
if (vf->is_compiled_frame()) {
|
|
compiledVFrame* cvf = compiledVFrame::cast(vf);
|
|
if ((cvf->has_ea_local_in_scope() || cvf->arg_escape()) &&
|
|
!deoptimize_objects_internal(jt, cvf->fr().id())) {
|
|
return false; // reallocation failure
|
|
}
|
|
// move to top frame
|
|
while(!vf->is_top()) {
|
|
vf = vf->sender();
|
|
}
|
|
}
|
|
// move to next physical frame
|
|
vf = vf->sender();
|
|
}
|
|
}
|
|
}
|
|
return true; // success
|
|
}
|
|
|
|
bool EscapeBarrier::_deoptimizing_objects_for_all_threads = false;
|
|
bool EscapeBarrier::_self_deoptimization_in_progress = false;
|
|
|
|
class EscapeBarrierSuspendHandshake : public HandshakeClosure {
|
|
public:
|
|
EscapeBarrierSuspendHandshake(const char* name) :
|
|
HandshakeClosure(name) { }
|
|
void do_thread(Thread* th) { }
|
|
};
|
|
|
|
void EscapeBarrier::sync_and_suspend_one() {
|
|
assert(_calling_thread != nullptr, "calling thread must not be null");
|
|
assert(_deoptee_thread != nullptr, "deoptee thread must not be null");
|
|
assert(barrier_active(), "should not call");
|
|
|
|
// Sync with other threads that might be doing deoptimizations
|
|
{
|
|
// Need to switch to _thread_blocked for the wait() call
|
|
ThreadBlockInVM tbivm(_calling_thread);
|
|
MonitorLocker ml(_calling_thread, EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
|
while (_self_deoptimization_in_progress || _deoptee_thread->is_obj_deopt_suspend()) {
|
|
ml.wait();
|
|
}
|
|
|
|
if (self_deopt()) {
|
|
_self_deoptimization_in_progress = true;
|
|
return;
|
|
}
|
|
|
|
// set suspend flag for target thread
|
|
_deoptee_thread->set_obj_deopt_flag();
|
|
}
|
|
|
|
// Use a handshake to synchronize with the target thread.
|
|
EscapeBarrierSuspendHandshake sh("EscapeBarrierSuspendOne");
|
|
Handshake::execute(&sh, _deoptee_thread);
|
|
assert(!_deoptee_thread->has_last_Java_frame() || _deoptee_thread->frame_anchor()->walkable(),
|
|
"stack should be walkable now");
|
|
}
|
|
|
|
void EscapeBarrier::sync_and_suspend_all() {
|
|
assert(barrier_active(), "should not call");
|
|
assert(_calling_thread != nullptr, "calling thread must not be null");
|
|
assert(all_threads(), "sanity");
|
|
|
|
// Sync with other threads that might be doing deoptimizations
|
|
{
|
|
// Need to switch to _thread_blocked for the wait() call
|
|
ThreadBlockInVM tbivm(_calling_thread);
|
|
MonitorLocker ml(_calling_thread, EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
|
|
|
bool deopt_in_progress;
|
|
do {
|
|
deopt_in_progress = _self_deoptimization_in_progress;
|
|
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
|
|
deopt_in_progress = (deopt_in_progress || jt->is_obj_deopt_suspend());
|
|
if (deopt_in_progress) {
|
|
break;
|
|
}
|
|
}
|
|
if (deopt_in_progress) {
|
|
ml.wait(); // then check again
|
|
}
|
|
} while(deopt_in_progress);
|
|
|
|
_self_deoptimization_in_progress = true;
|
|
_deoptimizing_objects_for_all_threads = true;
|
|
|
|
// We set the suspend flags before executing the handshake because then the
|
|
// setting will be visible after leaving the _thread_blocked state in
|
|
// JavaThread::wait_for_object_deoptimization(). If we set the flags in the
|
|
// handshake then the read must happen after the safepoint/handshake poll.
|
|
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
|
|
if (jt->is_Java_thread() && !jt->is_hidden_from_external_view() && (jt != _calling_thread)) {
|
|
jt->set_obj_deopt_flag();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Use a handshake to synchronize with the other threads.
|
|
EscapeBarrierSuspendHandshake sh("EscapeBarrierSuspendAll");
|
|
Handshake::execute(&sh);
|
|
#ifdef ASSERT
|
|
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
|
|
if (jt->is_hidden_from_external_view()) continue;
|
|
assert(!jt->has_last_Java_frame() || jt->frame_anchor()->walkable(),
|
|
"The stack of JavaThread " PTR_FORMAT " is not walkable. Thread state is %d",
|
|
p2i(jt), jt->thread_state());
|
|
}
|
|
#endif // ASSERT
|
|
}
|
|
|
|
void EscapeBarrier::resume_one() {
|
|
assert(barrier_active(), "should not call");
|
|
assert(!all_threads(), "use resume_all()");
|
|
MonitorLocker ml(_calling_thread, EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
|
if (self_deopt()) {
|
|
assert(_self_deoptimization_in_progress, "incorrect synchronization");
|
|
_self_deoptimization_in_progress = false;
|
|
} else {
|
|
_deoptee_thread->clear_obj_deopt_flag();
|
|
}
|
|
ml.notify_all();
|
|
}
|
|
|
|
void EscapeBarrier::resume_all() {
|
|
assert(barrier_active(), "should not call");
|
|
assert(all_threads(), "use resume_one()");
|
|
MonitorLocker ml(_calling_thread, EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
|
assert(_self_deoptimization_in_progress, "incorrect synchronization");
|
|
_deoptimizing_objects_for_all_threads = false;
|
|
_self_deoptimization_in_progress = false;
|
|
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
|
|
jt->clear_obj_deopt_flag();
|
|
}
|
|
ml.notify_all();
|
|
}
|
|
|
|
void EscapeBarrier::thread_added(JavaThread* jt) {
|
|
if (!jt->is_hidden_from_external_view()) {
|
|
MutexLocker ml(EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
|
if (_deoptimizing_objects_for_all_threads) {
|
|
jt->set_obj_deopt_flag();
|
|
}
|
|
}
|
|
}
|
|
|
|
void EscapeBarrier::thread_removed(JavaThread* jt) {
|
|
MonitorLocker ml(EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
|
if (jt->is_obj_deopt_suspend()) {
|
|
// jt terminated before it self suspended.
|
|
// Other threads might be waiting to perform deoptimizations for it.
|
|
jt->clear_obj_deopt_flag();
|
|
ml.notify_all();
|
|
}
|
|
}
|
|
|
|
// Remember that objects were reallocated and relocked for the compiled frame with the given id
|
|
static void set_objs_are_deoptimized(JavaThread* thread, intptr_t* fr_id) {
|
|
// set in first/oldest update
|
|
GrowableArrayView<jvmtiDeferredLocalVariableSet*>* list =
|
|
JvmtiDeferredUpdates::deferred_locals(thread);
|
|
DEBUG_ONLY(bool found = false);
|
|
if (list != nullptr) {
|
|
for (int i = 0; i < list->length(); i++) {
|
|
if (list->at(i)->matches(fr_id)) {
|
|
DEBUG_ONLY(found = true);
|
|
list->at(i)->set_objs_are_deoptimized();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
assert(found, "variable set should exist at least for one vframe");
|
|
}
|
|
|
|
// Deoptimize the given frame and deoptimize objects with optimizations based on
|
|
// escape analysis, i.e. reallocate scalar replaced objects on the heap and
|
|
// relock objects if locking has been eliminated.
|
|
// Deoptimized objects are kept as JVMTI deferred updates until the compiled
|
|
// frame is replaced with interpreter frames. Returns false iff at least one
|
|
// reallocation failed.
|
|
bool EscapeBarrier::deoptimize_objects_internal(JavaThread* deoptee, intptr_t* fr_id) {
|
|
assert(barrier_active(), "should not call");
|
|
|
|
JavaThread* ct = calling_thread();
|
|
bool realloc_failures = false;
|
|
|
|
if (!objs_are_deoptimized(deoptee, fr_id)) {
|
|
// Make sure the frame identified by fr_id is deoptimized and fetch its last vframe
|
|
compiledVFrame* last_cvf;
|
|
bool fr_is_deoptimized;
|
|
do {
|
|
StackFrameStream fst(deoptee, true /* update */, false /* process_frames */);
|
|
while (fst.current()->id() != fr_id && !fst.is_done()) {
|
|
fst.next();
|
|
}
|
|
assert(fst.current()->id() == fr_id, "frame not found");
|
|
assert(fst.current()->is_compiled_frame(),
|
|
"only compiled frames can contain stack allocated objects");
|
|
fr_is_deoptimized = fst.current()->is_deoptimized_frame();
|
|
if (!fr_is_deoptimized) {
|
|
// Execution must not continue in the compiled method, so we deoptimize the frame.
|
|
Deoptimization::deoptimize_frame(deoptee, fr_id);
|
|
} else {
|
|
last_cvf = compiledVFrame::cast(vframe::new_vframe(fst.current(), fst.register_map(), deoptee));
|
|
}
|
|
} while(!fr_is_deoptimized);
|
|
|
|
// collect inlined frames
|
|
compiledVFrame* cvf = last_cvf;
|
|
GrowableArray<compiledVFrame*>* vfs = new GrowableArray<compiledVFrame*>(10);
|
|
while (!cvf->is_top()) {
|
|
vfs->push(cvf);
|
|
cvf = compiledVFrame::cast(cvf->sender());
|
|
}
|
|
vfs->push(cvf);
|
|
|
|
// reallocate and relock optimized objects
|
|
bool deoptimized_objects = Deoptimization::deoptimize_objects_internal(ct, vfs, realloc_failures);
|
|
if (!realloc_failures && deoptimized_objects) {
|
|
// now do the updates
|
|
for (int frame_index = 0; frame_index < vfs->length(); frame_index++) {
|
|
cvf = vfs->at(frame_index);
|
|
cvf->create_deferred_updates_after_object_deoptimization();
|
|
}
|
|
set_objs_are_deoptimized(deoptee, fr_id);
|
|
}
|
|
}
|
|
return !realloc_failures;
|
|
}
|
|
|
|
#endif // COMPILER2_OR_JVMCI
|