diff --git a/src/hotspot/cpu/aarch64/relocInfo_aarch64.cpp b/src/hotspot/cpu/aarch64/relocInfo_aarch64.cpp index f5d7d9e4387ec..94694b58d2fae 100644 --- a/src/hotspot/cpu/aarch64/relocInfo_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/relocInfo_aarch64.cpp @@ -90,7 +90,6 @@ void Relocation::pd_set_call_destination(address x) { void trampoline_stub_Relocation::pd_fix_owner_after_move() { NativeCall* call = nativeCall_at(owner()); - assert(call->raw_destination() == owner(), "destination should be empty"); address trampoline = addr(); address dest = nativeCallTrampolineStub_at(trampoline)->destination(); if (!Assembler::reachable_from_branch_at(owner(), dest)) { diff --git a/src/hotspot/share/code/codeBehaviours.cpp b/src/hotspot/share/code/codeBehaviours.cpp index 1f9eb0e29141d..bc5a81c95b3ca 100644 --- a/src/hotspot/share/code/codeBehaviours.cpp +++ b/src/hotspot/share/code/codeBehaviours.cpp @@ -23,23 +23,24 @@ */ #include "code/codeBehaviours.hpp" +#include "code/nmethod.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/safepoint.hpp" CompiledICProtectionBehaviour* CompiledICProtectionBehaviour::_current = nullptr; -bool DefaultICProtectionBehaviour::lock(nmethod* method) { - if (is_safe(method)) { +bool DefaultICProtectionBehaviour::lock(nmethod* nm) { + if (is_safe(nm)) { return false; } CompiledIC_lock->lock_without_safepoint_check(); return true; } -void DefaultICProtectionBehaviour::unlock(nmethod* method) { +void DefaultICProtectionBehaviour::unlock(nmethod* nm) { CompiledIC_lock->unlock(); } -bool DefaultICProtectionBehaviour::is_safe(nmethod* method) { - return SafepointSynchronize::is_at_safepoint() || CompiledIC_lock->owned_by_self(); +bool DefaultICProtectionBehaviour::is_safe(nmethod* nm) { + return SafepointSynchronize::is_at_safepoint() || CompiledIC_lock->owned_by_self() || (NMethodState_lock->owned_by_self() && nm->is_not_installed()); } diff --git a/src/hotspot/share/code/codeBehaviours.hpp b/src/hotspot/share/code/codeBehaviours.hpp index 0350f5752f696..96a38fffc30e0 100644 --- a/src/hotspot/share/code/codeBehaviours.hpp +++ b/src/hotspot/share/code/codeBehaviours.hpp @@ -33,18 +33,18 @@ class CompiledICProtectionBehaviour { static CompiledICProtectionBehaviour* _current; public: - virtual bool lock(nmethod* method) = 0; - virtual void unlock(nmethod* method) = 0; - virtual bool is_safe(nmethod* method) = 0; + virtual bool lock(nmethod* nm) = 0; + virtual void unlock(nmethod* nm) = 0; + virtual bool is_safe(nmethod* nm) = 0; static CompiledICProtectionBehaviour* current() { return _current; } static void set_current(CompiledICProtectionBehaviour* current) { _current = current; } }; class DefaultICProtectionBehaviour: public CompiledICProtectionBehaviour, public CHeapObj { - virtual bool lock(nmethod* method); - virtual void unlock(nmethod* method); - virtual bool is_safe(nmethod* method); + virtual bool lock(nmethod* nm); + virtual void unlock(nmethod* nm); + virtual bool is_safe(nmethod* nm); }; #endif // SHARE_CODE_CODEBEHAVIOURS_HPP diff --git a/src/hotspot/share/code/codeCache.hpp b/src/hotspot/share/code/codeCache.hpp index 3e446ab8430fe..dc9e5d7dc2030 100644 --- a/src/hotspot/share/code/codeCache.hpp +++ b/src/hotspot/share/code/codeCache.hpp @@ -259,7 +259,7 @@ class CodeCache : AllStatic { static bool heap_available(CodeBlobType code_blob_type); // Returns the CodeBlobType for the given nmethod - static CodeBlobType get_code_blob_type(nmethod* nm) { + static CodeBlobType get_code_blob_type(const nmethod* nm) { return get_code_heap(nm)->code_blob_type(); } diff --git a/src/hotspot/share/code/compiledIC.cpp b/src/hotspot/share/code/compiledIC.cpp index 03d9adc8e242d..5f5c971144102 100644 --- a/src/hotspot/share/code/compiledIC.cpp +++ b/src/hotspot/share/code/compiledIC.cpp @@ -55,8 +55,8 @@ CompiledICLocker::~CompiledICLocker() { } } -bool CompiledICLocker::is_safe(nmethod* method) { - return CompiledICProtectionBehaviour::current()->is_safe(method); +bool CompiledICLocker::is_safe(nmethod* nm) { + return CompiledICProtectionBehaviour::current()->is_safe(nm); } bool CompiledICLocker::is_safe(address code) { diff --git a/src/hotspot/share/code/compiledIC.hpp b/src/hotspot/share/code/compiledIC.hpp index 624c1b428de79..db8a0860b39d7 100644 --- a/src/hotspot/share/code/compiledIC.hpp +++ b/src/hotspot/share/code/compiledIC.hpp @@ -50,7 +50,7 @@ class CompiledICLocker: public StackObj { public: CompiledICLocker(nmethod* method); ~CompiledICLocker(); - static bool is_safe(nmethod* method); + static bool is_safe(nmethod* nm); static bool is_safe(address code); }; diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index 1540fa0333a2e..7274b627f3e6b 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -754,7 +754,7 @@ Method* nmethod::attached_method_before_pc(address pc) { } void nmethod::clear_inline_caches() { - assert(SafepointSynchronize::is_at_safepoint(), "clearing of IC's only allowed at safepoint"); + assert(SafepointSynchronize::is_at_safepoint() || (NMethodState_lock->owned_by_self() && is_not_installed()), "clearing of IC's only allowed at safepoint or when not installed"); RelocIterator iter(this); while (iter.next()) { iter.reloc()->clear_inline_cache(); @@ -1146,7 +1146,8 @@ nmethod* nmethod::new_nmethod(const methodHandle& method, #if INCLUDE_JVMCI + align_up(speculations_len , oopSize) #endif - + align_up(debug_info->data_size() , oopSize); + + align_up(debug_info->data_size() , oopSize) + + align_up(ImmutableDataReferencesCounterSize, oopSize); // First, allocate space for immutable data in C heap. address immutable_data = nullptr; @@ -1371,10 +1372,266 @@ nmethod::nmethod( } } + +nmethod::nmethod(const nmethod &nm) : CodeBlob(nm._name, nm._kind, nm._size, nm._header_size) +{ + + if (nm._oop_maps != nullptr) { + _oop_maps = nm._oop_maps->clone(); + } else { + _oop_maps = nullptr; + } + + _size = nm._size; + _relocation_size = nm._relocation_size; + _content_offset = nm._content_offset; + _code_offset = nm._code_offset; + _data_offset = nm._data_offset; + _frame_size = nm._frame_size; + + S390_ONLY( _ctable_offset = nm._ctable_offset; ) + + _header_size = nm._header_size; + _frame_complete_offset = nm._frame_complete_offset; + + _kind = nm._kind; + + _caller_must_gc_arguments = nm._caller_must_gc_arguments; + +#ifndef PRODUCT + _asm_remarks.share(nm._asm_remarks); + _dbg_strings.share(nm._dbg_strings); +#endif + + // Allocate memory and copy mutable data to C heap + _mutable_data_size = nm._mutable_data_size; + if (_mutable_data_size > 0) { + _mutable_data = (address)os::malloc(_mutable_data_size, mtCode); + if (_mutable_data == nullptr) { + vm_exit_out_of_memory(_mutable_data_size, OOM_MALLOC_ERROR, "nmethod: no space for mutable data"); + } + memcpy(mutable_data_begin(), nm.mutable_data_begin(), nm.mutable_data_size()); + } else { + _mutable_data = nullptr; + } + + _deoptimization_generation = 0; + _gc_epoch = CodeCache::gc_epoch(); + _method = nm._method; + _osr_link = nullptr; + + // Increment number of references to immutable data to share it between nmethods + _immutable_data_size = nm._immutable_data_size; + if (_immutable_data_size > 0) { + _immutable_data = nm._immutable_data; + set_immutable_data_references_counter(get_immutable_data_references_counter() + 1); + } else { + _immutable_data = blob_end(); + } + + _exception_cache = nullptr; + _gc_data = nullptr; + _oops_do_mark_nmethods = nullptr; + _oops_do_mark_link = nullptr; + _compiled_ic_data = nullptr; + + if (nm._osr_entry_point != nullptr) { + _osr_entry_point = (nm._osr_entry_point - (address) &nm) + (address) this; + } else { + _osr_entry_point = nullptr; + } + + _entry_offset = nm._entry_offset; + _verified_entry_offset = nm._verified_entry_offset; + _entry_bci = nm._entry_bci; + + _skipped_instructions_size = nm._skipped_instructions_size; + _stub_offset = nm._stub_offset; + _exception_offset = nm._exception_offset; + _deopt_handler_offset = nm._deopt_handler_offset; + _unwind_handler_offset = nm._unwind_handler_offset; + _num_stack_arg_slots = nm._num_stack_arg_slots; + _oops_size = nm._oops_size; +#if INCLUDE_JVMCI + _metadata_size = nm._metadata_size; +#endif + _nul_chk_table_offset = nm._nul_chk_table_offset; + _handler_table_offset = nm._handler_table_offset; + _scopes_pcs_offset = nm._scopes_pcs_offset; + _scopes_data_offset = nm._scopes_data_offset; +#if INCLUDE_JVMCI + _speculations_offset = nm._speculations_offset; +#endif + + _orig_pc_offset = nm._orig_pc_offset; + _compile_id = nm._compile_id; + _comp_level = nm._comp_level; + _compiler_type = nm._compiler_type; + _is_unloading_state = nm._is_unloading_state; + _state = not_installed; + + _has_unsafe_access = nm._has_unsafe_access; + _has_wide_vectors = nm._has_wide_vectors; + _has_monitors = nm._has_monitors; + _has_scoped_access = nm._has_scoped_access; + _has_flushed_dependencies = nm._has_flushed_dependencies; + _is_unlinked = nm._is_unlinked; + _load_reported = nm._load_reported; + + _deoptimization_status = nm._deoptimization_status; + + if (nm._pc_desc_container != nullptr) { + _pc_desc_container = new PcDescContainer(scopes_pcs_begin()); + } else { + _pc_desc_container = nullptr; + } + + // Copy nmethod contents excluding header + // - Constant part (doubles, longs and floats used in nmethod) + // - Code part: + // - Code body + // - Exception handler + // - Stub code + // - OOP table + memcpy(consts_begin(), nm.consts_begin(), nm.data_end() - nm.consts_begin()); + + post_init(); +} + +nmethod* nmethod::relocate(CodeBlobType code_blob_type) { + assert(NMethodRelocation, "must enable use of function"); + + // Locks required to be held by caller to ensure the nmethod + // is not modified or purged from code cache during relocation + assert_lock_strong(CodeCache_lock); + assert_lock_strong(Compile_lock); + assert(CompiledICLocker::is_safe(this), "mt unsafe call"); + + if (!is_relocatable()) { + return nullptr; + } + + run_nmethod_entry_barrier(); + nmethod* nm_copy = new (size(), code_blob_type) nmethod(*this); + + if (nm_copy == nullptr) { + return nullptr; + } + + // Fix relocation + RelocIterator iter(nm_copy); + CodeBuffer src(this); + CodeBuffer dst(nm_copy); + while (iter.next()) { +#ifdef USE_TRAMPOLINE_STUB_FIX_OWNER + // Direct calls may no longer be in range and the use of a trampoline may now be required. + // Instead, allow trampoline relocations to update their owners and perform the necessary checks. + if (iter.reloc()->is_call()) { + address trampoline = trampoline_stub_Relocation::get_trampoline_for(iter.reloc()->addr(), nm_copy); + if (trampoline != nullptr) { + continue; + } + } +#endif + + iter.reloc()->fix_relocation_after_move(&src, &dst); + } + + // To make dependency checking during class loading fast, record + // the nmethod dependencies in the classes it is dependent on. + // This allows the dependency checking code to simply walk the + // class hierarchy above the loaded class, checking only nmethods + // which are dependent on those classes. The slow way is to + // check every nmethod for dependencies which makes it linear in + // the number of methods compiled. For applications with a lot + // classes the slow way is too slow. + for (Dependencies::DepStream deps(nm_copy); deps.next(); ) { + if (deps.type() == Dependencies::call_site_target_value) { + // CallSite dependencies are managed on per-CallSite instance basis. + oop call_site = deps.argument_oop(0); + MethodHandles::add_dependent_nmethod(call_site, nm_copy); + } else { + InstanceKlass* ik = deps.context_type(); + if (ik == nullptr) { + continue; // ignore things like evol_method + } + // record this nmethod as dependent on this klass + ik->add_dependent_nmethod(nm_copy); + } + } + + MutexLocker ml_NMethodState_lock(NMethodState_lock, Mutex::_no_safepoint_check_flag); + + // Verify the nm we copied from is still valid + if (!is_marked_for_deoptimization() && is_in_use()) { + assert(method() != nullptr && method()->code() == this, "should be if is in use"); + + nm_copy->clear_inline_caches(); + + // Attempt to start using the copy + if (nm_copy->make_in_use()) { + ICache::invalidate_range(nm_copy->code_begin(), nm_copy->code_size()); + + methodHandle mh(Thread::current(), nm_copy->method()); + nm_copy->method()->set_code(mh, nm_copy); + + make_not_used(); + + nm_copy->post_compiled_method_load_event(); + + nm_copy->log_relocated_nmethod(this); + + return nm_copy; + } + } + + nm_copy->make_not_used(); + + return nullptr; +} + +bool nmethod::is_relocatable() { + if (!is_java_method()) { + return false; + } + + if (!is_in_use()) { + return false; + } + + if (is_osr_method()) { + return false; + } + + if (is_marked_for_deoptimization()) { + return false; + } + +#if INCLUDE_JVMCI + if (jvmci_nmethod_data() != nullptr && jvmci_nmethod_data()->has_mirror()) { + return false; + } +#endif + + if (is_unloading()) { + return false; + } + + if (has_evol_metadata()) { + return false; + } + + return true; +} + void* nmethod::operator new(size_t size, int nmethod_size, int comp_level) throw () { return CodeCache::allocate(nmethod_size, CodeCache::get_code_blob_type(comp_level)); } +void* nmethod::operator new(size_t size, int nmethod_size, CodeBlobType code_blob_type) throw () { + return CodeCache::allocate(nmethod_size, code_blob_type); +} + void* nmethod::operator new(size_t size, int nmethod_size, bool allow_NonNMethod_space) throw () { // Try MethodNonProfiled and MethodProfiled. void* return_value = CodeCache::allocate(nmethod_size, CodeBlobType::MethodNonProfiled); @@ -1494,9 +1751,9 @@ nmethod::nmethod( #if INCLUDE_JVMCI _speculations_offset = _scopes_data_offset + align_up(debug_info->data_size(), oopSize); - DEBUG_ONLY( int immutable_data_end_offset = _speculations_offset + align_up(speculations_len, oopSize); ) + DEBUG_ONLY( int immutable_data_end_offset = _speculations_offset + align_up(speculations_len, oopSize) + align_up(ImmutableDataReferencesCounterSize, oopSize); ) #else - DEBUG_ONLY( int immutable_data_end_offset = _scopes_data_offset + align_up(debug_info->data_size(), oopSize); ) + DEBUG_ONLY( int immutable_data_end_offset = _scopes_data_offset + align_up(debug_info->data_size(), oopSize) + align_up(ImmutableDataReferencesCounterSize, oopSize); ) #endif assert(immutable_data_end_offset <= immutable_data_size, "wrong read-only data size: %d > %d", immutable_data_end_offset, immutable_data_size); @@ -1529,6 +1786,7 @@ nmethod::nmethod( memcpy(speculations_begin(), speculations, speculations_len); } #endif + set_immutable_data_references_counter(1); post_init(); @@ -1595,6 +1853,40 @@ void nmethod::log_new_nmethod() const { } } + +void nmethod::log_relocated_nmethod(nmethod* original) const { + if (LogCompilation && xtty != nullptr) { + ttyLocker ttyl; + xtty->begin_elem("relocated nmethod"); + log_identity(xtty); + xtty->print(" entry='" INTPTR_FORMAT "' size='%d'", p2i(code_begin()), size()); + + const char* original_code_heap_name = CodeCache::get_code_heap_name(CodeCache::get_code_blob_type(original)); + xtty->print(" original_address='" INTPTR_FORMAT "'", p2i(original)); + xtty->print(" original_code_heap='%s'", original_code_heap_name); + + const char* new_code_heap_name = CodeCache::get_code_heap_name(CodeCache::get_code_blob_type(this)); + xtty->print(" new_address='" INTPTR_FORMAT "'", p2i(this)); + xtty->print(" new_code_heap='%s'", new_code_heap_name); + + LOG_OFFSET(xtty, relocation); + LOG_OFFSET(xtty, consts); + LOG_OFFSET(xtty, insts); + LOG_OFFSET(xtty, stub); + LOG_OFFSET(xtty, scopes_data); + LOG_OFFSET(xtty, scopes_pcs); + LOG_OFFSET(xtty, dependencies); + LOG_OFFSET(xtty, handler_table); + LOG_OFFSET(xtty, nul_chk_table); + LOG_OFFSET(xtty, oops); + LOG_OFFSET(xtty, metadata); + + xtty->method(method()); + xtty->stamp(); + xtty->end_elem(); + } +} + #undef LOG_OFFSET @@ -2127,9 +2419,18 @@ void nmethod::purge(bool unregister_nmethod) { delete[] _compiled_ic_data; if (_immutable_data != blob_end()) { - os::free(_immutable_data); + int reference_count = get_immutable_data_references_counter(); + assert(reference_count > 0, "immutable data has no references"); + + set_immutable_data_references_counter(reference_count - 1); + // Free memory if this is the last nmethod referencing immutable data + if (reference_count == 0) { + os::free(_immutable_data); + } + _immutable_data = blob_end(); // Valid not null address } + if (unregister_nmethod) { Universe::heap()->unregister_nmethod(this); } diff --git a/src/hotspot/share/code/nmethod.hpp b/src/hotspot/share/code/nmethod.hpp index 1e8766570984a..2332766a47ce2 100644 --- a/src/hotspot/share/code/nmethod.hpp +++ b/src/hotspot/share/code/nmethod.hpp @@ -154,6 +154,7 @@ class PcDescContainer : public CHeapObj { // - Scopes data array // - Scopes pcs array // - JVMCI speculations array +// - Nmethod reference counter #if INCLUDE_JVMCI class FailedSpeculation; @@ -167,6 +168,8 @@ class nmethod : public CodeBlob { friend class JVMCINMethodData; friend class DeoptimizationScope; + #define ImmutableDataReferencesCounterSize ((int)sizeof(int)) + private: // Used to track in which deoptimize handshake this method will be deoptimized. @@ -330,8 +333,11 @@ class nmethod : public CodeBlob { #endif ); + nmethod(const nmethod &nm); + // helper methods void* operator new(size_t size, int nmethod_size, int comp_level) throw(); + void* operator new(size_t size, int nmethod_size, CodeBlobType code_blob_type) throw(); // For method handle intrinsics: Try MethodNonProfiled, MethodProfiled and NonNMethod. // Attention: Only allow NonNMethod space for special nmethods which don't need to be @@ -564,6 +570,12 @@ class nmethod : public CodeBlob { #endif ); + // Relocate the nmethod to the code heap identified by code_blob_type. + // Returns nullptr if the code heap does not have enough space, the + // nmethod is unrelocatable, or the nmethod is invalidated during relocation, + // otherwise the relocated nmethod. The original nmethod will be marked not entrant. + nmethod* relocate(CodeBlobType code_blob_type); + static nmethod* new_native_nmethod(const methodHandle& method, int compile_id, CodeBuffer *code_buffer, @@ -580,6 +592,8 @@ class nmethod : public CodeBlob { bool is_java_method () const { return _method != nullptr && !_method->is_native(); } bool is_osr_method () const { return _entry_bci != InvocationEntryBci; } + bool is_relocatable(); + // Compiler task identification. Note that all OSR methods // are numbered in an independent sequence if CICountOSR is true, // and native method wrappers are also numbered independently if @@ -632,11 +646,13 @@ class nmethod : public CodeBlob { #if INCLUDE_JVMCI address scopes_data_end () const { return _immutable_data + _speculations_offset ; } address speculations_begin () const { return _immutable_data + _speculations_offset ; } - address speculations_end () const { return immutable_data_end(); } + address speculations_end () const { return immutable_data_end() - ImmutableDataReferencesCounterSize ; } #else - address scopes_data_end () const { return immutable_data_end(); } + address scopes_data_end () const { return immutable_data_end() - ImmutableDataReferencesCounterSize ; } #endif + address immutable_data_references_counter_begin () const { return immutable_data_end() - ImmutableDataReferencesCounterSize ; } + // Sizes int immutable_data_size() const { return _immutable_data_size; } int consts_size () const { return int( consts_end () - consts_begin ()); } @@ -946,6 +962,9 @@ class nmethod : public CodeBlob { bool load_reported() const { return _load_reported; } void set_load_reported() { _load_reported = true; } + inline int get_immutable_data_references_counter() { return *((int*)immutable_data_references_counter_begin()); } + inline void set_immutable_data_references_counter(int count) { *((int*)immutable_data_references_counter_begin()) = count; } + public: // ScopeDesc retrieval operation PcDesc* pc_desc_at(address pc) { return find_pc_desc(pc, false); } @@ -1014,6 +1033,7 @@ class nmethod : public CodeBlob { // Logging void log_identity(xmlStream* log) const; void log_new_nmethod() const; + void log_relocated_nmethod(nmethod* original) const; void log_state_change(InvalidationReason invalidation_reason) const; // Prints block-level comments, including nmethod specific block labels: diff --git a/src/hotspot/share/code/relocInfo.cpp b/src/hotspot/share/code/relocInfo.cpp index 8fc22596d01c3..02a1e5faf16a3 100644 --- a/src/hotspot/share/code/relocInfo.cpp +++ b/src/hotspot/share/code/relocInfo.cpp @@ -406,11 +406,12 @@ void CallRelocation::fix_relocation_after_move(const CodeBuffer* src, CodeBuffer pd_set_call_destination(callee); } - #ifdef USE_TRAMPOLINE_STUB_FIX_OWNER void trampoline_stub_Relocation::fix_relocation_after_move(const CodeBuffer* src, CodeBuffer* dest) { // Finalize owner destination only for nmethods if (dest->blob() != nullptr) return; + // We either relocate a nmethod residing in CodeCache or just generated code from CodeBuffer + assert(src->blob() == nullptr || nativeCall_at(owner())->raw_destination() == owner(), "destination should be empty"); pd_fix_owner_after_move(); } #endif diff --git a/src/hotspot/share/compiler/oopMap.cpp b/src/hotspot/share/compiler/oopMap.cpp index aa0108729755f..87467d0640082 100644 --- a/src/hotspot/share/compiler/oopMap.cpp +++ b/src/hotspot/share/compiler/oopMap.cpp @@ -862,6 +862,12 @@ ImmutableOopMapSet* ImmutableOopMapSet::build_from(const OopMapSet* oopmap_set) return builder.build(); } +ImmutableOopMapSet* ImmutableOopMapSet::clone() const { + address buffer = NEW_C_HEAP_ARRAY(unsigned char, _size, mtCode); + memcpy(buffer, (address)this, _size); + return (ImmutableOopMapSet*)buffer; +} + void ImmutableOopMapSet::operator delete(void* p) { FREE_C_HEAP_ARRAY(unsigned char, p); } diff --git a/src/hotspot/share/compiler/oopMap.hpp b/src/hotspot/share/compiler/oopMap.hpp index ef8845ac9aa40..f7a8cd8496cfe 100644 --- a/src/hotspot/share/compiler/oopMap.hpp +++ b/src/hotspot/share/compiler/oopMap.hpp @@ -348,6 +348,8 @@ class ImmutableOopMapSet { static ImmutableOopMapSet* build_from(const OopMapSet* oopmap_set); + ImmutableOopMapSet* clone() const; + int find_slot_for_offset(int pc_offset) const; const ImmutableOopMap* find_map_at_offset(int pc_offset) const; const ImmutableOopMap* find_map_at_slot(int slot, int pc_offset) const; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahUnload.cpp b/src/hotspot/share/gc/shenandoah/shenandoahUnload.cpp index 83151313f7565..b248fab7958ac 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahUnload.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahUnload.cpp @@ -103,7 +103,7 @@ class ShenandoahCompiledICProtectionBehaviour : public CompiledICProtectionBehav } virtual bool is_safe(nmethod* nm) { - if (SafepointSynchronize::is_at_safepoint() || nm->is_unloading()) { + if (SafepointSynchronize::is_at_safepoint() || nm->is_unloading() || (NMethodState_lock->owned_by_self() && nm->is_not_installed())) { return true; } diff --git a/src/hotspot/share/gc/z/zUnload.cpp b/src/hotspot/share/gc/z/zUnload.cpp index c8b32385fcdb1..5c50b3077dd65 100644 --- a/src/hotspot/share/gc/z/zUnload.cpp +++ b/src/hotspot/share/gc/z/zUnload.cpp @@ -100,7 +100,7 @@ class ZCompiledICProtectionBehaviour : public CompiledICProtectionBehaviour { } virtual bool is_safe(nmethod* nm) { - if (SafepointSynchronize::is_at_safepoint() || nm->is_unloading()) { + if (SafepointSynchronize::is_at_safepoint() || nm->is_unloading() || (NMethodState_lock->owned_by_self() && nm->is_not_installed())) { return true; } diff --git a/src/hotspot/share/jvmci/jvmciRuntime.hpp b/src/hotspot/share/jvmci/jvmciRuntime.hpp index f4c322e831cae..885ff0dbf9b0b 100644 --- a/src/hotspot/share/jvmci/jvmciRuntime.hpp +++ b/src/hotspot/share/jvmci/jvmciRuntime.hpp @@ -137,6 +137,11 @@ class JVMCINMethodData : public ResourceObj { // Gets the JVMCI name of the nmethod (which may be null). const char* name() { return has_name() ? (char*)(((address) this) + sizeof(JVMCINMethodData)) : nullptr; } + // Returns true if this nmethod has a mirror + bool has_mirror() const { + return _nmethod_mirror_index != -1; + } + // Clears the HotSpotNmethod.address field in the mirror. If nm // is dead, the HotSpotNmethod.entryPoint field is also cleared. void invalidate_nmethod_mirror(nmethod* nm, nmethod::InvalidationReason invalidation_reason); diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index f77b648ba952c..1ecd105f2181c 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -39,6 +39,7 @@ #include "classfile/systemDictionary.hpp" #include "classfile/vmSymbols.hpp" #include "code/codeCache.hpp" +#include "code/compiledIC.hpp" #include "compiler/compilationPolicy.hpp" #include "compiler/compilerOracle.hpp" #include "compiler/directivesParser.hpp" @@ -1548,19 +1549,23 @@ struct CodeBlobStub { name(os::strdup(blob->name())), size(blob->size()), blob_type(static_cast(WhiteBox::get_blob_type(blob))), - address((jlong) blob) { } + address((jlong) blob), + code_begin((jlong) blob->code_begin()), + is_nmethod((jboolean) blob->is_nmethod()) { } ~CodeBlobStub() { os::free((void*) name); } const char* const name; const jint size; const jint blob_type; const jlong address; + const jlong code_begin; + const jboolean is_nmethod; }; static jobjectArray codeBlob2objectArray(JavaThread* thread, JNIEnv* env, CodeBlobStub* cb) { ResourceMark rm; jclass clazz = env->FindClass(vmSymbols::java_lang_Object()->as_C_string()); CHECK_JNI_EXCEPTION_(env, nullptr); - jobjectArray result = env->NewObjectArray(4, clazz, nullptr); + jobjectArray result = env->NewObjectArray(6, clazz, nullptr); jstring name = env->NewStringUTF(cb->name); CHECK_JNI_EXCEPTION_(env, nullptr); @@ -1578,6 +1583,14 @@ static jobjectArray codeBlob2objectArray(JavaThread* thread, JNIEnv* env, CodeBl CHECK_JNI_EXCEPTION_(env, nullptr); env->SetObjectArrayElement(result, 3, obj); + obj = longBox(thread, env, cb->code_begin); + CHECK_JNI_EXCEPTION_(env, nullptr); + env->SetObjectArrayElement(result, 4, obj); + + obj = booleanBox(thread, env, cb->is_nmethod); + CHECK_JNI_EXCEPTION_(env, nullptr); + env->SetObjectArrayElement(result, 5, obj); + return result; } @@ -1627,6 +1640,44 @@ WB_ENTRY(jobjectArray, WB_GetNMethod(JNIEnv* env, jobject o, jobject method, jbo return result; WB_END +WB_ENTRY(void, WB_RelocateNMethodFromMethod(JNIEnv* env, jobject o, jobject method, jint blob_type)) + ResourceMark rm(THREAD); + jmethodID jmid = reflected_method_to_jmid(thread, env, method); + CHECK_JNI_EXCEPTION(env); + methodHandle mh(THREAD, Method::checked_resolve_jmethod_id(jmid)); + nmethod* code = mh->code(); + if (code != nullptr) { + MutexLocker ml_Compile_lock(Compile_lock); + CompiledICLocker ic_locker(code); + MutexLocker ml_CodeCache_lock(CodeCache_lock, Mutex::_no_safepoint_check_flag); + code->relocate(static_cast(blob_type)); + } +WB_END + +WB_ENTRY(void, WB_RelocateNMethodFromAddr(JNIEnv* env, jobject o, jlong addr, jint blob_type)) + ResourceMark rm(THREAD); + CHECK_JNI_EXCEPTION(env); + void* address = (void*) addr; + + if (address == nullptr) { + return; + } + + MutexLocker ml_Compile_lock(Compile_lock); + MutexLocker ml_CompiledIC_lock(CompiledIC_lock, Mutex::_no_safepoint_check_flag); + MutexLocker ml_CodeCache_lock(CodeCache_lock, Mutex::_no_safepoint_check_flag); + + // Verify that nmethod address is still valid + CodeBlob* blob = CodeCache::find_blob(address); + if (blob != nullptr && blob->is_nmethod()) { + nmethod* code = blob->as_nmethod(); + if (code->is_in_use()) { + CompiledICLocker ic_locker(code); + code->relocate(static_cast(blob_type)); + } + } +WB_END + CodeBlob* WhiteBox::allocate_code_blob(int size, CodeBlobType blob_type) { guarantee(WhiteBoxAPI, "internal testing API :: WhiteBox has to be enabled"); BufferBlob* blob; @@ -2916,6 +2967,9 @@ static JNINativeMethod methods[] = { {CC"getCPUFeatures", CC"()Ljava/lang/String;", (void*)&WB_GetCPUFeatures }, {CC"getNMethod0", CC"(Ljava/lang/reflect/Executable;Z)[Ljava/lang/Object;", (void*)&WB_GetNMethod }, + {CC"relocateNMethodFromMethod0", CC"(Ljava/lang/reflect/Executable;I)V", + (void*)&WB_RelocateNMethodFromMethod }, + {CC"relocateNMethodFromAddr", CC"(JI)V", (void*)&WB_RelocateNMethodFromAddr }, {CC"allocateCodeBlob", CC"(II)J", (void*)&WB_AllocateCodeBlob }, {CC"freeCodeBlob", CC"(J)V", (void*)&WB_FreeCodeBlob }, {CC"getCodeHeapEntries", CC"(I)[Ljava/lang/Object;",(void*)&WB_GetCodeHeapEntries }, diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index dac01d018bf3f..513edaf658816 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -1565,6 +1565,9 @@ const int ObjectAlignmentInBytes = 8; "Start aggressive sweeping if less than X[%] of the total code cache is free.")\ range(0, 100) \ \ + product(bool, NMethodRelocation, false, EXPERIMENTAL, \ + "Enables use of experimental function nmethod::relocate()") \ + \ /* interpreter debugging */ \ develop(intx, BinarySwitchThreshold, 5, \ "Minimal number of lookupswitch entries for rewriting to binary " \ diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/NMethod.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/NMethod.java index c8ba2e8b5afc3..939b47fdd2a83 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/NMethod.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/NMethod.java @@ -37,6 +37,7 @@ public class NMethod extends CodeBlob { private static long pcDescSize; + private static long immutableDataReferencesCounterSize; private static AddressField methodField; /** != InvocationEntryBci if this nmethod is an on-stack replacement method */ private static CIntegerField entryBCIField; @@ -78,24 +79,25 @@ public void update(Observable o, Object data) { private static void initialize(TypeDataBase db) { Type type = db.lookupType("nmethod"); - methodField = type.getAddressField("_method"); - entryBCIField = type.getCIntegerField("_entry_bci"); - osrLinkField = type.getAddressField("_osr_link"); - immutableDataField = type.getAddressField("_immutable_data"); - immutableDataSizeField = type.getCIntegerField("_immutable_data_size"); - exceptionOffsetField = type.getCIntegerField("_exception_offset"); - deoptHandlerOffsetField = type.getCIntegerField("_deopt_handler_offset"); - origPCOffsetField = type.getCIntegerField("_orig_pc_offset"); - stubOffsetField = type.getCIntegerField("_stub_offset"); - scopesPCsOffsetField = type.getCIntegerField("_scopes_pcs_offset"); - scopesDataOffsetField = type.getCIntegerField("_scopes_data_offset"); - handlerTableOffsetField = new CIntField(type.getCIntegerField("_handler_table_offset"), 0); - nulChkTableOffsetField = new CIntField(type.getCIntegerField("_nul_chk_table_offset"), 0); - entryOffsetField = new CIntField(type.getCIntegerField("_entry_offset"), 0); - verifiedEntryOffsetField = new CIntField(type.getCIntegerField("_verified_entry_offset"), 0); - osrEntryPointField = type.getAddressField("_osr_entry_point"); - compLevelField = new CIntField(type.getCIntegerField("_comp_level"), 0); - pcDescSize = db.lookupType("PcDesc").getSize(); + methodField = type.getAddressField("_method"); + entryBCIField = type.getCIntegerField("_entry_bci"); + osrLinkField = type.getAddressField("_osr_link"); + immutableDataField = type.getAddressField("_immutable_data"); + immutableDataSizeField = type.getCIntegerField("_immutable_data_size"); + exceptionOffsetField = type.getCIntegerField("_exception_offset"); + deoptHandlerOffsetField = type.getCIntegerField("_deopt_handler_offset"); + origPCOffsetField = type.getCIntegerField("_orig_pc_offset"); + stubOffsetField = type.getCIntegerField("_stub_offset"); + scopesPCsOffsetField = type.getCIntegerField("_scopes_pcs_offset"); + scopesDataOffsetField = type.getCIntegerField("_scopes_data_offset"); + handlerTableOffsetField = new CIntField(type.getCIntegerField("_handler_table_offset"), 0); + nulChkTableOffsetField = new CIntField(type.getCIntegerField("_nul_chk_table_offset"), 0); + entryOffsetField = new CIntField(type.getCIntegerField("_entry_offset"), 0); + verifiedEntryOffsetField = new CIntField(type.getCIntegerField("_verified_entry_offset"), 0); + osrEntryPointField = type.getAddressField("_osr_entry_point"); + compLevelField = new CIntField(type.getCIntegerField("_comp_level"), 0); + pcDescSize = db.lookupType("PcDesc").getSize(); + immutableDataReferencesCounterSize = VM.getVM().getIntSize(); } public NMethod(Address addr) { @@ -139,7 +141,7 @@ public Method getMethod() { public Address scopesDataBegin() { return immutableDataBegin().addOffsetTo(getScopesDataOffset()); } public Address scopesDataEnd() { return immutableDataBegin().addOffsetTo(getScopesPCsOffset()); } public Address scopesPCsBegin() { return immutableDataBegin().addOffsetTo(getScopesPCsOffset()); } - public Address scopesPCsEnd() { return immutableDataEnd(); } + public Address scopesPCsEnd() { return immutableDataEnd().addOffsetTo(-immutableDataReferencesCounterSize); } public Address metadataBegin() { return mutableDataBegin().addOffsetTo(getRelocationSize()); } public Address metadataEnd() { return mutableDataEnd(); } @@ -169,7 +171,8 @@ public int immutableDataSize() { scopesPCsSize() + dependenciesSize() + handlerTableSize() + - nulChkTableSize(); + nulChkTableSize() + + (int) immutableDataReferencesCounterSize; } public boolean constantsContains (Address addr) { return constantsBegin() .lessThanOrEqual(addr) && constantsEnd() .greaterThan(addr); } diff --git a/test/hotspot/jtreg/compiler/whitebox/CompilerWhiteBoxTest.java b/test/hotspot/jtreg/compiler/whitebox/CompilerWhiteBoxTest.java index f87292be019c0..eb0f70af5c462 100644 --- a/test/hotspot/jtreg/compiler/whitebox/CompilerWhiteBoxTest.java +++ b/test/hotspot/jtreg/compiler/whitebox/CompilerWhiteBoxTest.java @@ -221,15 +221,28 @@ protected final void checkNotCompiled() { * compilation level. */ protected final void checkNotCompiled(boolean isOsr) { - if (WHITE_BOX.isMethodQueuedForCompilation(method)) { - throw new RuntimeException(method + " must not be in queue"); + checkNotCompiled(method, isOsr); + } + + /** + * Checks, that the specified executable is not (OSR-)compiled. + * + * @param executable The method or constructor to check. + * @param isOsr Check for OSR compilation if true + * @throws RuntimeException if {@linkplain #method} is in compiler queue or + * is compiled, or if {@linkplain #method} has zero + * compilation level. + */ + protected static final void checkNotCompiled(Executable executable, boolean isOsr) { + if (WHITE_BOX.isMethodQueuedForCompilation(executable)) { + throw new RuntimeException(executable + " must not be in queue"); } - if (WHITE_BOX.isMethodCompiled(method, isOsr)) { - throw new RuntimeException(method + " must not be " + + if (WHITE_BOX.isMethodCompiled(executable, isOsr)) { + throw new RuntimeException(executable + " must not be " + (isOsr ? "osr_" : "") + "compiled"); } - if (WHITE_BOX.getMethodCompilationLevel(method, isOsr) != 0) { - throw new RuntimeException(method + (isOsr ? " osr_" : " ") + + if (WHITE_BOX.getMethodCompilationLevel(executable, isOsr) != 0) { + throw new RuntimeException(executable + (isOsr ? " osr_" : " ") + "comp_level must be == 0"); } } @@ -242,21 +255,34 @@ protected final void checkNotCompiled(boolean isOsr) { * has nonzero compilation level */ protected final void checkCompiled() { + checkCompiled(method, testCase.isOsr()); + } + + /** + * Checks, that the specified executable is compiled. + * + * @param executable The method or constructor to check. + * @param isOsr Check for OSR compilation if true + * @throws RuntimeException if {@linkplain #method} isn't in compiler queue + * and isn't compiled, or if {@linkplain #method} + * has nonzero compilation level + */ + protected static final void checkCompiled(Executable executable, boolean isOsr) { final long start = System.currentTimeMillis(); - waitBackgroundCompilation(); - if (WHITE_BOX.isMethodQueuedForCompilation(method)) { + waitBackgroundCompilation(executable); + if (WHITE_BOX.isMethodQueuedForCompilation(executable)) { System.err.printf("Warning: %s is still in queue after %dms%n", - method, System.currentTimeMillis() - start); + executable, System.currentTimeMillis() - start); return; } - if (!WHITE_BOX.isMethodCompiled(method, testCase.isOsr())) { - throw new RuntimeException(method + " must be " - + (testCase.isOsr() ? "osr_" : "") + "compiled"); + if (!WHITE_BOX.isMethodCompiled(executable, isOsr)) { + throw new RuntimeException(executable + " must be " + + (isOsr ? "osr_" : "") + "compiled"); } - if (WHITE_BOX.getMethodCompilationLevel(method, testCase.isOsr()) + if (WHITE_BOX.getMethodCompilationLevel(executable, isOsr) == 0) { - throw new RuntimeException(method - + (testCase.isOsr() ? " osr_" : " ") + throw new RuntimeException(executable + + (isOsr ? " osr_" : " ") + "comp_level must be != 0"); } } diff --git a/test/hotspot/jtreg/compiler/whitebox/DeoptimizeRelocatedNMethod.java b/test/hotspot/jtreg/compiler/whitebox/DeoptimizeRelocatedNMethod.java new file mode 100644 index 0000000000000..25314051fdd46 --- /dev/null +++ b/test/hotspot/jtreg/compiler/whitebox/DeoptimizeRelocatedNMethod.java @@ -0,0 +1,157 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test id=Serial + * @bug 8316694 + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * @requires vm.opt.DeoptimizeALot != true + * @requires vm.gc.Serial + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache -XX:+UseSerialGC + * -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation compiler.whitebox.DeoptimizeRelocatedNMethod + */ + +/* + * @test id=Parallel + * @bug 8316694 + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * @requires vm.opt.DeoptimizeALot != true + * @requires vm.gc.Parallel + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache -XX:+UseParallelGC + * -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation compiler.whitebox.DeoptimizeRelocatedNMethod + */ + +/* + * @test id=G1 + * @bug 8316694 + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * @requires vm.opt.DeoptimizeALot != true + * @requires vm.gc.G1 + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache -XX:+UseG1GC + * -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation compiler.whitebox.DeoptimizeRelocatedNMethod + */ + +/* + * @test id=Shenandoah + * @bug 8316694 + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * @requires vm.opt.DeoptimizeALot != true + * @requires vm.gc.Shenandoah + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache -XX:+UseShenandoahGC + * -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation compiler.whitebox.DeoptimizeRelocatedNMethod + */ + +/* + * @test id=ZGC + * @bug 8316694 + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * @requires vm.opt.DeoptimizeALot != true + * @requires vm.gc.Z + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache -XX:+UseZGC + * -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation compiler.whitebox.DeoptimizeRelocatedNMethod + */ + +package compiler.whitebox; + +import compiler.whitebox.CompilerWhiteBoxTest; +import java.lang.reflect.Method; +import jdk.test.whitebox.WhiteBox; +import jdk.test.whitebox.code.BlobType; +import jdk.test.whitebox.code.NMethod; + +public class DeoptimizeRelocatedNMethod { + + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + public static double FUNCTION_RESULT = 0; + + public static void main(String [] args) throws Exception { + // Get method that will be relocated + Method method = DeoptimizeRelocatedNMethod.class.getMethod("function"); + WHITE_BOX.testSetDontInlineMethod(method, true); + + // Verify not initially compiled + CompilerWhiteBoxTest.checkNotCompiled(method, false); + + // Call function enough to compile + callFunction(); + + // Verify now compiled + CompilerWhiteBoxTest.checkCompiled(method, false); + + // Get newly created nmethod + NMethod origNmethod = NMethod.get(method, false); + + // Relocate nmethod and mark old for cleanup + WHITE_BOX.relocateNMethodFromMethod(method, BlobType.MethodProfiled.id); + + // Trigger GC to clean up old nmethod + WHITE_BOX.fullGC(); + + // Verify function still compiled after old was cleaned up + CompilerWhiteBoxTest.checkCompiled(method, false); + + // Get new nmethod and verify it's actually new + NMethod newNmethod = NMethod.get(method, false); + if (origNmethod.entry_point == newNmethod.entry_point) { + throw new RuntimeException("Did not create new nmethod"); + } + + // Call to verify everything still works + function(); + + // Deoptimized method + WHITE_BOX.deoptimizeMethod(method); + + CompilerWhiteBoxTest.checkNotCompiled(method, false); + + // Call to verify everything still works + function(); + } + + // Call function multiple times to trigger compilation + private static void callFunction() { + for (int i = 0; i < CompilerWhiteBoxTest.THRESHOLD; i++) { + function(); + } + } + + public static void function() { + FUNCTION_RESULT = Math.random(); + } +} diff --git a/test/hotspot/jtreg/compiler/whitebox/RelocateNMethod.java b/test/hotspot/jtreg/compiler/whitebox/RelocateNMethod.java new file mode 100644 index 0000000000000..c18a8afa40036 --- /dev/null +++ b/test/hotspot/jtreg/compiler/whitebox/RelocateNMethod.java @@ -0,0 +1,146 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test id=Serial + * @bug 8316694 + * @summary test that nmethod::relocate() correctly creates a new nmethod + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @requires vm.opt.DeoptimizeALot != true + * @requires vm.gc.Serial + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache + * -XX:+UseSerialGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation compiler.whitebox.RelocateNMethod + */ + +/* + * @test id=Parallel + * @bug 8316694 + * @summary test that nmethod::relocate() correctly creates a new nmethod + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @requires vm.opt.DeoptimizeALot != true + * @requires vm.gc.Parallel + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache + * -XX:+UseParallelGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation compiler.whitebox.RelocateNMethod + */ + +/* + * @test id=G1 + * @bug 8316694 + * @summary test that nmethod::relocate() correctly creates a new nmethod + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @requires vm.opt.DeoptimizeALot != true + * @requires vm.gc.G1 + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache + * -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation compiler.whitebox.RelocateNMethod + */ + +/* + * @test id=Shenandoah + * @bug 8316694 + * @summary test that nmethod::relocate() correctly creates a new nmethod + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @requires vm.opt.DeoptimizeALot != true + * @requires vm.gc.Shenandoah + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache + * -XX:+UseShenandoahGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation compiler.whitebox.RelocateNMethod + */ + +/* + * @test id=ZGC + * @bug 8316694 + * @summary test that nmethod::relocate() correctly creates a new nmethod + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @requires vm.opt.DeoptimizeALot != true + * @requires vm.gc.Z + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache + * -XX:+UseZGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation compiler.whitebox.RelocateNMethod + */ + +package compiler.whitebox; + +import java.lang.reflect.Method; +import jdk.test.whitebox.code.BlobType; +import jdk.test.whitebox.code.NMethod; +import jdk.test.whitebox.WhiteBox; + +import compiler.whitebox.CompilerWhiteBoxTest; + +public class RelocateNMethod extends CompilerWhiteBoxTest { + + public static void main(String[] args) throws Exception { + CompilerWhiteBoxTest.main(RelocateNMethod::new, new String[] {"CONSTRUCTOR_TEST", "METHOD_TEST", "STATIC_TEST"}); + } + + private RelocateNMethod(TestCase testCase) { + super(testCase); + // to prevent inlining of #method + WHITE_BOX.testSetDontInlineMethod(method, true); + } + + @Override + protected void test() throws Exception { + checkNotCompiled(); + + compile(); + + checkCompiled(); + NMethod origNmethod = NMethod.get(method, false); + + WHITE_BOX.relocateNMethodFromMethod(method, BlobType.MethodProfiled.id); + + WHITE_BOX.fullGC(); + + checkCompiled(); + + NMethod newNmethod = NMethod.get(method, false); + if (origNmethod.entry_point == newNmethod.entry_point) { + throw new RuntimeException("Did not create new nmethod"); + } + } +} diff --git a/test/hotspot/jtreg/compiler/whitebox/RelocateNMethodMultiplePaths.java b/test/hotspot/jtreg/compiler/whitebox/RelocateNMethodMultiplePaths.java new file mode 100644 index 0000000000000..49be3eff8c247 --- /dev/null +++ b/test/hotspot/jtreg/compiler/whitebox/RelocateNMethodMultiplePaths.java @@ -0,0 +1,261 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test id=SerialC1 + * @bug 8316694 + * @requires vm.debug == true + * @requires vm.gc.Serial + * @summary test that relocated nmethod is correctly deoptimized + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * -XX:+SegmentedCodeCache -XX:-DeoptimizeRandom -XX:+DeoptimizeALot -XX:+UseSerialGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation + * compiler.whitebox.RelocateNMethodMultiplePaths + */ + +/* + * @test id=SerialC2 + * @bug 8316694 + * @requires vm.debug == true + * @requires vm.gc.Serial + * @summary test that relocated nmethod is correctly deoptimized + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+TieredCompilation + * -XX:+SegmentedCodeCache -XX:-DeoptimizeRandom -XX:+DeoptimizeALot -XX:+UseSerialGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation + * compiler.whitebox.RelocateNMethodMultiplePaths + */ + +/* + * @test id=ParallelC1 + * @bug 8316694 + * @requires vm.debug == true + * @requires vm.gc.Parallel + * @summary test that relocated nmethod is correctly deoptimized + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * -XX:+SegmentedCodeCache -XX:-DeoptimizeRandom -XX:+DeoptimizeALot -XX:+UseParallelGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation + * compiler.whitebox.RelocateNMethodMultiplePaths + */ + +/* + * @test id=ParallelC2 + * @bug 8316694 + * @requires vm.debug == true + * @requires vm.gc.Parallel + * @summary test that relocated nmethod is correctly deoptimized + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+TieredCompilation + * -XX:+SegmentedCodeCache -XX:-DeoptimizeRandom -XX:+DeoptimizeALot -XX:+UseParallelGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation + * compiler.whitebox.RelocateNMethodMultiplePaths + */ + +/* + * @test id=G1C1 + * @bug 8316694 + * @requires vm.debug == true + * @requires vm.gc.G1 + * @summary test that relocated nmethod is correctly deoptimized + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * -XX:+SegmentedCodeCache -XX:-DeoptimizeRandom -XX:+DeoptimizeALot -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation + * compiler.whitebox.RelocateNMethodMultiplePaths + */ + +/* + * @test id=G1C2 + * @bug 8316694 + * @requires vm.debug == true + * @requires vm.gc.G1 + * @summary test that relocated nmethod is correctly deoptimized + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+TieredCompilation + * -XX:+SegmentedCodeCache -XX:-DeoptimizeRandom -XX:+DeoptimizeALot -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation + * compiler.whitebox.RelocateNMethodMultiplePaths + */ + +/* + * @test id=ShenandoahC1 + * @bug 8316694 + * @requires vm.debug == true + * @requires vm.gc.Shenandoah + * @summary test that relocated nmethod is correctly deoptimized + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * -XX:+SegmentedCodeCache -XX:-DeoptimizeRandom -XX:+DeoptimizeALot -XX:+UseShenandoahGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation + * compiler.whitebox.RelocateNMethodMultiplePaths + */ + +/* + * @test id=ShenandoahC2 + * @bug 8316694 + * @requires vm.debug == true + * @requires vm.gc.Shenandoah + * @summary test that relocated nmethod is correctly deoptimized + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+TieredCompilation + * -XX:+SegmentedCodeCache -XX:-DeoptimizeRandom -XX:+DeoptimizeALot -XX:+UseShenandoahGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation + * compiler.whitebox.RelocateNMethodMultiplePaths + */ + +/* + * @test id=ZGCC1 + * @bug 8316694 + * @requires vm.debug == true + * @requires vm.gc.Z + * @summary test that relocated nmethod is correctly deoptimized + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * -XX:+SegmentedCodeCache -XX:-DeoptimizeRandom -XX:+DeoptimizeALot -XX:+UseZGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation + * compiler.whitebox.RelocateNMethodMultiplePaths + */ + +/* + * @test id=ZGCC2 + * @bug 8316694 + * @requires vm.debug == true + * @requires vm.gc.Z + * @summary test that relocated nmethod is correctly deoptimized + * @library /test/lib / + * @modules java.base/jdk.internal.misc java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+TieredCompilation + * -XX:+SegmentedCodeCache -XX:-DeoptimizeRandom -XX:+DeoptimizeALot -XX:+UseZGC -XX:+UnlockExperimentalVMOptions -XX:+NMethodRelocation + * compiler.whitebox.RelocateNMethodMultiplePaths + */ + +package compiler.whitebox; + +import compiler.whitebox.CompilerWhiteBoxTest; +import java.lang.reflect.Method; +import jdk.test.whitebox.WhiteBox; +import jdk.test.whitebox.code.BlobType; +import jdk.test.whitebox.code.NMethod; + +public class RelocateNMethodMultiplePaths { + + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + private static final int PATH_ONE_RESULT = 1; + private static final int PATH_TWO_RESULT = 2; + + public static void main(String [] args) throws Exception { + // Get method that will be relocated + Method method = RelocateNMethodMultiplePaths.class.getMethod("function", boolean.class); + WHITE_BOX.testSetDontInlineMethod(method, true); + + // Verify not initially compiled + CompilerWhiteBoxTest.checkNotCompiled(method, false); + + // Call function enough to compile + callFunction(true); + + // Verify now compiled + CompilerWhiteBoxTest.checkCompiled(method, false); + + // Get newly created nmethod + NMethod origNmethod = NMethod.get(method, false); + + // Relocate nmethod and mark old for cleanup + WHITE_BOX.relocateNMethodFromMethod(method, BlobType.MethodNonProfiled.id); + + // Trigger GC to clean up old nmethod + WHITE_BOX.fullGC(); + + // Verify function still compiled after old was cleaned up + CompilerWhiteBoxTest.checkCompiled(method, false); + + // Get new nmethod and verify it's actually new + NMethod newNmethod = NMethod.get(method, false); + if (origNmethod.entry_point == newNmethod.entry_point) { + throw new RuntimeException("Did not create new nmethod"); + } + + // Verify function still produces correct result + if (function(true) != PATH_ONE_RESULT) { + throw new RuntimeException("Relocated function produced incorrect result in path one"); + } + + // Call function again with different path and verify result + if (function(false) != PATH_TWO_RESULT) { + throw new RuntimeException("Relocated function produced incorrect result in path two"); + } + + // Verify function can be correctly deoptimized + WHITE_BOX.deoptimizeMethod(method); + CompilerWhiteBoxTest.checkNotCompiled(method, false); + } + + // Call function multiple times to trigger compilation + private static void callFunction(boolean pathOne) { + for (int i = 0; i < CompilerWhiteBoxTest.THRESHOLD; i++) { + function(pathOne); + } + } + + public static int function(boolean pathOne) { + if (pathOne) { + return PATH_ONE_RESULT; + } else { + return PATH_TWO_RESULT; + } + } +} diff --git a/test/hotspot/jtreg/compiler/whitebox/StressNMethodRelocation.java b/test/hotspot/jtreg/compiler/whitebox/StressNMethodRelocation.java new file mode 100644 index 0000000000000..3b397f4830602 --- /dev/null +++ b/test/hotspot/jtreg/compiler/whitebox/StressNMethodRelocation.java @@ -0,0 +1,239 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test StressNMethodRelocation + * @summary Call and relocate methods concurrently + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+SegmentedCodeCache -XX:+UnlockExperimentalVMOptions + * -XX:+NMethodRelocation compiler.whitebox.StressNMethodRelocation + */ + +package compiler.whitebox; + +import jdk.test.whitebox.WhiteBox; +import jdk.test.whitebox.code.BlobType; +import jdk.test.whitebox.code.CodeBlob; +import jdk.test.whitebox.code.NMethod; + +import jdk.test.lib.compiler.InMemoryJavaCompiler; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Random; + +public class StressNMethodRelocation { + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + private static final int C2_LEVEL = 4; + private static final int ACTIVE_METHODS = 1024; + + private static TestMethod[] methods; + private static byte[] num1; + private static byte[] num2; + + private static long DURATION = 60_000; + + public static void main(String[] args) throws Exception { + // Initialize defaults + initNums(); + + // Generate compiled code + methods = new TestMethod[ACTIVE_METHODS]; + generateCode(methods); + + // Create thread that runs compiled methods + RunMethods runMethods = new RunMethods(); + Thread runMethodsThread = new Thread(runMethods); + + // Create thread that relocates compiled methods + RelocateNMethods relocate = new RelocateNMethods(); + Thread relocateThread = new Thread(relocate); + + // Start theads + runMethodsThread.start(); + relocateThread.start(); + + // Wait for threads to finish + runMethodsThread.join(); + relocateThread.join(); + } + + private static byte[] genNum(Random random, int digitCount) { + byte[] num = new byte[digitCount]; + int d; + do { + d = random.nextInt(10); + } while (d == 0); + + num[0] = (byte)d; + for (int i = 1; i < digitCount; ++i) { + num[i] = (byte)random.nextInt(10); + } + return num; + } + + private static void initNums() { + final long seed = 8374592837465123L; + Random random = new Random(seed); + + final int digitCount = 40; + num1 = genNum(random, digitCount); + num2 = genNum(random, digitCount); + } + + private static void generateCode(TestMethod[] m) throws Exception { + byte[] result = new byte[num1.length + 1]; + + for (int i = 0; i < ACTIVE_METHODS; ++i) { + m[i] = new TestMethod(); + m[i].profile(num1, num2, result); + m[i].compileWithC2(); + } + } + + private static final class TestMethod { + private static final String CLASS_NAME = "A"; + private static final String METHOD_TO_COMPILE = "sum"; + private static final String JAVA_CODE = """ + public class A { + + public static void sum(byte[] n1, byte[] n2, byte[] out) { + final int digitCount = n1.length; + int carry = 0; + for (int i = digitCount - 1; i >= 0; --i) { + int sum = n1[i] + n2[i] + carry; + out[i] = (byte)(sum % 10); + carry = sum / 10; + } + if (carry != 0) { + for (int i = digitCount; i > 0; --i) { + out[i] = out[i - 1]; + } + out[0] = (byte)carry; + } + } + }"""; + + private static final byte[] BYTE_CODE; + + static { + BYTE_CODE = InMemoryJavaCompiler.compile(CLASS_NAME, JAVA_CODE); + } + + private final Method method; + + private static ClassLoader createClassLoaderFor() { + return new ClassLoader() { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (!name.equals(CLASS_NAME)) { + return super.loadClass(name); + } + + return defineClass(name, BYTE_CODE, 0, BYTE_CODE.length); + } + }; + } + + public TestMethod() throws Exception { + var cl = createClassLoaderFor().loadClass(CLASS_NAME); + method = cl.getMethod(METHOD_TO_COMPILE, byte[].class, byte[].class, byte[].class); + WHITE_BOX.testSetDontInlineMethod(method, true); + } + + public void profile(byte[] num1, byte[] num2, byte[] result) throws Exception { + method.invoke(null, num1, num2, result); + WHITE_BOX.markMethodProfiled(method); + } + + public void invoke(byte[] num1, byte[] num2, byte[] result) throws Exception { + method.invoke(null, num1, num2, result); + } + + public void compileWithC2() throws Exception { + WHITE_BOX.enqueueMethodForCompilation(method, C2_LEVEL); + while (WHITE_BOX.isMethodQueuedForCompilation(method)) { + Thread.onSpinWait(); + } + if (WHITE_BOX.getMethodCompilationLevel(method) != C2_LEVEL) { + throw new IllegalStateException("Method " + method + " is not compiled by C2."); + } + } + } + + private static final class RelocateNMethods implements Runnable { + public RelocateNMethods() {} + + // Move nmethod back and forth between NonProfiled and Profiled code heaps + public void run() { + long startTime = System.currentTimeMillis(); + while (System.currentTimeMillis() - startTime < DURATION) { + // Relocate NonProfiled to Profiled + CodeBlob[] nonProfiledBlobs = CodeBlob.getCodeBlobs(BlobType.MethodNonProfiled); + for (CodeBlob blob : nonProfiledBlobs) { + if (blob.isNMethod) { + WHITE_BOX.relocateNMethodFromAddr(blob.address, BlobType.MethodProfiled.id); + } + } + + // Relocate Profiled to NonProfiled + CodeBlob[] profiledBlobs = CodeBlob.getCodeBlobs(BlobType.MethodProfiled); + for (CodeBlob blob : nonProfiledBlobs) { + if (blob.isNMethod) { + WHITE_BOX.relocateNMethodFromAddr(blob.address, BlobType.MethodNonProfiled.id); + } + } + } + } + } + + private static final class RunMethods implements Runnable { + public RunMethods() {} + + public void run() { + try { + long startTime = System.currentTimeMillis(); + while (System.currentTimeMillis() - startTime < DURATION) { + callMethods(); + } + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + } + + private void callMethods() throws Exception { + for (var m : methods) { + byte[] result = new byte[num1.length + 1]; + m.invoke(num1, num2, result); + } + } + } + +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/NMethodRelocation/NMethodRelocationTest.java b/test/hotspot/jtreg/serviceability/jvmti/NMethodRelocation/NMethodRelocationTest.java new file mode 100644 index 0000000000000..b12e2c3455c4e --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/NMethodRelocation/NMethodRelocationTest.java @@ -0,0 +1,178 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * + * @bug 8316694 + * @summary Verify that nmethod relocation posts the correct JVMTI events + * @requires vm.jvmti + * @library /test/lib /test/hotspot/jtreg + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/native NMethodRelocationTest + */ + +import static compiler.whitebox.CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION; + +import java.lang.reflect.Executable; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.whitebox.WhiteBox; +import jdk.test.whitebox.code.BlobType; +import jdk.test.whitebox.code.NMethod; + + +public class NMethodRelocationTest { + public static void main(String[] args) throws Exception { + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + "-agentlib:NMethodRelocationTest", + "--enable-native-access=ALL-UNNAMED", + "-Xbootclasspath/a:.", + "-XX:+UseSerialGC", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "-XX:+SegmentedCodeCache", + "-XX:-TieredCompilation", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+NMethodRelocation", + "DoWork"); + + OutputAnalyzer oa = new OutputAnalyzer(pb.start()); + String output = oa.getOutput(); + if (oa.getExitValue() != 0) { + System.err.println(oa.getOutput()); + throw new RuntimeException("Non-zero exit code returned from the test"); + } + Asserts.assertTrue(oa.getExitValue() == 0); + + Pattern pattern = Pattern.compile("(?m)^Relocated nmethod from (0x[0-9a-f]{16}) to (0x[0-9a-f]{16})$"); + Matcher matcher = pattern.matcher(output); + + if (matcher.find()) { + String fromAddr = matcher.group(1); + String toAddr = matcher.group(2); + + // Confirm events sent for both original and relocated nmethod + oa.shouldContain(": name: compiledMethod, code: " + fromAddr); + oa.shouldContain(": name: compiledMethod, code: " + toAddr); + oa.shouldContain(": name: compiledMethod, code: " + fromAddr); + oa.shouldContain(": name: compiledMethod, code: " + toAddr); + } else { + System.err.println(oa.getOutput()); + throw new RuntimeException("Unable to find relocation information"); + } + } +} + +class DoWork { + + protected static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + /** Load native library if required. */ + static { + try { + System.loadLibrary("NMethodRelocationTest"); + } catch (UnsatisfiedLinkError ule) { + System.err.println("Could not load NMethodRelocationTest library"); + System.err.println("java.library.path: " + + System.getProperty("java.library.path")); + throw ule; + } + } + + /** + * Returns value of VM option. + * + * @param name option's name + * @return value of option or {@code null}, if option doesn't exist + * @throws NullPointerException if name is null + */ + protected static String getVMOption(String name) { + Objects.requireNonNull(name); + return Objects.toString(WHITE_BOX.getVMFlag(name), null); + } + + /** + * Returns value of VM option or default value. + * + * @param name option's name + * @param defaultValue default value + * @return value of option or {@code defaultValue}, if option doesn't exist + * @throws NullPointerException if name is null + * @see #getVMOption(String) + */ + protected static String getVMOption(String name, String defaultValue) { + String result = getVMOption(name); + return result == null ? defaultValue : result; + } + + public static void main(String argv[]) throws Exception { + run(); + } + + public static void run() throws Exception { + Executable method = DoWork.class.getDeclaredMethod("compiledMethod"); + WHITE_BOX.testSetDontInlineMethod(method, true); + + WHITE_BOX.enqueueMethodForCompilation(method, COMP_LEVEL_FULL_OPTIMIZATION); + while (WHITE_BOX.isMethodQueuedForCompilation(method)) { + Thread.onSpinWait(); + } + + NMethod originalNMethod = NMethod.get(method, false); + if (originalNMethod == null) { + throw new AssertionError("Could not find original nmethod"); + } + + WHITE_BOX.relocateNMethodFromMethod(method, BlobType.MethodNonProfiled.id); + + NMethod relocatedNMethod = NMethod.get(method, false); + if (relocatedNMethod == null) { + throw new AssertionError("Could not find relocated nmethod"); + } + + if (originalNMethod.address == relocatedNMethod.address) { + throw new AssertionError("Relocated nmethod same as original"); + } + + WHITE_BOX.deoptimizeAll(); + + WHITE_BOX.fullGC(); + WHITE_BOX.fullGC(); + + WHITE_BOX.lockCompilation(); + + System.out.printf("Relocated nmethod from 0x%016x to 0x%016x%n", originalNMethod.code_begin, relocatedNMethod.code_begin); + System.out.flush(); + } + + public static long compiledMethod() { + return 0; + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/NMethodRelocation/libNMethodRelocationTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/NMethodRelocation/libNMethodRelocationTest.cpp new file mode 100644 index 0000000000000..41ba6b1060859 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/NMethodRelocation/libNMethodRelocationTest.cpp @@ -0,0 +1,114 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include + +/** + * Callback for COMPILED_METHOD_LOAD event. + */ +JNIEXPORT void JNICALL +callbackCompiledMethodLoad(jvmtiEnv* jvmti, jmethodID method, + jint code_size, const void* code_addr, + jint map_length, const jvmtiAddrLocationMap* map, + const void* compile_info) { + char* name = nullptr; + char* sig = nullptr; + + if (jvmti->GetMethodName(method, &name, &sig, nullptr) != JVMTI_ERROR_NONE) { + printf(" [Could not retrieve method name]\n"); + fflush(stdout); + return; + } + + printf(": name: %s, code: 0x%016" PRIxPTR "\n", + name, (uintptr_t)code_addr); + fflush(stdout); +} + +/** + * Callback for COMPILED_METHOD_UNLOAD event. + */ +JNIEXPORT void JNICALL +callbackCompiledMethodUnload(jvmtiEnv* jvmti, jmethodID method, + const void* code_addr) { + char* name = nullptr; + char* sig = nullptr; + + if (jvmti->GetMethodName(method, &name, &sig, nullptr) != JVMTI_ERROR_NONE) { + printf(" [Could not retrieve method name]\n"); + fflush(stdout); + return; + } + printf(": name: %s, code: 0x%016" PRIxPTR "\n", + name, (uintptr_t)code_addr); + fflush(stdout); +} + +JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + jvmtiEnv* jvmti = nullptr; + jvmtiError error; + + if (jvm->GetEnv((void **)&jvmti, JVMTI_VERSION_1_0) != JNI_OK) { + printf("Unable to access JVMTI!\n"); + return JNI_ERR; + } + + // Add required capabilities + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_generate_compiled_method_load_events = 1; + error = jvmti->AddCapabilities(&caps); + if (error != JVMTI_ERROR_NONE) { + printf("ERROR: Unable to add capabilities, error=%d\n", error); + return JNI_ERR; + } + + // Set event callbacks + jvmtiEventCallbacks eventCallbacks; + memset(&eventCallbacks, 0, sizeof(eventCallbacks)); + eventCallbacks.CompiledMethodLoad = callbackCompiledMethodLoad; + eventCallbacks.CompiledMethodUnload = callbackCompiledMethodUnload; + error = jvmti->SetEventCallbacks(&eventCallbacks, sizeof(eventCallbacks)); + if (error != JVMTI_ERROR_NONE) { + printf("ERROR: Unable to set event callbacks, error=%d\n", error); + return JNI_ERR; + } + + // Enable events + error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_LOAD, nullptr); + if (error != JVMTI_ERROR_NONE) { + printf("ERROR: Unable to enable COMPILED_METHOD_LOAD event, error=%d\n", error); + return JNI_ERR; + } + + error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_UNLOAD, nullptr); + if (error != JVMTI_ERROR_NONE) { + printf("ERROR: Unable to enable COMPILED_METHOD_UNLOAD event, error=%d\n", error); + return JNI_ERR; + } + + return JNI_OK; +} diff --git a/test/lib/jdk/test/whitebox/WhiteBox.java b/test/lib/jdk/test/whitebox/WhiteBox.java index 5adb7bf512762..669ec48b619b0 100644 --- a/test/lib/jdk/test/whitebox/WhiteBox.java +++ b/test/lib/jdk/test/whitebox/WhiteBox.java @@ -490,6 +490,12 @@ public Object[] getNMethod(Executable method, boolean isOsr) { Objects.requireNonNull(method); return getNMethod0(method, isOsr); } + private native void relocateNMethodFromMethod0(Executable method, int type); + public void relocateNMethodFromMethod(Executable method, int type) { + Objects.requireNonNull(method); + relocateNMethodFromMethod0(method, type); + } + public native void relocateNMethodFromAddr(long address, int type); public native long allocateCodeBlob(int size, int type); public long allocateCodeBlob(long size, int type) { int intSize = (int) size; diff --git a/test/lib/jdk/test/whitebox/code/CodeBlob.java b/test/lib/jdk/test/whitebox/code/CodeBlob.java index c6c23fdff0cb7..fd95b5a7e7d3d 100644 --- a/test/lib/jdk/test/whitebox/code/CodeBlob.java +++ b/test/lib/jdk/test/whitebox/code/CodeBlob.java @@ -46,18 +46,22 @@ public static CodeBlob getCodeBlob(long addr) { return new CodeBlob(obj); } protected CodeBlob(Object[] obj) { - assert obj.length == 4; + assert obj.length == 6; name = (String) obj[0]; size = (Integer) obj[1]; int blob_type_index = (Integer) obj[2]; code_blob_type = BlobType.values()[blob_type_index]; assert code_blob_type.id == (Integer) obj[2]; address = (Long) obj[3]; + code_begin = (Long) obj[4]; + isNMethod = (Boolean) obj[5]; } public final String name; public final int size; public final BlobType code_blob_type; public final long address; + public final long code_begin; + public final boolean isNMethod; @Override public String toString() { return "CodeBlob{" @@ -65,6 +69,7 @@ public String toString() { + ", size=" + size + ", code_blob_type=" + code_blob_type + ", address=" + address + + ", code_begin=" + code_begin + '}'; } }