8227745: Enable Escape Analysis for Better Performance in the Presence of JVMTI Agents

8233915: JVMTI FollowReferences: Java Heap Leak not found because of C2 Scalar Replacement

Reviewed-by: mdoerr, goetz, sspitsyn, kvn
This commit is contained in:
Richard Reingruber 2020-10-20 15:31:55 +00:00
parent f167a71f1d
commit 40f847e2fb
53 changed files with 5744 additions and 218 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2020, 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
@ -244,7 +244,11 @@ class IRScopeDebugInfo: public CompilationResourceObj {
bool reexecute = topmost ? should_reexecute() : false;
bool return_oop = false; // This flag will be ignored since it used only for C2 with escape analysis.
bool rethrow_exception = false;
recorder->describe_scope(pc_offset, methodHandle(), scope()->method(), bci(), reexecute, rethrow_exception, is_method_handle_invoke, return_oop, locvals, expvals, monvals);
bool has_ea_local_in_scope = false;
bool arg_escape = false;
recorder->describe_scope(pc_offset, methodHandle(), scope()->method(), bci(),
reexecute, rethrow_exception, is_method_handle_invoke, return_oop,
has_ea_local_in_scope, arg_escape, locvals, expvals, monvals);
}
};

View File

@ -242,6 +242,7 @@ bool ciEnv::cache_jvmti_state() {
_jvmti_can_post_on_exceptions = JvmtiExport::can_post_on_exceptions();
_jvmti_can_pop_frame = JvmtiExport::can_pop_frame();
_jvmti_can_get_owned_monitor_info = JvmtiExport::can_get_owned_monitor_info();
_jvmti_can_walk_any_space = JvmtiExport::can_walk_any_space();
return _task != NULL && _task->method()->is_old();
}
@ -271,6 +272,10 @@ bool ciEnv::jvmti_state_changed() const {
JvmtiExport::can_get_owned_monitor_info()) {
return true;
}
if (!_jvmti_can_walk_any_space &&
JvmtiExport::can_walk_any_space()) {
return true;
}
return false;
}

View File

@ -74,6 +74,7 @@ private:
bool _jvmti_can_post_on_exceptions;
bool _jvmti_can_pop_frame;
bool _jvmti_can_get_owned_monitor_info; // includes can_get_owned_monitor_stack_depth_info
bool _jvmti_can_walk_any_space;
// Cache DTrace flags
bool _dtrace_extended_probes;
@ -349,6 +350,7 @@ public:
bool jvmti_can_hotswap_or_post_breakpoint() const { return _jvmti_can_hotswap_or_post_breakpoint; }
bool jvmti_can_post_on_exceptions() const { return _jvmti_can_post_on_exceptions; }
bool jvmti_can_get_owned_monitor_info() const { return _jvmti_can_get_owned_monitor_info; }
bool jvmti_can_walk_any_space() const { return _jvmti_can_walk_any_space; }
// Cache DTrace flags
void cache_dtrace_flags();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2020, 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
@ -293,17 +293,13 @@ void CompiledMethod::verify_oop_relocations() {
ScopeDesc* CompiledMethod::scope_desc_at(address pc) {
PcDesc* pd = pc_desc_at(pc);
guarantee(pd != NULL, "scope must be present");
return new ScopeDesc(this, pd->scope_decode_offset(),
pd->obj_decode_offset(), pd->should_reexecute(), pd->rethrow_exception(),
pd->return_oop());
return new ScopeDesc(this, pd);
}
ScopeDesc* CompiledMethod::scope_desc_near(address pc) {
PcDesc* pd = pc_desc_near(pc);
guarantee(pd != NULL, "scope must be present");
return new ScopeDesc(this, pd->scope_decode_offset(),
pd->obj_decode_offset(), pd->should_reexecute(), pd->rethrow_exception(),
pd->return_oop());
return new ScopeDesc(this, pd);
}
address CompiledMethod::oops_reloc_begin() const {

View File

@ -288,6 +288,8 @@ void DebugInformationRecorder::describe_scope(int pc_offset,
bool rethrow_exception,
bool is_method_handle_invoke,
bool return_oop,
bool has_ea_local_in_scope,
bool arg_escape,
DebugToken* locals,
DebugToken* expressions,
DebugToken* monitors) {
@ -304,6 +306,8 @@ void DebugInformationRecorder::describe_scope(int pc_offset,
last_pd->set_rethrow_exception(rethrow_exception);
last_pd->set_is_method_handle_invoke(is_method_handle_invoke);
last_pd->set_return_oop(return_oop);
last_pd->set_has_ea_local_in_scope(has_ea_local_in_scope);
last_pd->set_arg_escape(arg_escape);
// serialize sender stream offest
stream()->write_int(sender_stream_offset);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2020, 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
@ -105,6 +105,8 @@ class DebugInformationRecorder: public ResourceObj {
bool rethrow_exception = false,
bool is_method_handle_invoke = false,
bool return_oop = false,
bool has_ea_local_in_scope = false,
bool arg_escape = false,
DebugToken* locals = NULL,
DebugToken* expressions = NULL,
DebugToken* monitors = NULL);

View File

@ -2419,9 +2419,7 @@ void nmethod::verify_interrupt_point(address call_site) {
PcDesc* pd = pc_desc_at(nativeCall_at(call_site)->return_address());
assert(pd != NULL, "PcDesc must exist");
for (ScopeDesc* sd = new ScopeDesc(this, pd->scope_decode_offset(),
pd->obj_decode_offset(), pd->should_reexecute(), pd->rethrow_exception(),
pd->return_oop());
for (ScopeDesc* sd = new ScopeDesc(this, pd);
!sd->is_top(); sd = sd->sender()) {
sd->verify();
}
@ -3056,9 +3054,7 @@ const char* nmethod::reloc_string_for(u_char* begin, u_char* end) {
ScopeDesc* nmethod::scope_desc_in(address begin, address end) {
PcDesc* p = pc_desc_near(begin+1);
if (p != NULL && p->real_pc(this) <= end) {
return new ScopeDesc(this, p->scope_decode_offset(),
p->obj_decode_offset(), p->should_reexecute(), p->rethrow_exception(),
p->return_oop());
return new ScopeDesc(this, p);
}
return NULL;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, 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
@ -42,7 +42,9 @@ class PcDesc {
PCDESC_reexecute = 1 << 0,
PCDESC_is_method_handle_invoke = 1 << 1,
PCDESC_return_oop = 1 << 2,
PCDESC_rethrow_exception = 1 << 3
PCDESC_rethrow_exception = 1 << 3,
PCDESC_has_ea_local_in_scope = 1 << 4,
PCDESC_arg_escape = 1 << 5
};
int _flags;
@ -89,6 +91,16 @@ class PcDesc {
bool return_oop() const { return (_flags & PCDESC_return_oop) != 0; }
void set_return_oop(bool z) { set_flag(PCDESC_return_oop, z); }
// Indicates if there are objects in scope that, based on escape analysis, are local to the
// compiled method or local to the current thread, i.e. NoEscape or ArgEscape
bool has_ea_local_in_scope() const { return (_flags & PCDESC_has_ea_local_in_scope) != 0; }
void set_has_ea_local_in_scope(bool z) { set_flag(PCDESC_has_ea_local_in_scope, z); }
// Indicates if this pc descriptor is at a call site where objects that do not escape the
// current thread are passed as arguments.
bool arg_escape() const { return (_flags & PCDESC_arg_escape) != 0; }
void set_arg_escape(bool z) { set_flag(PCDESC_arg_escape, z); }
// Returns the real pc
address real_pc(const CompiledMethod* code) const;

View File

@ -31,23 +31,16 @@
#include "oops/oop.inline.hpp"
#include "runtime/handles.inline.hpp"
ScopeDesc::ScopeDesc(const CompiledMethod* code, int decode_offset, int obj_decode_offset, bool reexecute, bool rethrow_exception, bool return_oop) {
ScopeDesc::ScopeDesc(const CompiledMethod* code, PcDesc* pd, bool ignore_objects) {
int obj_decode_offset = ignore_objects ? DebugInformationRecorder::serialized_null : pd->obj_decode_offset();
_code = code;
_decode_offset = decode_offset;
_decode_offset = pd->scope_decode_offset();
_objects = decode_object_values(obj_decode_offset);
_reexecute = reexecute;
_rethrow_exception = rethrow_exception;
_return_oop = return_oop;
decode_body();
}
ScopeDesc::ScopeDesc(const CompiledMethod* code, int decode_offset, bool reexecute, bool rethrow_exception, bool return_oop) {
_code = code;
_decode_offset = decode_offset;
_objects = decode_object_values(DebugInformationRecorder::serialized_null);
_reexecute = reexecute;
_rethrow_exception = rethrow_exception;
_return_oop = return_oop;
_reexecute = pd->should_reexecute();
_rethrow_exception = pd->rethrow_exception();
_return_oop = pd->return_oop();
_has_ea_local_in_scope = ignore_objects ? false : pd->has_ea_local_in_scope();
_arg_escape = ignore_objects ? false : pd->arg_escape();
decode_body();
}
@ -59,6 +52,8 @@ void ScopeDesc::initialize(const ScopeDesc* parent, int decode_offset) {
_reexecute = false; //reexecute only applies to the first scope
_rethrow_exception = false;
_return_oop = false;
_has_ea_local_in_scope = parent->has_ea_local_in_scope();
_arg_escape = false;
decode_body();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, 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
@ -60,12 +60,7 @@ class SimpleScopeDesc : public StackObj {
class ScopeDesc : public ResourceObj {
public:
// Constructor
ScopeDesc(const CompiledMethod* code, int decode_offset, int obj_decode_offset, bool reexecute, bool rethrow_exception, bool return_oop);
// Calls above, giving default value of "serialized_null" to the
// "obj_decode_offset" argument. (We don't use a default argument to
// avoid a .hpp-.hpp dependency.)
ScopeDesc(const CompiledMethod* code, int decode_offset, bool reexecute, bool rethrow_exception, bool return_oop);
ScopeDesc(const CompiledMethod* code, PcDesc* pd, bool ignore_objects = false);
// Direct access to scope
ScopeDesc* at_offset(int decode_offset) { return new ScopeDesc(this, decode_offset); }
@ -76,6 +71,10 @@ class ScopeDesc : public ResourceObj {
bool should_reexecute() const { return _reexecute; }
bool rethrow_exception() const { return _rethrow_exception; }
bool return_oop() const { return _return_oop; }
// Returns true if one or more NoEscape or ArgEscape objects exist in
// any of the scopes at compiled pc.
bool has_ea_local_in_scope() const { return _has_ea_local_in_scope; }
bool arg_escape() const { return _arg_escape; }
GrowableArray<ScopeValue*>* locals();
GrowableArray<ScopeValue*>* expressions();
@ -105,6 +104,9 @@ class ScopeDesc : public ResourceObj {
bool _reexecute;
bool _rethrow_exception;
bool _return_oop;
bool _has_ea_local_in_scope; // One or more NoEscape or ArgEscape objects exist in
// any of the scopes at compiled pc.
bool _arg_escape; // Compiled Java call in youngest scope passes ArgEscape
// Decoding offsets
int _decode_offset;

View File

@ -50,6 +50,7 @@
#include "prims/whitebox.hpp"
#include "runtime/arguments.hpp"
#include "runtime/atomic.hpp"
#include "runtime/escapeBarrier.hpp"
#include "runtime/globals_extension.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/init.hpp"
@ -798,19 +799,98 @@ Handle CompileBroker::create_thread_oop(const char* name, TRAPS) {
CHECK_NH);
}
#if defined(ASSERT) && COMPILER2_OR_JVMCI
// Stress testing. Dedicated threads revert optimizations based on escape analysis concurrently to
// the running java application. Configured with vm options DeoptimizeObjectsALot*.
class DeoptimizeObjectsALotThread : public JavaThread {
JavaThread* CompileBroker::make_thread(jobject thread_handle, CompileQueue* queue, AbstractCompiler* comp, Thread* THREAD) {
static void deopt_objs_alot_thread_entry(JavaThread* thread, TRAPS);
void deoptimize_objects_alot_loop_single();
void deoptimize_objects_alot_loop_all();
public:
DeoptimizeObjectsALotThread() : JavaThread(&deopt_objs_alot_thread_entry) { }
bool is_hidden_from_external_view() const { return true; }
};
// Entry for DeoptimizeObjectsALotThread. The threads are started in
// CompileBroker::init_compiler_sweeper_threads() iff DeoptimizeObjectsALot is enabled
void DeoptimizeObjectsALotThread::deopt_objs_alot_thread_entry(JavaThread* thread, TRAPS) {
DeoptimizeObjectsALotThread* dt = ((DeoptimizeObjectsALotThread*) thread);
bool enter_single_loop;
{
MonitorLocker ml(dt, EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
static int single_thread_count = 0;
enter_single_loop = single_thread_count++ < DeoptimizeObjectsALotThreadCountSingle;
}
if (enter_single_loop) {
dt->deoptimize_objects_alot_loop_single();
} else {
dt->deoptimize_objects_alot_loop_all();
}
}
// Execute EscapeBarriers in an endless loop to revert optimizations based on escape analysis. Each
// barrier targets a single thread which is selected round robin.
void DeoptimizeObjectsALotThread::deoptimize_objects_alot_loop_single() {
HandleMark hm(this);
while (true) {
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *deoptee_thread = jtiwh.next(); ) {
{ // Begin new scope for escape barrier
HandleMarkCleaner hmc(this);
ResourceMark rm(this);
EscapeBarrier eb(true, this, deoptee_thread);
eb.deoptimize_objects(100);
}
// Now sleep after the escape barriers destructor resumed deoptee_thread.
sleep(DeoptimizeObjectsALotInterval);
}
}
}
// Execute EscapeBarriers in an endless loop to revert optimizations based on escape analysis. Each
// barrier targets all java threads in the vm at once.
void DeoptimizeObjectsALotThread::deoptimize_objects_alot_loop_all() {
HandleMark hm(this);
while (true) {
{ // Begin new scope for escape barrier
HandleMarkCleaner hmc(this);
ResourceMark rm(this);
EscapeBarrier eb(true, this);
eb.deoptimize_objects_all_threads();
}
// Now sleep after the escape barriers destructor resumed the java threads.
sleep(DeoptimizeObjectsALotInterval);
}
}
#endif // defined(ASSERT) && COMPILER2_OR_JVMCI
JavaThread* CompileBroker::make_thread(ThreadType type, jobject thread_handle, CompileQueue* queue, AbstractCompiler* comp, Thread* THREAD) {
JavaThread* new_thread = NULL;
{
MutexLocker mu(THREAD, Threads_lock);
if (comp != NULL) {
if (!InjectCompilerCreationFailure || comp->num_compiler_threads() == 0) {
CompilerCounters* counters = new CompilerCounters();
new_thread = new CompilerThread(queue, counters);
}
} else {
new_thread = new CodeCacheSweeperThread();
switch (type) {
case compiler_t:
assert(comp != NULL, "Compiler instance missing.");
if (!InjectCompilerCreationFailure || comp->num_compiler_threads() == 0) {
CompilerCounters* counters = new CompilerCounters();
new_thread = new CompilerThread(queue, counters);
}
break;
case sweeper_t:
new_thread = new CodeCacheSweeperThread();
break;
#if defined(ASSERT) && COMPILER2_OR_JVMCI
case deoptimizer_t:
new_thread = new DeoptimizeObjectsALotThread();
break;
#endif // ASSERT
default:
ShouldNotReachHere();
}
// At this point the new CompilerThread data-races with this startup
// thread (which I believe is the primoridal thread and NOT the VM
// thread). This means Java bytecodes being executed at startup can
@ -852,7 +932,7 @@ JavaThread* CompileBroker::make_thread(jobject thread_handle, CompileQueue* queu
java_lang_Thread::set_daemon(JNIHandles::resolve_non_null(thread_handle));
new_thread->set_threadObj(JNIHandles::resolve_non_null(thread_handle));
if (comp != NULL) {
if (type == compiler_t) {
new_thread->as_CompilerThread()->set_compiler(comp);
}
Threads::add(new_thread);
@ -862,7 +942,7 @@ JavaThread* CompileBroker::make_thread(jobject thread_handle, CompileQueue* queu
// First release lock before aborting VM.
if (new_thread == NULL || new_thread->osthread() == NULL) {
if (UseDynamicNumberOfCompilerThreads && comp != NULL && comp->num_compiler_threads() > 0) {
if (UseDynamicNumberOfCompilerThreads && type == compiler_t && comp->num_compiler_threads() > 0) {
if (new_thread != NULL) {
new_thread->smr_delete();
}
@ -917,7 +997,7 @@ void CompileBroker::init_compiler_sweeper_threads() {
_compiler2_logs[i] = NULL;
if (!UseDynamicNumberOfCompilerThreads || i == 0) {
JavaThread *ct = make_thread(thread_handle, _c2_compile_queue, _compilers[1], THREAD);
JavaThread *ct = make_thread(compiler_t, thread_handle, _c2_compile_queue, _compilers[1], THREAD);
assert(ct != NULL, "should have been handled for initial thread");
_compilers[1]->set_num_compiler_threads(i + 1);
if (TraceCompilerThreads) {
@ -937,7 +1017,7 @@ void CompileBroker::init_compiler_sweeper_threads() {
_compiler1_logs[i] = NULL;
if (!UseDynamicNumberOfCompilerThreads || i == 0) {
JavaThread *ct = make_thread(thread_handle, _c1_compile_queue, _compilers[0], THREAD);
JavaThread *ct = make_thread(compiler_t, thread_handle, _c1_compile_queue, _compilers[0], THREAD);
assert(ct != NULL, "should have been handled for initial thread");
_compilers[0]->set_num_compiler_threads(i + 1);
if (TraceCompilerThreads) {
@ -956,8 +1036,20 @@ void CompileBroker::init_compiler_sweeper_threads() {
// Initialize the sweeper thread
Handle thread_oop = create_thread_oop("Sweeper thread", CHECK);
jobject thread_handle = JNIHandles::make_local(THREAD, thread_oop());
make_thread(thread_handle, NULL, NULL, THREAD);
make_thread(sweeper_t, thread_handle, NULL, NULL, THREAD);
}
#if defined(ASSERT) && COMPILER2_OR_JVMCI
if (DeoptimizeObjectsALot) {
// Initialize and start the object deoptimizer threads
const int total_count = DeoptimizeObjectsALotThreadCountSingle + DeoptimizeObjectsALotThreadCountAll;
for (int count = 0; count < total_count; count++) {
Handle thread_oop = create_thread_oop("Deoptimize objects a lot single mode", CHECK);
jobject thread_handle = JNIHandles::make_local(THREAD, thread_oop());
make_thread(deoptimizer_t, thread_handle, NULL, NULL, THREAD);
}
}
#endif // defined(ASSERT) && COMPILER2_OR_JVMCI
}
void CompileBroker::possibly_add_compiler_threads(Thread* THREAD) {
@ -1011,7 +1103,7 @@ void CompileBroker::possibly_add_compiler_threads(Thread* THREAD) {
_compiler2_objects[i] = thread_handle;
}
#endif
JavaThread *ct = make_thread(compiler2_object(i), _c2_compile_queue, _compilers[1], THREAD);
JavaThread *ct = make_thread(compiler_t, compiler2_object(i), _c2_compile_queue, _compilers[1], THREAD);
if (ct == NULL) break;
_compilers[1]->set_num_compiler_threads(i + 1);
if (TraceCompilerThreads) {
@ -1031,7 +1123,7 @@ void CompileBroker::possibly_add_compiler_threads(Thread* THREAD) {
(int)(available_cc_p / (128*K)));
for (int i = old_c1_count; i < new_c1_count; i++) {
JavaThread *ct = make_thread(compiler1_object(i), _c1_compile_queue, _compilers[0], THREAD);
JavaThread *ct = make_thread(compiler_t, compiler1_object(i), _c1_compile_queue, _compilers[0], THREAD);
if (ct == NULL) break;
_compilers[0]->set_num_compiler_threads(i + 1);
if (TraceCompilerThreads) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2020, 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
@ -228,8 +228,14 @@ class CompileBroker: AllStatic {
static volatile int _print_compilation_warning;
enum ThreadType {
compiler_t,
sweeper_t,
deoptimizer_t
};
static Handle create_thread_oop(const char* name, TRAPS);
static JavaThread* make_thread(jobject thread_oop, CompileQueue* queue, AbstractCompiler* comp, Thread* THREAD);
static JavaThread* make_thread(ThreadType type, jobject thread_oop, CompileQueue* queue, AbstractCompiler* comp, Thread* THREAD);
static void init_compiler_sweeper_threads();
static void possibly_add_compiler_threads(Thread* THREAD);
static bool compilation_is_prohibited(const methodHandle& method, int osr_bci, int comp_level, bool excluded);

View File

@ -33,6 +33,7 @@
#include "jfr/leakprofiler/checkpoint/rootResolver.hpp"
#include "jfr/utilities/jfrThreadIterator.hpp"
#include "memory/iterator.hpp"
#include "prims/jvmtiDeferredUpdates.hpp"
#include "oops/klass.hpp"
#include "oops/oop.hpp"
#include "prims/jvmtiThreadState.hpp"
@ -279,7 +280,7 @@ bool ReferenceToThreadRootClosure::do_thread_stack_detailed(JavaThread* jt) {
return true;
}
GrowableArray<jvmtiDeferredLocalVariableSet*>* const list = jt->deferred_locals();
GrowableArray<jvmtiDeferredLocalVariableSet*>* const list = JvmtiDeferredUpdates::deferred_locals(jt);
if (list != NULL) {
for (int i = 0; i < list->length(); i++) {
list->at(i)->oops_do(&rcl);

View File

@ -1188,7 +1188,11 @@ void CodeInstaller::record_scope(jint pc_offset, JVMCIObject position, ScopeMode
throw_exception = jvmci_env()->get_BytecodeFrame_rethrowException(frame) == JNI_TRUE;
}
// has_ea_local_in_scope and arg_escape should be added to JVMCI
const bool has_ea_local_in_scope = false;
const bool arg_escape = false;
_debug_recorder->describe_scope(pc_offset, method, NULL, bci, reexecute, throw_exception, is_mh_invoke, return_oop,
has_ea_local_in_scope, arg_escape,
locals_token, expressions_token, monitors_token);
}

View File

@ -93,8 +93,7 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo
assert(is_initialized(), "Compiler thread must be initialized");
bool subsume_loads = SubsumeLoads;
bool do_escape_analysis = DoEscapeAnalysis && !env->should_retain_local_variables()
&& !env->jvmti_can_get_owned_monitor_info();
bool do_escape_analysis = DoEscapeAnalysis;
bool eliminate_boxing = EliminateAutoBox;
while (!env->failing()) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, 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
@ -329,7 +329,8 @@ public:
const TypePtr* adr_type = NULL)
: MultiNode( edges ),
_jvms(jvms),
_adr_type(adr_type)
_adr_type(adr_type),
_has_ea_local_in_scope(false)
{
init_class_id(Class_SafePoint);
}
@ -337,6 +338,7 @@ public:
JVMState* const _jvms; // Pointer to list of JVM State objects
const TypePtr* _adr_type; // What type of memory does this node produce?
ReplacedNodes _replaced_nodes; // During parsing: list of pair of nodes from calls to GraphKit::replace_in_map()
bool _has_ea_local_in_scope; // NoEscape or ArgEscape objects in JVM States
// Many calls take *all* of memory as input,
// but some produce a limited subset of that memory as output.
@ -456,6 +458,12 @@ public:
bool has_replaced_nodes() const {
return !_replaced_nodes.is_empty();
}
void set_has_ea_local_in_scope(bool b) {
_has_ea_local_in_scope = b;
}
bool has_ea_local_in_scope() const {
return _has_ea_local_in_scope;
}
void disconnect_from_root(PhaseIterGVN *igvn);
@ -658,6 +666,7 @@ protected:
bool _method_handle_invoke;
bool _override_symbolic_info; // Override symbolic call site info from bytecode
ciMethod* _method; // Method being direct called
bool _arg_escape; // ArgEscape in parameter list
public:
const int _bci; // Byte Code Index of call byte code
CallJavaNode(const TypeFunc* tf , address addr, ciMethod* method, int bci)
@ -665,7 +674,8 @@ public:
_optimized_virtual(false),
_method_handle_invoke(false),
_override_symbolic_info(false),
_method(method), _bci(bci)
_method(method),
_arg_escape(false), _bci(bci)
{
init_class_id(Class_CallJava);
}
@ -679,6 +689,8 @@ public:
bool is_method_handle_invoke() const { return _method_handle_invoke; }
void set_override_symbolic_info(bool f) { _override_symbolic_info = f; }
bool override_symbolic_info() const { return _override_symbolic_info; }
void set_arg_escape(bool f) { _arg_escape = f; }
bool arg_escape() const { return _arg_escape; }
void copy_call_debug_info(PhaseIterGVN* phase, SafePointNode *sfpt);
DEBUG_ONLY( bool validate_symbolic_info() const; )

View File

@ -123,6 +123,7 @@ bool ConnectionGraph::compute_escape() {
GrowableArray<JavaObjectNode*> java_objects_worklist;
GrowableArray<JavaObjectNode*> non_escaped_worklist;
GrowableArray<FieldNode*> oop_fields_worklist;
GrowableArray<SafePointNode*> sfn_worklist;
DEBUG_ONLY( GrowableArray<Node*> addp_worklist; )
{ Compile::TracePhase tp("connectionGraph", &Phase::timers[Phase::_t_connectionGraph]);
@ -188,6 +189,9 @@ bool ConnectionGraph::compute_escape() {
Node* m = n->fast_out(i); // Get user
ideal_nodes.push(m);
}
if (n-> is_SafePoint()) {
sfn_worklist.append(n->as_SafePoint());
}
}
if (non_escaped_worklist.length() == 0) {
_collecting = false;
@ -317,9 +321,89 @@ bool ConnectionGraph::compute_escape() {
tty->cr();
#endif
}
// Annotate at safepoints if they have <= ArgEscape objects in their scope and at
// java calls if they pass ArgEscape objects as parameters.
if (has_non_escaping_obj &&
(C->env()->should_retain_local_variables() ||
C->env()->jvmti_can_get_owned_monitor_info() ||
C->env()->jvmti_can_walk_any_space() ||
DeoptimizeObjectsALot)) {
int sfn_length = sfn_worklist.length();
for (int next = 0; next < sfn_length; next++) {
SafePointNode* sfn = sfn_worklist.at(next);
sfn->set_has_ea_local_in_scope(has_ea_local_in_scope(sfn));
if (sfn->is_CallJava()) {
CallJavaNode* call = sfn->as_CallJava();
call->set_arg_escape(has_arg_escape(call));
}
}
}
return has_non_escaping_obj;
}
// Returns true if there is an object in the scope of sfn that does not escape globally.
bool ConnectionGraph::has_ea_local_in_scope(SafePointNode* sfn) {
Compile* C = _compile;
for (JVMState* jvms = sfn->jvms(); jvms != NULL; jvms = jvms->caller()) {
if (C->env()->should_retain_local_variables() || C->env()->jvmti_can_walk_any_space() ||
DeoptimizeObjectsALot) {
// Jvmti agents can access locals. Must provide info about local objects at runtime.
int num_locs = jvms->loc_size();
for (int idx = 0; idx < num_locs; idx++) {
Node* l = sfn->local(jvms, idx);
if (not_global_escape(l)) {
return true;
}
}
}
if (C->env()->jvmti_can_get_owned_monitor_info() ||
C->env()->jvmti_can_walk_any_space() || DeoptimizeObjectsALot) {
// Jvmti agents can read monitors. Must provide info about locked objects at runtime.
int num_mon = jvms->nof_monitors();
for (int idx = 0; idx < num_mon; idx++) {
Node* m = sfn->monitor_obj(jvms, idx);
if (m != NULL && not_global_escape(m)) {
return true;
}
}
}
}
return false;
}
// Returns true if at least one of the arguments to the call is an object
// that does not escape globally.
bool ConnectionGraph::has_arg_escape(CallJavaNode* call) {
if (call->method() != NULL) {
uint max_idx = TypeFunc::Parms + call->method()->arg_size();
for (uint idx = TypeFunc::Parms; idx < max_idx; idx++) {
Node* p = call->in(idx);
if (not_global_escape(p)) {
return true;
}
}
} else {
const char* name = call->as_CallStaticJava()->_name;
assert(name != NULL, "no name");
// no arg escapes through uncommon traps
if (strcmp(name, "uncommon_trap") != 0) {
// process_call_arguments() assumes that all arguments escape globally
const TypeTuple* d = call->tf()->domain();
for (uint i = TypeFunc::Parms; i < d->cnt(); i++) {
const Type* at = d->field_at(i);
if (at->isa_oopptr() != NULL) {
return true;
}
}
}
}
return false;
}
// Utility function for nodes that load an object
void ConnectionGraph::add_objload_to_connection_graph(Node *n, Unique_Node_List *delayed_worklist) {
// Using isa_ptr() instead of isa_oopptr() for LoadP and Phi because

View File

@ -553,6 +553,11 @@ private:
return (phi == NULL) ? NULL : phi->as_Phi();
}
// Returns true if there is an object in the scope of sfn that does not escape globally.
bool has_ea_local_in_scope(SafePointNode* sfn);
bool has_arg_escape(CallJavaNode* call);
// Notify optimizer that a node has been modified
void record_for_optimizer(Node *n);

View File

@ -820,10 +820,11 @@ public:
OopMap* _oop_map; // Array of OopMap info (8-bit char) for GC
JVMState* _jvms; // Pointer to list of JVM State Objects
uint _jvmadj; // Extra delta to jvms indexes (mach. args)
bool _has_ea_local_in_scope; // NoEscape or ArgEscape objects in JVM States
OopMap* oop_map() const { return _oop_map; }
void set_oop_map(OopMap* om) { _oop_map = om; }
MachSafePointNode() : MachReturnNode(), _oop_map(NULL), _jvms(NULL), _jvmadj(0) {
MachSafePointNode() : MachReturnNode(), _oop_map(NULL), _jvms(NULL), _jvmadj(0), _has_ea_local_in_scope(false) {
init_class_id(Class_MachSafePoint);
}
@ -922,6 +923,7 @@ public:
int _bci; // Byte Code index of call byte code
bool _optimized_virtual; // Tells if node is a static call or an optimized virtual
bool _method_handle_invoke; // Tells if the call has to preserve SP
bool _arg_escape; // ArgEscape in parameter list
MachCallJavaNode() : MachCallNode(), _override_symbolic_info(false) {
init_class_id(Class_MachCallJava);
}

View File

@ -1051,11 +1051,12 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) {
}
bool PhaseMacroExpand::eliminate_allocate_node(AllocateNode *alloc) {
// Don't do scalar replacement if the frame can be popped by JVMTI:
// if reallocation fails during deoptimization we'll pop all
// If reallocation fails during deoptimization we'll pop all
// interpreter frames for this compiled frame and that won't play
// nice with JVMTI popframe.
if (!EliminateAllocations || JvmtiExport::can_pop_frame() || !alloc->_is_non_escaping) {
// We avoid this issue by eager reallocation when the popframe request
// is received.
if (!EliminateAllocations || !alloc->_is_non_escaping) {
return false;
}
Node* klass = alloc->in(AllocateNode::KlassNode);

View File

@ -1251,6 +1251,7 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) {
is_method_handle_invoke = call_java->is_method_handle_invoke();
mcall_java->_method_handle_invoke = is_method_handle_invoke;
mcall_java->_override_symbolic_info = call_java->override_symbolic_info();
mcall_java->_arg_escape = call_java->arg_escape();
if (is_method_handle_invoke) {
C->set_has_method_handle_invokes(true);
}
@ -1275,6 +1276,7 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) {
msfpt = mn->as_MachSafePoint();
cnt = TypeFunc::Parms;
}
msfpt->_has_ea_local_in_scope = sfpt->has_ea_local_in_scope();
// Advertise the correct memory effects (for anti-dependence computation).
msfpt->set_adr_type(sfpt->adr_type());

View File

@ -1003,6 +1003,8 @@ void PhaseOutput::Process_OopMap_Node(MachNode *mach, int current_offset) {
int safepoint_pc_offset = current_offset;
bool is_method_handle_invoke = false;
bool return_oop = false;
bool has_ea_local_in_scope = sfn->_has_ea_local_in_scope;
bool arg_escape = false;
// Add the safepoint in the DebugInfoRecorder
if( !mach->is_MachCall() ) {
@ -1017,6 +1019,7 @@ void PhaseOutput::Process_OopMap_Node(MachNode *mach, int current_offset) {
assert(C->has_method_handle_invokes(), "must have been set during call generation");
is_method_handle_invoke = true;
}
arg_escape = mcall->as_MachCallJava()->_arg_escape;
}
// Check if a call returns an object.
@ -1137,7 +1140,10 @@ void PhaseOutput::Process_OopMap_Node(MachNode *mach, int current_offset) {
// Now we can describe the scope.
methodHandle null_mh;
bool rethrow_exception = false;
C->debug_info()->describe_scope(safepoint_pc_offset, null_mh, scope_method, jvms->bci(), jvms->should_reexecute(), rethrow_exception, is_method_handle_invoke, return_oop, locvals, expvals, monvals);
C->debug_info()->describe_scope(safepoint_pc_offset, null_mh, scope_method, jvms->bci(),
jvms->should_reexecute(), rethrow_exception, is_method_handle_invoke,
return_oop, has_ea_local_in_scope, arg_escape,
locvals, expvals, monvals);
} // End jvms loop
// Mark the end of the scope set.

View File

@ -275,7 +275,7 @@ void JvmtiCodeBlobEvents::build_jvmti_addr_location_map(nmethod *nm,
address scopes_data = nm->scopes_data_begin();
for( pcd = nm->scopes_pcs_begin(); pcd < nm->scopes_pcs_end(); ++pcd ) {
ScopeDesc sc0(nm, pcd->scope_decode_offset(), pcd->should_reexecute(), pcd->rethrow_exception(), pcd->return_oop());
ScopeDesc sc0(nm, pcd, true);
ScopeDesc *sd = &sc0;
while( !sd->is_top() ) { sd = sd->sender(); }
int bci = sd->bci();

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) 1997, 2020, 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 "prims/jvmtiDeferredUpdates.hpp"
void JvmtiDeferredUpdates::create_for(JavaThread* thread) {
assert(thread->deferred_updates() == NULL, "already allocated");
thread->set_deferred_updates(new JvmtiDeferredUpdates());
}
JvmtiDeferredUpdates::~JvmtiDeferredUpdates() {
while (_deferred_locals_updates.length() != 0) {
jvmtiDeferredLocalVariableSet* dlv = _deferred_locals_updates.pop();
// individual jvmtiDeferredLocalVariableSet are CHeapObj's
delete dlv;
}
}
void JvmtiDeferredUpdates::inc_relock_count_after_wait(JavaThread* thread) {
if (thread->deferred_updates() == NULL) {
create_for(thread);
}
thread->deferred_updates()->inc_relock_count_after_wait();
}
int JvmtiDeferredUpdates::get_and_reset_relock_count_after_wait(JavaThread* jt) {
JvmtiDeferredUpdates* updates = jt->deferred_updates();
int result = 0;
if (updates != NULL) {
result = updates->get_and_reset_relock_count_after_wait();
if (updates->count() == 0) {
delete updates;
jt->set_deferred_updates(NULL);
}
}
return result;
}
void JvmtiDeferredUpdates::delete_updates_for_frame(JavaThread* jt, intptr_t* frame_id) {
JvmtiDeferredUpdates* updates = jt->deferred_updates();
if (updates != NULL) {
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = updates->deferred_locals();
assert(list->length() > 0, "Updates holder not deleted");
int i = 0;
do {
// Because of inlining we could have multiple vframes for a single frame
// and several of the vframes could have deferred writes. Find them all.
jvmtiDeferredLocalVariableSet* dlv = list->at(i);
if (dlv->id() == frame_id) {
list->remove_at(i);
// individual jvmtiDeferredLocalVariableSet are CHeapObj's
delete dlv;
} else {
i++;
}
} while ( i < list->length() );
if (updates->count() == 0) {
jt->set_deferred_updates(NULL);
// Free deferred updates.
// Note, the 'list' of local variable updates is embedded in 'updates'.
delete updates;
}
}
}

View File

@ -0,0 +1,159 @@
/*
* Copyright (c) 1997, 2020, 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.
*
*/
#ifndef SHARE_PRIMS_JVMTIDEFERREDUPDATES_HPP
#define SHARE_PRIMS_JVMTIDEFERREDUPDATES_HPP
#include "runtime/thread.inline.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/growableArray.hpp"
class jvmtiDeferredLocalVariable : public CHeapObj<mtCompiler> {
private:
BasicType _type;
jvalue _value;
int _index;
public:
jvmtiDeferredLocalVariable(int index, BasicType type, jvalue value);
BasicType type(void) { return _type; }
int index(void) { return _index; }
jvalue value(void) { return _value; }
// Only mutator is for value as only it can change
void set_value(jvalue value) { _value = value; }
// For gc
oop* oop_addr(void) { return (oop*) &_value.l; }
};
// In order to implement set_locals for compiled vframes we must
// store updated locals in a data structure that contains enough
// information to recognize equality with a vframe and to store
// any updated locals.
class StackValueCollection;
class jvmtiDeferredLocalVariableSet : public CHeapObj<mtCompiler> {
friend class compiledVFrame;
private:
Method* _method;
int _bci;
intptr_t* _id;
int _vframe_id;
GrowableArray<jvmtiDeferredLocalVariable*>* _locals;
bool _objects_are_deoptimized;
void update_value(StackValueCollection* locals, BasicType type, int index, jvalue value);
void set_value_at(int idx, BasicType typ, jvalue val);
public:
// JVM state
Method* method() const { return _method; }
int bci() const { return _bci; }
intptr_t* id() const { return _id; }
int vframe_id() const { return _vframe_id; }
bool objects_are_deoptimized() const { return _objects_are_deoptimized; }
void update_locals(StackValueCollection* locals);
void update_stack(StackValueCollection* locals);
void update_monitors(GrowableArray<MonitorInfo*>* monitors);
void set_objs_are_deoptimized() { _objects_are_deoptimized = true; }
// Does the vframe match this jvmtiDeferredLocalVariableSet
bool matches(const vframe* vf);
// Does the underlying physical frame match this jvmtiDeferredLocalVariableSet
bool matches(intptr_t* fr_id) { return id() == fr_id; }
// GC
void oops_do(OopClosure* f);
// constructor
jvmtiDeferredLocalVariableSet(Method* method, int bci, intptr_t* id, int vframe_id);
// destructor
~jvmtiDeferredLocalVariableSet();
};
// Holds updates for compiled frames by JVMTI agents that cannot be performed immediately.
class JvmtiDeferredUpdates : public CHeapObj<mtCompiler> {
// Relocking has to be deferred if the lock owning thread is currently waiting on the monitor.
int _relock_count_after_wait;
// Deferred updates of locals, expressions, and monitors
GrowableArray<jvmtiDeferredLocalVariableSet*> _deferred_locals_updates;
void inc_relock_count_after_wait() {
_relock_count_after_wait++;
}
int get_and_reset_relock_count_after_wait() {
int result = _relock_count_after_wait;
_relock_count_after_wait = 0;
return result;
}
GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred_locals() { return &_deferred_locals_updates; }
JvmtiDeferredUpdates() :
_relock_count_after_wait(0),
_deferred_locals_updates((ResourceObj::set_allocation_type((address) &_deferred_locals_updates,
ResourceObj::C_HEAP), 1), mtCompiler) { }
public:
~JvmtiDeferredUpdates();
static void create_for(JavaThread* thread);
static GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred_locals(JavaThread* jt) {
return jt->deferred_updates() == NULL ? NULL : jt->deferred_updates()->deferred_locals();
}
// Relocking has to be deferred if the lock owning thread is currently waiting on the monitor.
static int get_and_reset_relock_count_after_wait(JavaThread* jt);
static void inc_relock_count_after_wait(JavaThread* thread);
// Delete deferred updates for the compiled frame with id 'frame_id' on the
// given thread's stack. The thread's JvmtiDeferredUpdates instance will be
// deleted too if no updates remain.
static void delete_updates_for_frame(JavaThread* jt, intptr_t* frame_id);
// Number of deferred updates
int count() const {
return _deferred_locals_updates.length() + (_relock_count_after_wait > 0 ? 1 : 0);
}
};
#endif // SHARE_PRIMS_JVMTIDEFERREDUPDATES_HPP

View File

@ -1206,6 +1206,11 @@ JvmtiEnv::GetOwnedMonitorInfo(JavaThread* java_thread, jint* owned_monitor_count
GrowableArray<jvmtiMonitorStackDepthInfo*> *owned_monitors_list =
new (ResourceObj::C_HEAP, mtServiceability) GrowableArray<jvmtiMonitorStackDepthInfo*>(1, mtServiceability);
EscapeBarrier eb(true, calling_thread, java_thread);
if (!eb.deoptimize_objects(MaxJavaStackTraceDepth)) {
return JVMTI_ERROR_OUT_OF_MEMORY;
}
// It is only safe to perform the direct operation on the current
// thread. All other usage needs to use a direct handshake for safety.
if (java_thread == calling_thread) {
@ -1251,6 +1256,11 @@ JvmtiEnv::GetOwnedMonitorStackDepthInfo(JavaThread* java_thread, jint* monitor_i
GrowableArray<jvmtiMonitorStackDepthInfo*> *owned_monitors_list =
new (ResourceObj::C_HEAP, mtServiceability) GrowableArray<jvmtiMonitorStackDepthInfo*>(1, mtServiceability);
EscapeBarrier eb(true, calling_thread, java_thread);
if (!eb.deoptimize_objects(MaxJavaStackTraceDepth)) {
return JVMTI_ERROR_OUT_OF_MEMORY;
}
// It is only safe to perform the direct operation on the current
// thread. All other usage needs to use a direct handshake for safety.
if (java_thread == calling_thread) {
@ -1666,6 +1676,13 @@ JvmtiEnv::PopFrame(JavaThread* java_thread) {
}
}
if (java_thread->frames_to_pop_failed_realloc() > 0) {
// VM is in the process of popping the top frame because it has scalar replaced objects which
// could not be reallocated on the heap.
// Return JVMTI_ERROR_OUT_OF_MEMORY to avoid interfering with the VM.
return JVMTI_ERROR_OUT_OF_MEMORY;
}
{
ResourceMark rm(current_thread);
// Check if there are more than one Java frame in this thread, that the top two frames
@ -1697,9 +1714,15 @@ JvmtiEnv::PopFrame(JavaThread* java_thread) {
}
// If any of the top 2 frames is a compiled one, need to deoptimize it
EscapeBarrier eb(!is_interpreted[0] || !is_interpreted[1], current_thread, java_thread);
for (int i = 0; i < 2; i++) {
if (!is_interpreted[i]) {
Deoptimization::deoptimize_frame(java_thread, frame_sp[i]);
// Eagerly reallocate scalar replaced objects.
if (!eb.deoptimize_objects(frame_sp[i])) {
// Reallocation of scalar replaced objects failed -> return with error
return JVMTI_ERROR_OUT_OF_MEMORY;
}
}
}

View File

@ -1306,10 +1306,17 @@ VM_GetAllStackTraces::doit() {
// HandleMark must be defined in the caller only.
// It is to keep a ret_ob_h handle alive after return to the caller.
jvmtiError
JvmtiEnvBase::check_top_frame(Thread* current_thread, JavaThread* java_thread,
JvmtiEnvBase::check_top_frame(JavaThread* current_thread, JavaThread* java_thread,
jvalue value, TosState tos, Handle* ret_ob_h) {
ResourceMark rm(current_thread);
if (java_thread->frames_to_pop_failed_realloc() > 0) {
// VM is in the process of popping the top frame because it has scalar replaced objects
// which could not be reallocated on the heap.
// Return JVMTI_ERROR_OUT_OF_MEMORY to avoid interfering with the VM.
return JVMTI_ERROR_OUT_OF_MEMORY;
}
vframe *vf = vframeForNoProcess(java_thread, 0);
NULL_CHECK(vf, JVMTI_ERROR_NO_MORE_FRAMES);
@ -1324,6 +1331,12 @@ JvmtiEnvBase::check_top_frame(Thread* current_thread, JavaThread* java_thread,
return JVMTI_ERROR_OPAQUE_FRAME;
}
Deoptimization::deoptimize_frame(java_thread, jvf->fr().id());
// Eagerly reallocate scalar replaced objects.
EscapeBarrier eb(true, current_thread, java_thread);
if (!eb.deoptimize_objects(jvf->fr().id())) {
// Reallocation of scalar replaced objects failed -> return with error
return JVMTI_ERROR_OUT_OF_MEMORY;
}
}
// Get information about method return type
@ -1369,7 +1382,7 @@ JvmtiEnvBase::check_top_frame(Thread* current_thread, JavaThread* java_thread,
jvmtiError
JvmtiEnvBase::force_early_return(JavaThread* java_thread, jvalue value, TosState tos) {
Thread* current_thread = Thread::current();
JavaThread* current_thread = JavaThread::current();
HandleMark hm(current_thread);
uint32_t debug_bits = 0;

View File

@ -306,7 +306,7 @@ class JvmtiEnvBase : public CHeapObj<mtInternal> {
jobject *monitor_ptr);
jvmtiError get_owned_monitors(JavaThread *calling_thread, JavaThread* java_thread,
GrowableArray<jvmtiMonitorStackDepthInfo*> *owned_monitors_list);
jvmtiError check_top_frame(Thread* current_thread, JavaThread* java_thread,
jvmtiError check_top_frame(JavaThread* current_thread, JavaThread* java_thread,
jvalue value, TosState tos, Handle* ret_ob_h);
jvmtiError force_early_return(JavaThread* java_thread, jvalue value, TosState tos);
};

View File

@ -426,6 +426,7 @@ VM_GetOrSetLocal::VM_GetOrSetLocal(JavaThread* thread, jint depth, jint index, B
, _type(type)
, _jvf(NULL)
, _set(false)
, _eb(false, NULL, NULL)
, _result(JVMTI_ERROR_NONE)
{
}
@ -440,6 +441,7 @@ VM_GetOrSetLocal::VM_GetOrSetLocal(JavaThread* thread, jint depth, jint index, B
, _value(value)
, _jvf(NULL)
, _set(true)
, _eb(type == T_OBJECT, JavaThread::current(), thread)
, _result(JVMTI_ERROR_NONE)
{
}
@ -453,6 +455,7 @@ VM_GetOrSetLocal::VM_GetOrSetLocal(JavaThread* thread, JavaThread* calling_threa
, _type(T_OBJECT)
, _jvf(NULL)
, _set(false)
, _eb(true, calling_thread, thread)
, _result(JVMTI_ERROR_NONE)
{
}
@ -625,8 +628,64 @@ static bool can_be_deoptimized(vframe* vf) {
return (vf->is_compiled_frame() && vf->fr().can_be_deoptimized());
}
// Revert optimizations based on escape analysis if this is an access to a local object
bool VM_GetOrSetLocal::deoptimize_objects(javaVFrame* jvf) {
#if COMPILER2_OR_JVMCI
assert(_type == T_OBJECT, "EscapeBarrier should not be active if _type != T_OBJECT");
if (_depth < _thread->frames_to_pop_failed_realloc()) {
// cannot access frame with failed reallocations
_result = JVMTI_ERROR_OUT_OF_MEMORY;
return false;
}
if (can_be_deoptimized(jvf)) {
compiledVFrame* cf = compiledVFrame::cast(jvf);
if (cf->has_ea_local_in_scope() && !_eb.deoptimize_objects(cf->fr().id())) {
// reallocation of scalar replaced objects failed because heap is exhausted
_result = JVMTI_ERROR_OUT_OF_MEMORY;
return false;
}
}
// With this access the object could escape the thread changing its escape state from ArgEscape,
// to GlobalEscape so we must deoptimize callers which could have optimized on the escape state.
vframe* vf = jvf;
do {
// move to next physical frame
while(!vf->is_top()) {
vf = vf->sender();
}
vf = vf->sender();
if (vf != NULL && vf->is_compiled_frame()) {
compiledVFrame* cvf = compiledVFrame::cast(vf);
// Deoptimize objects if arg escape is being passed down the stack.
// Note that deoptimizing the frame is not enough because objects need to be relocked
if (cvf->arg_escape() && !_eb.deoptimize_objects(cvf->fr().id())) {
// reallocation of scalar replaced objects failed because heap is exhausted
_result = JVMTI_ERROR_OUT_OF_MEMORY;
return false;
}
}
} while(vf != NULL && !vf->is_entry_frame());
#endif // COMPILER2_OR_JVMCI
return true;
}
bool VM_GetOrSetLocal::doit_prologue() {
if (_eb.barrier_active()) {
_jvf = get_java_vframe();
NULL_CHECK(_jvf, false);
if (!deoptimize_objects(_jvf)) {
return false;
}
}
return true;
}
void VM_GetOrSetLocal::doit() {
_jvf = get_java_vframe();
_jvf = _jvf == NULL ? get_java_vframe() : _jvf;
if (_jvf == NULL) {
return;
};

View File

@ -32,6 +32,7 @@
#include "prims/jvmtiEventController.hpp"
#include "prims/jvmtiTrace.hpp"
#include "prims/jvmtiUtil.hpp"
#include "runtime/escapeBarrier.hpp"
#include "runtime/stackValueCollection.hpp"
#include "runtime/vmOperations.hpp"
#include "utilities/ostream.hpp"
@ -321,6 +322,8 @@ class VM_GetOrSetLocal : public VM_Operation {
javaVFrame* _jvf;
bool _set;
EscapeBarrier _eb;
// It is possible to get the receiver out of a non-static native wrapper
// frame. Use VM_GetReceiver to do this.
virtual bool getting_receiver() const { return false; }
@ -331,6 +334,7 @@ class VM_GetOrSetLocal : public VM_Operation {
javaVFrame* get_java_vframe();
bool check_slot_type_lvt(javaVFrame* vf);
bool check_slot_type_no_lvt(javaVFrame* vf);
bool deoptimize_objects(javaVFrame* vf);
public:
// Constructor for non-object getter
@ -347,6 +351,7 @@ public:
jvalue value() { return _value; }
jvmtiError result() { return _result; }
bool doit_prologue();
void doit();
bool allow_nested_vm_operations() const;
const char* name() const { return "get/set locals"; }

View File

@ -47,6 +47,7 @@
#include "prims/jvmtiImpl.hpp"
#include "prims/jvmtiTagMap.hpp"
#include "runtime/biasedLocking.hpp"
#include "runtime/deoptimization.hpp"
#include "runtime/frame.inline.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/javaCalls.hpp"
@ -1482,6 +1483,11 @@ void JvmtiTagMap::iterate_over_heap(jvmtiHeapObjectFilter object_filter,
jvmtiHeapObjectCallback heap_object_callback,
const void* user_data)
{
// EA based optimizations on tagged objects are already reverted.
EscapeBarrier eb(object_filter == JVMTI_HEAP_OBJECT_UNTAGGED ||
object_filter == JVMTI_HEAP_OBJECT_EITHER,
JavaThread::current());
eb.deoptimize_objects_all_threads();
MutexLocker ml(Heap_lock);
IterateOverHeapObjectClosure blk(this,
klass,
@ -1499,6 +1505,9 @@ void JvmtiTagMap::iterate_through_heap(jint heap_filter,
const jvmtiHeapCallbacks* callbacks,
const void* user_data)
{
// EA based optimizations on tagged objects are already reverted.
EscapeBarrier eb(!(heap_filter & JVMTI_HEAP_FILTER_UNTAGGED), JavaThread::current());
eb.deoptimize_objects_all_threads();
MutexLocker ml(Heap_lock);
IterateThroughHeapObjectClosure blk(this,
klass,
@ -3246,6 +3255,9 @@ void JvmtiTagMap::iterate_over_reachable_objects(jvmtiHeapRootCallback heap_root
jvmtiStackReferenceCallback stack_ref_callback,
jvmtiObjectReferenceCallback object_ref_callback,
const void* user_data) {
JavaThread* jt = JavaThread::current();
EscapeBarrier eb(true, jt);
eb.deoptimize_objects_all_threads();
MutexLocker ml(Heap_lock);
BasicHeapWalkContext context(heap_root_callback, stack_ref_callback, object_ref_callback);
VM_HeapWalkOperation op(this, Handle(), context, user_data);
@ -3273,8 +3285,13 @@ void JvmtiTagMap::follow_references(jint heap_filter,
const void* user_data)
{
oop obj = JNIHandles::resolve(object);
Handle initial_object(Thread::current(), obj);
JavaThread* jt = JavaThread::current();
Handle initial_object(jt, obj);
// EA based optimizations that are tagged or reachable from initial_object are already reverted.
EscapeBarrier eb(initial_object.is_null() &&
!(heap_filter & JVMTI_HEAP_FILTER_UNTAGGED),
jt);
eb.deoptimize_objects_all_threads();
MutexLocker ml(Heap_lock);
AdvancedHeapWalkContext context(heap_filter, klass, callbacks);
VM_HeapWalkOperation op(this, initial_object, context, user_data);

View File

@ -79,6 +79,7 @@
#include "runtime/synchronizer.hpp"
#include "runtime/thread.hpp"
#include "runtime/threadSMR.hpp"
#include "runtime/vframe.hpp"
#include "runtime/vm_version.hpp"
#include "services/memoryService.hpp"
#include "utilities/align.hpp"
@ -882,6 +883,19 @@ WB_ENTRY(jint, WB_DeoptimizeFrames(JNIEnv* env, jobject o, jboolean make_not_ent
return op.result();
WB_END
WB_ENTRY(jboolean, WB_IsFrameDeoptimized(JNIEnv* env, jobject o, jint depth))
bool result = false;
if (thread->has_last_Java_frame()) {
RegisterMap reg_map(thread);
javaVFrame *jvf = thread->last_java_vframe(&reg_map);
for (jint d = 0; d < depth && jvf != NULL; d++) {
jvf = jvf->java_sender();
}
result = jvf != NULL && jvf->fr().is_deoptimized_frame();
}
return result;
WB_END
WB_ENTRY(void, WB_DeoptimizeAll(JNIEnv* env, jobject o))
CodeCache::mark_all_nmethods_for_deoptimization();
Deoptimization::deoptimize_all_marked();
@ -2426,6 +2440,7 @@ static JNINativeMethod methods[] = {
{CC"NMTArenaMalloc", CC"(JJ)V", (void*)&WB_NMTArenaMalloc },
#endif // INCLUDE_NMT
{CC"deoptimizeFrames", CC"(Z)I", (void*)&WB_DeoptimizeFrames },
{CC"isFrameDeoptimized", CC"(I)Z", (void*)&WB_IsFrameDeoptimized},
{CC"deoptimizeAll", CC"()V", (void*)&WB_DeoptimizeAll },
{CC"deoptimizeMethod0", CC"(Ljava/lang/reflect/Executable;Z)I",
(void*)&WB_DeoptimizeMethod },

View File

@ -48,18 +48,21 @@
#include "oops/fieldStreams.inline.hpp"
#include "oops/typeArrayOop.inline.hpp"
#include "oops/verifyOopClosure.hpp"
#include "prims/jvmtiDeferredUpdates.hpp"
#include "prims/jvmtiThreadState.hpp"
#include "prims/vectorSupport.hpp"
#include "prims/methodHandles.hpp"
#include "runtime/atomic.hpp"
#include "runtime/biasedLocking.hpp"
#include "runtime/deoptimization.hpp"
#include "runtime/escapeBarrier.hpp"
#include "runtime/fieldDescriptor.hpp"
#include "runtime/fieldDescriptor.inline.hpp"
#include "runtime/frame.inline.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/jniHandles.inline.hpp"
#include "runtime/objectMonitor.inline.hpp"
#include "runtime/safepointVerifiers.hpp"
#include "runtime/sharedRuntime.hpp"
#include "runtime/signature.hpp"
@ -175,10 +178,15 @@ JRT_END
#if COMPILER2_OR_JVMCI
static bool eliminate_allocations(JavaThread* thread, int exec_mode, CompiledMethod* compiled_method,
frame& deoptee, RegisterMap& map, GrowableArray<compiledVFrame*>* chunk) {
frame& deoptee, RegisterMap& map, GrowableArray<compiledVFrame*>* chunk,
bool& deoptimized_objects) {
bool realloc_failures = false;
assert (chunk->at(0)->scope() != NULL,"expect only compiled java frames");
JavaThread* deoptee_thread = chunk->at(0)->thread();
assert(exec_mode == Deoptimization::Unpack_none || (deoptee_thread == thread),
"a frame can only be deoptimized by the owner thread");
GrowableArray<ScopeValue*>* objects = chunk->at(0)->scope()->objects();
// The flag return_oop() indicates call sites which return oop
@ -205,15 +213,28 @@ static bool eliminate_allocations(JavaThread* thread, int exec_mode, CompiledMet
}
}
if (objects != NULL) {
JRT_BLOCK
if (exec_mode == Deoptimization::Unpack_none) {
assert(thread->thread_state() == _thread_in_vm, "assumption");
Thread* THREAD = thread;
// Clear pending OOM if reallocation fails and return true indicating allocation failure
realloc_failures = Deoptimization::realloc_objects(thread, &deoptee, &map, objects, CHECK_AND_CLEAR_(true));
// Make sure the deoptee frame gets processed after a potential safepoint during
// object reallocation. This is necessary because (a) deoptee_thread can be
// different from the current thread and (b) the deoptee frame does not need to be
// the top frame.
StackWatermarkSet::finish_processing(deoptee_thread, NULL /* context */, StackWatermarkKind::gc);
deoptimized_objects = true;
} else {
JRT_BLOCK
realloc_failures = Deoptimization::realloc_objects(thread, &deoptee, &map, objects, THREAD);
JRT_END
JRT_END
}
bool skip_internal = (compiled_method != NULL) && !compiled_method->is_compiled_by_jvmci();
Deoptimization::reassign_fields(&deoptee, &map, objects, realloc_failures, skip_internal);
#ifndef PRODUCT
if (TraceDeoptimization) {
ttyLocker ttyl;
tty->print_cr("REALLOC OBJECTS in thread " INTPTR_FORMAT, p2i(thread));
tty->print_cr("REALLOC OBJECTS in thread " INTPTR_FORMAT, p2i(deoptee_thread));
Deoptimization::print_objects(objects, realloc_failures);
}
#endif
@ -225,7 +246,10 @@ static bool eliminate_allocations(JavaThread* thread, int exec_mode, CompiledMet
return realloc_failures;
}
static void eliminate_locks(JavaThread* thread, GrowableArray<compiledVFrame*>* chunk, bool realloc_failures) {
static void eliminate_locks(JavaThread* thread, GrowableArray<compiledVFrame*>* chunk, bool realloc_failures,
frame& deoptee, int exec_mode, bool& deoptimized_objects) {
JavaThread* deoptee_thread = chunk->at(0)->thread();
assert(!EscapeBarrier::objs_are_deoptimized(deoptee_thread, deoptee.id()), "must relock just once");
assert(thread == Thread::current(), "should be");
HandleMark hm(thread);
#ifndef PRODUCT
@ -236,7 +260,9 @@ static void eliminate_locks(JavaThread* thread, GrowableArray<compiledVFrame*>*
assert (cvf->scope() != NULL,"expect only compiled java frames");
GrowableArray<MonitorInfo*>* monitors = cvf->monitors();
if (monitors->is_nonempty()) {
Deoptimization::relock_objects(monitors, thread, realloc_failures);
bool relocked = Deoptimization::relock_objects(thread, monitors, deoptee_thread, deoptee,
exec_mode, realloc_failures);
deoptimized_objects = deoptimized_objects || relocked;
#ifndef PRODUCT
if (PrintDeoptimizationDetails) {
ttyLocker ttyl;
@ -247,6 +273,13 @@ static void eliminate_locks(JavaThread* thread, GrowableArray<compiledVFrame*>*
first = false;
tty->print_cr("RELOCK OBJECTS in thread " INTPTR_FORMAT, p2i(thread));
}
if (exec_mode == Deoptimization::Unpack_none) {
ObjectMonitor* monitor = deoptee_thread->current_waiting_monitor();
if (monitor != NULL && (oop)monitor->object() == mi->owner()) {
tty->print_cr(" object <" INTPTR_FORMAT "> DEFERRED relocking after wait", p2i(mi->owner()));
continue;
}
}
if (mi->owner_is_scalar_replaced()) {
Klass* k = java_lang_Class::as_Klass(mi->owner_klass());
tty->print_cr(" failed reallocation for klass %s", k->external_name());
@ -260,6 +293,36 @@ static void eliminate_locks(JavaThread* thread, GrowableArray<compiledVFrame*>*
}
}
}
// Deoptimize objects, that is reallocate and relock them, just before they escape through JVMTI.
// The given vframes cover one physical frame.
bool Deoptimization::deoptimize_objects_internal(JavaThread* thread, GrowableArray<compiledVFrame*>* chunk,
bool& realloc_failures) {
frame deoptee = chunk->at(0)->fr();
JavaThread* deoptee_thread = chunk->at(0)->thread();
CompiledMethod* cm = deoptee.cb()->as_compiled_method_or_null();
RegisterMap map(chunk->at(0)->register_map());
bool deoptimized_objects = false;
bool const jvmci_enabled = JVMCI_ONLY(UseJVMCICompiler) NOT_JVMCI(false);
// Reallocate the non-escaping objects and restore their fields.
if (jvmci_enabled COMPILER2_PRESENT(|| (DoEscapeAnalysis && EliminateAllocations))) {
realloc_failures = eliminate_allocations(thread, Unpack_none, cm, deoptee, map, chunk, deoptimized_objects);
}
// Revoke biases of objects with eliminated locks in the given frame.
Deoptimization::revoke_for_object_deoptimization(deoptee_thread, deoptee, &map, thread);
// MonitorInfo structures used in eliminate_locks are not GC safe.
NoSafepointVerifier no_safepoint;
// Now relock objects if synchronization on them was eliminated.
if (jvmci_enabled COMPILER2_PRESENT(|| ((DoEscapeAnalysis || EliminateNestedLocks) && EliminateLocks))) {
eliminate_locks(thread, chunk, realloc_failures, deoptee, Unpack_none, deoptimized_objects);
}
return deoptimized_objects;
}
#endif // COMPILER2_OR_JVMCI
// This is factored, since it is both called from a JRT_LEAF (deoptimization) and a JRT_ENTRY (uncommon_trap)
@ -318,7 +381,8 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread
// Reallocate the non-escaping objects and restore their fields. Then
// relock objects if synchronization on them was eliminated.
if (jvmci_enabled COMPILER2_PRESENT( || (DoEscapeAnalysis && EliminateAllocations) )) {
realloc_failures = eliminate_allocations(thread, exec_mode, cm, deoptee, map, chunk);
bool unused;
realloc_failures = eliminate_allocations(thread, exec_mode, cm, deoptee, map, chunk, unused);
}
#endif // COMPILER2_OR_JVMCI
@ -333,8 +397,10 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread
NoSafepointVerifier no_safepoint;
#if COMPILER2_OR_JVMCI
if (jvmci_enabled COMPILER2_PRESENT( || ((DoEscapeAnalysis || EliminateNestedLocks) && EliminateLocks) )) {
eliminate_locks(thread, chunk, realloc_failures);
if ((jvmci_enabled COMPILER2_PRESENT( || ((DoEscapeAnalysis || EliminateNestedLocks) && EliminateLocks) ))
&& !EscapeBarrier::objs_are_deoptimized(thread, deoptee.id())) {
bool unused;
eliminate_locks(thread, chunk, realloc_failures, deoptee, exec_mode, unused);
}
#endif // COMPILER2_OR_JVMCI
@ -365,28 +431,7 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread
// added by jvmti then we can free up that structure as the data is now in the
// vframeArray
if (thread->deferred_locals() != NULL) {
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = thread->deferred_locals();
int i = 0;
do {
// Because of inlining we could have multiple vframes for a single frame
// and several of the vframes could have deferred writes. Find them all.
if (list->at(i)->id() == array->original().id()) {
jvmtiDeferredLocalVariableSet* dlv = list->at(i);
list->remove_at(i);
// individual jvmtiDeferredLocalVariableSet are CHeapObj's
delete dlv;
} else {
i++;
}
} while ( i < list->length() );
if (list->length() == 0) {
thread->set_deferred_locals(NULL);
// free the list and elements back to C heap.
delete list;
}
}
JvmtiDeferredUpdates::delete_updates_for_frame(thread, array->original().id());
// Compute the caller frame based on the sender sp of stub_frame and stored frame sizes info.
CodeBlob* cb = stub_frame.cb();
@ -1380,11 +1425,14 @@ void Deoptimization::reassign_fields(frame* fr, RegisterMap* reg_map, GrowableAr
// relock objects for which synchronization was eliminated
void Deoptimization::relock_objects(GrowableArray<MonitorInfo*>* monitors, JavaThread* thread, bool realloc_failures) {
bool Deoptimization::relock_objects(JavaThread* thread, GrowableArray<MonitorInfo*>* monitors,
JavaThread* deoptee_thread, frame& fr, int exec_mode, bool realloc_failures) {
bool relocked_objects = false;
for (int i = 0; i < monitors->length(); i++) {
MonitorInfo* mon_info = monitors->at(i);
if (mon_info->eliminated()) {
assert(!mon_info->owner_is_scalar_replaced() || realloc_failures, "reallocation was missed");
relocked_objects = true;
if (!mon_info->owner_is_scalar_replaced()) {
Handle obj(thread, mon_info->owner());
markWord mark = obj->mark();
@ -1393,17 +1441,37 @@ void Deoptimization::relock_objects(GrowableArray<MonitorInfo*>* monitors, JavaT
// Also the deoptimized method may called methods with synchronization
// where the thread-local object is bias locked to the current thread.
assert(mark.is_biased_anonymously() ||
mark.biased_locker() == thread, "should be locked to current thread");
mark.biased_locker() == deoptee_thread, "should be locked to current thread");
// Reset mark word to unbiased prototype.
markWord unbiased_prototype = markWord::prototype().set_age(mark.age());
obj->set_mark(unbiased_prototype);
} else if (exec_mode == Unpack_none) {
if (mark.has_locker() && fr.sp() > (intptr_t*)mark.locker()) {
// With exec_mode == Unpack_none obj may be thread local and locked in
// a callee frame. In this case the bias was revoked before in revoke_for_object_deoptimization().
// Make the lock in the callee a recursive lock and restore the displaced header.
markWord dmw = mark.displaced_mark_helper();
mark.locker()->set_displaced_header(markWord::encode((BasicLock*) NULL));
obj->set_mark(dmw);
}
if (mark.has_monitor()) {
// defer relocking if the deoptee thread is currently waiting for obj
ObjectMonitor* waiting_monitor = deoptee_thread->current_waiting_monitor();
if (waiting_monitor != NULL && (oop)waiting_monitor->object() == obj()) {
assert(fr.is_deoptimized_frame(), "frame must be scheduled for deoptimization");
mon_info->lock()->set_displaced_header(markWord::unused_mark());
JvmtiDeferredUpdates::inc_relock_count_after_wait(deoptee_thread);
continue;
}
}
}
BasicLock* lock = mon_info->lock();
ObjectSynchronizer::enter(obj, lock, thread);
ObjectSynchronizer::enter(obj, lock, deoptee_thread);
assert(mon_info->owner()->is_locked(), "object must be locked now");
}
}
}
return relocked_objects;
}
@ -1521,18 +1589,22 @@ void Deoptimization::pop_frames_failed_reallocs(JavaThread* thread, vframeArray*
}
#endif
static void collect_monitors(compiledVFrame* cvf, GrowableArray<Handle>* objects_to_revoke) {
static void collect_monitors(compiledVFrame* cvf, GrowableArray<Handle>* objects_to_revoke,
bool only_eliminated) {
GrowableArray<MonitorInfo*>* monitors = cvf->monitors();
Thread* thread = Thread::current();
for (int i = 0; i < monitors->length(); i++) {
MonitorInfo* mon_info = monitors->at(i);
if (!mon_info->eliminated() && mon_info->owner() != NULL) {
if (mon_info->eliminated() == only_eliminated &&
!mon_info->owner_is_scalar_replaced() &&
mon_info->owner() != NULL) {
objects_to_revoke->append(Handle(thread, mon_info->owner()));
}
}
}
static void get_monitors_from_stack(GrowableArray<Handle>* objects_to_revoke, JavaThread* thread, frame fr, RegisterMap* map) {
static void get_monitors_from_stack(GrowableArray<Handle>* objects_to_revoke, JavaThread* thread,
frame fr, RegisterMap* map, bool only_eliminated) {
// Unfortunately we don't have a RegisterMap available in most of
// the places we want to call this routine so we need to walk the
// stack again to update the register map.
@ -1552,10 +1624,10 @@ static void get_monitors_from_stack(GrowableArray<Handle>* objects_to_revoke, Ja
compiledVFrame* cvf = compiledVFrame::cast(vf);
// Revoke monitors' biases in all scopes
while (!cvf->is_top()) {
collect_monitors(cvf, objects_to_revoke);
collect_monitors(cvf, objects_to_revoke, only_eliminated);
cvf = compiledVFrame::cast(cvf->sender());
}
collect_monitors(cvf, objects_to_revoke);
collect_monitors(cvf, objects_to_revoke, only_eliminated);
}
void Deoptimization::revoke_from_deopt_handler(JavaThread* thread, frame fr, RegisterMap* map) {
@ -1566,7 +1638,7 @@ void Deoptimization::revoke_from_deopt_handler(JavaThread* thread, frame fr, Reg
ResourceMark rm(thread);
HandleMark hm(thread);
GrowableArray<Handle>* objects_to_revoke = new GrowableArray<Handle>();
get_monitors_from_stack(objects_to_revoke, thread, fr, map);
get_monitors_from_stack(objects_to_revoke, thread, fr, map, false);
int len = objects_to_revoke->length();
for (int i = 0; i < len; i++) {
@ -1576,6 +1648,41 @@ void Deoptimization::revoke_from_deopt_handler(JavaThread* thread, frame fr, Reg
}
}
// Revoke the bias of objects with eliminated locking to prepare subsequent relocking.
void Deoptimization::revoke_for_object_deoptimization(JavaThread* deoptee_thread, frame fr,
RegisterMap* map, JavaThread* thread) {
if (!UseBiasedLocking) {
return;
}
GrowableArray<Handle>* objects_to_revoke = new GrowableArray<Handle>();
if (deoptee_thread != thread) {
// Process stack of deoptee thread as we will access oops during object deoptimization.
StackWatermarkSet::start_processing(deoptee_thread, StackWatermarkKind::gc);
}
// Collect monitors but only those with eliminated locking.
get_monitors_from_stack(objects_to_revoke, deoptee_thread, fr, map, true);
int len = objects_to_revoke->length();
for (int i = 0; i < len; i++) {
oop obj = (objects_to_revoke->at(i))();
markWord mark = obj->mark();
if (!mark.has_bias_pattern() ||
mark.is_biased_anonymously() || // eliminated locking does not bias an object if it wasn't before
!obj->klass()->prototype_header().has_bias_pattern() || // bulk revoke ignores eliminated monitors
(obj->klass()->prototype_header().bias_epoch() != mark.bias_epoch())) { // bulk rebias ignores eliminated monitors
// We reach here regularly if there's just eliminated locking on obj.
// We must not call BiasedLocking::revoke_own_lock() in this case, as we
// would hit assertions because it is a prerequisite that there has to be
// non-eliminated locking on obj by deoptee_thread.
// Luckily we don't have to revoke here because obj has to be a
// non-escaping obj and can be relocked without revoking the bias. See
// Deoptimization::relock_objects().
continue;
}
BiasedLocking::revoke(objects_to_revoke->at(i), thread);
assert(!objects_to_revoke->at(i)->mark().has_bias_pattern(), "biases should be revoked by now");
}
}
void Deoptimization::deoptimize_single_frame(JavaThread* thread, frame fr, Deoptimization::DeoptReason reason) {
assert(fr.can_be_deoptimized(), "checking frame type");
@ -2622,6 +2729,7 @@ void Deoptimization::print_statistics() {
if (xtty != NULL) xtty->tail("statistics");
}
}
#else // COMPILER2_OR_JVMCI

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, 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
@ -41,6 +41,7 @@ template<class E> class GrowableArray;
class Deoptimization : AllStatic {
friend class VMStructs;
friend class EscapeBarrier;
public:
// What condition caused the deoptimization?
@ -134,7 +135,8 @@ class Deoptimization : AllStatic {
Unpack_exception = 1, // exception is pending
Unpack_uncommon_trap = 2, // redo last byte code (C2 only)
Unpack_reexecute = 3, // reexecute bytecode (C1 only)
Unpack_LIMIT = 4
Unpack_none = 4, // not deoptimizing the frame, just reallocating/relocking for JVMTI
Unpack_LIMIT = 5
};
#if INCLUDE_JVMCI
@ -152,6 +154,9 @@ class Deoptimization : AllStatic {
// Revoke biased locks at deopt.
static void revoke_from_deopt_handler(JavaThread* thread, frame fr, RegisterMap* map);
static void revoke_for_object_deoptimization(JavaThread* deoptee_thread, frame fr,
RegisterMap* map, JavaThread* thread);
public:
// Deoptimizes a frame lazily. Deopt happens on return to the frame.
static void deoptimize(JavaThread* thread, frame fr, DeoptReason reason = Reason_constraint);
@ -166,6 +171,11 @@ class Deoptimization : AllStatic {
static void deoptimize_single_frame(JavaThread* thread, frame fr, DeoptReason reason);
#if COMPILER2_OR_JVMCI
// Deoptimize objects, that is reallocate and relock them, just before they
// escape through JVMTI. The given vframes cover one physical frame.
static bool deoptimize_objects_internal(JavaThread* thread, GrowableArray<compiledVFrame*>* chunk,
bool& realloc_failures);
public:
// Support for restoring non-escaping objects
@ -173,7 +183,8 @@ class Deoptimization : AllStatic {
static void reassign_type_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, typeArrayOop obj, BasicType type);
static void reassign_object_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, objArrayOop obj);
static void reassign_fields(frame* fr, RegisterMap* reg_map, GrowableArray<ScopeValue*>* objects, bool realloc_failures, bool skip_internal);
static void relock_objects(GrowableArray<MonitorInfo*>* monitors, JavaThread* thread, bool realloc_failures);
static bool relock_objects(JavaThread* thread, GrowableArray<MonitorInfo*>* monitors,
JavaThread* deoptee_thread, frame& fr, int exec_mode, bool realloc_failures);
static void pop_frames_failed_reallocs(JavaThread* thread, vframeArray* array);
NOT_PRODUCT(static void print_objects(GrowableArray<ScopeValue*>* objects, bool realloc_failures);)
#endif // COMPILER2_OR_JVMCI
@ -465,6 +476,7 @@ class Deoptimization : AllStatic {
static void update_method_data_from_interpreter(MethodData* trap_mdo, int trap_bci, int reason);
};
class DeoptimizationMarker : StackObj { // for profiling
static bool _is_active;
public:

View File

@ -0,0 +1,353 @@
/*
* Copyright (c) 1997, 2020, 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/mutexLocker.hpp"
#include "runtime/registerMap.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
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread);
bool result = false;
if (list != NULL) {
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;
}
// Object references of frames up to the given depth are about to be
// accessed. Frames with optimizations based on escape state that is potentially
// changed by the accesses need to be deoptimized and the referenced objects
// need to be reallocated and relocked. Up to depth this is done for frames
// with not escaping objects in scope. For deeper frames it is done only if
// they pass not escaping objects as arguments because they potentially escape
// from callee frames within the given depth.
// The search for deeper frames is ended if an entry frame is found because
// arguments to native methods are considered to escape globally.
bool EscapeBarrier::deoptimize_objects(int depth) {
if (barrier_active() && deoptee_thread()->has_last_Java_frame()) {
assert(calling_thread() == Thread::current(), "should be");
ResourceMark rm(calling_thread());
HandleMark hm(calling_thread());
RegisterMap reg_map(deoptee_thread(), false /* update_map */, false /* process_frames */);
vframe* vf = deoptee_thread()->last_java_vframe(&reg_map);
int cur_depth = 0;
while (vf != NULL && ((cur_depth <= depth) || !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 <= depth ? 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(); ) {
if (jt->has_last_Java_frame()) {
RegisterMap reg_map(jt, false /* update_map */, false /* process_frames */);
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 != NULL) {
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 != NULL, "calling thread must not be NULL");
assert(_deoptee_thread != NULL, "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 != NULL, "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
GrowableArray<jvmtiDeferredLocalVariableSet*>* list =
JvmtiDeferredUpdates::deferred_locals(thread);
DEBUG_ONLY(bool found = false);
if (list != NULL) {
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) {
if (!barrier_active()) return true;
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 {
if (!self_deopt()) {
// Process stack of deoptee thread as we will access oops during object deoptimization.
StackWatermarkSet::start_processing(deoptee, StackWatermarkKind::gc);
}
StackFrameStream fst(deoptee, true /* update */, true /* 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

View File

@ -0,0 +1,139 @@
/*
* Copyright (c) 1997, 2020, 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.
*
*/
#ifndef SHARE_RUNTIME_ESCAPEBARRIER_HPP
#define SHARE_RUNTIME_ESCAPEBARRIER_HPP
#include "memory/allocation.hpp"
#include "runtime/globals.hpp"
#include "utilities/macros.hpp"
class JavaThread;
// EscapeBarriers should be put on execution paths where JVMTI agents can access object
// references held by java threads.
// They provide means to revert optimizations based on escape analysis in a well synchronized manner
// just before local references escape through JVMTI.
class EscapeBarrier : StackObj {
#if COMPILER2_OR_JVMCI
JavaThread* const _calling_thread;
JavaThread* const _deoptee_thread;
bool const _barrier_active;
static bool _deoptimizing_objects_for_all_threads;
static bool _self_deoptimization_in_progress;
// Suspending is necessary because the target thread's stack must be walked and
// object reallocation is not possible in a handshake or at a safepoint.
// Suspending is based on handshakes. It is sufficient if the target thread(s)
// cannot return to executing bytecodes. Acquiring a lock is ok. Leaving a
// safepoint/handshake safe state is not ok.
// See also JavaThread::wait_for_object_deoptimization().
void sync_and_suspend_one();
void sync_and_suspend_all();
void resume_one();
void resume_all();
// Deoptimize the given frame and deoptimize objects with optimizations based on escape analysis.
bool deoptimize_objects_internal(JavaThread* deoptee, intptr_t* fr_id);
public:
// Revert ea based optimizations for given deoptee thread
EscapeBarrier(bool barrier_active, JavaThread* calling_thread, JavaThread* deoptee_thread)
: _calling_thread(calling_thread), _deoptee_thread(deoptee_thread),
_barrier_active(barrier_active && (JVMCI_ONLY(UseJVMCICompiler) NOT_JVMCI(false)
COMPILER2_PRESENT(|| DoEscapeAnalysis)))
{
if (_barrier_active) sync_and_suspend_one();
}
// Revert ea based optimizations for all java threads
EscapeBarrier(bool barrier_active, JavaThread* calling_thread)
: _calling_thread(calling_thread), _deoptee_thread(NULL),
_barrier_active(barrier_active && (JVMCI_ONLY(UseJVMCICompiler) NOT_JVMCI(false)
COMPILER2_PRESENT(|| DoEscapeAnalysis)))
{
if (_barrier_active) sync_and_suspend_all();
}
#else
public:
EscapeBarrier(bool barrier_active, JavaThread* calling_thread, JavaThread* deoptee_thread) { }
EscapeBarrier(bool barrier_active, JavaThread* calling_thread) { }
static bool deoptimizing_objects_for_all_threads() { return false; }
bool barrier_active() const { return false; }
#endif // COMPILER2_OR_JVMCI
// Deoptimize objects, i.e. reallocate and relock them. The target frames are deoptimized.
// The methods return false iff at least one reallocation failed.
bool deoptimize_objects(intptr_t* fr_id) {
return true COMPILER2_OR_JVMCI_PRESENT(&& deoptimize_objects_internal(deoptee_thread(), fr_id));
}
bool deoptimize_objects(int depth) NOT_COMPILER2_OR_JVMCI_RETURN_(true);
// Find and deoptimize non escaping objects and the holding frames on all stacks.
bool deoptimize_objects_all_threads() NOT_COMPILER2_OR_JVMCI_RETURN_(true);
// A java thread was added to the list of threads.
static void thread_added(JavaThread* jt) NOT_COMPILER2_OR_JVMCI_RETURN;
// A java thread was removed from the list of threads.
static void thread_removed(JavaThread* jt) NOT_COMPILER2_OR_JVMCI_RETURN;
#if COMPILER2_OR_JVMCI
// Returns true iff objects were reallocated and relocked because of access through JVMTI.
static bool objs_are_deoptimized(JavaThread* thread, intptr_t* fr_id);
static bool deoptimizing_objects_for_all_threads() { return _deoptimizing_objects_for_all_threads; }
~EscapeBarrier() {
if (!barrier_active()) return;
if (all_threads()) {
resume_all();
} else {
resume_one();
}
}
// Should revert optimizations for all threads.
bool all_threads() const { return _deoptee_thread == NULL; }
// Current thread deoptimizes its own objects.
bool self_deopt() const { return _calling_thread == _deoptee_thread; }
// Inactive barriers are created if no local objects can escape.
bool barrier_active() const { return _barrier_active; }
// accessors
JavaThread* calling_thread() const { return _calling_thread; }
JavaThread* deoptee_thread() const { return _deoptee_thread; }
#endif // COMPILER2_OR_JVMCI
};
#endif // SHARE_RUNTIME_ESCAPEBARRIER_HPP

View File

@ -391,6 +391,29 @@ const intx ObjectAlignmentInBytes = 8;
notproduct(bool, WalkStackALot, false, \
"Trace stack (no print) at every exit from the runtime system") \
\
develop(bool, DeoptimizeObjectsALot, false, \
"For testing purposes concurrent threads revert optimizations " \
"based on escape analysis at intervals given with " \
"DeoptimizeObjectsALotInterval=n. The thread count is given " \
"with DeoptimizeObjectsALotThreadCountSingle and " \
"DeoptimizeObjectsALotThreadCountAll.") \
\
develop(uint64_t, DeoptimizeObjectsALotInterval, 5, \
"Interval for DeoptimizeObjectsALot.") \
range(0, max_jlong) \
\
develop(int, DeoptimizeObjectsALotThreadCountSingle, 1, \
"The number of threads that revert optimizations based on " \
"escape analysis for a single thread if DeoptimizeObjectsALot " \
"is enabled. The target thread is selected round robin." ) \
range(0, max_jint) \
\
develop(int, DeoptimizeObjectsALotThreadCountAll, 1, \
"The number of threads that revert optimizations based on " \
"escape analysis for all threads if DeoptimizeObjectsALot " \
"is enabled." ) \
range(0, max_jint) \
\
notproduct(bool, VerifyLastFrame, false, \
"Verify oops on last frame on entry to VM") \
\

View File

@ -52,6 +52,7 @@ Mutex* JmethodIdCreation_lock = NULL;
Mutex* JfieldIdCreation_lock = NULL;
Monitor* JNICritical_lock = NULL;
Mutex* JvmtiThreadState_lock = NULL;
Monitor* EscapeBarrier_lock = NULL;
Monitor* Heap_lock = NULL;
Mutex* ExpandHeap_lock = NULL;
Mutex* AdapterHandlerLibrary_lock = NULL;
@ -296,6 +297,7 @@ void mutex_init() {
def(MultiArray_lock , PaddedMutex , nonleaf+2, false, _safepoint_check_always);
def(JvmtiThreadState_lock , PaddedMutex , nonleaf+2, false, _safepoint_check_always); // Used by JvmtiThreadState/JvmtiEventController
def(EscapeBarrier_lock , PaddedMonitor, leaf, false, _safepoint_check_never); // Used to synchronize object reallocation/relocking triggered by JVMTI
def(Management_lock , PaddedMutex , nonleaf+2, false, _safepoint_check_always); // used for JVM management
def(ConcurrentGCBreakpoints_lock , PaddedMonitor, nonleaf, true, _safepoint_check_always);

View File

@ -45,6 +45,7 @@ extern Mutex* JmethodIdCreation_lock; // a lock on creating JNI metho
extern Mutex* JfieldIdCreation_lock; // a lock on creating JNI static field identifiers
extern Monitor* JNICritical_lock; // a lock used while entering and exiting JNI critical regions, allows GC to sometimes get in
extern Mutex* JvmtiThreadState_lock; // a lock on modification of JVMTI thread data
extern Monitor* EscapeBarrier_lock; // a lock to sync reallocating and relocking objects because of JVMTI access
extern Monitor* Heap_lock; // a lock on the heap
extern Mutex* ExpandHeap_lock; // a lock on expanding the heap
extern Mutex* AdapterHandlerLibrary_lock; // a lock on the AdapterHandlerLibrary

View File

@ -34,6 +34,7 @@
#include "memory/resourceArea.hpp"
#include "oops/markWord.hpp"
#include "oops/oop.inline.hpp"
#include "prims/jvmtiDeferredUpdates.hpp"
#include "runtime/atomic.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/interfaceSupport.inline.hpp"
@ -1560,7 +1561,8 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
jt->set_current_waiting_monitor(NULL);
guarantee(_recursions == 0, "invariant");
_recursions = save; // restore the old recursion count
_recursions = save // restore the old recursion count
+ JvmtiDeferredUpdates::get_and_reset_relock_count_after_wait(jt); // increased by the deferred relock count
_waiters--; // decrement the number of waiters
// Verify a few postconditions

View File

@ -989,6 +989,10 @@ void ThreadSafepointState::handle_polling_page_exception() {
// Process pending operation
SafepointMechanism::process_if_requested(self);
// We have to wait if we are here because of a handshake for object deoptimization.
if (self->is_obj_deopt_suspend()) {
self->wait_for_object_deoptimization();
}
self->check_and_handle_async_exceptions();
// restore oop result, if any
@ -1006,6 +1010,10 @@ void ThreadSafepointState::handle_polling_page_exception() {
// Process pending operation
SafepointMechanism::process_if_requested(self);
// We have to wait if we are here because of a handshake for object deoptimization.
if (self->is_obj_deopt_suspend()) {
self->wait_for_object_deoptimization();
}
set_at_poll_safepoint(false);
// If we have a pending async exception deoptimize the frame

View File

@ -62,6 +62,7 @@
#include "oops/typeArrayOop.inline.hpp"
#include "oops/verifyOopClosure.hpp"
#include "prims/jvm_misc.hpp"
#include "prims/jvmtiDeferredUpdates.hpp"
#include "prims/jvmtiExport.hpp"
#include "prims/jvmtiThreadState.hpp"
#include "runtime/arguments.hpp"
@ -119,6 +120,7 @@
#include "utilities/macros.hpp"
#include "utilities/preserveException.hpp"
#include "utilities/singleWriterSynchronizer.hpp"
#include "utilities/spinYield.hpp"
#include "utilities/vmError.hpp"
#if INCLUDE_JVMCI
#include "jvmci/jvmci.hpp"
@ -1702,7 +1704,7 @@ JavaThread::JavaThread() :
_deopt_nmethod(nullptr),
_vframe_array_head(nullptr),
_vframe_array_last(nullptr),
_deferred_locals_updates(nullptr),
_jvmti_deferred_updates(nullptr),
_callee_target(nullptr),
_vm_result(nullptr),
_vm_result_2(nullptr),
@ -1903,17 +1905,13 @@ JavaThread::~JavaThread() {
delete old_array;
}
GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred = deferred_locals();
if (deferred != NULL) {
JvmtiDeferredUpdates* updates = deferred_updates();
if (updates != NULL) {
// This can only happen if thread is destroyed before deoptimization occurs.
assert(deferred->length() != 0, "empty array!");
do {
jvmtiDeferredLocalVariableSet* dlv = deferred->at(0);
deferred->remove_at(0);
// individual jvmtiDeferredLocalVariableSet are CHeapObj's
delete dlv;
} while (deferred->length() != 0);
delete deferred;
assert(updates->count() > 0, "Updates holder not deleted");
// free deferred updates.
delete updates;
set_deferred_updates(NULL);
}
// All Java related clean up happens in exit
@ -2405,6 +2403,11 @@ void JavaThread::handle_special_runtime_exit_condition(bool check_asyncs) {
java_suspend_self_with_safepoint_check();
}
if (is_obj_deopt_suspend()) {
frame_anchor()->make_walkable(this);
wait_for_object_deoptimization();
}
// We might be here for reasons in addition to the self-suspend request
// so check for other async requests.
if (check_asyncs) {
@ -2610,6 +2613,63 @@ void JavaThread::java_suspend_self_with_safepoint_check() {
}
}
// Wait for another thread to perform object reallocation and relocking on behalf of
// this thread.
// This method is very similar to JavaThread::java_suspend_self_with_safepoint_check()
// and has the same callers. It also performs a raw thread state transition to
// _thread_blocked and back again to the original state before returning. The current
// thread is required to change to _thread_blocked in order to be seen to be
// safepoint/handshake safe whilst suspended and only after becoming handshake safe,
// the other thread can complete the handshake used to synchronize with this thread
// and then perform the reallocation and relocking. We cannot use the thread state
// transition helpers because we arrive here in various states and also because the
// helpers indirectly call this method. After leaving _thread_blocked we have to
// check for safepoint/handshake, except if _thread_in_native. The thread is safe
// without blocking then. Allowed states are enumerated in
// SafepointSynchronize::block(). See also EscapeBarrier::sync_and_suspend_*()
void JavaThread::wait_for_object_deoptimization() {
assert(!has_last_Java_frame() || frame_anchor()->walkable(), "should have walkable stack");
assert(this == Thread::current(), "invariant");
JavaThreadState state = thread_state();
bool spin_wait = os::is_MP();
do {
set_thread_state(_thread_blocked);
// Check if _external_suspend was set in the previous loop iteration.
if (is_external_suspend()) {
java_suspend_self();
}
// Wait for object deoptimization if requested.
if (spin_wait) {
// A single deoptimization is typically very short. Microbenchmarks
// showed 5% better performance when spinning.
const uint spin_limit = 10 * SpinYield::default_spin_limit;
SpinYield spin(spin_limit);
for (uint i = 0; is_obj_deopt_suspend() && i < spin_limit; i++) {
spin.wait();
}
// Spin just once
spin_wait = false;
} else {
MonitorLocker ml(this, EscapeBarrier_lock, Monitor::_no_safepoint_check_flag);
if (is_obj_deopt_suspend()) {
ml.wait();
}
}
// The current thread could have been suspended again. We have to check for
// suspend after restoring the saved state. Without this the current thread
// might return to _thread_in_Java and execute bytecode.
set_thread_state_fence(state);
if (state != _thread_in_native) {
SafepointMechanism::process_if_requested(this);
}
// A handshake for obj. deoptimization suspend could have been processed so
// we must check after processing.
} while (is_obj_deopt_suspend() || is_external_suspend());
}
#ifdef ASSERT
// Verify the JavaThread has not yet been published in the Threads::list, and
// hence doesn't need protection from concurrent access at this stage.
@ -2639,6 +2699,10 @@ void JavaThread::check_safepoint_and_suspend_for_native_trans(JavaThread *thread
SafepointMechanism::process_if_requested(thread);
}
if (thread->is_obj_deopt_suspend()) {
thread->wait_for_object_deoptimization();
}
JFR_ONLY(SUSPEND_THREAD_CONDITIONAL(thread);)
}
@ -2802,7 +2866,7 @@ void JavaThread::oops_do_no_frames(OopClosure* f, CodeBlobClosure* cf) {
assert(vframe_array_head() == NULL, "deopt in progress at a safepoint!");
// If we have deferred set_locals there might be oops waiting to be
// written
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = deferred_locals();
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(this);
if (list != NULL) {
for (int i = 0; i < list->length(); i++) {
list->at(i)->oops_do(f);
@ -4384,6 +4448,9 @@ void Threads::add(JavaThread* p, bool force_daemon) {
// Possible GC point.
Events::log(p, "Thread added: " INTPTR_FORMAT, p2i(p));
// Make new thread known to active EscapeBarrier
EscapeBarrier::thread_added(p);
}
void Threads::remove(JavaThread* p, bool is_daemon) {
@ -4424,6 +4491,9 @@ void Threads::remove(JavaThread* p, bool is_daemon) {
// to do callbacks into the safepoint code. However, the safepoint code is not aware
// of this thread since it is removed from the queue.
p->set_terminated_value();
// Notify threads waiting in EscapeBarriers
EscapeBarrier::thread_removed(p);
} // unlock Threads_lock
// Since Events::log uses a lock, we grab it outside the Threads_lock

View File

@ -83,7 +83,7 @@ class vframe;
class javaVFrame;
class DeoptResourceMark;
class jvmtiDeferredLocalVariableSet;
class JvmtiDeferredUpdates;
class ThreadClosure;
class ICRefillVerifier;
@ -303,7 +303,8 @@ class Thread: public ThreadShadow {
_has_async_exception = 0x00000001U, // there is a pending async exception
_critical_native_unlock = 0x00000002U, // Must call back to unlock JNI critical lock
_trace_flag = 0x00000004U // call tracing backend
_trace_flag = 0x00000004U, // call tracing backend
_obj_deopt = 0x00000008U // suspend for object reallocation and relocking for JVMTI agent
};
// various suspension related flags - atomically updated
@ -550,6 +551,9 @@ class Thread: public ThreadShadow {
inline void set_trace_flag();
inline void clear_trace_flag();
inline void set_obj_deopt_flag();
inline void clear_obj_deopt_flag();
// Support for Unhandled Oop detection
// Add the field for both, fastdebug and debug, builds to keep
// Thread's fields layout the same.
@ -624,6 +628,8 @@ class Thread: public ThreadShadow {
bool is_trace_suspend() { return (_suspend_flags & _trace_flag) != 0; }
bool is_obj_deopt_suspend() { return (_suspend_flags & _obj_deopt) != 0; }
// For tracking the heavyweight monitor the thread is pending on.
ObjectMonitor* current_pending_monitor() {
return _current_pending_monitor;
@ -1059,11 +1065,10 @@ class JavaThread: public Thread {
CompiledMethod* _deopt_nmethod; // CompiledMethod that is currently being deoptimized
vframeArray* _vframe_array_head; // Holds the heap of the active vframeArrays
vframeArray* _vframe_array_last; // Holds last vFrameArray we popped
// Because deoptimization is lazy we must save jvmti requests to set locals
// in compiled frames until we deoptimize and we have an interpreter frame.
// This holds the pointer to array (yeah like there might be more than one) of
// description of compiled vframes that have locals that need to be updated.
GrowableArray<jvmtiDeferredLocalVariableSet*>* _deferred_locals_updates;
// Holds updates by JVMTI agents for compiled frames that cannot be performed immediately. They
// will be carried out as soon as possible which, in most cases, is just before deoptimization of
// the frame, when control returns to it.
JvmtiDeferredUpdates* _jvmti_deferred_updates;
// Handshake value for fixing 6243940. We need a place for the i2c
// adapter to store the callee Method*. This value is NEVER live
@ -1356,6 +1361,10 @@ class JavaThread: public Thread {
void java_resume(); // higher-level resume logic called by the public APIs
int java_suspend_self(); // low-level self-suspension mechanics
// Synchronize with another thread that is deoptimizing objects of the
// current thread, i.e. reverts optimizations based on escape analysis.
void wait_for_object_deoptimization();
private:
// mid-level wrapper around java_suspend_self to set up correct state and
// check for a pending safepoint at the end
@ -1415,7 +1424,7 @@ class JavaThread: public Thread {
// Whenever a thread transitions from native to vm/java it must suspend
// if external|deopt suspend is present.
bool is_suspend_after_native() const {
return (_suspend_flags & (_external_suspend JFR_ONLY(| _trace_flag))) != 0;
return (_suspend_flags & (_external_suspend | _obj_deopt JFR_ONLY(| _trace_flag))) != 0;
}
// external suspend request is completed
@ -1488,7 +1497,7 @@ class JavaThread: public Thread {
// we have checked is_external_suspend(), we will recheck its value
// under SR_lock in java_suspend_self().
return (_special_runtime_exit_condition != _no_async_condition) ||
is_external_suspend() || is_trace_suspend();
is_external_suspend() || is_trace_suspend() || is_obj_deopt_suspend();
}
void set_pending_unsafe_access_error() { _special_runtime_exit_condition = _async_unsafe_access_error; }
@ -1505,8 +1514,8 @@ class JavaThread: public Thread {
vframeArray* vframe_array_head() const { return _vframe_array_head; }
// Side structure for deferring update of java frame locals until deopt occurs
GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred_locals() const { return _deferred_locals_updates; }
void set_deferred_locals(GrowableArray<jvmtiDeferredLocalVariableSet *>* vf) { _deferred_locals_updates = vf; }
JvmtiDeferredUpdates* deferred_updates() const { return _jvmti_deferred_updates; }
void set_deferred_updates(JvmtiDeferredUpdates* du) { _jvmti_deferred_updates = du; }
// These only really exist to make debugging deopt problems simpler

View File

@ -65,6 +65,12 @@ inline void Thread::set_trace_flag() {
inline void Thread::clear_trace_flag() {
clear_suspend_flag(_trace_flag);
}
inline void Thread::set_obj_deopt_flag() {
set_suspend_flag(_obj_deopt);
}
inline void Thread::clear_obj_deopt_flag() {
clear_suspend_flag(_obj_deopt);
}
inline jlong Thread::cooked_allocated_bytes() {
jlong allocated_bytes = Atomic::load_acquire(&_allocated_bytes);

View File

@ -82,6 +82,11 @@ vframe* vframe::new_vframe(const frame* f, const RegisterMap* reg_map, JavaThrea
}
}
// Entry frame
if (f->is_entry_frame()) {
return new entryVFrame(f, reg_map, thread);
}
// External frame
return new externalVFrame(f, reg_map, thread);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, 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
@ -33,6 +33,7 @@
#include "interpreter/oopMapCache.hpp"
#include "oops/instanceKlass.hpp"
#include "oops/oop.inline.hpp"
#include "prims/jvmtiDeferredUpdates.hpp"
#include "runtime/basicLock.hpp"
#include "runtime/frame.inline.hpp"
#include "runtime/handles.inline.hpp"
@ -64,7 +65,7 @@ StackValueCollection* compiledVFrame::locals() const {
// Replace the original values with any stores that have been
// performed through compiledVFrame::update_locals.
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = thread()->deferred_locals();
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread());
if (list != NULL ) {
// In real life this never happens or is typically a single element search
for (int i = 0; i < list->length(); i++) {
@ -103,7 +104,7 @@ void compiledVFrame::update_monitor(int index, MonitorInfo* val) {
void compiledVFrame::update_deferred_value(BasicType type, int index, jvalue value) {
assert(fr().is_deoptimized_frame(), "frame must be scheduled for deoptimization");
GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred = thread()->deferred_locals();
GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred = JvmtiDeferredUpdates::deferred_locals(thread());
jvmtiDeferredLocalVariableSet* locals = NULL;
if (deferred != NULL ) {
// See if this vframe has already had locals with deferred writes
@ -117,8 +118,8 @@ void compiledVFrame::update_deferred_value(BasicType type, int index, jvalue val
} else {
// No deferred updates pending for this thread.
// allocate in C heap
deferred = new(ResourceObj::C_HEAP, mtCompiler) GrowableArray<jvmtiDeferredLocalVariableSet*> (1, mtCompiler);
thread()->set_deferred_locals(deferred);
JvmtiDeferredUpdates::create_for(thread());
deferred = JvmtiDeferredUpdates::deferred_locals(thread());
}
if (locals == NULL) {
locals = new jvmtiDeferredLocalVariableSet(method(), bci(), fr().id(), vframe_id());
@ -128,6 +129,56 @@ void compiledVFrame::update_deferred_value(BasicType type, int index, jvalue val
locals->set_value_at(index, type, value);
}
// After object deoptimization, that is object reallocation and relocking, we
// create deferred updates for all objects in scope. No new update will be
// created if a deferred update already exists. It is not easy to see how this
// is achieved: the deoptimized objects are in the arrays returned by locals(),
// expressions(), and monitors(). For each object in these arrays we create a
// deferred updated. If an update already exists, then it will override the
// corresponding deoptimized object returned in one of the arrays. So the
// original update is kept.
void compiledVFrame::create_deferred_updates_after_object_deoptimization() {
// locals
GrowableArray<ScopeValue*>* scopeLocals = scope()->locals();
StackValueCollection* lcls = locals();
if (lcls != NULL) {
for (int i2 = 0; i2 < lcls->size(); i2++) {
StackValue* var = lcls->at(i2);
if (var->type() == T_OBJECT && scopeLocals->at(i2)->is_object()) {
jvalue val;
val.l = cast_from_oop<jobject>(lcls->at(i2)->get_obj()());
update_local(T_OBJECT, i2, val);
}
}
}
// expressions
GrowableArray<ScopeValue*>* scopeExpressions = scope()->expressions();
StackValueCollection* exprs = expressions();
if (exprs != NULL) {
for (int i2 = 0; i2 < exprs->size(); i2++) {
StackValue* var = exprs->at(i2);
if (var->type() == T_OBJECT && scopeExpressions->at(i2)->is_object()) {
jvalue val;
val.l = cast_from_oop<jobject>(exprs->at(i2)->get_obj()());
update_stack(T_OBJECT, i2, val);
}
}
}
// monitors
GrowableArray<MonitorInfo*>* mtrs = monitors();
if (mtrs != NULL) {
for (int i2 = 0; i2 < mtrs->length(); i2++) {
if (mtrs->at(i2)->eliminated()) {
assert(!mtrs->at(i2)->owner_is_scalar_replaced(),
"reallocation failure, should not update");
update_monitor(i2, mtrs->at(i2));
}
}
}
}
StackValueCollection* compiledVFrame::expressions() const {
// Natives has no scope
if (scope() == NULL) return new StackValueCollection(0);
@ -144,7 +195,7 @@ StackValueCollection* compiledVFrame::expressions() const {
// Replace the original values with any stores that have been
// performed through compiledVFrame::update_stack.
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = thread()->deferred_locals();
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread());
if (list != NULL ) {
// In real life this never happens or is typically a single element search
for (int i = 0; i < list->length(); i++) {
@ -218,7 +269,7 @@ GrowableArray<MonitorInfo*>* compiledVFrame::monitors() const {
// Replace the original values with any stores that have been
// performed through compiledVFrame::update_monitors.
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = thread()->deferred_locals();
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread());
if (list != NULL ) {
// In real life this never happens or is typically a single element search
for (int i = 0; i < list->length(); i++) {
@ -309,6 +360,24 @@ bool compiledVFrame::should_reexecute() const {
return scope()->should_reexecute();
}
bool compiledVFrame::has_ea_local_in_scope() const {
if (scope() == NULL) {
// native nmethod, all objs escape
assert(code()->as_nmethod()->is_native_method(), "must be native");
return false;
}
return (scope()->objects() != NULL) || scope()->has_ea_local_in_scope();
}
bool compiledVFrame::arg_escape() const {
if (scope() == NULL) {
// native nmethod, all objs escape
assert(code()->as_nmethod()->is_native_method(), "must be native");
return false;
}
return scope()->arg_escape();
}
vframe* compiledVFrame::sender() const {
const frame f = fr();
if (scope() == NULL) {
@ -330,6 +399,7 @@ jvmtiDeferredLocalVariableSet::jvmtiDeferredLocalVariableSet(Method* method, int
_vframe_id = vframe_id;
// Alway will need at least one, must be on C heap
_locals = new(ResourceObj::C_HEAP, mtCompiler) GrowableArray<jvmtiDeferredLocalVariable*> (1, mtCompiler);
_objects_are_deoptimized = false;
}
jvmtiDeferredLocalVariableSet::~jvmtiDeferredLocalVariableSet() {
@ -424,7 +494,11 @@ void jvmtiDeferredLocalVariableSet::update_monitors(GrowableArray<MonitorInfo*>*
if (val->index() >= method()->max_locals() + method()->max_stack()) {
int lock_index = val->index() - (method()->max_locals() + method()->max_stack());
MonitorInfo* info = monitors->at(lock_index);
MonitorInfo* new_info = new MonitorInfo((oopDesc*)val->value().l, info->lock(), info->eliminated(), info->owner_is_scalar_replaced());
// Originally the owner may have been scalar replaced but as an update
// exists it must have been deoptimized, i.e. reallocated to the heap, and
// now it is considered not to be scalar replaced.
MonitorInfo* new_info = new MonitorInfo((oopDesc*)val->value().l, info->lock(),
info->eliminated(), false);
monitors->at_put(lock_index, new_info);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, 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
@ -28,6 +28,9 @@
#include "runtime/vframe.hpp"
class compiledVFrame: public javaVFrame {
friend class EscapeBarrier;
public:
// JVM state
Method* method() const;
@ -37,6 +40,8 @@ class compiledVFrame: public javaVFrame {
StackValueCollection* expressions() const;
GrowableArray<MonitorInfo*>* monitors() const;
int vframe_id() const { return _vframe_id; }
bool has_ea_local_in_scope() const;
bool arg_escape() const; // at call with arg escape in parameter list
void set_locals(StackValueCollection* values) const;
@ -53,6 +58,11 @@ class compiledVFrame: public javaVFrame {
void update_deferred_value(BasicType type, int index, jvalue value);
// After object deoptimization, that is object reallocation and relocking, we
// create deferred updates for all objects in scope. No new update will be
// created if a deferred update already exists.
void create_deferred_updates_after_object_deoptimization();
public:
// Constructors
compiledVFrame(const frame* fr, const RegisterMap* reg_map, JavaThread* thread, CompiledMethod* nm);
@ -95,71 +105,4 @@ class compiledVFrame: public javaVFrame {
#endif
};
// In order to implement set_locals for compiled vframes we must
// store updated locals in a data structure that contains enough
// information to recognize equality with a vframe and to store
// any updated locals.
class jvmtiDeferredLocalVariable;
class jvmtiDeferredLocalVariableSet : public CHeapObj<mtCompiler> {
friend class compiledVFrame;
private:
Method* _method;
int _bci;
intptr_t* _id;
int _vframe_id;
GrowableArray<jvmtiDeferredLocalVariable*>* _locals;
void update_value(StackValueCollection* locals, BasicType type, int index, jvalue value);
void set_value_at(int idx, BasicType typ, jvalue val);
public:
// JVM state
Method* method() const { return _method; }
int bci() const { return _bci; }
intptr_t* id() const { return _id; }
int vframe_id() const { return _vframe_id; }
void update_locals(StackValueCollection* locals);
void update_stack(StackValueCollection* locals);
void update_monitors(GrowableArray<MonitorInfo*>* monitors);
// Does the vframe match this jvmtiDeferredLocalVariableSet
bool matches(const vframe* vf);
// GC
void oops_do(OopClosure* f);
// constructor
jvmtiDeferredLocalVariableSet(Method* method, int bci, intptr_t* id, int vframe_id);
// destructor
~jvmtiDeferredLocalVariableSet();
};
class jvmtiDeferredLocalVariable : public CHeapObj<mtCompiler> {
public:
jvmtiDeferredLocalVariable(int index, BasicType type, jvalue value);
BasicType type(void) { return _type; }
int index(void) { return _index; }
jvalue value(void) { return _value; }
// Only mutator is for value as only it can change
void set_value(jvalue value) { _value = value; }
// For gc
oop* oop_addr(void) { return (oop*) &_value.l; }
private:
BasicType _type;
jvalue _value;
int _index;
};
#endif // SHARE_RUNTIME_VFRAME_HP_HPP

View File

@ -327,10 +327,14 @@
#define COMPILER2_OR_JVMCI 1
#define COMPILER2_OR_JVMCI_PRESENT(code) code
#define NOT_COMPILER2_OR_JVMCI(code)
#define NOT_COMPILER2_OR_JVMCI_RETURN /* next token must be ; */
#define NOT_COMPILER2_OR_JVMCI_RETURN_(code) /* next token must be ; */
#else
#define COMPILER2_OR_JVMCI 0
#define COMPILER2_OR_JVMCI_PRESENT(code)
#define NOT_COMPILER2_OR_JVMCI(code) code
#define NOT_COMPILER2_OR_JVMCI_RETURN {}
#define NOT_COMPILER2_OR_JVMCI_RETURN_(code) { return code; }
#endif
#ifdef TIERED

View File

@ -0,0 +1,610 @@
/*
* 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.
*/
/**
* @test
* @bug 8230956
* @summary JVMTI agents can obtain references to not escaping objects using JVMTI Heap functions.
* Therefore optimizations based on escape analysis have to be reverted,
* i.e. scalar replaced objects need to be reallocated on the heap and objects with eliminated locking
* need to be relocked.
* @requires ((vm.compMode == "Xmixed") & vm.compiler2.enabled & vm.jvmti)
* @library /test/lib /test/hotspot/jtreg
* @build sun.hotspot.WhiteBox
* @run driver ClassFileInstaller sun.hotspot.WhiteBox
* @compile IterateHeapWithEscapeAnalysisEnabled.java
*
* @comment BLOCK BEGIN EXCLUSIVE TESTCASES {
*
* The following test cases are executed in fresh VMs because they require that the
* capability can_tag_objects is not taken until dontinline_testMethod is jit compiled and
* an activation of the compiled version is on stack of the target thread.
*
* Without JDK-8227745 these test cases require that escape analysis is disabled at
* start-up because can_tag_objects can be taken lazily, potentially after loading an
* agent dynamically by means of the attach API. Disabling escape analysis and invalidating
* compiled methods does not help then because there may be compiled frames with ea-based
* optimizations on stack. Just like in this collection of test cases.
*
* @run main/othervm/native
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
* -XX:+PrintCompilation -XX:+PrintInlining
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
* -Xbatch
* -XX:CompileCommand=dontinline,*::dontinline_*
* -XX:+DoEscapeAnalysis
* IterateHeapWithEscapeAnalysisEnabled IterateOverReachableObjects
* @run main/othervm/native
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
* -XX:+PrintCompilation -XX:+PrintInlining
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
* -Xbatch
* -XX:CompileCommand=dontinline,*::dontinline_*
* -XX:+DoEscapeAnalysis
* IterateHeapWithEscapeAnalysisEnabled IterateOverHeap
* @run main/othervm/native
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
* -XX:+PrintCompilation -XX:+PrintInlining
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
* -Xbatch
* -XX:CompileCommand=dontinline,*::dontinline_*
* -XX:+DoEscapeAnalysis
* IterateHeapWithEscapeAnalysisEnabled IterateOverInstancesOfClass
* @run main/othervm/native
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
* -XX:+PrintCompilation -XX:+PrintInlining
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
* -Xbatch
* -XX:CompileCommand=dontinline,*::dontinline_*
* -XX:+DoEscapeAnalysis
* IterateHeapWithEscapeAnalysisEnabled FollowReferences
* @run main/othervm/native
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
* -XX:+PrintCompilation -XX:+PrintInlining
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
* -Xbatch
* -XX:CompileCommand=dontinline,*::dontinline_*
* -XX:+DoEscapeAnalysis
* IterateHeapWithEscapeAnalysisEnabled IterateThroughHeap
*
* @comment } BLOCK END EXCLUSIVE TESTCASES
*
* @comment BLOCK BEGIN NON EXCLUSIVE TESTCASES {
*
* @run main/othervm/native
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
* -XX:CompileCommand=dontinline,*::dontinline_*
* -XX:+PrintCompilation
* -XX:+PrintInlining
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
* -Xbatch
* -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:+UseBiasedLocking
* IterateHeapWithEscapeAnalysisEnabled
* @run main/othervm/native
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
* -XX:CompileCommand=dontinline,*::dontinline_*
* -XX:+PrintCompilation
* -XX:+PrintInlining
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
* -Xbatch
* -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:+UseBiasedLocking
* IterateHeapWithEscapeAnalysisEnabled
* @run main/othervm/native
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
* -XX:CompileCommand=dontinline,*::dontinline_*
* -XX:+PrintCompilation
* -XX:+PrintInlining
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
* -Xbatch
* -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:+UseBiasedLocking
* IterateHeapWithEscapeAnalysisEnabled
*
* @comment } BLOCK END NON EXCLUSIVE TESTCASES
*/
import compiler.whitebox.CompilerWhiteBoxTest;
import jdk.test.lib.Asserts;
import sun.hotspot.WhiteBox;
public class IterateHeapWithEscapeAnalysisEnabled {
public static final WhiteBox WB = WhiteBox.getWhiteBox();
public static final int COMPILE_THRESHOLD = CompilerWhiteBoxTest.THRESHOLD;
public static native int jvmtiTagClass(Class<?> cls, long tag);
// Methods to tag or count instances of a given class available in JVMTI
public static enum TaggingAndCountingMethods {
IterateOverReachableObjects,
IterateOverHeap,
IterateOverInstancesOfClass,
FollowReferences,
IterateThroughHeap
}
public static native int acquireCanTagObjectsCapability();
public static native int registerMethod(TaggingAndCountingMethods m, String name);
public static native void agentTearDown();
/**
* Count and tag instances of a given class.
* @param cls Used by the method {@link TaggingAndCountingMethods#IterateOverInstancesOfClass} as class to count and tag instances of.
* Ignored by other counting methods.
* @param clsTag Tag of the class to count and tag instances of. Used by all methods except
* {@link TaggingAndCountingMethods#IterateOverInstancesOfClass}
* @param instanceTag The tag to be set for selected instances.
* @param method JVMTI counting and tagging method to be used.
* @return The number of instances or -1 if the call fails.
*/
public static native int countAndTagInstancesOfClass(Class<?> cls, long clsTag, long instanceTag, TaggingAndCountingMethods method);
/**
* Get all objects tagged with the given tag.
* @param tag The tag used to select objects.
* @param result Selected objects are copied into this array.
* @return -1 to indicated failure and 0 for success.
*/
public static native int getObjectsWithTag(long tag, Object[] result);
public static void main(String[] args) throws Exception {
try {
new IterateHeapWithEscapeAnalysisEnabled().runTestCases(args);
} finally {
agentTearDown();
}
}
public void runTestCases(String[] args) throws Exception {
// register various instance tagging and counting methods with agent
for (TaggingAndCountingMethods m : TaggingAndCountingMethods.values()) {
msg("register instance count method " + m.name());
int rc = registerMethod(m, m.name());
Asserts.assertGreaterThanOrEqual(rc, 0, "method " + m.name() + " is unknown to agent");
}
if (args.length > 0) {
// EXCLUSIVE TEST CASES
// cant_tag_objects is acquired after warmup. Use given tagging/counting method.
new TestCase01(true, 100, TaggingAndCountingMethods.valueOf(args[0])).run();
} else {
// NON-EXCLUSIVE TEST CASES
// cant_tag_objects is acquired before test cases are run but still during live phase.
msgHL("Acquire capability can_tag_objects before first test case.");
int err = acquireCanTagObjectsCapability();
Asserts.assertEQ(0, err, "acquireCanTagObjectsCapability FAILED");
// run test cases
for (TaggingAndCountingMethods m : TaggingAndCountingMethods.values()) {
new TestCase01(false, 200, m).run();
}
new TestCase02a(200).run();
new TestCase02b(300).run();
}
}
static class ABBox {
public int aVal;
public int bVal;
public TestCaseBase testCase;
public ABBox() { /* empty */ }
public ABBox(TestCaseBase testCase) {
this.testCase = testCase;
}
/**
* Increment {@link #aVal} and {@link #bVal} under lock. The method is supposed to
* be inlined into the test method and locking is supposed to be eliminated. After
* this object escaped to the JVMTI agent, the code with eliminated locking must
* not be used anymore.
*/
public synchronized void synchronizedSlowInc() {
aVal++;
testCase.waitingForCheck = true;
dontinline_waitForCheck(testCase);
testCase.waitingForCheck = false;
bVal++;
}
public static void dontinline_waitForCheck(TestCaseBase testCase) {
if (testCase.warmUpDone) {
while(!testCase.checkingNow) {
try {
Thread.sleep(50);
} catch (InterruptedException e) { /*ign*/ }
}
}
}
/**
* This method and incrementing {@link #aVal} and {@link #bVal} are synchronized.
* So {@link #aVal} and {@link #bVal} should always be equal. Unless the optimized version
* of {@link #synchronizedSlowInc()} without locking is still used after this object
* escaped to the JVMTI agent.
* @return
*/
public synchronized boolean check() {
return aVal == bVal;
}
}
public static abstract class TestCaseBase implements Runnable {
public final long classTag;
public long instanceTag;
public final Class<?> taggedClass;
public long checkSum;
public long loopCount;
public volatile boolean doLoop;
public volatile boolean targetIsInLoop;
public volatile boolean waitingForCheck;
public volatile boolean checkingNow;
public boolean warmUpDone;
public TestCaseBase(long classTag, Class<?> taggedClass) {
this.classTag = classTag;
this.taggedClass = taggedClass;
}
public void setUp() {
// Tag the class of instances to be scalar replaced
msg("tagging " + taggedClass.getName() + " with tag " + classTag);
int err = jvmtiTagClass(taggedClass, classTag);
Asserts.assertEQ(0, err, "jvmtiTagClass FAILED");
}
// to be overridden by test cases
abstract public void dontinline_testMethod();
public void warmUp() {
msg("WarmUp: START");
int callCount = COMPILE_THRESHOLD + 1000;
doLoop = true;
while (callCount-- > 0) {
dontinline_testMethod();
}
warmUpDone = true;
msg("WarmUp: DONE");
}
public Object dontinline_endlessLoop(Object argEscape) {
long cs = checkSum;
while (loopCount-- > 0 && doLoop) {
targetIsInLoop = true;
checkSum += checkSum % ++cs;
}
loopCount = 3;
targetIsInLoop = false;
return argEscape;
}
public void waitUntilTargetThreadHasEnteredEndlessLoop() {
while(!targetIsInLoop) {
msg("Target has not yet entered the loop. Sleep 100ms.");
try { Thread.sleep(100); } catch (InterruptedException e) { /*ignore */ }
}
msg("Target has entered the loop.");
}
public void terminateEndlessLoop() throws Exception {
msg("Terminate endless loop");
doLoop = false;
}
}
/**
* Use JVMTI heap functions associated with the elements of {@link TaggingAndCountingMethods} to
* get a reference to an object allocated in {@link TestCase01#dontinline_testMethod()}. The
* allocation can be eliminated / scalar replaced. The test case can be run in two modes: (1)
* the capability can_tag_objects which is required to use the JVMTI heap functions is taken
* before the test case (2) the capability is taken after {@link TestCase01#dontinline_testMethod()}
* is compiled and the target thread has an activation of it on stack.
*/
public static class TestCase01 extends TestCaseBase {
public volatile int testMethod_result;
public boolean acquireCanTagObjectsCapabilityAfterWarmup;
public TaggingAndCountingMethods taggingMethod;
public TestCase01(boolean acquireCanTagObjectsCapabilityAfterWarmup, long classTag, TaggingAndCountingMethods taggingMethod) {
super(classTag, ABBox.class);
instanceTag = classTag + 1;
this.acquireCanTagObjectsCapabilityAfterWarmup = acquireCanTagObjectsCapabilityAfterWarmup;
this.taggingMethod = taggingMethod;
}
@Override
public void setUp() {
if (!acquireCanTagObjectsCapabilityAfterWarmup) {
super.setUp();
}
}
public void setUpAfterWarmUp() {
if (acquireCanTagObjectsCapabilityAfterWarmup) {
msg("Acquire capability can_tag_objects " + (warmUpDone ? "after" : "before") + " warmup.");
int err = acquireCanTagObjectsCapability();
Asserts.assertEQ(0, err, "acquireCanTagObjectsCapability FAILED");
super.setUp();
}
}
public void run() {
try {
msgHL(getClass().getName() + ": test if object that may be scalar replaced is found using " + taggingMethod);
msg("The capability can_tag_object is acquired " + (acquireCanTagObjectsCapabilityAfterWarmup ? "AFTER" : "BEFORE")
+ " warmup.");
setUp();
warmUp();
WB.deflateIdleMonitors();
WB.fullGC(); // get rid of dead instances from previous test cases
runTest(taggingMethod);
} catch (Exception e) {
Asserts.fail("Unexpected Exception", e);
}
}
public void runTest(TaggingAndCountingMethods m) throws Exception {
loopCount = 1L << 62; // endless loop
doLoop = true;
testMethod_result = 0;
Thread t1 = new Thread(() -> dontinline_testMethod(), "Target Thread (" + getClass().getName() + ")");
try {
t1.start();
try {
waitUntilTargetThreadHasEnteredEndlessLoop();
setUpAfterWarmUp();
msg("count and tag instances of " + taggedClass.getName() + " with tag " + instanceTag + " using JVMTI " + m.name());
int count = countAndTagInstancesOfClass(taggedClass, classTag, instanceTag, m);
msg("Done. Count is " + count);
Asserts.assertGreaterThanOrEqual(count, 0, "countAndTagInstancesOfClass FAILED");
Asserts.assertEQ(count, 1, "unexpected number of instances");
ABBox[] result = new ABBox[1];
msg("get instances tagged with " + instanceTag + ". The instances escape thereby.");
int err = getObjectsWithTag(instanceTag, result);
msg("Done.");
Asserts.assertEQ(0, err, "getObjectsWithTag FAILED");
msg("change the now escaped instance' bVal");
ABBox abBox = result[0];
abBox.bVal = 3;
terminateEndlessLoop();
msg("wait until target thread has set testMethod_result");
while (testMethod_result == 0) {
Thread.sleep(50);
}
msg("check if the modification of bVal is reflected in testMethod_result.");
Asserts.assertEQ(7, testMethod_result, " testMethod_result has wrong value");
msg("ok.");
} finally {
terminateEndlessLoop();
}
} finally {
t1.join();
}
}
@Override
public void dontinline_testMethod() {
ABBox ab = new ABBox(); // can be scalar replaced
ab.aVal = 4;
ab.bVal = 2;
dontinline_endlessLoop(null); // JVMTI agent acquires reference to ab and changes bVal
testMethod_result = ab.aVal + ab.bVal;
}
}
/**
* {@link #dontinline_testMethod()} creates an ArgEscape instance of {@link TestCaseBase#taggedClass} on stack.
* The jvmti agent tags all instances of this class using one of the {@link TaggingAndCountingMethods}. Then it gets the tagged
* instances using <code>GetObjectsWithTags()</code>. This is where the ArgEscape globally escapes.
* It happens at a location without eliminated locking but there is
* eliminated locking following, so the compiled frame must be deoptimized. This is checked by letting the agent call the
* synchronized method {@link ABBox#check()} on the escaped instance.
*/
public static class TestCase02a extends TestCaseBase {
public long instanceTag;
public TestCase02a(long classTag) {
super(classTag, ABBox.class);
instanceTag = classTag + 1;
}
public void run() {
try {
msgHL(getClass().getName() + ": test if owning frame is deoptimized if ArgEscape escapes globally");
setUp();
warmUp();
for (TaggingAndCountingMethods m : TaggingAndCountingMethods.values()) {
msgHL(getClass().getName() + ": Tag and Get of ArgEscapes using " + m.name());
waitingForCheck = false;
checkingNow = false;
WB.deflateIdleMonitors();
WB.fullGC(); // get rid of dead instances from previous test cases
runTest(m);
}
} catch (Exception e) {
Asserts.fail("Unexpected Exception", e);
}
}
public void runTest(TaggingAndCountingMethods m) throws Exception {
loopCount = 1L << 62; // endless loop
doLoop = true;
Thread t1 = new Thread(() -> dontinline_testMethod(), "Target Thread (" + getClass().getName() + ")");
try {
t1.start();
try {
waitUntilTargetThreadHasEnteredEndlessLoop();
msg("count and tag instances of " + taggedClass.getName() + " with tag " + instanceTag + " using JVMTI " + m.name());
int count = countAndTagInstancesOfClass(taggedClass, classTag, instanceTag, m);
msg("Done. Count is " + count);
Asserts.assertGreaterThanOrEqual(count, 0, "countAndTagInstancesOfClass FAILED");
Asserts.assertEQ(count, 1, "unexpected number of instances");
} finally {
terminateEndlessLoop();
}
ABBox[] result = new ABBox[1];
msg("get instances tagged with " + instanceTag);
int err = getObjectsWithTag(instanceTag, result);
msg("Done.");
Asserts.assertEQ(0, err, "getObjectsWithTag FAILED");
ABBox abBoxArgEscape = result[0];
while (!waitingForCheck) {
Thread.yield();
}
msg("Check abBoxArgEscape's state is consistent");
checkingNow = true;
Asserts.assertTrue(abBoxArgEscape.check(), "Detected inconsistent state. abBoxArgEscape.aVal != abBoxArgEscape.bVal");
msg("Ok.");
} finally {
checkingNow = true;
t1.join();
}
}
@Override
public void dontinline_testMethod() {
ABBox ab = new ABBox(this);
dontinline_endlessLoop(ab);
ab.synchronizedSlowInc();
}
}
/**
* Like {@link TestCase02a}, with the exception that at the location in {@link #dontinline_testMethod()} where the
* ArgEscape escapes it is not referenced by a local variable.
*/
public static class TestCase02b extends TestCaseBase {
public long instanceTag;
public TestCase02b(long classTag) {
super(classTag, ABBox.class);
instanceTag = classTag + 1;
}
public void run() {
try {
msgHL(getClass().getName() + ": test if owning frame is deoptimized if ArgEscape escapes globally");
setUp();
warmUp();
for (TaggingAndCountingMethods m : TaggingAndCountingMethods.values()) {
msgHL(getClass().getName() + ": Tag and Get of ArgEscapes using " + m.name());
waitingForCheck = false;
checkingNow = false;
WB.deflateIdleMonitors();
WB.fullGC(); // get rid of dead instances from previous test cases
runTest(m);
}
} catch (Exception e) {
Asserts.fail("Unexpected Exception", e);
}
}
public void runTest(TaggingAndCountingMethods m) throws Exception {
loopCount = 1L << 62; // endless loop
doLoop = true;
Thread t1 = new Thread(() -> dontinline_testMethod(), "Target Thread (" + getClass().getName() + ")");
try {
t1.start();
try {
waitUntilTargetThreadHasEnteredEndlessLoop();
msg("count and tag instances of " + taggedClass.getName() + " with tag " + instanceTag + " using JVMTI " + m.name());
int count = countAndTagInstancesOfClass(taggedClass, classTag, instanceTag, m);
msg("Done. Count is " + count);
Asserts.assertGreaterThanOrEqual(count, 0, "countAndTagInstancesOfClass FAILED");
Asserts.assertEQ(count, 1, "unexpected number of instances");
} finally {
terminateEndlessLoop();
}
ABBox[] result = new ABBox[1];
msg("get instances tagged with " + instanceTag);
int err = getObjectsWithTag(instanceTag, result);
msg("Done.");
Asserts.assertEQ(0, err, "getObjectsWithTag FAILED");
ABBox abBoxArgEscape = result[0];
while (!waitingForCheck) {
Thread.yield();
}
msg("Check abBoxArgEscape's state is consistent");
checkingNow = true;
Asserts.assertTrue(abBoxArgEscape.check(), "Detected inconsistent state. abBoxArgEscape.aVal != abBoxArgEscape.bVal");
msg("Ok.");
} finally {
checkingNow = true;
t1.join();
}
}
@Override
public void dontinline_testMethod() {
// The new instance is an ArgEscape instance and escapes to the JVMTI agent
// while the target thread is in the call to dontinline_endlessLoop(). At this
// location there is no local variable that references the ArgEscape.
((ABBox) dontinline_endlessLoop(new ABBox(this))).synchronizedSlowInc();;
}
}
public static void msg(String m) {
System.out.println();
System.out.println("### " + m);
System.out.println();
}
public static void msgHL(String m) {
System.out.println(); System.out.println(); System.out.println();
System.out.println("#####################################################");
System.out.println("### " + m);
System.out.println("###");
System.out.println();
}
}

View File

@ -0,0 +1,370 @@
/*
* 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 <stdio.h>
#include <string.h>
#include "jvmti.h"
#include "jni.h"
extern "C" {
#define FAILED -1
#define OK 0
static jvmtiEnv *jvmti;
static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved);
static void ShowErrorMessage(jvmtiEnv *jvmti, jvmtiError errCode, const char *message) {
char *errMsg;
jvmtiError result;
result = jvmti->GetErrorName(errCode, &errMsg);
if (result == JVMTI_ERROR_NONE) {
fprintf(stderr, "%s: %s (%d)\n", message, errMsg, errCode);
jvmti->Deallocate((unsigned char *)errMsg);
} else {
fprintf(stderr, "%s (%d)\n", message, errCode);
}
}
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
return Agent_Initialize(jvm, options, reserved);
}
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM *jvm, char *options, void *reserved) {
return Agent_Initialize(jvm, options, reserved);
}
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved) {
jint res;
JNIEnv *env;
res = jvm->GetEnv((void **) &env, JNI_VERSION_9);
if (res != JNI_OK || env == NULL) {
fprintf(stderr, "Error: GetEnv call failed(%d)!\n", res);
return JNI_ERR;
}
return JNI_VERSION_9;
}
static jint
Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {
jint res;
printf("Agent_OnLoad started\n");
res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_9);
if (res != JNI_OK || jvmti == NULL) {
fprintf(stderr, "Error: wrong result of a valid call to GetEnv!\n");
return JNI_ERR;
}
printf("Agent_OnLoad finished\n");
return JNI_OK;
}
JNIEXPORT jint JNICALL
Java_IterateHeapWithEscapeAnalysisEnabled_acquireCanTagObjectsCapability(JNIEnv *env, jclass cls) {
jvmtiError err;
jvmtiCapabilities caps;
memset(&caps, 0, sizeof(caps));
caps.can_tag_objects = 1;
err = jvmti->AddCapabilities(&caps);
if (err != JVMTI_ERROR_NONE) {
ShowErrorMessage(jvmti, err,
"acquireCanTagObjectsCapability: error in JVMTI AddCapabilities");
return JNI_ERR;
}
err = jvmti->GetCapabilities(&caps);
if (err != JVMTI_ERROR_NONE) {
ShowErrorMessage(jvmti, err,
"acquireCanTagObjectsCapability: error in JVMTI GetCapabilities");
return JNI_ERR;
}
if (!caps.can_tag_objects) {
fprintf(stderr, "Warning: didn't get the capability can_tag_objects\n");
return JNI_ERR;
}
return JNI_OK;
}
static jobject method_IterateOverReachableObjects;
static jobject method_IterateOverHeap;
static jobject method_IterateOverInstancesOfClass;
static jobject method_FollowReferences;
static jobject method_IterateThroughHeap;
JNIEXPORT jint JNICALL
Java_IterateHeapWithEscapeAnalysisEnabled_registerMethod(JNIEnv *env, jclass cls, jobject method, jstring name) {
const char *name_chars = env->GetStringUTFChars(name, 0);
int rc = FAILED;
if (rc != OK && strcmp(name_chars, "IterateOverReachableObjects") == 0) {
method_IterateOverReachableObjects = env->NewGlobalRef(method);
rc = OK;
}
if (rc != OK && strcmp(name_chars, "IterateOverHeap") == 0) {
method_IterateOverHeap = env->NewGlobalRef(method);
rc = OK;
}
if (rc != OK && strcmp(name_chars, "IterateOverInstancesOfClass") == 0) {
method_IterateOverInstancesOfClass = env->NewGlobalRef(method);
rc = OK;
}
if (rc != OK && strcmp(name_chars, "IterateThroughHeap") == 0) {
method_IterateThroughHeap = env->NewGlobalRef(method);
rc = OK;
}
if (rc != OK && strcmp(name_chars, "FollowReferences") == 0) {
method_FollowReferences = env->NewGlobalRef(method);
rc = OK;
}
env->ReleaseStringUTFChars(name, name_chars);
return rc;
}
JNIEXPORT void JNICALL
Java_IterateHeapWithEscapeAnalysisEnabled_agentTearDown(JNIEnv *env, jclass cls) {
env->DeleteGlobalRef(method_IterateOverReachableObjects);
env->DeleteGlobalRef(method_IterateOverHeap);
env->DeleteGlobalRef(method_IterateOverInstancesOfClass);
env->DeleteGlobalRef(method_FollowReferences);
env->DeleteGlobalRef(method_IterateThroughHeap);
}
JNIEXPORT jint JNICALL
Java_IterateHeapWithEscapeAnalysisEnabled_jvmtiTagClass(JNIEnv *env, jclass cls, jclass clsToTag, jlong tag) {
jvmtiError err;
err = jvmti->SetTag(clsToTag, tag);
if (err != JVMTI_ERROR_NONE) {
ShowErrorMessage(jvmti, err,
"jvmtiTagClass: error in JVMTI SetTag");
return FAILED;
}
return OK;
}
typedef struct Tag_And_Counter {
jlong instance_counter;
jlong class_tag;
jlong instance_tag;
} Tag_And_Counter;
static jvmtiIterationControl JNICALL
__stackReferenceCallback(jvmtiHeapRootKind root_kind,
jlong class_tag,
jlong size,
jlong* tag_ptr,
jlong thread_tag,
jint depth,
jmethodID method,
jint slot,
void* d) {
Tag_And_Counter* data = (Tag_And_Counter*) d;
if (class_tag == data->class_tag && *tag_ptr == 0) {
data->instance_counter++;
*tag_ptr = data->instance_tag;
}
return JVMTI_ITERATION_CONTINUE;
}
static jvmtiIterationControl JNICALL
__jvmtiHeapObjectCallback(jlong class_tag, jlong size, jlong* tag_ptr, void* d) {
Tag_And_Counter* data = (Tag_And_Counter*) d;
if (class_tag == data->class_tag && *tag_ptr == 0) {
data->instance_counter++;
*tag_ptr = data->instance_tag;
}
return JVMTI_ITERATION_CONTINUE;
}
static jint JNICALL
__jvmtiHeapReferenceCallback(jvmtiHeapReferenceKind reference_kind,
const jvmtiHeapReferenceInfo* reference_info,
jlong class_tag,
jlong referrer_class_tag,
jlong size,
jlong* tag_ptr,
jlong* referrer_tag_ptr,
jint length,
void* d) {
Tag_And_Counter* data = (Tag_And_Counter*) d;
if (class_tag == data->class_tag && *tag_ptr == 0) {
data->instance_counter++;
*tag_ptr = data->instance_tag;
}
return JVMTI_VISIT_OBJECTS;
}
static jint JNICALL
__jvmtiHeapIterationCallback(jlong class_tag,
jlong size,
jlong* tag_ptr,
jint length,
void* d) {
Tag_And_Counter* data = (Tag_And_Counter*) d;
if (class_tag == data->class_tag && *tag_ptr == 0) {
data->instance_counter++;
*tag_ptr = data->instance_tag;
}
return JVMTI_VISIT_OBJECTS;
}
JNIEXPORT jlong JNICALL
Java_IterateHeapWithEscapeAnalysisEnabled_countAndTagInstancesOfClass(JNIEnv *env,
jclass cls,
jclass tagged_class,
jlong cls_tag,
jlong instance_tag,
jobject method) {
jvmtiError err;
jvmtiHeapCallbacks callbacks = {};
Tag_And_Counter data = {0, cls_tag, instance_tag};
jboolean method_found = JNI_FALSE;
jint idx = 0;
if (env->IsSameObject(method, method_IterateOverReachableObjects)) {
method_found = JNI_TRUE;
err = jvmti->IterateOverReachableObjects(NULL /*jvmtiHeapRootCallback*/,
__stackReferenceCallback,
NULL /* jvmtiObjectReferenceCallback */,
&data);
if (err != JVMTI_ERROR_NONE) {
ShowErrorMessage(jvmti, err,
"countAndTagInstancesOfClass: error in JVMTI IterateOverReachableObjects");
return FAILED;
}
}
if (env->IsSameObject(method, method_IterateOverHeap)) {
method_found = JNI_TRUE;
err = jvmti->IterateOverHeap(JVMTI_HEAP_OBJECT_EITHER,
__jvmtiHeapObjectCallback,
&data);
if (err != JVMTI_ERROR_NONE) {
ShowErrorMessage(jvmti, err,
"countAndTagInstancesOfClass: error in JVMTI IterateOverHeap");
return FAILED;
}
}
if (env->IsSameObject(method, method_IterateOverInstancesOfClass)) {
method_found = JNI_TRUE;
err = jvmti->IterateOverInstancesOfClass(tagged_class,
JVMTI_HEAP_OBJECT_EITHER,
__jvmtiHeapObjectCallback,
&data);
if (err != JVMTI_ERROR_NONE) {
ShowErrorMessage(jvmti, err,
"countAndTagInstancesOfClass: error in JVMTI IterateOverHeap");
return FAILED;
}
}
if (env->IsSameObject(method, method_FollowReferences)) {
method_found = JNI_TRUE;
callbacks.heap_reference_callback = __jvmtiHeapReferenceCallback;
err = jvmti->FollowReferences(0 /* filter nothing */,
NULL /* no class filter */,
NULL /* no initial object, follow roots */,
&callbacks,
&data);
if (err != JVMTI_ERROR_NONE) {
ShowErrorMessage(jvmti, err,
"countAndTagInstancesOfClass: error in JVMTI FollowReferences");
return FAILED;
}
}
if (env->IsSameObject(method, method_IterateThroughHeap)) {
method_found = JNI_TRUE;
callbacks.heap_iteration_callback = __jvmtiHeapIterationCallback;
err = jvmti->IterateThroughHeap(0 /* filter nothing */,
NULL /* no class filter */,
&callbacks,
&data);
if (err != JVMTI_ERROR_NONE) {
ShowErrorMessage(jvmti, err,
"countAndTagInstancesOfClass: error in JVMTI IterateThroughHeap");
return FAILED;
}
}
if (!method_found) {
fprintf(stderr, "countAndTagInstancesOfClass: unknown method\n");
return FAILED;
}
return data.instance_counter;
}
JNIEXPORT jlong JNICALL
Java_IterateHeapWithEscapeAnalysisEnabled_getObjectsWithTag(JNIEnv *env,
jclass cls,
jlong tag,
jobjectArray res_instances) {
jvmtiError err;
const jlong tags[1] = {tag};
jint res_count = -1;
jobject* res_instances_raw;
jlong* res_tags;
jint res_instances_length;
jint idx;
err = jvmti->GetObjectsWithTags(1,
tags,
&res_count,
&res_instances_raw,
&res_tags);
if (err != JVMTI_ERROR_NONE) {
ShowErrorMessage(jvmti, err,
"getObjectsWithTags: error in JVMTI GetObjectsWithTags");
return FAILED;
}
res_instances_length = env->GetArrayLength(res_instances);
if (res_count != res_instances_length) {
fprintf(stderr, "getObjectsWithTags: result array lenght (%d) does not match instance count returned by GetObjectsWithTags (%d) \n",
res_instances_length, res_count);
return FAILED;
}
for (idx = 0; idx < res_count; idx++) {
env->SetObjectArrayElement(res_instances, idx, res_instances_raw[idx]);
}
jvmti->Deallocate((unsigned char *)res_instances_raw);
jvmti->Deallocate((unsigned char *)res_tags);
return OK;
}
}

View File

@ -56,6 +56,7 @@ requires.properties= \
vm.debug \
vm.hasSA \
vm.hasJFR \
vm.jvmci \
docker.support \
release.implementor

File diff suppressed because it is too large Load Diff

View File

@ -240,6 +240,7 @@ public class WhiteBox {
public native int matchesInline(Executable method, String pattern);
public native boolean shouldPrintAssembly(Executable method, int comp_level);
public native int deoptimizeFrames(boolean makeNotEntrant);
public native boolean isFrameDeoptimized(int depth);
public native void deoptimizeAll();
public boolean isMethodCompiled(Executable method) {