# HG changeset patch # User rrich # Date 1594585392 -7200 # Sun Jul 12 22:23:12 2020 +0200 # Node ID e0b7cdcb68be0f59c48ec2fba27d467f1346a5b4 # Parent 2630948916e8e8f3fb3a03cd0f984fb7b508f540 8227745: Enable Escape Analysis for Better Performance in the Presence of JVMTI Agents Reviewed-by: mdoerr, goetz diff --git a/src/hotspot/share/c1/c1_IR.hpp b/src/hotspot/share/c1/c1_IR.hpp --- a/src/hotspot/share/c1/c1_IR.hpp +++ b/src/hotspot/share/c1/c1_IR.hpp @@ -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 @@ 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 not_global_escape_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, + not_global_escape_in_scope, arg_escape, locvals, expvals, monvals); } }; diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp --- a/src/hotspot/share/ci/ciEnv.cpp +++ b/src/hotspot/share/ci/ciEnv.cpp @@ -241,6 +241,7 @@ _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(); } @@ -270,6 +271,10 @@ JvmtiExport::can_get_owned_monitor_info()) { return true; } + if (!_jvmti_can_walk_any_space && + JvmtiExport::can_walk_any_space()) { + return true; + } return false; } diff --git a/src/hotspot/share/ci/ciEnv.hpp b/src/hotspot/share/ci/ciEnv.hpp --- a/src/hotspot/share/ci/ciEnv.hpp +++ b/src/hotspot/share/ci/ciEnv.hpp @@ -73,6 +73,7 @@ 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; @@ -348,6 +349,7 @@ 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(); diff --git a/src/hotspot/share/code/compiledMethod.cpp b/src/hotspot/share/code/compiledMethod.cpp --- a/src/hotspot/share/code/compiledMethod.cpp +++ b/src/hotspot/share/code/compiledMethod.cpp @@ -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 @@ -286,17 +286,13 @@ 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 { diff --git a/src/hotspot/share/code/debugInfoRec.cpp b/src/hotspot/share/code/debugInfoRec.cpp --- a/src/hotspot/share/code/debugInfoRec.cpp +++ b/src/hotspot/share/code/debugInfoRec.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2015, 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 @@ -287,6 +287,8 @@ bool rethrow_exception, bool is_method_handle_invoke, bool return_oop, + bool not_global_escape_in_scope, + bool arg_escape, DebugToken* locals, DebugToken* expressions, DebugToken* monitors) { @@ -303,6 +305,8 @@ 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_not_global_escape_in_scope(not_global_escape_in_scope); + last_pd->set_arg_escape(arg_escape); // serialize sender stream offest stream()->write_int(sender_stream_offset); diff --git a/src/hotspot/share/code/debugInfoRec.hpp b/src/hotspot/share/code/debugInfoRec.hpp --- a/src/hotspot/share/code/debugInfoRec.hpp +++ b/src/hotspot/share/code/debugInfoRec.hpp @@ -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 @@ bool rethrow_exception = false, bool is_method_handle_invoke = false, bool return_oop = false, + bool not_global_escape_in_scope = false, + bool arg_escape = false, DebugToken* locals = NULL, DebugToken* expressions = NULL, DebugToken* monitors = NULL); diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -2417,9 +2417,7 @@ 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(); } @@ -3057,9 +3055,7 @@ 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; } diff --git a/src/hotspot/share/code/pcDesc.hpp b/src/hotspot/share/code/pcDesc.hpp --- a/src/hotspot/share/code/pcDesc.hpp +++ b/src/hotspot/share/code/pcDesc.hpp @@ -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 @@ 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_not_global_escape_in_scope = 1 << 4, + PCDESC_arg_escape = 1 << 5 }; int _flags; @@ -89,6 +91,16 @@ 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. + bool not_global_escape_in_scope() const { return (_flags & PCDESC_not_global_escape_in_scope) != 0; } + void set_not_global_escape_in_scope(bool z) { set_flag(PCDESC_not_global_escape_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; diff --git a/src/hotspot/share/code/scopeDesc.cpp b/src/hotspot/share/code/scopeDesc.cpp --- a/src/hotspot/share/code/scopeDesc.cpp +++ b/src/hotspot/share/code/scopeDesc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2016, 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 @@ -30,23 +30,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(); + _not_global_escape_in_scope = ignore_objects ? false : pd->not_global_escape_in_scope(); + _arg_escape = ignore_objects ? false : pd->arg_escape(); decode_body(); } @@ -58,6 +51,8 @@ _reexecute = false; //reexecute only applies to the first scope _rethrow_exception = false; _return_oop = false; + _not_global_escape_in_scope = parent->not_global_escape_in_scope(); + _arg_escape = false; decode_body(); } diff --git a/src/hotspot/share/code/scopeDesc.hpp b/src/hotspot/share/code/scopeDesc.hpp --- a/src/hotspot/share/code/scopeDesc.hpp +++ b/src/hotspot/share/code/scopeDesc.hpp @@ -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 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,8 @@ bool should_reexecute() const { return _reexecute; } bool rethrow_exception() const { return _rethrow_exception; } bool return_oop() const { return _return_oop; } + bool not_global_escape_in_scope() const { return _not_global_escape_in_scope; } + bool arg_escape() const { return _arg_escape; } GrowableArray* locals(); GrowableArray* expressions(); @@ -105,6 +102,8 @@ bool _reexecute; bool _rethrow_exception; bool _return_oop; + bool _not_global_escape_in_scope; // NoEscape or ArgEscape in any of the scopes at compiled pc + bool _arg_escape; // Compiled Java call in youngest scope passes ArgEscape // Decoding offsets int _decode_offset; diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -803,19 +803,93 @@ CHECK_NH); } - -JavaThread* CompileBroker::make_thread(jobject thread_handle, CompileQueue* queue, AbstractCompiler* comp, Thread* THREAD) { +#if defined(ASSERT) && COMPILER2_OR_JVMCI +// Stress testing. Revert optimizations based on escape analysis. +class DeoptimizeObjectsALotThread : public JavaThread { + + 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; } +}; + +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(); + } + } + +// Revert optimizations for a single deoptee_thread which gets selected round robin +void DeoptimizeObjectsALotThread::deoptimize_objects_alot_loop_single() { + HandleMark hm(this); + while (!this->is_terminated()) { + for (JavaThreadIteratorWithHandle jtiwh; JavaThread *deoptee_thread = jtiwh.next(); ) { + { // Begin new scope for escape barrier + HandleMarkCleaner hmc(this); + ResourceMark rm(this); + EscapeBarrier eb(this, deoptee_thread, true); + eb.deoptimize_objects(100); + } + // Now sleep after the escape barriers destructor resumed deoptee_thread. + sleep(DeoptimizeObjectsALotInterval); + } + } +} + +// Revert optimizations for all threads at once +void DeoptimizeObjectsALotThread::deoptimize_objects_alot_loop_all() { + HandleMark hm(this); + while (!is_terminated()) { + { // Begin new scope for escape barrier + HandleMarkCleaner hmc(this); + ResourceMark rm(this); + EscapeBarrier eb(this, true); + 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 @@ -857,7 +931,7 @@ 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); @@ -867,7 +941,7 @@ // 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(); } @@ -922,7 +996,7 @@ _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) { @@ -942,7 +1016,7 @@ _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) { @@ -961,8 +1035,20 @@ // 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) { @@ -1016,7 +1102,7 @@ _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) { @@ -1036,7 +1122,7 @@ (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) { diff --git a/src/hotspot/share/compiler/compileBroker.hpp b/src/hotspot/share/compiler/compileBroker.hpp --- a/src/hotspot/share/compiler/compileBroker.hpp +++ b/src/hotspot/share/compiler/compileBroker.hpp @@ -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 @@ -226,8 +226,14 @@ 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); diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.cpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.cpp --- a/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.cpp +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.cpp @@ -335,7 +335,7 @@ return true; } - GrowableArray* const list = jt->deferred_locals(); + GrowableArray* const list = JvmtiDeferredUpdates::deferred_locals(jt); if (list != NULL) { for (int i = 0; i < list->length(); i++) { list->at(i)->oops_do(&rcl); diff --git a/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp b/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp --- a/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp +++ b/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -1181,7 +1181,11 @@ throw_exception = jvmci_env()->get_BytecodeFrame_rethrowException(frame) == JNI_TRUE; } + // not_global_escape_in_scope and arg_escape should be added to JVMCI + const bool not_global_escape_in_scope = false; + const bool arg_escape = false; _debug_recorder->describe_scope(pc_offset, method, NULL, bci, reexecute, throw_exception, false, return_oop, + not_global_escape_in_scope, arg_escape, locals_token, expressions_token, monitors_token); } diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -103,8 +103,7 @@ 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()) { diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -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 @@ -331,7 +331,8 @@ : MultiNode( edges ), _oop_map(NULL), _jvms(jvms), - _adr_type(adr_type) + _adr_type(adr_type), + _not_global_escape_in_scope(false) { init_class_id(Class_SafePoint); } @@ -340,6 +341,7 @@ 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 _not_global_escape_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. @@ -461,6 +463,12 @@ bool has_replaced_nodes() const { return !_replaced_nodes.is_empty(); } + void set_not_global_escape_in_scope(bool b) { + _not_global_escape_in_scope = b; + } + bool not_global_escape_in_scope() const { + return _not_global_escape_in_scope; + } void disconnect_from_root(PhaseIterGVN *igvn); @@ -661,6 +669,7 @@ 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) @@ -668,7 +677,8 @@ _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); } @@ -682,6 +692,8 @@ 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; } DEBUG_ONLY( bool validate_symbolic_info() const; ) diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -123,6 +123,7 @@ GrowableArray java_objects_worklist; GrowableArray non_escaped_worklist; GrowableArray oop_fields_worklist; + GrowableArray sfn_worklist; DEBUG_ONLY( GrowableArray addp_worklist; ) { Compile::TracePhase tp("connectionGraph", &Phase::timers[Phase::_t_connectionGraph]); @@ -188,6 +189,9 @@ 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 @@ 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_not_global_escape_in_scope(has_not_global_escape_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 an object in the scope of sfn does not escape globally. +bool ConnectionGraph::has_not_global_escape_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 diff --git a/src/hotspot/share/opto/escape.hpp b/src/hotspot/share/opto/escape.hpp --- a/src/hotspot/share/opto/escape.hpp +++ b/src/hotspot/share/opto/escape.hpp @@ -552,6 +552,10 @@ return (phi == NULL) ? NULL : phi->as_Phi(); } + bool has_not_global_escape_in_scope(SafePointNode* sfn); + + bool has_arg_escape(CallJavaNode* call); + // Notify optimizer that a node has been modified void record_for_optimizer(Node *n); diff --git a/src/hotspot/share/opto/machnode.hpp b/src/hotspot/share/opto/machnode.hpp --- a/src/hotspot/share/opto/machnode.hpp +++ b/src/hotspot/share/opto/machnode.hpp @@ -820,10 +820,11 @@ 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 _not_global_escape_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), _not_global_escape_in_scope(false) { init_class_id(Class_MachSafePoint); } @@ -925,6 +926,7 @@ 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); } diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -1093,7 +1093,7 @@ // 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) { + if (!EliminateAllocations || !alloc->_is_non_escaping) { return false; } Node* klass = alloc->in(AllocateNode::KlassNode); diff --git a/src/hotspot/share/opto/matcher.cpp b/src/hotspot/share/opto/matcher.cpp --- a/src/hotspot/share/opto/matcher.cpp +++ b/src/hotspot/share/opto/matcher.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2017, 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 @@ -1160,6 +1160,7 @@ 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); } @@ -1184,6 +1185,7 @@ msfpt = mn->as_MachSafePoint(); cnt = TypeFunc::Parms; } + msfpt->_not_global_escape_in_scope = sfpt->not_global_escape_in_scope(); // Advertise the correct memory effects (for anti-dependence computation). msfpt->set_adr_type(sfpt->adr_type()); diff --git a/src/hotspot/share/opto/output.cpp b/src/hotspot/share/opto/output.cpp --- a/src/hotspot/share/opto/output.cpp +++ b/src/hotspot/share/opto/output.cpp @@ -933,6 +933,8 @@ int safepoint_pc_offset = current_offset; bool is_method_handle_invoke = false; bool return_oop = false; + bool not_global_escape_in_scope = sfn->_not_global_escape_in_scope; + bool arg_escape = false; // Add the safepoint in the DebugInfoRecorder if( !mach->is_MachCall() ) { @@ -947,6 +949,7 @@ 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. @@ -1067,7 +1070,9 @@ // 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, + not_global_escape_in_scope, arg_escape, + locvals, expvals, monvals); } // End jvms loop // Mark the end of the scope set. diff --git a/src/hotspot/share/prims/jvmtiCodeBlobEvents.cpp b/src/hotspot/share/prims/jvmtiCodeBlobEvents.cpp --- a/src/hotspot/share/prims/jvmtiCodeBlobEvents.cpp +++ b/src/hotspot/share/prims/jvmtiCodeBlobEvents.cpp @@ -275,7 +275,7 @@ 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(); diff --git a/src/hotspot/share/prims/jvmtiEnv.cpp b/src/hotspot/share/prims/jvmtiEnv.cpp --- a/src/hotspot/share/prims/jvmtiEnv.cpp +++ b/src/hotspot/share/prims/jvmtiEnv.cpp @@ -1206,6 +1206,11 @@ GrowableArray *owned_monitors_list = new (ResourceObj::C_HEAP, mtServiceability) GrowableArray(1, mtServiceability); + EscapeBarrier eb(calling_thread, java_thread, true); + 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 @@ GrowableArray *owned_monitors_list = new (ResourceObj::C_HEAP, mtServiceability) GrowableArray(1, mtServiceability); + EscapeBarrier eb(calling_thread, java_thread, true); + 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) { @@ -1667,6 +1677,13 @@ } } + 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 @@ -1698,9 +1715,15 @@ } // If any of the top 2 frames is a compiled one, need to deoptimize it + EscapeBarrier eb(current_thread, java_thread, !is_interpreted[0] || !is_interpreted[1]); 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; + } } } diff --git a/src/hotspot/share/prims/jvmtiEnvBase.cpp b/src/hotspot/share/prims/jvmtiEnvBase.cpp --- a/src/hotspot/share/prims/jvmtiEnvBase.cpp +++ b/src/hotspot/share/prims/jvmtiEnvBase.cpp @@ -1309,6 +1309,13 @@ 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 = vframeFor(java_thread, 0); NULL_CHECK(vf, JVMTI_ERROR_NO_MORE_FRAMES); @@ -1323,6 +1330,12 @@ return JVMTI_ERROR_OPAQUE_FRAME; } Deoptimization::deoptimize_frame(java_thread, jvf->fr().id()); + // eagerly reallocate scalar replaced objects + EscapeBarrier eb(current_thread, java_thread, true); + 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 diff --git a/src/hotspot/share/prims/jvmtiImpl.cpp b/src/hotspot/share/prims/jvmtiImpl.cpp --- a/src/hotspot/share/prims/jvmtiImpl.cpp +++ b/src/hotspot/share/prims/jvmtiImpl.cpp @@ -441,6 +441,7 @@ , _type(type) , _jvf(NULL) , _set(false) + , _eb(NULL, NULL, false) // no references escape , _result(JVMTI_ERROR_NONE) { } @@ -455,6 +456,7 @@ , _value(value) , _jvf(NULL) , _set(true) + , _eb(JavaThread::current(), thread, type == T_OBJECT) , _result(JVMTI_ERROR_NONE) { } @@ -468,6 +470,7 @@ , _type(T_OBJECT) , _jvf(NULL) , _set(false) + , _eb(calling_thread, thread, true) , _result(JVMTI_ERROR_NONE) { } @@ -643,6 +646,50 @@ 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 + if (NOT_JVMCI(DoEscapeAnalysis &&) _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->not_global_escape_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() { _jvf = get_java_vframe(); NULL_CHECK(_jvf, false); @@ -664,9 +711,14 @@ if (!check_slot_type_no_lvt(_jvf)) { return false; } - if (method_oop->has_localvariable_table()) { - return check_slot_type_lvt(_jvf); + if (method_oop->has_localvariable_table() && !check_slot_type_lvt(_jvf)) { + return false; } + + if (!deoptimize_objects(_jvf)) { + return false; + } + return true; } diff --git a/src/hotspot/share/prims/jvmtiImpl.hpp b/src/hotspot/share/prims/jvmtiImpl.hpp --- a/src/hotspot/share/prims/jvmtiImpl.hpp +++ b/src/hotspot/share/prims/jvmtiImpl.hpp @@ -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 @@ -32,6 +32,7 @@ #include "prims/jvmtiEventController.hpp" #include "prims/jvmtiTrace.hpp" #include "prims/jvmtiUtil.hpp" +#include "runtime/deoptimization.hpp" #include "runtime/stackValueCollection.hpp" #include "runtime/vmOperations.hpp" #include "utilities/ostream.hpp" @@ -321,6 +322,8 @@ 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 @@ 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 diff --git a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp --- a/src/hotspot/share/prims/jvmtiTagMap.cpp +++ b/src/hotspot/share/prims/jvmtiTagMap.cpp @@ -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" @@ -1488,6 +1489,10 @@ jvmtiHeapObjectCallback heap_object_callback, const void* user_data) { + // EA based optimizations on tagged objects are already reverted. + EscapeBarrier eb(JavaThread::current(), + object_filter == JVMTI_HEAP_OBJECT_UNTAGGED || object_filter == JVMTI_HEAP_OBJECT_EITHER); + eb.deoptimize_objects_all_threads(); MutexLocker ml(Heap_lock); IterateOverHeapObjectClosure blk(this, klass, @@ -1505,6 +1510,9 @@ const jvmtiHeapCallbacks* callbacks, const void* user_data) { + // EA based optimizations on tagged objects are already reverted. + EscapeBarrier eb(JavaThread::current(), !(heap_filter & JVMTI_HEAP_FILTER_UNTAGGED)); + eb.deoptimize_objects_all_threads(); MutexLocker ml(Heap_lock); IterateThroughHeapObjectClosure blk(this, klass, @@ -3260,6 +3268,9 @@ jvmtiStackReferenceCallback stack_ref_callback, jvmtiObjectReferenceCallback object_ref_callback, const void* user_data) { + JavaThread* jt = JavaThread::current(); + EscapeBarrier eb(jt, true); + 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); @@ -3287,8 +3298,12 @@ 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(jt, + initial_object.is_null() && !(heap_filter & JVMTI_HEAP_FILTER_UNTAGGED)); + eb.deoptimize_objects_all_threads(); MutexLocker ml(Heap_lock); AdvancedHeapWalkContext context(heap_filter, klass, callbacks); VM_HeapWalkOperation op(this, initial_object, context, user_data); diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -77,6 +77,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" @@ -877,6 +878,20 @@ return op.result(); WB_END +WB_ENTRY(jboolean, WB_IsFrameDeoptimized(JNIEnv* env, jobject o, jint depth)) + JavaThread* t = JavaThread::current(); + bool result = false; + if (t->has_last_Java_frame()) { + RegisterMap reg_map(t); + javaVFrame *jvf = t->last_java_vframe(®_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(); @@ -2356,6 +2371,7 @@ {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 }, diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -60,6 +60,7 @@ #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" @@ -167,10 +168,14 @@ #if COMPILER2_OR_JVMCI static bool eliminate_allocations(JavaThread* thread, int exec_mode, CompiledMethod* compiled_method, - frame& deoptee, RegisterMap& map, GrowableArray* chunk) { + frame& deoptee, RegisterMap& map, GrowableArray* 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* objects = chunk->at(0)->scope()->objects(); // The flag return_oop() indicates call sites which return oop @@ -197,15 +202,23 @@ } } 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)); + 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 @@ -217,7 +230,11 @@ return realloc_failures; } -static void eliminate_locks(JavaThread* thread, GrowableArray* chunk, bool realloc_failures) { +static void eliminate_locks(JavaThread* thread, GrowableArray* 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"); + #ifndef PRODUCT bool first = true; #endif @@ -226,7 +243,8 @@ assert (cvf->scope() != NULL,"expect only compiled java frames"); GrowableArray* 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; @@ -237,6 +255,13 @@ 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()); @@ -250,6 +275,35 @@ } } } + +// 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* 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) @@ -304,7 +358,8 @@ // 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 @@ -319,8 +374,10 @@ 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 @@ -351,8 +408,8 @@ // added by jvmti then we can free up that structure as the data is now in the // vframeArray - if (thread->deferred_locals() != NULL) { - GrowableArray* list = thread->deferred_locals(); + if (JvmtiDeferredUpdates::deferred_locals(thread) != NULL) { + GrowableArray* list = JvmtiDeferredUpdates::deferred_locals(thread); int i = 0; do { // Because of inlining we could have multiple vframes for a single frame @@ -367,9 +424,10 @@ } } 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* updates = thread->deferred_updates(); + thread->set_deferred_updates(NULL); + // free deferred updates. + delete updates; } } @@ -1353,11 +1411,14 @@ // relock objects for which synchronization was eliminated -void Deoptimization::relock_objects(GrowableArray* monitors, JavaThread* thread, bool realloc_failures) { +bool Deoptimization::relock_objects(JavaThread* thread, GrowableArray* 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(); @@ -1366,17 +1427,37 @@ // 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; } @@ -1494,18 +1575,19 @@ } #endif -static void collect_monitors(compiledVFrame* cvf, GrowableArray* objects_to_revoke) { +static void collect_monitors(compiledVFrame* cvf, GrowableArray* objects_to_revoke, bool only_eliminated) { GrowableArray* 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* objects_to_revoke, JavaThread* thread, frame fr, RegisterMap* map) { +static void get_monitors_from_stack(GrowableArray* 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. @@ -1525,10 +1607,10 @@ 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) { @@ -1536,7 +1618,7 @@ return; } GrowableArray* objects_to_revoke = new GrowableArray(); - 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++) { @@ -1546,6 +1628,34 @@ } } +// 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* objects_to_revoke = new GrowableArray(); + // 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"); @@ -2598,6 +2708,333 @@ if (xtty != NULL) xtty->tail("statistics"); } } + +// 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* 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()) { + ResourceMark rm(calling_thread()); + HandleMark hm; + RegisterMap reg_map(deoptee_thread()); + vframe* vf = deoptee_thread()->last_java_vframe(®_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->not_global_escape_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); + vframe* vf = jt->last_java_vframe(®_map); + assert(jt->frame_anchor()->walkable(), + "The stack of JavaThread " PTR_FORMAT " is not walkable. Thread state is %d", + p2i(jt), jt->thread_state()); + while (vf != NULL) { + if (vf->is_compiled_frame()) { + compiledVFrame* cvf = compiledVFrame::cast(vf); + if ((cvf->not_global_escape_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 { + JavaThread* _excluded_thread; + public: + EscapeBarrierSuspendHandshake(JavaThread* excluded_thread, const char* name) : HandshakeClosure(name), _excluded_thread(excluded_thread) { } + void do_thread(Thread* th) { + if (th->is_Java_thread() && !th->is_hidden_from_external_view() && (th != _excluded_thread)) { + th->set_obj_deopt_flag(); + } + } +}; + +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(); + } + + // suspend target thread + EscapeBarrierSuspendHandshake sh(NULL, "EscapeBarrierSuspendOne"); + Handshake::execute_direct(&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; + } + + EscapeBarrierSuspendHandshake sh(_calling_thread, "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* 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 { + StackFrameStream fst(deoptee); + 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* vfs = new GrowableArray(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); + + // locals + GrowableArray* scopeLocals = cvf->scope()->locals(); + StackValueCollection* locals = cvf->locals(); + if (locals != NULL) { + for (int i2 = 0; i2 < locals->size(); i2++) { + StackValue* var = locals->at(i2); + if (var->type() == T_OBJECT && scopeLocals->at(i2)->is_object()) { + jvalue val; + val.l = cast_from_oop(locals->at(i2)->get_obj()()); + cvf->update_local(T_OBJECT, i2, val); + } + } + } + + // expressions + GrowableArray* scopeExpressions = cvf->scope()->expressions(); + StackValueCollection* expressions = cvf->expressions(); + if (expressions != NULL) { + for (int i2 = 0; i2 < expressions->size(); i2++) { + StackValue* var = expressions->at(i2); + if (var->type() == T_OBJECT && scopeExpressions->at(i2)->is_object()) { + jvalue val; + val.l = cast_from_oop(expressions->at(i2)->get_obj()()); + cvf->update_stack(T_OBJECT, i2, val); + } + } + } + + // monitors + GrowableArray* monitors = cvf->monitors(); + if (monitors != NULL) { + for (int i2 = 0; i2 < monitors->length(); i2++) { + if (monitors->at(i2)->eliminated()) { + assert(!monitors->at(i2)->owner_is_scalar_replaced(), "reallocation failure, should not update"); + cvf->update_monitor(i2, monitors->at(i2)); + } + } + } + } + set_objs_are_deoptimized(deoptee, fr_id); + } + } + return !realloc_failures; +} + #else // COMPILER2_OR_JVMCI diff --git a/src/hotspot/share/runtime/deoptimization.hpp b/src/hotspot/share/runtime/deoptimization.hpp --- a/src/hotspot/share/runtime/deoptimization.hpp +++ b/src/hotspot/share/runtime/deoptimization.hpp @@ -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 @@ -36,11 +36,13 @@ class AutoBoxObjectValue; class ScopeValue; class compiledVFrame; +class EscapeBarrier; template class GrowableArray; class Deoptimization : AllStatic { friend class VMStructs; + friend class EscapeBarrier; public: // What condition caused the deoptimization? @@ -134,7 +136,8 @@ 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 +155,8 @@ // 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,10 @@ 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* chunk, bool& realloc_failures); + public: // Support for restoring non-escaping objects @@ -173,7 +182,8 @@ 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* objects, bool realloc_failures, bool skip_internal); - static void relock_objects(GrowableArray* monitors, JavaThread* thread, bool realloc_failures); + static bool relock_objects(JavaThread* thread, GrowableArray* 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* objects, bool realloc_failures);) #endif // COMPILER2_OR_JVMCI @@ -466,6 +476,92 @@ static void update_method_data_from_interpreter(MethodData* trap_mdo, int trap_bci, int reason); }; +// 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; + + 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(JavaThread* calling_thread, JavaThread* deoptee_thread, bool barrier_active) + : _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(JavaThread* calling_thread, bool barrier_active) + : _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(JavaThread* calling_thread, JavaThread* deoptee_thread, bool barrier_active) { } + EscapeBarrier(JavaThread* calling_thread, bool barrier_active) { } + static bool deoptimizing_objects_for_all_threads() { 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(); + } + } + + + bool all_threads() const { return _deoptee_thread == NULL; } // Should revert optimizations for all threads. + bool self_deopt() const { return _calling_thread == _deoptee_thread; } // Current thread deoptimizes its own objects. + bool barrier_active() const { return _barrier_active; } // Inactive barriers are created if no local objects can escape. + + // accessors + JavaThread* calling_thread() const { return _calling_thread; } + JavaThread* deoptee_thread() const { return _deoptee_thread; } +#endif // COMPILER2_OR_JVMCI +}; + class DeoptimizationMarker : StackObj { // for profiling static bool _is_active; public: diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -372,6 +372,29 @@ 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) \ + \ product(bool, Debugging, false, \ "Set when executing debug methods in debug.cpp " \ "(to prevent triggering assertions)") \ diff --git a/src/hotspot/share/runtime/mutexLocker.cpp b/src/hotspot/share/runtime/mutexLocker.cpp --- a/src/hotspot/share/runtime/mutexLocker.cpp +++ b/src/hotspot/share/runtime/mutexLocker.cpp @@ -52,6 +52,7 @@ 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; @@ -300,6 +301,7 @@ 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); diff --git a/src/hotspot/share/runtime/mutexLocker.hpp b/src/hotspot/share/runtime/mutexLocker.hpp --- a/src/hotspot/share/runtime/mutexLocker.hpp +++ b/src/hotspot/share/runtime/mutexLocker.hpp @@ -45,6 +45,7 @@ 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 diff --git a/src/hotspot/share/runtime/objectMonitor.cpp b/src/hotspot/share/runtime/objectMonitor.cpp --- a/src/hotspot/share/runtime/objectMonitor.cpp +++ b/src/hotspot/share/runtime/objectMonitor.cpp @@ -44,6 +44,7 @@ #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" #include "runtime/thread.inline.hpp" +#include "runtime/vframe_hp.hpp" #include "services/threadService.hpp" #include "utilities/dtrace.hpp" #include "utilities/macros.hpp" @@ -1529,7 +1530,8 @@ 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 diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -1639,7 +1639,7 @@ set_vm_result_2(NULL); set_vframe_array_head(NULL); set_vframe_array_last(NULL); - set_deferred_locals(NULL); + set_deferred_updates(NULL); set_deopt_mark(NULL); set_deopt_compiled_method(NULL); set_monitor_chunks(NULL); @@ -1866,7 +1866,7 @@ delete old_array; } - GrowableArray* deferred = deferred_locals(); + GrowableArray* deferred = JvmtiDeferredUpdates::deferred_locals(this); if (deferred != NULL) { // This can only happen if thread is destroyed before deoptimization occurs. assert(deferred->length() != 0, "empty array!"); @@ -1876,7 +1876,7 @@ // individual jvmtiDeferredLocalVariableSet are CHeapObj's delete dlv; } while (deferred->length() != 0); - delete deferred; + delete deferred_updates(); } // All Java related clean up happens in exit @@ -2384,6 +2384,11 @@ check_and_handle_async_exceptions(); } + if (is_obj_deopt_suspend()) { + frame_anchor()->make_walkable(this); + wait_for_object_deoptimization(); + } + JFR_ONLY(SUSPEND_THREAD_CONDITIONAL(this);) } @@ -2576,6 +2581,59 @@ } } +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 should_spin_wait = true; + do { + set_thread_state(_thread_blocked); + set_suspend_equivalent(); + { + MonitorLocker ml(this, EscapeBarrier_lock, Monitor::_no_safepoint_check_flag); + if (EscapeBarrier::deoptimizing_objects_for_all_threads() || + (is_obj_deopt_suspend() && !should_spin_wait)) { + ml.wait(); + } + should_spin_wait = should_spin_wait && is_obj_deopt_suspend(); + } + // Single deoptimization is typically very short. Microbenchmarks + // showed 5% better performance when spinning + if (should_spin_wait) { + // Inspired by HandshakeSpinYield + const jlong max_spin_time_ns = 100 /* us */ * (NANOUNITS / MICROUNITS); + const int free_cpus = os::active_processor_count() - 1; + jlong spin_time_ns = (5 /* us */ * (NANOUNITS / MICROUNITS)) * free_cpus; // zero on UP + spin_time_ns = spin_time_ns > max_spin_time_ns ? max_spin_time_ns : spin_time_ns; + jlong spin_start = os::javaTimeNanos(); + while (is_obj_deopt_suspend()) { + os::naked_yield(); + if ((os::javaTimeNanos() - spin_start) > spin_time_ns) { + should_spin_wait = false; + break; + } + } + } + set_thread_state_fence(state); + + if (handle_special_suspend_equivalent_condition()) { + java_suspend_self_with_safepoint_check(); + } + + // Since we are not using a regular thread-state transition helper here, + // we must manually emit the instruction barrier after leaving a safe state. + OrderAccess::cross_modify_fence(); + if (state != _thread_in_native) { + SafepointMechanism::block_if_requested(this); + } + + // Check for another deopt suspend _after_ checking for safepont/handshake, + // or otherwise a stale value can be seen if the flag was changed with a + // handshake while the current thread was _thread_blocked above. + } while (is_obj_deopt_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. @@ -2605,6 +2663,10 @@ SafepointMechanism::block_if_requested(thread); } + if (thread->is_obj_deopt_suspend()) { + thread->wait_for_object_deoptimization(); + } + JFR_ONLY(SUSPEND_THREAD_CONDITIONAL(thread);) } @@ -2982,7 +3044,7 @@ 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* list = deferred_locals(); + GrowableArray* list = JvmtiDeferredUpdates::deferred_locals(this); if (list != NULL) { for (int i = 0; i < list->length(); i++) { list->at(i)->oops_do(f); @@ -4544,6 +4606,9 @@ // 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) { @@ -4577,6 +4642,9 @@ // 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 diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -80,7 +80,7 @@ class javaVFrame; class DeoptResourceMark; -class jvmtiDeferredLocalVariableSet; +class JvmtiDeferredUpdates; class ThreadClosure; class ICRefillVerifier; @@ -290,7 +290,8 @@ _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 @@ -541,6 +542,9 @@ 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. @@ -615,6 +619,8 @@ bool is_trace_suspend() { return (_suspend_flags & _trace_flag) != 0; } + bool is_obj_deopt_suspend() { return (_suspend_flags & _obj_deopt) != 0; } + // VM operation support int vm_operation_ticket() { return ++_vm_operation_started_count; } int vm_operation_completed_count() { return _vm_operation_completed_count; } @@ -1053,11 +1059,10 @@ 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* _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 @@ -1372,6 +1377,10 @@ inline void set_ext_suspended(); inline void clear_ext_suspended(); + // Synchronize with another thread (most likely a JVMTI agent) that is deoptimizing objects of the + // current thread, i.e. reverts optimizations based on escape analysis. + void wait_for_object_deoptimization(); + public: void java_suspend(); // higher-level suspension logic called by the public APIs void java_resume(); // higher-level resume logic called by the public APIs @@ -1436,7 +1445,7 @@ // 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 @@ -1509,7 +1518,7 @@ // 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; } @@ -1526,8 +1535,8 @@ vframeArray* vframe_array_head() const { return _vframe_array_head; } // Side structure for deferring update of java frame locals until deopt occurs - GrowableArray* deferred_locals() const { return _deferred_locals_updates; } - void set_deferred_locals(GrowableArray* 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 diff --git a/src/hotspot/share/runtime/thread.inline.hpp b/src/hotspot/share/runtime/thread.inline.hpp --- a/src/hotspot/share/runtime/thread.inline.hpp +++ b/src/hotspot/share/runtime/thread.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -65,6 +65,12 @@ 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); diff --git a/src/hotspot/share/runtime/vframe.cpp b/src/hotspot/share/runtime/vframe.cpp --- a/src/hotspot/share/runtime/vframe.cpp +++ b/src/hotspot/share/runtime/vframe.cpp @@ -82,6 +82,11 @@ } } + // Entry frame + if (f->is_entry_frame()) { + return new entryVFrame(f, reg_map, thread); + } + // External frame return new externalVFrame(f, reg_map, thread); } diff --git a/src/hotspot/share/runtime/vframe_hp.cpp b/src/hotspot/share/runtime/vframe_hp.cpp --- a/src/hotspot/share/runtime/vframe_hp.cpp +++ b/src/hotspot/share/runtime/vframe_hp.cpp @@ -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 @@ -64,7 +64,7 @@ // Replace the original values with any stores that have been // performed through compiledVFrame::update_locals. - GrowableArray* list = thread()->deferred_locals(); + GrowableArray* 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 +103,7 @@ void compiledVFrame::update_deferred_value(BasicType type, int index, jvalue value) { assert(fr().is_deoptimized_frame(), "frame must be scheduled for deoptimization"); - GrowableArray* deferred = thread()->deferred_locals(); + GrowableArray* deferred = JvmtiDeferredUpdates::deferred_locals(thread()); jvmtiDeferredLocalVariableSet* locals = NULL; if (deferred != NULL ) { // See if this vframe has already had locals with deferred writes @@ -117,8 +117,8 @@ } else { // No deferred updates pending for this thread. // allocate in C heap - deferred = new(ResourceObj::C_HEAP, mtCompiler) GrowableArray (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()); @@ -144,7 +144,7 @@ // Replace the original values with any stores that have been // performed through compiledVFrame::update_stack. - GrowableArray* list = thread()->deferred_locals(); + GrowableArray* 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 +218,7 @@ // Replace the original values with any stores that have been // performed through compiledVFrame::update_monitors. - GrowableArray* list = thread()->deferred_locals(); + GrowableArray* 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 +309,24 @@ return scope()->should_reexecute(); } +bool compiledVFrame::not_global_escape_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()->not_global_escape_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) { @@ -323,6 +341,18 @@ } } +void JvmtiDeferredUpdates::create_for(JavaThread* thread) { + assert(thread->deferred_updates() == NULL, "already allocated"); + thread->set_deferred_updates(new JvmtiDeferredUpdates()); +} + +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(); +} + jvmtiDeferredLocalVariableSet::jvmtiDeferredLocalVariableSet(Method* method, int bci, intptr_t* id, int vframe_id) { _method = method; _bci = bci; @@ -330,6 +360,7 @@ _vframe_id = vframe_id; // Alway will need at least one, must be on C heap _locals = new(ResourceObj::C_HEAP, mtCompiler) GrowableArray (1, mtCompiler); + _objects_are_deoptimized = false; } jvmtiDeferredLocalVariableSet::~jvmtiDeferredLocalVariableSet() { @@ -424,7 +455,9 @@ 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); } } diff --git a/src/hotspot/share/runtime/vframe_hp.hpp b/src/hotspot/share/runtime/vframe_hp.hpp --- a/src/hotspot/share/runtime/vframe_hp.hpp +++ b/src/hotspot/share/runtime/vframe_hp.hpp @@ -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 @@ -37,6 +37,8 @@ StackValueCollection* expressions() const; GrowableArray* monitors() const; int vframe_id() const { return _vframe_id; } + bool not_global_escape_in_scope() const; + bool arg_escape() const; // at call with arg escape in parameter list void set_locals(StackValueCollection* values) const; @@ -95,6 +97,48 @@ #endif }; +// Holds updates for compiled frames by JVMTI agents that cannot be performed immediately. +class jvmtiDeferredLocalVariableSet; +class JvmtiDeferredUpdates : public CHeapObj { + + // 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 _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* 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: + static void create_for(JavaThread* thread); + + static GrowableArray* 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) { + return jt->deferred_updates() == NULL ? 0 : jt->deferred_updates()->get_and_reset_relock_count_after_wait(); + } + static void inc_relock_count_after_wait(JavaThread* thread); +}; + + // 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 @@ -111,6 +155,7 @@ intptr_t* _id; int _vframe_id; GrowableArray* _locals; + bool _objects_are_deoptimized; void update_value(StackValueCollection* locals, BasicType type, int index, jvalue value); @@ -122,13 +167,17 @@ 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* 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); diff --git a/src/hotspot/share/utilities/macros.hpp b/src/hotspot/share/utilities/macros.hpp --- a/src/hotspot/share/utilities/macros.hpp +++ b/src/hotspot/share/utilities/macros.hpp @@ -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 diff --git a/test/hotspot/jtreg/serviceability/jvmti/Heap/IterateHeapWithEscapeAnalysisEnabled.java b/test/hotspot/jtreg/serviceability/jvmti/Heap/IterateHeapWithEscapeAnalysisEnabled.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/Heap/IterateHeapWithEscapeAnalysisEnabled.java @@ -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) + * @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 GetObjectsWithTags(). 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(); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/Heap/libIterateHeapWithEscapeAnalysisEnabled.c b/test/hotspot/jtreg/serviceability/jvmti/Heap/libIterateHeapWithEscapeAnalysisEnabled.c new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/Heap/libIterateHeapWithEscapeAnalysisEnabled.c @@ -0,0 +1,380 @@ +/* + * 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 +#include +#include "jvmti.h" +#include "jni.h" + +#ifndef JNI_ENV_ARG +#define JNI_ENV_ARG(x,y) x, y +#define JNI_ENV_PTR(x) (*x) +#endif + +#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(jvmti, errCode, &errMsg); + if (result == JVMTI_ERROR_NONE) { + fprintf(stderr, "%s: %s (%d)\n", message, errMsg, errCode); + (*jvmti)->Deallocate(jvmti, (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 = JNI_ENV_PTR(jvm)->GetEnv(JNI_ENV_ARG(jvm, (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 = JNI_ENV_PTR(jvm)->GetEnv(JNI_ENV_ARG(jvm, (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(jvmti, &caps); + if (err != JVMTI_ERROR_NONE) { + ShowErrorMessage(jvmti, err, + "acquireCanTagObjectsCapability: error in JVMTI AddCapabilities"); + return JNI_ERR; + } + + err = (*jvmti)->GetCapabilities(jvmti, &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(env, name, 0); + int rc = FAILED; + if (rc != OK && strcmp(name_chars, "IterateOverReachableObjects") == 0) { + method_IterateOverReachableObjects = (*env)->NewGlobalRef(env, method); + rc = OK; + } + if (rc != OK && strcmp(name_chars, "IterateOverHeap") == 0) { + method_IterateOverHeap = (*env)->NewGlobalRef(env, method); + rc = OK; + } + if (rc != OK && strcmp(name_chars, "IterateOverInstancesOfClass") == 0) { + method_IterateOverInstancesOfClass = (*env)->NewGlobalRef(env, method); + rc = OK; + } + if (rc != OK && strcmp(name_chars, "IterateThroughHeap") == 0) { + method_IterateThroughHeap = (*env)->NewGlobalRef(env, method); + rc = OK; + } + if (rc != OK && strcmp(name_chars, "FollowReferences") == 0) { + method_FollowReferences = (*env)->NewGlobalRef(env, method); + rc = OK; + } + (*env)->ReleaseStringUTFChars(env, name, name_chars); + return rc; +} + +JNIEXPORT void JNICALL +Java_IterateHeapWithEscapeAnalysisEnabled_agentTearDown(JNIEnv *env, jclass cls) { + (*env)->DeleteGlobalRef(env, method_IterateOverReachableObjects); + (*env)->DeleteGlobalRef(env, method_IterateOverHeap); + (*env)->DeleteGlobalRef(env, method_IterateOverInstancesOfClass); + (*env)->DeleteGlobalRef(env, method_FollowReferences); + (*env)->DeleteGlobalRef(env, method_IterateThroughHeap); +} + +JNIEXPORT jint JNICALL +Java_IterateHeapWithEscapeAnalysisEnabled_jvmtiTagClass(JNIEnv *env, jclass cls, jclass clsToTag, jlong tag) { + jvmtiError err; + err = (*jvmti)->SetTag(jvmti, 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; + Tag_And_Counter data = {0, cls_tag, instance_tag}; + jboolean method_found = JNI_FALSE; + + jint idx = 0; + + if ((*env)->IsSameObject(env, method, method_IterateOverReachableObjects)) { + method_found = JNI_TRUE; + err = (*jvmti)->IterateOverReachableObjects(jvmti, + NULL /*jvmtiHeapRootCallback*/, + __stackReferenceCallback, + NULL /* jvmtiObjectReferenceCallback */, + &data); + if (err != JVMTI_ERROR_NONE) { + ShowErrorMessage(jvmti, err, + "countAndTagInstancesOfClass: error in JVMTI IterateOverReachableObjects"); + return FAILED; + } + } + if ((*env)->IsSameObject(env, method, method_IterateOverHeap)) { + method_found = JNI_TRUE; + err = (*jvmti)->IterateOverHeap(jvmti, + JVMTI_HEAP_OBJECT_EITHER, + __jvmtiHeapObjectCallback, + &data); + if (err != JVMTI_ERROR_NONE) { + ShowErrorMessage(jvmti, err, + "countAndTagInstancesOfClass: error in JVMTI IterateOverHeap"); + return FAILED; + } + } + if ((*env)->IsSameObject(env, method, method_IterateOverInstancesOfClass)) { + method_found = JNI_TRUE; + err = (*jvmti)->IterateOverInstancesOfClass(jvmti, + 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(env, method, method_FollowReferences)) { + method_found = JNI_TRUE; + jvmtiHeapCallbacks callbacks = {0}; + callbacks.heap_reference_callback = __jvmtiHeapReferenceCallback; + err = (*jvmti)->FollowReferences(jvmti, + 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(env, method, method_IterateThroughHeap)) { + method_found = JNI_TRUE; + jvmtiHeapCallbacks callbacks = {0}; + callbacks.heap_iteration_callback = __jvmtiHeapIterationCallback; + err = (*jvmti)->IterateThroughHeap(jvmti, + 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, + jarray 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(jvmti, + 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(env, 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(env, res_instances, idx, res_instances_raw[idx]); + } + + (*jvmti)->Deallocate(jvmti, (unsigned char *)res_instances_raw); + (*jvmti)->Deallocate(jvmti, (unsigned char *)res_tags); + + return OK; +} diff --git a/test/jdk/TEST.ROOT b/test/jdk/TEST.ROOT --- a/test/jdk/TEST.ROOT +++ b/test/jdk/TEST.ROOT @@ -55,6 +55,7 @@ vm.debug \ vm.hasSA \ vm.hasJFR \ + vm.jvmci \ docker.support \ release.implementor diff --git a/test/jdk/com/sun/jdi/EATests.java b/test/jdk/com/sun/jdi/EATests.java new file mode 100644 --- /dev/null +++ b/test/jdk/com/sun/jdi/EATests.java @@ -0,0 +1,3102 @@ +/* + * 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 8227745 + * @summary Collection of test cases that check if optimizations based on escape analysis are reverted just before non-escaping objects escape through JVMTI. + * @author Richard Reingruber richard DOT reingruber AT sap DOT com + * + * @requires ((vm.compMode == "Xmixed") & vm.compiler2.enabled) + * @library /test/lib /test/hotspot/jtreg + * + * @run build TestScaffold VMConnection TargetListener TargetAdapter sun.hotspot.WhiteBox + * @run driver ClassFileInstaller sun.hotspot.WhiteBox + * @run compile -g EATests.java + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:+UseBiasedLocking + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:-EliminateLocks -XX:+EliminateNestedLocks -XX:+UseBiasedLocking -XX:-UseOptoBiasInlining + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:+UseBiasedLocking + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:+UseBiasedLocking + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:-UseBiasedLocking + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:-UseBiasedLocking + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:-UseBiasedLocking + * + * @comment Excercise -XX:+DeoptimizeObjectsALot. Mostly to prevent bit-rot, because the option is meant to stress object deoptimization + * with non-synthetic workloads. + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:-UseBiasedLocking + * -XX:+IgnoreUnrecognizedVMOptions -XX:+DeoptimizeObjectsALot + */ + +import com.sun.jdi.*; +import com.sun.jdi.event.*; +import compiler.testlibrary.CompilerUtils; +import compiler.whitebox.CompilerWhiteBoxTest; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import jdk.test.lib.Asserts; +import sun.hotspot.WhiteBox; + + +///////////////////////////////////////////////////////////////////////////// +// +// Shared base class for test cases for both, debugger and debuggee. +// +///////////////////////////////////////////////////////////////////////////// + +class EATestCaseBaseShared { + // In interactive mode we wait for a keypress before every test case. + public static final boolean INTERACTIVE = + System.getProperty("EATests.interactive") != null && + System.getProperty("EATests.interactive").equals("true"); + + // If the property is given, then just the test case it refers to is executed. + // Use it to diagnose test failures. + public static final String RUN_ONLY_TEST_CASE_PROPERTY = "EATests.onlytestcase"; + public static final String RUN_ONLY_TEST_CASE = System.getProperty(RUN_ONLY_TEST_CASE_PROPERTY); + + public final String testCaseName; + + public EATestCaseBaseShared() { + String clName = getClass().getName(); + int tidx = clName.lastIndexOf("Target"); + testCaseName = tidx > 0 ? clName.substring(0, tidx) : clName; + } + + public boolean shouldSkip() { + return EATestCaseBaseShared.RUN_ONLY_TEST_CASE != null && + EATestCaseBaseShared.RUN_ONLY_TEST_CASE.length() > 0 && + !testCaseName.equals(EATestCaseBaseShared.RUN_ONLY_TEST_CASE); + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Target main class, i.e. the program to be debugged. +// +///////////////////////////////////////////////////////////////////////////// + +class EATestsTarget { + + public static void main(String[] args) { + EATestCaseBaseTarget.staticSetUp(); + EATestCaseBaseTarget.staticSetUpDone(); + + // Materializing test cases, i.e. reallocating objects on the heap + new EAMaterializeLocalVariableUponGetTarget() .run(); + new EAGetWithoutMaterializeTarget() .run(); + new EAMaterializeLocalAtObjectReturnTarget() .run(); + new EAMaterializeLocalAtObjectPollReturnReturnTarget() .run(); + new EAMaterializeIntArrayTarget() .run(); + new EAMaterializeLongArrayTarget() .run(); + new EAMaterializeFloatArrayTarget() .run(); + new EAMaterializeDoubleArrayTarget() .run(); + new EAMaterializeObjectArrayTarget() .run(); + new EAMaterializeObjectWithConstantAndNotConstantValuesTarget() .run(); + new EAMaterializeObjReferencedBy2LocalsTarget() .run(); + new EAMaterializeObjReferencedBy2LocalsAndModifyTarget() .run(); + new EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesTarget() .run(); + new EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesAndModifyTarget() .run(); + new EAMaterializeObjReferencedFromOperandStackTarget() .run(); + new EAMaterializeLocalVariableUponGetAfterSetIntegerTarget() .run(); + + // Relocking test cases + new EARelockingSimpleTarget() .run(); + new EARelockingSimple_2Target() .run(); + new EARelockingRecursiveTarget() .run(); + new EARelockingNestedInflatedTarget() .run(); + new EARelockingNestedInflated_02Target() .run(); + new EARelockingArgEscapeLWLockedInCalleeFrameTarget() .run(); + new EARelockingArgEscapeLWLockedInCalleeFrame_2Target() .run(); + new EARelockingArgEscapeLWLockedInCalleeFrame_3Target() .run(); + new EARelockingArgEscapeLWLockedInCalleeFrame_4Target() .run(); + new EAGetOwnedMonitorsTarget() .run(); + new EAEntryCountTarget() .run(); + new EARelockingObjectCurrentlyWaitingOnTarget() .run(); + + // Test cases that require deoptimization even though neither + // locks nor allocations are eliminated at the point where + // escape state is changed. + new EADeoptFrameAfterReadLocalObject_01Target() .run(); + new EADeoptFrameAfterReadLocalObject_01BTarget() .run(); + new EADeoptFrameAfterReadLocalObject_02Target() .run(); + new EADeoptFrameAfterReadLocalObject_02BTarget() .run(); + new EADeoptFrameAfterReadLocalObject_02CTarget() .run(); + new EADeoptFrameAfterReadLocalObject_03Target() .run(); + + // PopFrame test cases + new EAPopFrameNotInlinedTarget() .run(); + new EAPopFrameNotInlinedReallocFailureTarget() .run(); + new EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget() .run(); + + // ForceEarlyReturn test cases + new EAForceEarlyReturnNotInlinedTarget() .run(); + new EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsTarget() .run(); + new EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailureTarget().run(); + + // Instances of ReferenceType + new EAGetInstancesOfReferenceTypeTarget() .run(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Debugger main class +// +///////////////////////////////////////////////////////////////////////////// + +public class EATests extends TestScaffold { + + public TargetVMOptions targetVMOptions; + public ThreadReference targetMainThread; + + EATests(String args[]) { + super(args); + } + + public static void main(String[] args) throws Exception { + if (EATestCaseBaseShared.RUN_ONLY_TEST_CASE != null) { + args = Arrays.copyOf(args, args.length + 1); + args[args.length - 1] = "-D" + EATestCaseBaseShared.RUN_ONLY_TEST_CASE_PROPERTY + "=" + EATestCaseBaseShared.RUN_ONLY_TEST_CASE; + } + new EATests(args).startTests(); + } + + public static class TargetVMOptions { + + public final boolean UseJVMCICompiler; + public final boolean EliminateAllocations; + public final boolean DeoptimizeObjectsALot; + + public TargetVMOptions(EATests env, ClassType testCaseBaseTargetClass) { + Value val = testCaseBaseTargetClass.getValue(testCaseBaseTargetClass.fieldByName("EliminateAllocations")); + EliminateAllocations = ((PrimitiveValue) val).booleanValue(); + val = testCaseBaseTargetClass.getValue(testCaseBaseTargetClass.fieldByName("DeoptimizeObjectsALot")); + DeoptimizeObjectsALot = ((PrimitiveValue) val).booleanValue(); + val = testCaseBaseTargetClass.getValue(testCaseBaseTargetClass.fieldByName("UseJVMCICompiler")); + UseJVMCICompiler = ((PrimitiveValue) val).booleanValue(); + } + + } + + // Execute known test cases + protected void runTests() throws Exception { + String targetProgName = EATestsTarget.class.getName(); + msg("starting to main method in class " + targetProgName); + startToMain(targetProgName); + msg("resuming to EATestCaseBaseTarget.staticSetUpDone()V"); + targetMainThread = resumeTo("EATestCaseBaseTarget", "staticSetUpDone", "()V").thread(); + Location loc = targetMainThread.frame(0).location(); + Asserts.assertEQ("staticSetUpDone", loc.method().name()); + + targetVMOptions = new TargetVMOptions(this, (ClassType) loc.declaringType()); + + // Materializing test cases, i.e. reallocating objects on the heap + new EAMaterializeLocalVariableUponGet() .run(this); + new EAGetWithoutMaterialize() .run(this); + new EAMaterializeLocalAtObjectReturn() .run(this); + new EAMaterializeLocalAtObjectPollReturnReturn() .run(this); + new EAMaterializeIntArray() .run(this); + new EAMaterializeLongArray() .run(this); + new EAMaterializeFloatArray() .run(this); + new EAMaterializeDoubleArray() .run(this); + new EAMaterializeObjectArray() .run(this); + new EAMaterializeObjectWithConstantAndNotConstantValues() .run(this); + new EAMaterializeObjReferencedBy2Locals() .run(this); + new EAMaterializeObjReferencedBy2LocalsAndModify() .run(this); + new EAMaterializeObjReferencedBy2LocalsInDifferentVirtFrames() .run(this); + new EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesAndModify() .run(this); + new EAMaterializeObjReferencedFromOperandStack() .run(this); + new EAMaterializeLocalVariableUponGetAfterSetInteger() .run(this); + + // Relocking test cases + new EARelockingSimple() .run(this); + new EARelockingSimple_2() .run(this); + new EARelockingRecursive() .run(this); + new EARelockingNestedInflated() .run(this); + new EARelockingNestedInflated_02() .run(this); + new EARelockingArgEscapeLWLockedInCalleeFrame() .run(this); + new EARelockingArgEscapeLWLockedInCalleeFrame_2() .run(this); + new EARelockingArgEscapeLWLockedInCalleeFrame_3() .run(this); + new EARelockingArgEscapeLWLockedInCalleeFrame_4() .run(this); + new EAGetOwnedMonitors() .run(this); + new EAEntryCount() .run(this); + new EARelockingObjectCurrentlyWaitingOn() .run(this); + + // Test cases that require deoptimization even though neither + // locks nor allocations are eliminated at the point where + // escape state is changed. + new EADeoptFrameAfterReadLocalObject_01() .run(this); + new EADeoptFrameAfterReadLocalObject_01B() .run(this); + new EADeoptFrameAfterReadLocalObject_02() .run(this); + new EADeoptFrameAfterReadLocalObject_02B() .run(this); + new EADeoptFrameAfterReadLocalObject_02C() .run(this); + new EADeoptFrameAfterReadLocalObject_03() .run(this); + + // PopFrame test cases + new EAPopFrameNotInlined() .run(this); + new EAPopFrameNotInlinedReallocFailure() .run(this); + new EAPopInlinedMethodWithScalarReplacedObjectsReallocFailure() .run(this); + + // ForceEarlyReturn test cases + new EAForceEarlyReturnNotInlined() .run(this); + new EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjects() .run(this); + new EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailure().run(this); + + // Instances of ReferenceType + new EAGetInstancesOfReferenceType() .run(this); + + // resume the target listening for events + listenUntilVMDisconnect(); + } + + // Print a Message + public void msg(String m) { + System.out.println(); + System.out.println("###(Debugger) " + m); + System.out.println(); + } + + // Highlighted message. + public void msgHL(String m) { + System.out.println(); + System.out.println(); + System.out.println("##########################################################"); + System.out.println("### " + m); + System.out.println("### "); + System.out.println(); + System.out.println(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Base class for debugger side of test cases. +// +///////////////////////////////////////////////////////////////////////////// + +abstract class EATestCaseBaseDebugger extends EATestCaseBaseShared { + + protected EATests env; + + public ObjectReference testCase; + + public static final String TARGET_TESTCASE_BASE_NAME = EATestCaseBaseTarget.class.getName(); + + public static final String XYVAL_NAME = XYVal.class.getName(); + + public abstract void runTestCase() throws Exception; + + public void run(EATests env) { + this.env = env; + if (shouldSkip()) { + msg("skipping " + testCaseName); + return; + } + try { + msgHL("Executing test case " + getClass().getName()); + env.testFailed = false; + + if (INTERACTIVE) + env.waitForInput(); + + resumeToWarmupDone(); + runTestCase(); + Asserts.assertTrue(env.targetMainThread.isSuspended(), "must be suspended after the testcase"); + resumeToTestCaseDone(); + checkPostConditions(); + } catch (Exception e) { + Asserts.fail("Unexpected exception in test case " + getClass().getName(), e); + } + } + + public void resumeToWarmupDone() throws Exception { + msg("resuming to " + TARGET_TESTCASE_BASE_NAME + ".warmupDone()V"); + env.resumeTo(TARGET_TESTCASE_BASE_NAME, "warmupDone", "()V"); + testCase = env.targetMainThread.frame(0).thisObject(); + } + + public void resumeToTestCaseDone() { + msg("resuming to " + TARGET_TESTCASE_BASE_NAME + ".testCaseDone()V"); + env.resumeTo(TARGET_TESTCASE_BASE_NAME, "testCaseDone", "()V"); + } + + public void checkPostConditions() throws Exception { + Asserts.assertFalse(env.getExceptionCaught(), "Uncaught exception in Debuggee"); + + String testName = getClass().getName(); + if (!env.testFailed) { + env.println(testName + ": passed"); + } else { + throw new Exception(testName + ": failed"); + } + } + + public void printStack(ThreadReference thread) throws Exception { + msg("Debuggee Stack:"); + List stack_frames = thread.frames(); + int i = 0; + for (StackFrame ff : stack_frames) { + System.out.println("frame[" + i++ +"]: " + ff.location().method() + " (bci:" + ff.location().codeIndex() + ")"); + } + } + + public void msg(String m) { + env.msg(m); + } + + public void msgHL(String m) { + env.msgHL(m); + } + + // See Field Descriptors in The Java Virtual Machine Specification + // (https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.3.2) + enum FD { + I, // int + J, // long + F, // float + D, // double + } + + // Map field descriptor to jdi type string + public static final Map FD2JDIArrType = Map.of(FD.I, "int[]", FD.J, "long[]", FD.F, "float[]", FD.D, "double[]"); + + // Map field descriptor to PrimitiveValue getter + public static final Function v2I = PrimitiveValue::intValue; + public static final Function v2J = PrimitiveValue::longValue; + public static final Function v2F = PrimitiveValue::floatValue; + public static final Function v2D = PrimitiveValue::doubleValue; + Map> FD2getter = Map.of(FD.I, v2I, FD.J, v2J, FD.F, v2F, FD.D, v2D); + + /** + * Retrieve array of primitive values referenced by a local variable in target and compare with + * an array of expected values. + * @param frame Frame in the target holding the local variable + * @param lName Name of the local variable referencing the array to be retrieved + * @param desc Array element type given as field descriptor. + * @param expVals Array of expected values. + * @throws Exception + */ + protected void checkLocalPrimitiveArray(StackFrame frame, String lName, FD desc, Object expVals) throws Exception { + String lType = FD2JDIArrType.get(desc); + Asserts.assertNotNull(lType, "jdi type not found"); + Asserts.assertEQ(EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME, frame .location().method().name()); + List localVars = frame.visibleVariables(); + msg("Check if the local array variable '" + lName + "' in " + EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME + " has the expected elements: "); + boolean found = false; + for (LocalVariable lv : localVars) { + if (lv.name().equals(lName)) { + found = true; + Value lVal = frame.getValue(lv); + Asserts.assertNotNull(lVal); + Asserts.assertEQ(lVal.type().name(), lType); + ArrayReference aRef = (ArrayReference) lVal; + Asserts.assertEQ(3, aRef.length()); + // now check the elements + for (int i = 0; i < aRef.length(); i++) { + Object actVal = FD2getter.get(desc).apply((PrimitiveValue)aRef.getValue(i)); + Object expVal = Array.get(expVals, i); + Asserts.assertEQ(expVal, actVal, "checking element at index " + i); + } + } + } + Asserts.assertTrue(found); + msg("OK."); + } + + /** + * Retrieve array of objects referenced by a local variable in target and compare with an array + * of expected values. + * @param frame Frame in the target holding the local variable + * @param lName Name of the local variable referencing the array to be retrieved + * @param lType Local type, e.g. java.lang.Long[] + * @param expVals Array of expected values. + * @throws Exception + */ + protected void checkLocalObjectArray(StackFrame frame, String lName, String lType, ObjectReference[] expVals) throws Exception { + Asserts.assertEQ(EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME, frame .location().method().name()); + List localVars = frame.visibleVariables(); + msg("Check if the local array variable '" + lName + "' in " + EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME + " has the expected elements: "); + boolean found = false; + for (LocalVariable lv : localVars) { + if (lv.name().equals(lName)) { + found = true; + Value lVal = frame.getValue(lv); + Asserts.assertNotNull(lVal); + Asserts.assertEQ(lType, lVal.type().name()); + ArrayReference aRef = (ArrayReference) lVal; + Asserts.assertEQ(3, aRef.length()); + // now check the elements + for (int i = 0; i < aRef.length(); i++) { + ObjectReference actVal = (ObjectReference)aRef.getValue(i); + Asserts.assertSame(expVals[i], actVal, "checking element at index " + i); + } + } + } + Asserts.assertTrue(found); + msg("OK."); + } + + /** + * Retrieve a reference held by a local variable in the given frame. Check if the frame's method + * is the expected method, if the retrieved local value has the expected type and is not null. + * @param frame The frame to retrieve the local variable value from. + * @param expectedMethodName The name of the frames method should match the expectedMethodName. + * @param lName The name of the local variable which is read. + * @param expectedType Is the expected type of the object referenced by the local variable. + * @return + * @throws Exception + */ + protected ObjectReference getLocalRef(StackFrame frame, String expectedMethodName, String lName, String expectedType) throws Exception { + Asserts.assertEQ(expectedMethodName, frame.location().method().name()); + List localVars = frame.visibleVariables(); + msg("Get and check local variable '" + lName + "' in " + expectedMethodName); + ObjectReference lRef = null; + for (LocalVariable lv : localVars) { + if (lv.name().equals(lName)) { + Value lVal = frame.getValue(lv); + Asserts.assertNotNull(lVal); + Asserts.assertEQ(expectedType, lVal.type().name()); + lRef = (ObjectReference) lVal; + break; + } + } + Asserts.assertNotNull(lRef, "Local variable '" + lName + "' not found"); + msg("OK."); + return lRef; + } + + /** + * Retrieve a reference held by a local variable in the given frame. Check if the frame's method + * matches {@link EATestCaseBaseTarget#TESTMETHOD_DEFAULT_NAME}, if the retrieved local value has + * the expected type and is not null. + * @param frame The frame to retrieve the local variable value from. + * @param expectedMethodName The name of the frames method should match the expectedMethodName. + * @param lName The name of the local variable which is read. + * @param expectedType Is the expected type of the object referenced by the local variable. + * @return + * @throws Exception + */ + protected ObjectReference getLocalRef(StackFrame frame, String lType, String lName) throws Exception { + return getLocalRef(frame, EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME, lName, lType); + } + + /** + * Set the value of a local variable in the given frame. Check if the frame's method is the expected method. + * @param frame The frame holding the local variable. + * @param expectedMethodName The expected name of the frame's method. + * @param lName The name of the local variable to change. + * @param val The new value of the local variable. + * @throws Exception + */ + public void setLocal(StackFrame frame, String expectedMethodName, String lName, Value val) throws Exception { + Asserts.assertEQ(expectedMethodName, frame.location().method().name()); + List localVars = frame.visibleVariables(); + msg("Set local variable '" + lName + "' = " + val + " in " + expectedMethodName); + for (LocalVariable lv : localVars) { + if (lv.name().equals(lName)) { + frame.setValue(lv, val); + break; + } + } + msg("OK."); + } + + /** + * Set the value of a local variable in the given frame. Check if the frame's method matches + * {@link EATestCaseBaseTarget#TESTMETHOD_DEFAULT_NAME}. + * @param frame The frame holding the local variable. + * @param expectedMethodName The expected name of the frame's method. + * @param lName The name of the local variable to change. + * @param val The new value of the local variable. + * @throws Exception + */ + public void setLocal(StackFrame frame, String lName, Value val) throws Exception { + setLocal(frame, EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME, lName, val); + } + + /** + * Check if a field has the expected primitive value. + * @param o Object holding the field. + * @param desc Field descriptor. + * @param fName Field name + * @param expVal Expected primitive value + * @throws Exception + */ + protected void checkPrimitiveField(ObjectReference o, FD desc, String fName, Object expVal) throws Exception { + msg("check field " + fName); + ReferenceType rt = o.referenceType(); + Field fld = rt.fieldByName(fName); + Value val = o.getValue(fld); + Object actVal = FD2getter.get(desc).apply((PrimitiveValue) val); + Asserts.assertEQ(expVal, actVal, "field '" + fName + "' has unexpected value."); + msg("ok"); + } + + /** + * Check if a field references the expected object. + * @param obj Object holding the field. + * @param fName Field name + * @param expVal Object expected to be referenced by the field + * @throws Exception + */ + protected void checkObjField(ObjectReference obj, String fName, ObjectReference expVal) throws Exception { + msg("check field " + fName); + ReferenceType rt = obj.referenceType(); + Field fld = rt.fieldByName(fName); + Value actVal = obj.getValue(fld); + Asserts.assertEQ(expVal, actVal, "field '" + fName + "' has unexpected value."); + msg("ok"); + } + + protected void setField(ObjectReference obj, String fName, Value val) throws Exception { + msg("set field " + fName + " = " + val); + ReferenceType rt = obj.referenceType(); + Field fld = rt.fieldByName(fName); + obj.setValue(fld, val); + msg("ok"); + } + + protected Value getField(ObjectReference obj, String fName) throws Exception { + msg("get field " + fName); + ReferenceType rt = obj.referenceType(); + Field fld = rt.fieldByName(fName); + Value val = obj.getValue(fld); + msg("result : " + val); + return val; + } + + /** + * Free the memory consumed in the target by {@link EATestCaseBaseTarget#consumedMemory} + * @throws Exception + */ + public void freeAllMemory() throws Exception { + msg("free consumed memory"); + setField(testCase, "consumedMemory", null); + } + + /** + * @return The value of {@link EATestCaseBaseTarget#targetIsInLoop}. The target must set that field to true as soon as it + * enters the endless loop. + * @throws Exception + */ + public boolean targetHasEnteredEndlessLoop() throws Exception { + Value v = getField(testCase, "targetIsInLoop"); + return ((PrimitiveValue) v).booleanValue(); + } + + /** + * Poll {@link EATestCaseBaseTarget#targetIsInLoop} and return if it is found to be true. + * @throws Exception + */ + public void waitUntilTargetHasEnteredEndlessLoop() throws Exception { + while(!targetHasEnteredEndlessLoop()) { + msg("Target has not yet entered the loop. Sleep 200ms."); + try { Thread.sleep(200); } catch (InterruptedException e) { /*ignore */ } + } + } + + /** + * Set {@link EATestCaseBaseTarget#doLoop} to false. This will allow the target to + * leave the endless loop. + * @throws Exception + */ + public void terminateEndlessLoop() throws Exception { + msg("terminate loop"); + setField(testCase, "doLoop", env.vm().mirrorOf(false)); + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Base class for debuggee side of test cases. +// +///////////////////////////////////////////////////////////////////////////// + +abstract class EATestCaseBaseTarget extends EATestCaseBaseShared implements Runnable { + + /** + * The target must set that field to true as soon as it enters the endless loop. + */ + public volatile boolean targetIsInLoop; + + /** + * Used for busy loops. See {@link #dontinline_endlessLoop()}. + */ + public volatile long loopCount; + + /** + * Used in {@link EATestCaseBaseDebugger#terminateEndlessLoop()} to signal target to leave the endless loop. + */ + public volatile boolean doLoop; + + public long checkSum; + + public static final String TESTMETHOD_DEFAULT_NAME = "dontinline_testMethod"; + + public static final WhiteBox WB = WhiteBox.getWhiteBox(); + + public static boolean unbox(Boolean value, boolean dflt) { + return value == null ? dflt : value; + } + + public static final boolean UseJVMCICompiler = unbox(WB.getBooleanVMFlag("UseJVMCICompiler"), false); // read by debugger + public static final boolean DoEscapeAnalysis = unbox(WB.getBooleanVMFlag("DoEscapeAnalysis"), UseJVMCICompiler); + public static final boolean EliminateAllocations = unbox(WB.getBooleanVMFlag("EliminateAllocations"), UseJVMCICompiler); // read by debugger + public static final boolean DeoptimizeObjectsALot = WB.getBooleanVMFlag("DeoptimizeObjectsALot"); // read by debugger + public static final long BiasedLockingBulkRebiasThreshold = WB.getIntxVMFlag("BiasedLockingBulkRebiasThreshold"); + public static final long BiasedLockingBulkRevokeThreshold = WB.getIntxVMFlag("BiasedLockingBulkRevokeThreshold"); + + public String testMethodName; + public int testMethodDepth; + + // Results produced by dontinline_testMethod() + public int iResult; + public long lResult; + public float fResult; + public double dResult; + + + public boolean warmupDone; + + public volatile Object biasToBeRevoked; + + // an object with an inflated monitor + public static XYVal inflatedLock; + public static Thread inflatorThread; + public static boolean inflatedLockIsPermanentlyInflated; + + public static int NOT_CONST_1I = 1; + public static long NOT_CONST_1L = 1L; + public static float NOT_CONST_1F = 1.1F; + public static double NOT_CONST_1D = 1.1D; + + public static Long NOT_CONST_1_OBJ = Long.valueOf(1); + + + public static final Long CONST_2_OBJ = Long.valueOf(2); + public static final Long CONST_3_OBJ = Long.valueOf(3); + + /** + * Main driver of a test case. + *
    + *
  • Skips test case if not selected (see {@link EATestCaseBaseShared#RUN_ONLY_TEST_CASE} + *
  • Call {@link #setUp()} + *
  • warm-up and compile {@link #dontinline_testMethod()} (see {@link #compileTestMethod()} + *
  • calling {@link #dontinline_testMethod()} + *
  • checking the result (see {@link #checkResult()} + *
      + */ + public void run() { + try { + if (shouldSkip()) { + msg("skipping " + testCaseName); + return; + } + setUp(); + msg(testCaseName + " is up and running."); + compileTestMethod(); + msg(testCaseName + " warmup done."); + warmupDone(); + checkCompLevel(); + dontinline_testMethod(); + checkResult(); + msg(testCaseName + " done."); + testCaseDone(); + } catch (Exception e) { + Asserts.fail("Caught unexpected exception", e); + } + } + + public static void staticSetUp() { + inflatedLock = new XYVal(1, 1); + synchronized (inflatedLock) { + inflatorThread = new Thread("Lock Inflator (test thread)") { + @Override + public void run() { + synchronized (inflatedLock) { + inflatedLockIsPermanentlyInflated = true; + inflatedLock.notify(); // main thread + while (true) { + try { + // calling wait() on a monitor will cause inflation into a heavy monitor + inflatedLock.wait(); + } catch (InterruptedException e) { /* ignored */ } + } + } + } + }; + inflatorThread.setDaemon(true); + inflatorThread.start(); + + // wait until the lock is permanently inflated by the inflatorThread + while(!inflatedLockIsPermanentlyInflated) { + try { + inflatedLock.wait(); // until inflated + } catch (InterruptedException e1) { /* ignored */ } + } + } + } + + // Debugger will set breakpoint here to sync with target. + public static void staticSetUpDone() { + } + + public void setUp() { + testMethodDepth = 1; + testMethodName = TESTMETHOD_DEFAULT_NAME; + } + + public abstract void dontinline_testMethod() throws Exception; + + public int dontinline_brkpt_iret() { + dontinline_brkpt(); + return 42; + } + + /** + * It is a common protocol to have the debugger set a breakpoint in this method and have {@link + * #dontinline_testMethod()} call it and then perform some test actions on debugger side. + * After that it is checked if a frame of {@link #dontinline_testMethod()} is found at the + * expected depth on stack and if it is (not) marked for deoptimization as expected. + */ + public void dontinline_brkpt() { + // will set breakpoint here after warmup + if (warmupDone) { + // check if test method is at expected depth + StackTraceElement[] frames = Thread.currentThread().getStackTrace(); + int stackTraceDepth = testMethodDepth + 1; // ignore java.lang.Thread.getStackTrace() + Asserts.assertEQ(testMethodName, frames[stackTraceDepth].getMethodName(), + testCaseName + ": test method not found at depth " + testMethodDepth); + // check if the frame is (not) deoptimized as expected + if (!DeoptimizeObjectsALot) { + if (testFrameShouldBeDeoptimized()) { + Asserts.assertTrue(WB.isFrameDeoptimized(testMethodDepth+1), + testCaseName + ": expected test method frame at depth " + testMethodDepth + " to be deoptimized"); + } else { + Asserts.assertFalse(WB.isFrameDeoptimized(testMethodDepth+1), + testCaseName + ": expected test method frame at depth " + testMethodDepth + " not to be deoptimized"); + } + } + } + } + + /** + * Some test cases run busy endless loops by initializing {@link #loopCount} + * to {@link Long#MAX_VALUE} after warm-up and then counting down to 0 in their main test method. + * During warm-up {@link #loopCount} is initialized to a small value. + */ + public long dontinline_endlessLoop() { + long cs = checkSum; + doLoop = true; + while (loopCount-- > 0 && doLoop) { + targetIsInLoop = true; + checkSum += checkSum % ++cs; + } + loopCount = 3; + targetIsInLoop = false; + return checkSum; + } + + public boolean testFrameShouldBeDeoptimized() { + return DoEscapeAnalysis; + } + + public void warmupDone() { + warmupDone = true; + } + + // Debugger will set breakpoint here to sync with target. + public void testCaseDone() { + } + + public void compileTestMethod() throws Exception { + int callCount = CompilerWhiteBoxTest.THRESHOLD; + while (callCount-- > 0) { + dontinline_testMethod(); + } + } + + public void checkCompLevel() { + java.lang.reflect.Method m = null; + try { + m = getClass().getMethod(TESTMETHOD_DEFAULT_NAME); + } catch (NoSuchMethodException | SecurityException e) { + Asserts.fail("could not check compilation level of", e); + } + int highest_level = CompilerUtils.getMaxCompilationLevel(); + Asserts.assertEQ(highest_level, WB.getMethodCompilationLevel(m), + m + " not on expected compilation level"); + } + + // to be overridden as appropriate + public int getExpectedIResult() { + return 0; + } + + // to be overridden as appropriate + public long getExpectedLResult() { + return 0; + } + + // to be overridden as appropriate + public float getExpectedFResult() { + return 0f; + } + + // to be overridden as appropriate + public double getExpectedDResult() { + return 0d; + } + + private void checkResult() { + Asserts.assertEQ(getExpectedIResult(), iResult, "checking iResult"); + Asserts.assertEQ(getExpectedLResult(), lResult, "checking lResult"); + Asserts.assertEQ(getExpectedFResult(), fResult, "checking fResult"); + Asserts.assertEQ(getExpectedDResult(), dResult, "checking dResult"); + } + + public void msg(String m) { + System.out.println(); + System.out.println("###(Target) " + m); + System.out.println(); + } + + // The object passed will be ArgEscape if it was NoEscape before. + public final void dontinline_make_arg_escape(XYVal xy) { + } + + /** + * Call a method indirectly using reflection. The indirection is a limit for escape + * analysis in the sense that the VM need not search beyond for frames that might have + * an object being read by an JVMTI agent as ArgEscape. + * @param receiver The receiver object of the call. + * @param methodName The name of the method to be called. + */ + public final void dontinline_call_with_entry_frame(Object receiver, String methodName) { + Asserts.assertTrue(warmupDone, "We want to take the slow path through jni, so don't call in warmup"); + + Class cls = receiver.getClass(); + Class[] none = {}; + + java.lang.reflect.Method m; + try { + m = cls.getDeclaredMethod(methodName, none); + m.invoke(receiver); + } catch (Exception e) { + Asserts.fail("Call through reflection failed", e); + } + } + + /** + * Trigger bulk rebiasing for the given class by creating new instances and calling hashCode() on them. + * @param cls The class to bulk rebias + */ + public void dontinline_bulkRebiasAfterWarmup(Class cls) { + if (warmupDone) { + try { + for (int i=0; i < BiasedLockingBulkRebiasThreshold+2; i++) { + biasToBeRevoked = cls.getDeclaredConstructor(int.class, int.class).newInstance(1, 1); + synchronized (biasToBeRevoked) { // bias towards current thread + checkSum++; + } + biasToBeRevoked.hashCode(); // calling hashCode triggers revocation + } + } catch (Throwable e) { + Asserts.fail("failed to create new instance of " + cls.getName(), e); + } + } + } + + /** + * Trigger bulk revoke of biases for the given class by creating new instances and calling hashCode() on them. + * @param cls The class to bulk rebias + */ + public void dontinline_bulkRevokeAfterWarmup(Class cls) { + if (warmupDone) { + try { + for (int i=0; i < BiasedLockingBulkRevokeThreshold+2; i++) { + biasToBeRevoked = cls.getDeclaredConstructor(int.class, int.class).newInstance(1, 1); + synchronized (biasToBeRevoked) { // bias towards current thread + checkSum++; + } + biasToBeRevoked.hashCode(); // calling hashCode triggers revocation + } + } catch (Throwable e) { + Asserts.fail("failed to create new instance of " + cls.getName(), e); + } + } + } + + static class LinkedList { + LinkedList l; + public long[] array; + public LinkedList(LinkedList l, int size) { + this.array = new long[size]; + this.l = l; + } + } + + public LinkedList consumedMemory; + + public void consumeAllMemory() { + msg("consume all memory"); + int size = 128 * 1024 * 1024; + while(size > 0) { + try { + while(true) { + consumedMemory = new LinkedList(consumedMemory, size); + } + } catch(OutOfMemoryError oom) { + } + size = size / 2; + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Test Cases +// +///////////////////////////////////////////////////////////////////////////// + +// make sure a compiled frame is not deoptimized if an escaping local is accessed +class EAGetWithoutMaterializeTarget extends EATestCaseBaseTarget { + + public XYVal getAway; + + public void dontinline_testMethod() { + XYVal xy = new XYVal(4, 2); + getAway = xy; // allocated object escapes + dontinline_brkpt(); + iResult = xy.x + xy.y; + } + + @Override + public int getExpectedIResult() { + return 4 + 2; + } + + @Override + public boolean testFrameShouldBeDeoptimized() { + return false; + } +} + +class EAGetWithoutMaterialize extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + ObjectReference o = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy"); + checkPrimitiveField(o, FD.I, "x", 4); + checkPrimitiveField(o, FD.I, "y", 2); + } +} + +///////////////////////////////////////////////////////////////////////////// + +// +// Tests the following: +// +// 1. Debugger can obtain a reference to a scalar replaced object R from java thread J. +// See runTestCase. +// +// 2. Subsequent modifications of R by J are noticed by the debugger. +// See checkPostConditions. +// +class EAMaterializeLocalVariableUponGet extends EATestCaseBaseDebugger { + + private ObjectReference o; + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + // check 1. + o = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy"); + checkPrimitiveField(o, FD.I, "x", 4); + checkPrimitiveField(o, FD.I, "y", 2); + } + + @Override + public void checkPostConditions() throws Exception { + super.checkPostConditions(); + // check 2. + checkPrimitiveField(o, FD.I, "x", 5); + } +} + +class EAMaterializeLocalVariableUponGetTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal xy = new XYVal(4, 2); + dontinline_brkpt(); // Debugger obtains scalar replaced object at this point. + xy.x += 1; // Change scalar replaced object after debugger obtained a reference to it. + iResult = xy.x + xy.y; + } + + @Override + public int getExpectedIResult() { + return 4 + 2 + 1; + } +} + +///////////////////////////////////////////////////////////////////////////// + +// Test if an eliminated object can be reallocated in a frame with an active +// call that will return another object +class EAMaterializeLocalAtObjectReturn extends EATestCaseBaseDebugger { + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + ObjectReference o = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "xy"); + checkPrimitiveField(o, FD.I, "x", 4); + checkPrimitiveField(o, FD.I, "y", 2); + } +} + +class EAMaterializeLocalAtObjectReturnTarget extends EATestCaseBaseTarget { + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + public void dontinline_testMethod() { + XYVal xy = new XYVal(4, 2); + Integer io = // Read xy here triggers reallocation + dontinline_brkpt_return_Integer(); + iResult = xy.x + xy.y + io; + } + + public Integer dontinline_brkpt_return_Integer() { + // We can't break directly in this method, as this results in making + // the test method not entrant caused by an existing dependency + dontinline_brkpt(); + return Integer.valueOf(23); + } + + @Override + public int getExpectedIResult() { + return 4 + 2 + 23; + } +} + +///////////////////////////////////////////////////////////////////////////// + +// Test if an eliminated object can be reallocated *just* before a call returns an object. +// (See CompiledMethod::is_at_poll_return()) +// Details: the callee method has just one safepoint poll at the return. The other safepoint +// is at the end of an iteration of the endless loop. We can detect if we suspended the target +// there, because the local xy is out of scope there. +class EAMaterializeLocalAtObjectPollReturnReturn extends EATestCaseBaseDebugger { + public void runTestCase() throws Exception { + msg("Resume " + env.targetMainThread); + env.targetMainThread.resume(); + waitUntilTargetHasEnteredEndlessLoop(); + ObjectReference o = null; + do { + env.targetMainThread.suspend(); + printStack(env.targetMainThread); + try { + o = getLocalRef(env.targetMainThread.frame(0), XYVAL_NAME, "xy"); + } catch (Exception e) { + msg("The local variable xy is out of scope, because we suspended at the wrong bci. Resume and try again!"); + env.targetMainThread.resume(); + } + } while (o == null); + checkPrimitiveField(o, FD.I, "x", 4); + checkPrimitiveField(o, FD.I, "y", 2); + terminateEndlessLoop(); + } +} + +class EAMaterializeLocalAtObjectPollReturnReturnTarget extends EATestCaseBaseTarget { + @Override + public void setUp() { + super.setUp(); + loopCount = 3; + doLoop = true; + } + + public void warmupDone() { + super.warmupDone(); + msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE"); + loopCount = Long.MAX_VALUE; // endless loop + } + + public void dontinline_testMethod() { + long result = 0; + while (doLoop && loopCount-- > 0) { + targetIsInLoop = true; + XYVal xy = new XYVal(4, 2); + Integer io = // Read xy here triggers reallocation just before the call returns + dontinline_brkpt_return_Integer(); + result += xy.x + xy.y + io; + } // Here is a second safepoint. We were suspended here, if xy is not in scope. + targetIsInLoop = false; + lResult = result; + } + + public Integer dontinline_brkpt_return_Integer() { + return Integer.valueOf(23); + } + + @Override + public long getExpectedLResult() { + return (Long.MAX_VALUE - loopCount) * (4+2+23); + } +} + +///////////////////////////////////////////////////////////////////////////// +// Test case collection that tests rematerialization of different +// array types, where the first element is always not constant and the +// other elements are constants. Not constant values are stored in +// the stack frame for rematerialization whereas constants are kept +// in the debug info of the nmethod. + +class EAMaterializeIntArrayTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + int nums[] = {NOT_CONST_1I , 2, 3}; + dontinline_brkpt(); + iResult = nums[0] + nums[1] + nums[2]; + } + + @Override + public int getExpectedIResult() { + return NOT_CONST_1I + 2 + 3; + } +} + +class EAMaterializeIntArray extends EATestCaseBaseDebugger { + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + int[] expectedVals = {1, 2, 3}; + checkLocalPrimitiveArray(bpe.thread().frame(1), "nums", FD.I, expectedVals); + } +} + +///////////////////////////////////////////////////////////////////////////// + +class EAMaterializeLongArrayTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + long nums[] = {NOT_CONST_1L , 2, 3}; + dontinline_brkpt(); + lResult = nums[0] + nums[1] + nums[2]; + } + + @Override + public long getExpectedLResult() { + return NOT_CONST_1L + 2 + 3; + } +} + +class EAMaterializeLongArray extends EATestCaseBaseDebugger { + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + long[] expectedVals = {1, 2, 3}; + checkLocalPrimitiveArray(bpe.thread().frame(1), "nums", FD.J, expectedVals); + } +} + +///////////////////////////////////////////////////////////////////////////// + +class EAMaterializeFloatArrayTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + float nums[] = {NOT_CONST_1F , 2.2f, 3.3f}; + dontinline_brkpt(); + fResult = nums[0] + nums[1] + nums[2]; + } + + @Override + public float getExpectedFResult() { + return NOT_CONST_1F + 2.2f + 3.3f; + } +} + +class EAMaterializeFloatArray extends EATestCaseBaseDebugger { + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + float[] expectedVals = {1.1f, 2.2f, 3.3f}; + checkLocalPrimitiveArray(bpe.thread().frame(1), "nums", FD.F, expectedVals); + } +} + +///////////////////////////////////////////////////////////////////////////// + +class EAMaterializeDoubleArrayTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + double nums[] = {NOT_CONST_1D , 2.2d, 3.3d}; + dontinline_brkpt(); + dResult = nums[0] + nums[1] + nums[2]; + } + + @Override + public double getExpectedDResult() { + return NOT_CONST_1D + 2.2d + 3.3d; + } +} + +class EAMaterializeDoubleArray extends EATestCaseBaseDebugger { + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + double[] expectedVals = {1.1d, 2.2d, 3.3d}; + checkLocalPrimitiveArray(bpe.thread().frame(1), "nums", FD.D, expectedVals); + } +} + +///////////////////////////////////////////////////////////////////////////// + +class EAMaterializeObjectArrayTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + Long nums[] = {NOT_CONST_1_OBJ , CONST_2_OBJ, CONST_3_OBJ}; + dontinline_brkpt(); + lResult = nums[0] + nums[1] + nums[2]; + } + + @Override + public long getExpectedLResult() { + return 1 + 2 + 3; + } +} + +class EAMaterializeObjectArray extends EATestCaseBaseDebugger { + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + ReferenceType clazz = bpe.thread().frame(0).location().declaringType(); + ObjectReference[] expectedVals = { + (ObjectReference) clazz.getValue(clazz.fieldByName("NOT_CONST_1_OBJ")), + (ObjectReference) clazz.getValue(clazz.fieldByName("CONST_2_OBJ")), + (ObjectReference) clazz.getValue(clazz.fieldByName("CONST_3_OBJ")) + }; + checkLocalObjectArray(bpe.thread().frame(1), "nums", "java.lang.Long[]", expectedVals); + } +} + +///////////////////////////////////////////////////////////////////////////// + +// Materialize an object whose fields have constant and not constant values at +// the point where the object is materialize. +class EAMaterializeObjectWithConstantAndNotConstantValuesTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + ILFDO o = new ILFDO(NOT_CONST_1I, 2, + NOT_CONST_1L, 2L, + NOT_CONST_1F, 2.1F, + NOT_CONST_1D, 2.1D, + NOT_CONST_1_OBJ, CONST_2_OBJ + ); + dontinline_brkpt(); + dResult = + o.i + o.i2 + o.l + o.l2 + o.f + o.f2 + o.d + o.d2 + o.o + o.o2; + } + + @Override + public double getExpectedDResult() { + return NOT_CONST_1I + 2 + NOT_CONST_1L + 2L + NOT_CONST_1F + 2.1F + NOT_CONST_1D + 2.1D + NOT_CONST_1_OBJ + CONST_2_OBJ; + } +} + +class EAMaterializeObjectWithConstantAndNotConstantValues extends EATestCaseBaseDebugger { + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + ObjectReference o = getLocalRef(bpe.thread().frame(1), "ILFDO", "o"); + checkPrimitiveField(o, FD.I, "i", 1); + checkPrimitiveField(o, FD.I, "i2", 2); + checkPrimitiveField(o, FD.J, "l", 1L); + checkPrimitiveField(o, FD.J, "l2", 2L); + checkPrimitiveField(o, FD.F, "f", 1.1f); + checkPrimitiveField(o, FD.F, "f2", 2.1f); + checkPrimitiveField(o, FD.D, "d", 1.1d); + checkPrimitiveField(o, FD.D, "d2", 2.1d); + ReferenceType clazz = bpe.thread().frame(1).location().declaringType(); + ObjectReference[] expVals = { + (ObjectReference) clazz.getValue(clazz.fieldByName("NOT_CONST_1_OBJ")), + (ObjectReference) clazz.getValue(clazz.fieldByName("CONST_2_OBJ")), + }; + checkObjField(o, "o", expVals[0]); + checkObjField(o, "o2", expVals[1]); + } +} + +///////////////////////////////////////////////////////////////////////////// + +// Two local variables reference the same object. +// Check if the debugger obtains the same object when reading the two variables +class EAMaterializeObjReferencedBy2LocalsTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal xy = new XYVal(2, 3); + XYVal alias = xy; + dontinline_brkpt(); + iResult = xy.x + alias.x; + } + + @Override + public int getExpectedIResult() { + return 2 + 2; + } +} + +class EAMaterializeObjReferencedBy2Locals extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + ObjectReference xy = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy"); + ObjectReference alias = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "alias"); + Asserts.assertSame(xy, alias, "xy and alias are expected to reference the same object"); + } + +} + +///////////////////////////////////////////////////////////////////////////// + +// Two local variables reference the same object. +// Check if it has the expected effect in the target, if the debugger modifies the object. +class EAMaterializeObjReferencedBy2LocalsAndModifyTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal xy = new XYVal(2, 3); + XYVal alias = xy; + dontinline_brkpt(); // debugger: alias.x = 42 + iResult = xy.x + alias.x; + } + + @Override + public int getExpectedIResult() { + return 42 + 42; + } +} + +class EAMaterializeObjReferencedBy2LocalsAndModify extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + ObjectReference alias = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "alias"); + setField(alias, "x", env.vm().mirrorOf(42)); + } +} + +///////////////////////////////////////////////////////////////////////////// + +// Two local variables of the same compiled frame but in different virtual frames reference the same +// object. +// Check if the debugger obtains the same object when reading the two variables +class EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesTarget extends EATestCaseBaseTarget { + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + public void dontinline_testMethod() { + XYVal xy = new XYVal(2, 3); + testMethod_inlined(xy); + iResult += xy.x; + } + + public void testMethod_inlined(XYVal xy) { + XYVal alias = xy; + dontinline_brkpt(); + iResult = alias.x; + } + + @Override + public int getExpectedIResult() { + return 2 + 2; + } +} + +class EAMaterializeObjReferencedBy2LocalsInDifferentVirtFrames extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + ObjectReference xy = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "xy"); + ObjectReference alias = getLocalRef(bpe.thread().frame(1), "testMethod_inlined", "alias", XYVAL_NAME); + Asserts.assertSame(xy, alias, "xy and alias are expected to reference the same object"); + } + +} + +///////////////////////////////////////////////////////////////////////////// + +// Two local variables of the same compiled frame but in different virtual frames reference the same +// object. +// Check if it has the expected effect in the target, if the debugger modifies the object. +class EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesAndModifyTarget extends EATestCaseBaseTarget { + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + public void dontinline_testMethod() { + XYVal xy = new XYVal(2, 3); + testMethod_inlined(xy); // debugger: xy.x = 42 + iResult += xy.x; + } + + public void testMethod_inlined(XYVal xy) { + XYVal alias = xy; + dontinline_brkpt(); + iResult = alias.x; + } + + @Override + public int getExpectedIResult() { + return 42 + 42; + } +} + +class EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesAndModify extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + ObjectReference alias = getLocalRef(bpe.thread().frame(1), "testMethod_inlined", "alias", XYVAL_NAME); + setField(alias, "x", env.vm().mirrorOf(42)); + } + +} + +///////////////////////////////////////////////////////////////////////////// + +// Test materialization of an object referenced only from expression stack +class EAMaterializeObjReferencedFromOperandStackTarget extends EATestCaseBaseTarget { + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + public void dontinline_testMethod() { + @SuppressWarnings("unused") + XYVal xy1 = new XYVal(2, 3); + // Debugger breaks in call to dontinline_brkpt_ret_100() and reads + // the value of the local 'xy1'. This triggers materialization + // of the object on the operand stack + iResult = testMethodInlined(new XYVal(4, 2), dontinline_brkpt_ret_100()); + } + + public int testMethodInlined(XYVal xy2, int dontinline_brkpt_ret_100) { + return xy2.x + dontinline_brkpt_ret_100; + } + + public int dontinline_brkpt_ret_100() { + dontinline_brkpt(); + return 100; + } + + @Override + public int getExpectedIResult() { + return 4 + 100; + } +} + +class EAMaterializeObjReferencedFromOperandStack extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + ObjectReference xy1 = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "xy1"); + checkPrimitiveField(xy1, FD.I, "x", 2); + checkPrimitiveField(xy1, FD.I, "y", 3); + } + +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Tests a regression in the implementation by setting the value of a local int which triggers the + * creation of a deferred update and then getting the reference to a scalar replaced object. The + * issue was that the scalar replaced object was not reallocated. Because of the deferred update it + * was assumed that the reallocation already happened. + */ +class EAMaterializeLocalVariableUponGetAfterSetInteger extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + setLocal(bpe.thread().frame(1), "i", env.vm().mirrorOf(43)); + ObjectReference o = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy"); + checkPrimitiveField(o, FD.I, "x", 4); + checkPrimitiveField(o, FD.I, "y", 2); + } +} + +class EAMaterializeLocalVariableUponGetAfterSetIntegerTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal xy = new XYVal(4, 2); + int i = 42; + dontinline_brkpt(); + iResult = xy.x + xy.y + i; + } + + @Override + public int getExpectedIResult() { + return 4 + 2 + 43; + } + + @Override + public boolean testFrameShouldBeDeoptimized() { + return true; // setting local variable i triggers always deoptimization + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Locking Tests +// +///////////////////////////////////////////////////////////////////////////// + +class EARelockingSimple extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference o = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "l1"); + } +} + +class EARelockingSimpleTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal l1 = new XYVal(4, 2); + synchronized (l1) { + dontinline_brkpt(); + } + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Test if the bias of an object O that escapes globally is revoked correctly if local objects + * escape through JVMTI. O is referenced by field l0. + * This tests a regression of a previous version of the implementation. + */ +class EARelockingSimple_2 extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference o = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "l1"); + } +} + +class EARelockingSimple_2Target extends EATestCaseBaseTarget { + + public XYVal l0; + + public void dontinline_testMethod() { + l0 = new XYVal(4, 2); // GobalEscape + XYVal l1 = new XYVal(4, 2); + synchronized (l0) { + synchronized (l1) { + dontinline_brkpt(); + } + } + } +} + +///////////////////////////////////////////////////////////////////////////// + +// Test recursive locking +class EARelockingRecursiveTarget extends EATestCaseBaseTarget { + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + public void dontinline_testMethod() { + XYVal l1 = new XYVal(4, 2); + synchronized (l1) { + testMethod_inlined(l1); + } + } + + public void testMethod_inlined(XYVal l2) { + synchronized (l2) { + dontinline_brkpt(); + } + } +} + +class EARelockingRecursive extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference o = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "l1"); + } +} + +///////////////////////////////////////////////////////////////////////////// + +// Object ref l1 is retrieved by the debugger at a location where nested locks are omitted. The +// accessed object is globally reachable already before the access, therefore no relocking is done. +class EARelockingNestedInflatedTarget extends EATestCaseBaseTarget { + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + @Override + public boolean testFrameShouldBeDeoptimized() { + // Access does not trigger deopt., as escape state is already global escape. + return false; + } + + public void dontinline_testMethod() { + XYVal l1 = inflatedLock; + synchronized (l1) { + testMethod_inlined(l1); + } + } + + public void testMethod_inlined(XYVal l2) { + synchronized (l2) { // eliminated nested locking + dontinline_brkpt(); + } + } +} + +class EARelockingNestedInflated extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference o = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "l1"); + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Like {@link EARelockingNestedInflated} with the difference that there is + * a scalar replaced object in the scope from which the object with eliminated nested locking + * is read. This triggers materialization and relocking. + */ +class EARelockingNestedInflated_02 extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference o = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "l1"); + } +} + +class EARelockingNestedInflated_02Target extends EATestCaseBaseTarget { + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + public void dontinline_testMethod() { + @SuppressWarnings("unused") + XYVal xy = new XYVal(1, 1); // scalar replaced + XYVal l1 = inflatedLock; // read by debugger + synchronized (l1) { + testMethod_inlined(l1); + } + } + + public void testMethod_inlined(XYVal l2) { + synchronized (l2) { // eliminated nested locking + dontinline_brkpt(); + } + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Checks if an eliminated lock of an ArgEscape object l1 can be relocked if + * l1 is locked in a callee frame. + */ +class EARelockingArgEscapeLWLockedInCalleeFrame extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference o = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "l1"); + } +} + +class EARelockingArgEscapeLWLockedInCalleeFrameTarget extends EATestCaseBaseTarget { + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + public void dontinline_testMethod() { + XYVal l1 = new XYVal(1, 1); // ArgEscape + synchronized (l1) { // eliminated + l1.dontinline_sync_method(this); // l1 escapes + } + } + + @Override + public boolean testFrameShouldBeDeoptimized() { + // Graal does not provide debug info about arg escape objects, therefore the frame is not deoptimized + return !UseJVMCICompiler && super.testFrameShouldBeDeoptimized(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Similar to {@link EARelockingArgEscapeLWLockedInCalleeFrame}. In addition + * the test method has got a scalar replaced object with eliminated locking. + * This pattern matches a regression in the implementation. + */ +class EARelockingArgEscapeLWLockedInCalleeFrame_2 extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference o = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "l1"); + } +} + +class EARelockingArgEscapeLWLockedInCalleeFrame_2Target extends EATestCaseBaseTarget { + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + public void dontinline_testMethod() { + XYVal l1 = new XYVal(1, 1); // ArgEscape + XYVal l2 = new XYVal(4, 2); // NoEscape, scalar replaced + synchronized (l1) { // eliminated + synchronized (l2) { // eliminated + l1.dontinline_sync_method(this); // l1 escapes + } + } + iResult = l2.x + l2.y; + } + + @Override + public int getExpectedIResult() { + return 6; + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Similar to {@link EARelockingArgEscapeLWLockedInCalleeFrame}. + * A bulk rebias operation is triggered at a position where all locks on the local object referenced + * by l1 are eliminated. This leaves the object with an outdated biased locking epoch which has to be + * considered when relocking. + * This tests a regression in a previous version. + */ +class EARelockingArgEscapeLWLockedInCalleeFrame_3 extends EATestCaseBaseDebugger { + + public static final String XYVAL_LOCAL_NAME = EARelockingArgEscapeLWLockedInCalleeFrame_3Target.XYValLocal.class.getName(); + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference o = getLocalRef(bpe.thread().frame(1), XYVAL_LOCAL_NAME, "l1"); + } +} + +class EARelockingArgEscapeLWLockedInCalleeFrame_3Target extends EATestCaseBaseTarget { + + // Using local type to avoid side effects on biased locking heuristics + public static class XYValLocal extends XYVal { + public XYValLocal(int x, int y) { + super(x,y); + } + } + + public void dontinline_testMethod() { + XYVal l1 = new XYValLocal(1, 1); // ArgEscape + synchronized (l1) { // eliminated + l1.dontinline_sync_method_no_brkpt(this); // l1 escapes + // trigger bulk rebias + dontinline_bulkRebiasAfterWarmup(l1.getClass()); + // Now the epoch of l1 does not match the epoch of its class. + // This has to be considered when relocking, because of JVMTI access + dontinline_brkpt(); + } + } + + @Override + public boolean testFrameShouldBeDeoptimized() { + // Graal does not provide debug info about arg escape objects, therefore the frame is not deoptimized + return !UseJVMCICompiler && super.testFrameShouldBeDeoptimized(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Similar to {@link EARelockingArgEscapeLWLockedInCalleeFrame_3}. + * But instead of a bulk rebias a bulk revoke operation is triggered. + * This leaves the object with a stale bias as the prototype header of its calls lost its bias + * pattern in the bulk revoke, which has to be considered during relocking. + * This tests a regression in a previous version. + */ +class EARelockingArgEscapeLWLockedInCalleeFrame_4 extends EATestCaseBaseDebugger { + + public static final String XYVAL_LOCAL_NAME = EARelockingArgEscapeLWLockedInCalleeFrame_4Target.XYValLocal.class.getName(); + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference o = getLocalRef(bpe.thread().frame(1), XYVAL_LOCAL_NAME, "l1"); + } +} + +class EARelockingArgEscapeLWLockedInCalleeFrame_4Target extends EATestCaseBaseTarget { + + // Using local type to avoid side effects on biased locking heuristics + public static class XYValLocal extends XYVal { + public XYValLocal(int x, int y) { + super(x,y); + } + } + + public void dontinline_testMethod() { + XYVal l1 = new XYValLocal(1, 1); // ArgEscape + synchronized (l1) { // eliminated + l1.dontinline_sync_method_no_brkpt(this); // l1 escapes + // trigger bulk rebias + dontinline_bulkRevokeAfterWarmup(l1.getClass()); + // Now the epoch of l1 does not match the epoch of its class. + // This has to be considered when relocking, because of JVMTI access + dontinline_brkpt(); + } + } + + + @Override + public boolean testFrameShouldBeDeoptimized() { + // Graal does not provide debug info about arg escape objects, therefore the frame is not deoptimized + return !UseJVMCICompiler && super.testFrameShouldBeDeoptimized(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Test relocking eliminated (nested) locks of an object on which the + * target thread currently waits. + */ +class EARelockingObjectCurrentlyWaitingOn extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + env.targetMainThread.resume(); + boolean inWait = false; + do { + Thread.sleep(100); + env.targetMainThread.suspend(); + printStack(env.targetMainThread); + inWait = env.targetMainThread.frame(0).location().method().name().equals("wait"); + if (!inWait) { + msg("Target not yet in java.lang.Object.wait(long)."); + env.targetMainThread.resume(); + } + } while(!inWait); + StackFrame testMethodFrame = env.targetMainThread.frame(4); + // Access triggers relocking of all eliminated locks, including nested locks of l1 which references + // the object on which the target main thread is currently waiting. + ObjectReference l0 = getLocalRef(testMethodFrame, EARelockingObjectCurrentlyWaitingOnTarget.ForLocking.class.getName(), "l0"); + Asserts.assertEQ(l0.entryCount(), 1, "wrong entry count"); + ObjectReference l1 = getLocalRef(testMethodFrame, EARelockingObjectCurrentlyWaitingOnTarget.ForLocking.class.getName(), "l1"); + Asserts.assertEQ(l1.entryCount(), 0, "wrong entry count"); + setField(testCase, "objToNotifyOn", l1); + } +} + +class EARelockingObjectCurrentlyWaitingOnTarget extends EATestCaseBaseTarget { + + public static class ForLocking { + } + + public volatile Object objToNotifyOn; // debugger assigns value when notify thread should call objToNotifyOn.notifyAll() + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + @Override + public void warmupDone() { + super.warmupDone(); + Thread t = new Thread(() -> doNotify()); + t.start(); + } + + public void doNotify() { + while (objToNotifyOn == null) { + try { + msg("objToNotifyOn is still null"); + Thread.sleep(100); + } catch (InterruptedException e) { /* ignored */ } + } + synchronized (objToNotifyOn) { + // will be received by the target main thread waiting in dontinline_waitWhenWarmupDone + msg("calling objToNotifyOn.notifyAll()"); + objToNotifyOn.notifyAll(); + } + } + + @Override + public boolean testFrameShouldBeDeoptimized() { + return false; + } + + @Override + public void dontinline_testMethod() throws Exception { + ForLocking l0 = new ForLocking(); // will be scalar replaced; access triggers realloc/relock + ForLocking l1 = new ForLocking(); + synchronized (l0) { + synchronized (l1) { + testMethod_inlined(l1); + } + } + } + + public void testMethod_inlined(ForLocking l2) throws Exception { + synchronized (l2) { // eliminated nested locking + dontinline_waitWhenWarmupDone(l2); + } + } + + public void dontinline_waitWhenWarmupDone(ForLocking l2) throws Exception { + if (warmupDone) { + l2.wait(); + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Test cases that require deoptimization even though neither locks +// nor allocations are eliminated at the point where escape state is changed. +// +///////////////////////////////////////////////////////////////////////////// + +/** + * Let xy be NoEscape whose allocation cannot be eliminated (simulated by + * -XX:-EliminateAllocations). The holding compiled frame has to be deoptimized when debugger + * accesses xy, because afterwards locking on xy is omitted. + * Note: there are no EA based optimizations at the escape point. + */ +class EADeoptFrameAfterReadLocalObject_01 extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference xy = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy"); + } +} + +class EADeoptFrameAfterReadLocalObject_01Target extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal xy = new XYVal(1, 1); + dontinline_brkpt(); // Debugger reads xy, when there are no virtual objects or eliminated locks in scope + synchronized (xy) { // Locking is eliminated. + xy.x++; + xy.y++; + } + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Similar to {@link EADeoptFrameAfterReadLocalObject_01} with the difference that the debugger + * reads xy from an inlined callee. So xy is NoEscape instead of ArgEscape. + */ +class EADeoptFrameAfterReadLocalObject_01BTarget extends EATestCaseBaseTarget { + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + public void dontinline_testMethod() { + XYVal xy = new XYVal(1, 1); + callee(xy); // Debugger acquires ref to xy from inlined callee + // xy is NoEscape, nevertheless the object is not replaced + // by scalars if running with -XX:-EliminateAllocations. + // In that case there are no EA based optimizations were + // the debugger reads the NoEscape object. + synchronized (xy) { // Locking is eliminated. + xy.x++; + xy.y++; + } + } + + public void callee(XYVal xy) { + dontinline_brkpt(); // Debugger reads xy. + // There are no virtual objects or eliminated locks. + } +} + +class EADeoptFrameAfterReadLocalObject_01B extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference xy = getLocalRef(bpe.thread().frame(1), "callee", "xy", XYVAL_NAME); + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Let xy be ArgEscape. The frame dontinline_testMethod() has to be deoptimized when debugger + * acquires xy from dontinline_calee(), because afterwards locking on xy is omitted. + * Note: there are no EA based optimizations at the escape point. + */ +class EADeoptFrameAfterReadLocalObject_02 extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference xy = getLocalRef(bpe.thread().frame(1), "dontinline_callee", "xy", XYVAL_NAME); + } +} + +class EADeoptFrameAfterReadLocalObject_02Target extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal xy = new XYVal(1, 1); + dontinline_callee(xy); // xy is ArgEscape, debugger acquires ref to xy from callee + synchronized (xy) { // Locking is eliminated. + xy.x++; + xy.y++; + } + } + + public void dontinline_callee(XYVal xy) { + dontinline_brkpt(); // Debugger reads xy. + // There are no virtual objects or eliminated locks. + } + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + @Override + public boolean testFrameShouldBeDeoptimized() { + // Graal does not provide debug info about arg escape objects, therefore the frame is not deoptimized + return !UseJVMCICompiler && super.testFrameShouldBeDeoptimized(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Similar to {@link EADeoptFrameAfterReadLocalObject_02} there is an ArgEscape object xy, but in + * contrast it is not in the parameter list of a call when the debugger reads an object. + * Therefore the frame of the test method should not be deoptimized + */ +class EADeoptFrameAfterReadLocalObject_02B extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference xy = getLocalRef(bpe.thread().frame(1), "dontinline_callee", "xy", XYVAL_NAME); + } +} + +class EADeoptFrameAfterReadLocalObject_02BTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal xy = new XYVal(1, 1); + dontinline_make_arg_escape(xy); // because of this call xy is ArgEscape + dontinline_callee(); // xy is ArgEscape, but not a parameter of this call + synchronized (xy) { // Locking is eliminated. + xy.x++; + xy.y++; + } + } + + public void dontinline_callee() { + @SuppressWarnings("unused") + XYVal xy = new XYVal(2, 2); + dontinline_brkpt(); // Debugger reads xy. + // No need to deoptimize the caller frame + } + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + @Override + public boolean testFrameShouldBeDeoptimized() { + return false; + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Similar to {@link EADeoptFrameAfterReadLocalObject_02} there is an ArgEscape object xy in + * dontinline_testMethod() which is being passed as parameter when the debugger accesses a local object. + * Nevertheless dontinline_testMethod must not be deoptimized, because there is an entry frame + * between it and the frame accessed by the debugger. + */ +class EADeoptFrameAfterReadLocalObject_02C extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference xy = getLocalRef(bpe.thread().frame(1), "dontinline_callee_accessed_by_debugger", "xy", XYVAL_NAME); + } +} + +class EADeoptFrameAfterReadLocalObject_02CTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal xy = new XYVal(1, 1); + dontinline_callee(xy); // xy is ArgEscape and being passed as parameter + synchronized (xy) { // Locking is eliminated. + xy.x++; + xy.y++; + } + } + + public void dontinline_callee(XYVal xy) { + if (warmupDone) { + dontinline_call_with_entry_frame(this, "dontinline_callee_accessed_by_debugger"); + } + } + + public void dontinline_callee_accessed_by_debugger() { + @SuppressWarnings("unused") + XYVal xy = new XYVal(2, 2); + dontinline_brkpt(); // Debugger reads xy. + // No need to deoptimize the caller frame + } + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 8; + } + + @Override + public boolean testFrameShouldBeDeoptimized() { + return false; + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Let xy be NoEscape whose allocation cannot be eliminated (e.g. because of + * -XX:-EliminateAllocations). The holding compiled frame has to be deoptimized when debugger + * accesses xy, because the following field accesses get eliminated. Note: there are no EA based + * optimizations at the escape point. + */ +class EADeoptFrameAfterReadLocalObject_03 extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + ObjectReference xy = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy"); + setField(xy, "x", env.vm().mirrorOf(1)); + } +} + +class EADeoptFrameAfterReadLocalObject_03Target extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal xy = new XYVal(0, 1); + dontinline_brkpt(); // Debugger reads xy, when there are no virtual objects or + // eliminated locks in scope and modifies xy.x + iResult = xy.x + xy.y; // Loads are replaced by constants 0 and 1. + } + + @Override + public int getExpectedIResult() { + return 1 + 1; + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Monitor info tests +// +///////////////////////////////////////////////////////////////////////////// + +class EAGetOwnedMonitorsTarget extends EATestCaseBaseTarget { + + public long checkSum; + + public void dontinline_testMethod() { + XYVal l1 = new XYVal(4, 2); + synchronized (l1) { + dontinline_endlessLoop(); + } + } + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + loopCount = 3; + } + + public void warmupDone() { + super.warmupDone(); + msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE"); + loopCount = Long.MAX_VALUE; // endless loop + } +} + +class EAGetOwnedMonitors extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + msg("resume"); + env.targetMainThread.resume(); + waitUntilTargetHasEnteredEndlessLoop(); + // In contrast to JVMTI, JDWP requires a target thread to be suspended, before the owned monitors can be queried + msg("suspend target"); + env.targetMainThread.suspend(); + msg("Get owned monitors"); + List monitors = env.targetMainThread.ownedMonitors(); + Asserts.assertEQ(monitors.size(), 1, "unexpected number of owned monitors"); + terminateEndlessLoop(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +class EAEntryCountTarget extends EATestCaseBaseTarget { + + public long checkSum; + + public void dontinline_testMethod() { + XYVal l1 = new XYVal(4, 2); + synchronized (l1) { + inline_testMethod2(l1); + } + } + + public void inline_testMethod2(XYVal l1) { + synchronized (l1) { + dontinline_endlessLoop(); + } + } + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + loopCount = 3; + } + + public void warmupDone() { + super.warmupDone(); + msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE"); + loopCount = Long.MAX_VALUE; // endless loop + } +} + +class EAEntryCount extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + msg("resume"); + env.targetMainThread.resume(); + waitUntilTargetHasEnteredEndlessLoop(); + // In contrast to JVMTI, JDWP requires a target thread to be suspended, before the owned monitors can be queried + msg("suspend target"); + env.targetMainThread.suspend(); + msg("Get owned monitors"); + List monitors = env.targetMainThread.ownedMonitors(); + Asserts.assertEQ(monitors.size(), 1, "unexpected number of owned monitors"); + msg("Get entry count"); + int entryCount = monitors.get(0).entryCount(); + Asserts.assertEQ(entryCount, 2, "wrong entry count"); + terminateEndlessLoop(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// PopFrame tests +// +///////////////////////////////////////////////////////////////////////////// + +/** + * PopFrame into caller frame with scalar replaced objects. + */ +class EAPopFrameNotInlined extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + msg("PopFrame"); + bpe.thread().popFrames(bpe.thread().frame(0)); + msg("PopFrame DONE"); + } + + @Override + public boolean shouldSkip() { + // And Graal currently doesn't support PopFrame + return super.shouldSkip() || env.targetVMOptions.UseJVMCICompiler; + } +} + +class EAPopFrameNotInlinedTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal xy = new XYVal(4, 2); + dontinline_brkpt(); + iResult = xy.x + xy.y; + } + + @Override + public boolean testFrameShouldBeDeoptimized() { + // Test is only performed after the frame pop. + // Then dontinline_testMethod is interpreted. + return false; + } + + @Override + public int getExpectedIResult() { + return 4 + 2; + } + + @Override + public boolean shouldSkip() { + // And Graal currently doesn't support PopFrame + return super.shouldSkip() || UseJVMCICompiler; + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Pop frames into {@link EAPopFrameNotInlinedReallocFailureTarget#dontinline_testMethod()} which + * holds scalar replaced objects. In preparation of the pop frame operations the vm eagerly + * reallocates scalar replaced objects to avoid failures when actually poping the frames. We provoke + * a reallocation failures and expect {@link VMOutOfMemoryException}. + */ +class EAPopFrameNotInlinedReallocFailure extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + ThreadReference thread = bpe.thread(); + printStack(thread); + // frame[0]: EATestCaseBaseTarget.dontinline_brkpt() + // frame[1]: EAPopFrameNotInlinedReallocFailureTarget.dontinline_consume_all_memory_brkpt() + // frame[2]: EAPopFrameNotInlinedReallocFailureTarget.dontinline_testMethod() + // frame[3]: EATestCaseBaseTarget.run() + // frame[4]: EATestsTarget.main(java.lang.String[]) + msg("PopFrame"); + boolean coughtOom = false; + try { + // try to pop dontinline_consume_all_memory_brkpt + thread.popFrames(thread.frame(1)); + } catch (VMOutOfMemoryException oom) { + // as expected + msg("cought OOM"); + coughtOom = true; + } + freeAllMemory(); + // We succeeded to pop just one frame. When we continue, we will call dontinline_brkpt() again. + Asserts.assertTrue(coughtOom || !env.targetVMOptions.EliminateAllocations, "PopFrame should have triggered an OOM exception in target"); + String expectedTopFrame = + env.targetVMOptions.EliminateAllocations ? "dontinline_consume_all_memory_brkpt" : "dontinline_testMethod"; + Asserts.assertEQ(expectedTopFrame, thread.frame(0).location().method().name()); + printStack(thread); + } + + @Override + public boolean shouldSkip() { + // OOMEs because of realloc failures with DeoptimizeObjectsALot are too random. + // And Graal currently doesn't provide all information about non-escaping objects in debug info + return super.shouldSkip() || env.targetVMOptions.DeoptimizeObjectsALot || env.targetVMOptions.UseJVMCICompiler; + } +} + +class EAPopFrameNotInlinedReallocFailureTarget extends EATestCaseBaseTarget { + + public boolean doneAlready; + + public void dontinline_testMethod() { + long a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // scalar replaced + Vector10 v = new Vector10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // scalar replaced + dontinline_consume_all_memory_brkpt(); + lResult = a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8] + a[9] + + v.i0 + v.i1 + v.i2 + v.i3 + v.i4 + v.i5 + v.i6 + v.i7 + v.i8 + v.i9; + } + + public void dontinline_consume_all_memory_brkpt() { + if (warmupDone && !doneAlready) { + doneAlready = true; + consumeAllMemory(); // provoke reallocation failure + dontinline_brkpt(); + } + } + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + @Override + public long getExpectedLResult() { + long n = 10; + return 2*n*(n+1)/2; + } + + @Override + public boolean shouldSkip() { + // OOMEs because of realloc failures with DeoptimizeObjectsALot are too random. + // And Graal currently doesn't provide all information about non-escaping objects in debug info + return super.shouldSkip() || DeoptimizeObjectsALot || UseJVMCICompiler; + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Pop inlined top frame dropping into method with scalar replaced opjects. + */ +class EAPopInlinedMethodWithScalarReplacedObjectsReallocFailure extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + ThreadReference thread = env.targetMainThread; + thread.resume(); + waitUntilTargetHasEnteredEndlessLoop(); + + thread.suspend(); + printStack(thread); + // frame[0]: EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.inlinedCallForcedToReturn() + // frame[1]: EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.dontinline_testMethod() + // frame[2]: EATestCaseBaseTarget.run() + + msg("Pop Frames"); + boolean coughtOom = false; + try { + thread.popFrames(thread.frame(0)); // Request pop frame of inlinedCallForcedToReturn() + // reallocation is triggered here + } catch (VMOutOfMemoryException oom) { + // as expected + msg("cought OOM"); + coughtOom = true; + } + printStack(thread); + // frame[0]: EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.inlinedCallForcedToReturn() + // frame[1]: EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.dontinline_testMethod() + // frame[2]: EATestCaseBaseTarget.run() + + freeAllMemory(); + setField(testCase, "loopCount", env.vm().mirrorOf(0)); // terminate loop + Asserts.assertTrue(coughtOom || !env.targetVMOptions.EliminateAllocations, "PopFrame should have triggered an OOM exception in target"); + String expectedTopFrame = + env.targetVMOptions.EliminateAllocations ? "inlinedCallForcedToReturn" : "dontinline_testMethod"; + Asserts.assertEQ(expectedTopFrame, thread.frame(0).location().method().name()); + } + + @Override + public boolean shouldSkip() { + // OOMEs because of realloc failures with DeoptimizeObjectsALot are too random. + // And Graal currently doesn't provide all information about non-escaping objects in debug info + return super.shouldSkip() || env.targetVMOptions.DeoptimizeObjectsALot || env.targetVMOptions.UseJVMCICompiler; + } +} + +class EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget extends EATestCaseBaseTarget { + + public long checkSum; + + public void dontinline_testMethod() { + long a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // scalar replaced + Vector10 v = new Vector10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // scalar replaced + long l = inlinedCallForcedToReturn(); + lResult = a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8] + a[9] + + v.i0 + v.i1 + v.i2 + v.i3 + v.i4 + v.i5 + v.i6 + v.i7 + v.i8 + v.i9; + } + + public long inlinedCallForcedToReturn() { + long cs = checkSum; + dontinline_consumeAllMemory(); + while (loopCount-- > 0) { + targetIsInLoop = true; + checkSum += checkSum % ++cs; + } + loopCount = 3; + targetIsInLoop = false; + return checkSum; + } + + public void dontinline_consumeAllMemory() { + if (warmupDone && (loopCount > 3)) { + consumeAllMemory(); + } + } + + @Override + public long getExpectedLResult() { + long n = 10; + return 2*n*(n+1)/2; + } + + @Override + public void setUp() { + super.setUp(); + loopCount = 3; + } + + public void warmupDone() { + super.warmupDone(); + msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE"); + loopCount = Long.MAX_VALUE; // endless loop + } + + @Override + public boolean shouldSkip() { + // OOMEs because of realloc failures with DeoptimizeObjectsALot are too random. + // And Graal currently doesn't provide all information about non-escaping objects in debug info + return super.shouldSkip() || DeoptimizeObjectsALot || UseJVMCICompiler; + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// ForceEarlyReturn tests +// +///////////////////////////////////////////////////////////////////////////// + +/** + * ForceEarlyReturn into caller frame with scalar replaced objects. + */ +class EAForceEarlyReturnNotInlined extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = env.resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + ThreadReference thread = bpe.thread(); + printStack(thread); + // frame[0]: EATestCaseBaseTarget.dontinline_brkpt() + // frame[1]: EATestCaseBaseTarget.dontinline_brkpt_iret() + // frame[2]: EAForceEarlyReturnNotInlinedTarget.dontinline_testMethod() + // frame[3]: EATestCaseBaseTarget.run() + // frame[4]: EATestsTarget.main(java.lang.String[]) + + msg("Step out"); + env.stepOut(thread); // return from dontinline_brkpt + printStack(thread); + msg("ForceEarlyReturn"); + thread.forceEarlyReturn(env.vm().mirrorOf(43)); // return from dontinline_brkpt_iret, + // does not trigger reallocation in contrast to PopFrame + msg("Step over line"); + env.stepOverLine(thread); // reallocation is triggered here + printStack(thread); + msg("ForceEarlyReturn DONE"); + } + + @Override + public boolean shouldSkip() { + // Graal currently doesn't support Force Early Return + return super.shouldSkip() || env.targetVMOptions.UseJVMCICompiler; + } +} + +class EAForceEarlyReturnNotInlinedTarget extends EATestCaseBaseTarget { + + public void dontinline_testMethod() { + XYVal xy = new XYVal(4, 2); + int i = dontinline_brkpt_iret(); + iResult = xy.x + xy.y + i; + } + + @Override + public int getExpectedIResult() { + return 4 + 2 + 43; + } + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + public boolean testFrameShouldBeDeoptimized() { + return true; // because of stepping + } + + @Override + public boolean shouldSkip() { + // Graal currently doesn't support Force Early Return + return super.shouldSkip() || UseJVMCICompiler; + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * ForceEarlyReturn at safepoint in frame with scalar replaced objects. + */ +class EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjects extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + ThreadReference thread = env.targetMainThread; + thread.resume(); + waitUntilTargetHasEnteredEndlessLoop(); + + thread.suspend(); + printStack(thread); + // frame[0]: EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsTarget.inlinedCallForcedToReturn() + // frame[1]: EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsTarget.dontinline_testMethod() + // frame[2]: EATestCaseBaseTarget.run() + + msg("ForceEarlyReturn"); + thread.forceEarlyReturn(env.vm().mirrorOf(43)); // Request force return 43 from inlinedCallForcedToReturn() + // reallocation is triggered here + msg("Step over instruction to do the forced return"); + env.stepOverInstruction(thread); + printStack(thread); + msg("ForceEarlyReturn DONE"); + } + + @Override + public boolean shouldSkip() { + // Graal currently doesn't support Force Early Return + return super.shouldSkip() || env.targetVMOptions.UseJVMCICompiler; + } +} + +class EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsTarget extends EATestCaseBaseTarget { + + public int checkSum; + + public void dontinline_testMethod() { + XYVal xy = new XYVal(4, 2); + int i = inlinedCallForcedToReturn(); + iResult = xy.x + xy.y + i; + } + + public int inlinedCallForcedToReturn() { // forced to return 43 + int i = checkSum; + while (loopCount-- > 0) { + targetIsInLoop = true; + checkSum += checkSum % ++i; + } + loopCount = 3; + targetIsInLoop = false; + return checkSum; + } + + @Override + public int getExpectedIResult() { + return 4 + 2 + 43; + } + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + loopCount = 3; + } + + public void warmupDone() { + super.warmupDone(); + msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE"); + loopCount = Long.MAX_VALUE; // endless loop + } + + public boolean testFrameShouldBeDeoptimized() { + return true; // because of stepping + } + + @Override + public boolean shouldSkip() { + // Graal currently doesn't support Force Early Return + return super.shouldSkip() || UseJVMCICompiler; + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * ForceEarlyReturn with reallocation failure. + */ +class EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailure extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + ThreadReference thread = env.targetMainThread; + thread.resume(); + waitUntilTargetHasEnteredEndlessLoop(); + + thread.suspend(); + printStack(thread); + // frame[0]: EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.inlinedCallForcedToReturn() + // frame[1]: EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.dontinline_testMethod() + // frame[2]: EATestCaseBaseTarget.run() + + msg("ForceEarlyReturn"); + boolean coughtOom = false; + try { + thread.forceEarlyReturn(env.vm().mirrorOf(43)); // Request force return 43 from inlinedCallForcedToReturn() + // reallocation is triggered here + } catch (VMOutOfMemoryException oom) { + // as expected + msg("cought OOM"); + coughtOom = true; + } + freeAllMemory(); + if (env.targetVMOptions.EliminateAllocations) { + Asserts.assertTrue(coughtOom, "PopFrame should have triggered an OOM exception in target"); + thread.forceEarlyReturn(env.vm().mirrorOf(43)); + } + msg("Step over instruction to do the forced return"); + env.stepOverInstruction(thread); + printStack(thread); + msg("ForceEarlyReturn DONE"); + } + + @Override + public boolean shouldSkip() { + // OOMEs because of realloc failures with DeoptimizeObjectsALot are too random. + // And Graal currently doesn't support Force Early Return + return super.shouldSkip() || env.targetVMOptions.DeoptimizeObjectsALot || env.targetVMOptions.UseJVMCICompiler; + } +} + +class EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailureTarget extends EATestCaseBaseTarget { + + public int checkSum; + + public void dontinline_testMethod() { + long a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // scalar replaced + Vector10 v = new Vector10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // scalar replaced + long l = inlinedCallForcedToReturn(); + lResult = a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8] + a[9] + + v.i0 + v.i1 + v.i2 + v.i3 + v.i4 + v.i5 + v.i6 + v.i7 + v.i8 + v.i9 + l; + } + + public long inlinedCallForcedToReturn() { // forced to return 43 + long cs = checkSum; + dontinline_consumeAllMemory(); + while (loopCount-- > 0) { + targetIsInLoop = true; + checkSum += checkSum % ++cs; + } + loopCount = 3; + targetIsInLoop = false; + return checkSum; + } + + public void dontinline_consumeAllMemory() { + if (warmupDone) { + consumeAllMemory(); + } + } + + @Override + public long getExpectedLResult() { + long n = 10; + return 2*n*(n+1)/2 + 43; + } + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + loopCount = 3; + } + + public void warmupDone() { + super.warmupDone(); + msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE"); + loopCount = Long.MAX_VALUE; // endless loop + } + + @Override + public boolean shouldSkip() { + // OOMEs because of realloc failures with DeoptimizeObjectsALot are too random. + // And Graal currently doesn't support Force Early Return + return super.shouldSkip() || DeoptimizeObjectsALot || UseJVMCICompiler; + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Get Instances of ReferenceType +// +///////////////////////////////////////////////////////////////////////////// + +/** + * Check if instances of a type are found even if they are scalar replaced. To stress the + * implementation a little more, the instances should be retrieved while the target is running. + */ +class EAGetInstancesOfReferenceType extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + printStack(env.targetMainThread); + ReferenceType cls = ((ClassObjectReference)getField(testCase, "cls")).reflectedType(); + msg("reflected type is " + cls); + msg("resume"); + env.targetMainThread.resume(); + waitUntilTargetHasEnteredEndlessLoop(); + // do this while thread is running! + msg("Retrieve instances of " + cls.name()); + List instances = cls.instances(10); + Asserts.assertEQ(instances.size(), 3, "unexpected number of instances of " + cls.name()); + // invariant: main thread is suspended at the end of the test case + msg("suspend"); + env.targetMainThread.suspend(); + terminateEndlessLoop(); + } +} + +class EAGetInstancesOfReferenceTypeTarget extends EATestCaseBaseTarget { + + public long checkSum; + + public static Class cls = LocalXYVal.class; + + public static class LocalXYVal { + public int x, y; + + public LocalXYVal(int x, int y) { + this.x = x; this.y = y; + } + } + + @Override + public void dontinline_testMethod() { + LocalXYVal p1 = new LocalXYVal(4, 2); + LocalXYVal p2 = new LocalXYVal(5, 3); + LocalXYVal p3 = new LocalXYVal(6, 4); + dontinline_endlessLoop(); + iResult = p1.x+p1.y + p2.x+p2.y + p3.x+p3.y; + } + + @Override + public int getExpectedIResult() { + return 6+8+10; + } + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + loopCount = 3; + } + + public void warmupDone() { + super.warmupDone(); + msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE"); + loopCount = Long.MAX_VALUE; // endless loop + } +} + + +// End of test case collection +///////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////// +// Helper classes +class XYVal { + + public int x, y; + + public XYVal(int x, int y) { + this.x = x; + this.y = y; + } + + /** + * Note that we don't use a sync block here, because javac would generate an synthetic exception + * handler for the synchronized block that catches Throwable E, unlocks and throws E + * again. The throw bytecode causes the BCEscapeAnalyzer to set the escape state to GlobalEscape + * (see comment on exception handlers in BCEscapeAnalyzer::iterate_blocks()) + */ + public synchronized void dontinline_sync_method(EATestCaseBaseTarget target) { + target.dontinline_brkpt(); + } + + /** + * Just like {@link #dontinline_sync_method(EATestCaseBaseTarget)} but without the call to + * {@link EATestCaseBaseTarget#dontinline_brkpt()}. + */ + public synchronized void dontinline_sync_method_no_brkpt(EATestCaseBaseTarget target) { + } +} + +class Vector10 { + int i0, i1, i2, i3, i4, i5, i6, i7, i8, i9; + public Vector10(int j0, int j1, int j2, int j3, int j4, int j5, int j6, int j7, int j8, int j9) { + i0=j0; i1=j1; i2=j2; i3=j3; i4=j4; i5=j5; i6=j6; i7=j7; i8=j8; i9=j9; + } +} + +class ILFDO { + + public int i; + public int i2; + public long l; + public long l2; + public float f; + public float f2; + public double d; + public double d2; + public Long o; + public Long o2; + + public ILFDO(int i, + int i2, + long l, + long l2, + float f, + float f2, + double d, + double d2, + Long o, + Long o2) { + this.i = i; + this.i2 = i2; + this.l = l; + this.l2 = l2; + this.f = f; + this.f2 = f2; + this.d = d; + this.d2 = d2; + this.o = o; + this.o2 = o2; + } + +} diff --git a/test/jdk/com/sun/jdi/EATestsJVMCI.java b/test/jdk/com/sun/jdi/EATestsJVMCI.java new file mode 100644 --- /dev/null +++ b/test/jdk/com/sun/jdi/EATestsJVMCI.java @@ -0,0 +1,52 @@ +/* + * 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 8227745 + * + * @summary This is another configuration of EATests.java to test Graal. Some testcases are expected + * to fail, because Graal does not provide all information about non-escaping objects in + * scope. These are skipped. + * + * @author Richard Reingruber richard DOT reingruber AT sap DOT com + * + * @requires ((vm.compMode == "Xmixed") & vm.jvmci) + * + * @library /test/lib /test/hotspot/jtreg + * + * @run build TestScaffold VMConnection TargetListener TargetAdapter sun.hotspot.WhiteBox + * @run driver ClassFileInstaller sun.hotspot.WhiteBox + * @run compile -g EATests.java + * + * @comment Test with Graal. Some testcases are expected to fail, because Graal does not provide all information about non-escaping + * objects in scope. These are skipped. + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler + */ diff --git a/test/lib/sun/hotspot/WhiteBox.java b/test/lib/sun/hotspot/WhiteBox.java --- a/test/lib/sun/hotspot/WhiteBox.java +++ b/test/lib/sun/hotspot/WhiteBox.java @@ -240,6 +240,7 @@ 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) {