mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 15:24:43 +02:00
475 lines
16 KiB
C++
475 lines
16 KiB
C++
/*
|
|
* Copyright (c) 2018, 2022, 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.
|
|
*
|
|
* 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 "runtime/arguments.hpp"
|
|
#include "runtime/continuation.hpp"
|
|
#include "runtime/continuationEntry.inline.hpp"
|
|
#include "runtime/continuationHelper.inline.hpp"
|
|
#include "runtime/continuationWrapper.inline.hpp"
|
|
#include "runtime/interfaceSupport.inline.hpp"
|
|
#include "runtime/osThread.hpp"
|
|
#include "runtime/vframe.inline.hpp"
|
|
#include "runtime/vframe_hp.hpp"
|
|
|
|
// defined in continuationFreezeThaw.cpp
|
|
extern "C" jint JNICALL CONT_isPinned0(JNIEnv* env, jobject cont_scope);
|
|
|
|
JVM_ENTRY(void, CONT_pin(JNIEnv* env, jclass cls)) {
|
|
if (!Continuation::pin(JavaThread::thread_from_jni_environment(env))) {
|
|
THROW_MSG(vmSymbols::java_lang_IllegalStateException(), "pin overflow");
|
|
}
|
|
}
|
|
JVM_END
|
|
|
|
JVM_ENTRY(void, CONT_unpin(JNIEnv* env, jclass cls)) {
|
|
if (!Continuation::unpin(JavaThread::thread_from_jni_environment(env))) {
|
|
THROW_MSG(vmSymbols::java_lang_IllegalStateException(), "pin underflow");
|
|
}
|
|
}
|
|
JVM_END
|
|
|
|
#ifndef PRODUCT
|
|
static jlong java_tid(JavaThread* thread) {
|
|
return java_lang_Thread::thread_id(thread->threadObj());
|
|
}
|
|
#endif
|
|
|
|
const ContinuationEntry* Continuation::last_continuation(const JavaThread* thread, oop cont_scope) {
|
|
// guarantee (thread->has_last_Java_frame(), "");
|
|
for (ContinuationEntry* entry = thread->last_continuation(); entry != nullptr; entry = entry->parent()) {
|
|
if (cont_scope == jdk_internal_vm_Continuation::scope(entry->cont_oop())) {
|
|
return entry;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ContinuationEntry* Continuation::get_continuation_entry_for_continuation(JavaThread* thread, oop continuation) {
|
|
if (thread == nullptr || continuation == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (ContinuationEntry* entry = thread->last_continuation(); entry != nullptr; entry = entry->parent()) {
|
|
if (continuation == entry->cont_oop()) {
|
|
return entry;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static bool is_on_stack(JavaThread* thread, const ContinuationEntry* entry) {
|
|
if (entry == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
assert(thread->is_in_full_stack((address)entry), "");
|
|
return true;
|
|
// return false if called when transitioning to Java on return from freeze
|
|
// return !thread->has_last_Java_frame() || thread->last_Java_sp() < cont->entry_sp();
|
|
}
|
|
|
|
bool Continuation::is_continuation_mounted(JavaThread* thread, oop continuation) {
|
|
return is_on_stack(thread, get_continuation_entry_for_continuation(thread, continuation));
|
|
}
|
|
|
|
bool Continuation::is_continuation_scope_mounted(JavaThread* thread, oop cont_scope) {
|
|
return is_on_stack(thread, last_continuation(thread, cont_scope));
|
|
}
|
|
|
|
// When walking the virtual stack, this method returns true
|
|
// iff the frame is a thawed continuation frame whose
|
|
// caller is still frozen on the h-stack.
|
|
// The continuation object can be extracted from the thread.
|
|
bool Continuation::is_cont_barrier_frame(const frame& f) {
|
|
assert(f.is_interpreted_frame() || f.cb() != nullptr, "");
|
|
if (!Continuations::enabled()) return false;
|
|
return is_return_barrier_entry(f.is_interpreted_frame() ? ContinuationHelper::InterpretedFrame::return_pc(f)
|
|
: ContinuationHelper::CompiledFrame::return_pc(f));
|
|
}
|
|
|
|
bool Continuation::is_return_barrier_entry(const address pc) {
|
|
if (!Continuations::enabled()) return false;
|
|
return pc == StubRoutines::cont_returnBarrier();
|
|
}
|
|
|
|
bool Continuation::is_continuation_enterSpecial(const frame& f) {
|
|
if (f.cb() == nullptr || !f.cb()->is_compiled()) {
|
|
return false;
|
|
}
|
|
Method* m = f.cb()->as_compiled_method()->method();
|
|
return (m != nullptr && m->is_continuation_enter_intrinsic());
|
|
}
|
|
|
|
bool Continuation::is_continuation_entry_frame(const frame& f, const RegisterMap *map) {
|
|
// we can do this because the entry frame is never inlined
|
|
Method* m = (map != nullptr && map->in_cont() && f.is_interpreted_frame())
|
|
? map->stack_chunk()->interpreter_frame_method(f)
|
|
: ContinuationHelper::Frame::frame_method(f);
|
|
return m != nullptr && m->intrinsic_id() == vmIntrinsics::_Continuation_enter;
|
|
}
|
|
|
|
static inline bool is_sp_in_continuation(const ContinuationEntry* entry, intptr_t* const sp) {
|
|
return entry->entry_sp() > sp;
|
|
}
|
|
|
|
bool Continuation::is_frame_in_continuation(const ContinuationEntry* entry, const frame& f) {
|
|
return is_sp_in_continuation(entry, f.unextended_sp());
|
|
}
|
|
|
|
ContinuationEntry* Continuation::get_continuation_entry_for_sp(JavaThread* thread, intptr_t* const sp) {
|
|
assert(thread != nullptr, "");
|
|
ContinuationEntry* entry = thread->last_continuation();
|
|
while (entry != nullptr && !is_sp_in_continuation(entry, sp)) {
|
|
entry = entry->parent();
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
bool Continuation::is_frame_in_continuation(JavaThread* thread, const frame& f) {
|
|
return f.is_heap_frame() || (get_continuation_entry_for_sp(thread, f.unextended_sp()) != nullptr);
|
|
}
|
|
|
|
static frame continuation_top_frame(const ContinuationWrapper& cont, RegisterMap* map) {
|
|
stackChunkOop chunk = cont.last_nonempty_chunk();
|
|
map->set_stack_chunk(chunk);
|
|
return chunk != nullptr ? chunk->top_frame(map) : frame();
|
|
}
|
|
|
|
bool Continuation::has_last_Java_frame(oop continuation, frame* frame, RegisterMap* map) {
|
|
ContinuationWrapper cont(continuation);
|
|
if (!cont.is_empty()) {
|
|
*frame = continuation_top_frame(cont, map);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
frame Continuation::last_frame(oop continuation, RegisterMap *map) {
|
|
assert(map != nullptr, "a map must be given");
|
|
return continuation_top_frame(ContinuationWrapper(continuation), map);
|
|
}
|
|
|
|
frame Continuation::top_frame(const frame& callee, RegisterMap* map) {
|
|
assert(map != nullptr, "");
|
|
ContinuationEntry* ce = get_continuation_entry_for_sp(map->thread(), callee.sp());
|
|
assert(ce != nullptr, "");
|
|
oop continuation = ce->cont_oop();
|
|
ContinuationWrapper cont(continuation);
|
|
return continuation_top_frame(cont, map);
|
|
}
|
|
|
|
javaVFrame* Continuation::last_java_vframe(Handle continuation, RegisterMap *map) {
|
|
assert(map != nullptr, "a map must be given");
|
|
if (!ContinuationWrapper(continuation()).is_empty()) {
|
|
frame f = last_frame(continuation(), map);
|
|
for (vframe* vf = vframe::new_vframe(&f, map, nullptr); vf; vf = vf->sender()) {
|
|
if (vf->is_java_frame()) {
|
|
return javaVFrame::cast(vf);
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
frame Continuation::continuation_parent_frame(RegisterMap* map) {
|
|
assert(map->in_cont(), "");
|
|
ContinuationWrapper cont(map);
|
|
assert(map->thread() != nullptr || !cont.is_mounted(), "");
|
|
|
|
log_develop_trace(continuations)("continuation_parent_frame");
|
|
if (map->update_map()) {
|
|
// we need to register the link address for the entry frame
|
|
if (cont.entry() != nullptr) {
|
|
cont.entry()->update_register_map(map);
|
|
} else {
|
|
map->clear();
|
|
}
|
|
}
|
|
|
|
if (!cont.is_mounted()) { // When we're walking an unmounted continuation and reached the end
|
|
oop parent = jdk_internal_vm_Continuation::parent(cont.continuation());
|
|
stackChunkOop chunk = parent != nullptr ? ContinuationWrapper(parent).last_nonempty_chunk() : nullptr;
|
|
if (chunk != nullptr) {
|
|
return chunk->top_frame(map);
|
|
}
|
|
|
|
map->set_stack_chunk(nullptr);
|
|
return frame();
|
|
}
|
|
|
|
map->set_stack_chunk(nullptr);
|
|
|
|
#if (defined(X86) || defined(AARCH64)) && !defined(ZERO)
|
|
frame sender(cont.entrySP(), cont.entryFP(), cont.entryPC());
|
|
#else
|
|
frame sender = frame();
|
|
Unimplemented();
|
|
#endif
|
|
|
|
return sender;
|
|
}
|
|
|
|
oop Continuation::continuation_scope(oop continuation) {
|
|
return continuation != nullptr ? jdk_internal_vm_Continuation::scope(continuation) : nullptr;
|
|
}
|
|
|
|
bool Continuation::is_scope_bottom(oop cont_scope, const frame& f, const RegisterMap* map) {
|
|
if (cont_scope == nullptr || !is_continuation_entry_frame(f, map)) {
|
|
return false;
|
|
}
|
|
|
|
oop continuation;
|
|
if (map->in_cont()) {
|
|
continuation = map->cont();
|
|
} else {
|
|
ContinuationEntry* ce = get_continuation_entry_for_sp(map->thread(), f.sp());
|
|
if (ce == nullptr) {
|
|
return false;
|
|
}
|
|
continuation = ce->cont_oop();
|
|
}
|
|
if (continuation == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
oop sc = continuation_scope(continuation);
|
|
assert(sc != nullptr, "");
|
|
return sc == cont_scope;
|
|
}
|
|
|
|
bool Continuation::is_in_usable_stack(address addr, const RegisterMap* map) {
|
|
ContinuationWrapper cont(map);
|
|
stackChunkOop chunk = cont.find_chunk_by_address(addr);
|
|
return chunk != nullptr ? chunk->is_usable_in_chunk(addr) : false;
|
|
}
|
|
|
|
bool Continuation::pin(JavaThread* current) {
|
|
ContinuationEntry* ce = current->last_continuation();
|
|
if (ce == nullptr) {
|
|
return true; // no continuation mounted
|
|
}
|
|
return ce->pin();
|
|
}
|
|
|
|
bool Continuation::unpin(JavaThread* current) {
|
|
ContinuationEntry* ce = current->last_continuation();
|
|
if (ce == nullptr) {
|
|
return true; // no continuation mounted
|
|
}
|
|
return ce->unpin();
|
|
}
|
|
|
|
frame Continuation::continuation_bottom_sender(JavaThread* thread, const frame& callee, intptr_t* sender_sp) {
|
|
assert (thread != nullptr, "");
|
|
ContinuationEntry* ce = get_continuation_entry_for_sp(thread,
|
|
callee.is_interpreted_frame() ? callee.interpreter_frame_last_sp() : callee.unextended_sp());
|
|
assert(ce != nullptr, "callee.unextended_sp(): " INTPTR_FORMAT, p2i(callee.unextended_sp()));
|
|
|
|
log_develop_debug(continuations)("continuation_bottom_sender: [" JLONG_FORMAT "] [%d] callee: " INTPTR_FORMAT
|
|
" sender_sp: " INTPTR_FORMAT,
|
|
java_tid(thread), thread->osthread()->thread_id(), p2i(callee.sp()), p2i(sender_sp));
|
|
|
|
frame entry = ce->to_frame();
|
|
if (callee.is_interpreted_frame()) {
|
|
entry.set_sp(sender_sp); // sp != unextended_sp
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
address Continuation::get_top_return_pc_post_barrier(JavaThread* thread, address pc) {
|
|
ContinuationEntry* ce;
|
|
if (thread != nullptr && is_return_barrier_entry(pc) && (ce = thread->last_continuation()) != nullptr) {
|
|
return ce->entry_pc();
|
|
}
|
|
return pc;
|
|
}
|
|
|
|
void Continuation::set_cont_fastpath_thread_state(JavaThread* thread) {
|
|
assert(thread != nullptr, "");
|
|
bool fast = !thread->is_interp_only_mode();
|
|
thread->set_cont_fastpath_thread_state(fast);
|
|
}
|
|
|
|
void Continuation::notify_deopt(JavaThread* thread, intptr_t* sp) {
|
|
ContinuationEntry* entry = thread->last_continuation();
|
|
|
|
if (entry == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (is_sp_in_continuation(entry, sp)) {
|
|
thread->push_cont_fastpath(sp);
|
|
return;
|
|
}
|
|
|
|
ContinuationEntry* prev;
|
|
do {
|
|
prev = entry;
|
|
entry = entry->parent();
|
|
} while (entry != nullptr && !is_sp_in_continuation(entry, sp));
|
|
|
|
if (entry == nullptr) {
|
|
return;
|
|
}
|
|
assert(is_sp_in_continuation(entry, sp), "");
|
|
if (sp > prev->parent_cont_fastpath()) {
|
|
prev->set_parent_cont_fastpath(sp);
|
|
}
|
|
}
|
|
|
|
#ifndef PRODUCT
|
|
void Continuation::describe(FrameValues &values) {
|
|
JavaThread* thread = JavaThread::active();
|
|
if (thread != nullptr) {
|
|
for (ContinuationEntry* ce = thread->last_continuation(); ce != nullptr; ce = ce->parent()) {
|
|
intptr_t* bottom = ce->entry_sp();
|
|
if (bottom != nullptr) {
|
|
values.describe(-1, bottom, "continuation entry");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef ASSERT
|
|
void Continuation::debug_verify_continuation(oop contOop) {
|
|
if (!VerifyContinuations) {
|
|
return;
|
|
}
|
|
assert(contOop != nullptr, "");
|
|
assert(oopDesc::is_oop(contOop), "");
|
|
ContinuationWrapper cont(contOop);
|
|
|
|
assert(oopDesc::is_oop_or_null(cont.tail()), "");
|
|
assert(cont.chunk_invariant(), "");
|
|
|
|
bool nonempty_chunk = false;
|
|
size_t max_size = 0;
|
|
int num_chunks = 0;
|
|
int num_frames = 0;
|
|
int num_interpreted_frames = 0;
|
|
int num_oops = 0;
|
|
|
|
for (stackChunkOop chunk = cont.tail(); chunk != nullptr; chunk = chunk->parent()) {
|
|
log_develop_trace(continuations)("debug_verify_continuation chunk %d", num_chunks);
|
|
chunk->verify(&max_size, &num_oops, &num_frames, &num_interpreted_frames);
|
|
if (!chunk->is_empty()) {
|
|
nonempty_chunk = true;
|
|
}
|
|
num_chunks++;
|
|
}
|
|
|
|
const bool is_empty = cont.is_empty();
|
|
assert(!nonempty_chunk || !is_empty, "");
|
|
assert(is_empty == (!nonempty_chunk && cont.last_frame().is_empty()), "");
|
|
}
|
|
|
|
void Continuation::print(oop continuation) { print_on(tty, continuation); }
|
|
|
|
void Continuation::print_on(outputStream* st, oop continuation) {
|
|
ContinuationWrapper cont(continuation);
|
|
|
|
st->print_cr("CONTINUATION: " PTR_FORMAT " done: %d",
|
|
continuation->identity_hash(), jdk_internal_vm_Continuation::done(continuation));
|
|
st->print_cr("CHUNKS:");
|
|
for (stackChunkOop chunk = cont.tail(); chunk != nullptr; chunk = chunk->parent()) {
|
|
st->print("* ");
|
|
chunk->print_on(true, st);
|
|
}
|
|
}
|
|
#endif // ASSERT
|
|
|
|
|
|
void continuations_init() { Continuations::init(); }
|
|
|
|
void Continuations::init() {
|
|
Continuation::init();
|
|
}
|
|
|
|
// While virtual threads are in Preview, there are some VM mechanisms we disable if continuations aren't used
|
|
// See NMethodSweeper::do_stack_scanning and nmethod::is_not_on_continuation_stack
|
|
bool Continuations::enabled() {
|
|
return VMContinuations && Arguments::enable_preview();
|
|
}
|
|
|
|
// We initialize the _gc_epoch to 2, because previous_completed_gc_marking_cycle
|
|
// subtracts the value by 2, and the type is unsigned. We don't want underflow.
|
|
//
|
|
// Odd values mean that marking is in progress, and even values mean that no
|
|
// marking is currently active.
|
|
uint64_t Continuations::_gc_epoch = 2;
|
|
|
|
uint64_t Continuations::gc_epoch() {
|
|
return _gc_epoch;
|
|
}
|
|
|
|
bool Continuations::is_gc_marking_cycle_active() {
|
|
// Odd means that marking is active
|
|
return (_gc_epoch % 2) == 1;
|
|
}
|
|
|
|
uint64_t Continuations::previous_completed_gc_marking_cycle() {
|
|
if (is_gc_marking_cycle_active()) {
|
|
return _gc_epoch - 2;
|
|
} else {
|
|
return _gc_epoch - 1;
|
|
}
|
|
}
|
|
|
|
void Continuations::on_gc_marking_cycle_start() {
|
|
assert(!is_gc_marking_cycle_active(), "Previous marking cycle never ended");
|
|
++_gc_epoch;
|
|
}
|
|
|
|
void Continuations::on_gc_marking_cycle_finish() {
|
|
assert(is_gc_marking_cycle_active(), "Marking cycle started before last one finished");
|
|
++_gc_epoch;
|
|
}
|
|
|
|
void Continuations::arm_all_nmethods() {
|
|
BarrierSetNMethod* bs_nm = BarrierSet::barrier_set()->barrier_set_nmethod();
|
|
if (bs_nm != NULL) {
|
|
bs_nm->arm_all_nmethods();
|
|
}
|
|
}
|
|
|
|
#define CC (char*) /*cast a literal from (const char*)*/
|
|
#define FN_PTR(f) CAST_FROM_FN_PTR(void*, &f)
|
|
|
|
static JNINativeMethod CONT_methods[] = {
|
|
{CC"pin", CC"()V", FN_PTR(CONT_pin)},
|
|
{CC"unpin", CC"()V", FN_PTR(CONT_unpin)},
|
|
{CC"isPinned0", CC"(Ljdk/internal/vm/ContinuationScope;)I", FN_PTR(CONT_isPinned0)},
|
|
};
|
|
|
|
void CONT_RegisterNativeMethods(JNIEnv *env, jclass cls) {
|
|
Thread* thread = Thread::current();
|
|
assert(thread->is_Java_thread(), "");
|
|
ThreadToNativeFromVM trans((JavaThread*)thread);
|
|
int status = env->RegisterNatives(cls, CONT_methods, sizeof(CONT_methods)/sizeof(JNINativeMethod));
|
|
guarantee(status == JNI_OK, "register jdk.internal.vm.Continuation natives");
|
|
guarantee(!env->ExceptionOccurred(), "register jdk.internal.vm.Continuation natives");
|
|
}
|