blob: bc01d90028564cc8ff5d6ed9961e69a2da38ccf5 [file] [log] [blame]
/*
* 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()) {
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(&reg_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(&reg_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