diff --git a/make/autoconf/libraries.m4 b/make/autoconf/libraries.m4 index a1fc81564b1..51d4f724c33 100644 --- a/make/autoconf/libraries.m4 +++ b/make/autoconf/libraries.m4 @@ -108,12 +108,6 @@ AC_DEFUN([LIB_SETUP_JVM_LIBS], BASIC_JVM_LIBS_$1="$BASIC_JVM_LIBS_$1 -latomic" fi fi - - # Because RISC-V only has word-sized atomics, it requires libatomic where - # other common architectures do not, so link libatomic by default. - if test "x$OPENJDK_$1_OS" = xlinux && test "x$OPENJDK_$1_CPU" = xriscv64; then - BASIC_JVM_LIBS_$1="$BASIC_JVM_LIBS_$1 -latomic" - fi ]) ################################################################################ diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index f219d5653c3..a4c4e4a9d7f 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -945,10 +945,7 @@ var getJibProfilesProfiles = function (input, common, data) { target_os: input.build_os, target_cpu: input.build_cpu, dependencies: [ "jtreg", "gnumake", "boot_jdk", "devkit", "jib" ], - labels: "test", - environment: { - "JT_JAVA": common.boot_jdk_home - } + labels: "test" } }; profiles = concatObjects(profiles, testOnlyProfiles); diff --git a/src/hotspot/os_cpu/linux_riscv/atomic_linux_riscv.hpp b/src/hotspot/os_cpu/linux_riscv/atomic_linux_riscv.hpp index 393c245ec02..e85bd60b226 100644 --- a/src/hotspot/os_cpu/linux_riscv/atomic_linux_riscv.hpp +++ b/src/hotspot/os_cpu/linux_riscv/atomic_linux_riscv.hpp @@ -33,10 +33,23 @@ // Note that memory_order_conservative requires a full barrier after atomic stores. // See https://patchwork.kernel.org/patch/3575821/ +#if defined(__clang_major__) +#define FULL_COMPILER_ATOMIC_SUPPORT +#elif (__GNUC__ > 13) || ((__GNUC__ == 13) && (__GNUC_MINOR__ >= 2)) +#define FULL_COMPILER_ATOMIC_SUPPORT +#endif + template struct Atomic::PlatformAdd { template D add_then_fetch(D volatile* dest, I add_value, atomic_memory_order order) const { + +#ifndef FULL_COMPILER_ATOMIC_SUPPORT + // If we add add and fetch for sub word and are using older compiler + // it must be added here due to not using lib atomic. + STATIC_ASSERT(byte_size >= 4); +#endif + if (order != memory_order_relaxed) { FULL_MEM_BARRIER; } @@ -55,76 +68,101 @@ struct Atomic::PlatformAdd { } }; -template +#ifndef FULL_COMPILER_ATOMIC_SUPPORT +template<> template -inline T Atomic::PlatformXchg::operator()(T volatile* dest, - T exchange_value, - atomic_memory_order order) const { - STATIC_ASSERT(byte_size == sizeof(T)); +inline T Atomic::PlatformCmpxchg<1>::operator()(T volatile* dest __attribute__((unused)), + T compare_value, + T exchange_value, + atomic_memory_order order) const { + STATIC_ASSERT(1 == sizeof(T)); + if (order != memory_order_relaxed) { FULL_MEM_BARRIER; } - T res = __atomic_exchange_n(dest, exchange_value, __ATOMIC_RELAXED); + uint32_t volatile* aligned_dst = (uint32_t volatile*)(((uintptr_t)dest) & (~((uintptr_t)0x3))); + int shift = 8 * (((uintptr_t)dest) - ((uintptr_t)aligned_dst)); // 0, 8, 16, 24 + + uint64_t mask = 0xfful << shift; // 0x00000000..FF.. + uint64_t remask = ~mask; // 0xFFFFFFFF..00.. + + uint64_t w_cv = ((uint64_t)(unsigned char)compare_value) << shift; // widen to 64-bit 0x00000000..CC.. + uint64_t w_ev = ((uint64_t)(unsigned char)exchange_value) << shift; // widen to 64-bit 0x00000000..EE.. + + uint64_t old_value; + uint64_t rc_temp; + + __asm__ __volatile__ ( + "1: lr.w %0, %2 \n\t" + " and %1, %0, %5 \n\t" // ignore unrelated bytes and widen to 64-bit 0x00000000..XX.. + " bne %1, %3, 2f \n\t" // compare 64-bit w_cv + " and %1, %0, %6 \n\t" // remove old byte + " or %1, %1, %4 \n\t" // add new byte + " sc.w %1, %1, %2 \n\t" // store new word + " bnez %1, 1b \n\t" + "2: \n\t" + : /*%0*/"=&r" (old_value), /*%1*/"=&r" (rc_temp), /*%2*/"+A" (*aligned_dst) + : /*%3*/"r" (w_cv), /*%4*/"r" (w_ev), /*%5*/"r" (mask), /*%6*/"r" (remask) + : "memory" ); if (order != memory_order_relaxed) { FULL_MEM_BARRIER; } - return res; + + return (T)((old_value & mask) >> shift); } +#endif -// __attribute__((unused)) on dest is to get rid of spurious GCC warnings. template template -inline T Atomic::PlatformCmpxchg::operator()(T volatile* dest __attribute__((unused)), - T compare_value, - T exchange_value, - atomic_memory_order order) const { +inline T Atomic::PlatformXchg::operator()(T volatile* dest, + T exchange_value, + atomic_memory_order order) const { +#ifndef FULL_COMPILER_ATOMIC_SUPPORT + // If we add xchg for sub word and are using older compiler + // it must be added here due to not using lib atomic. + STATIC_ASSERT(byte_size >= 4); +#endif + STATIC_ASSERT(byte_size == sizeof(T)); - T value = compare_value; + if (order != memory_order_relaxed) { FULL_MEM_BARRIER; } - __atomic_compare_exchange(dest, &value, &exchange_value, /* weak */ false, - __ATOMIC_RELAXED, __ATOMIC_RELAXED); + T res = __atomic_exchange_n(dest, exchange_value, __ATOMIC_RELAXED); if (order != memory_order_relaxed) { FULL_MEM_BARRIER; } - return value; + return res; } -template<> +// __attribute__((unused)) on dest is to get rid of spurious GCC warnings. +template template -inline T Atomic::PlatformCmpxchg<4>::operator()(T volatile* dest __attribute__((unused)), - T compare_value, - T exchange_value, - atomic_memory_order order) const { - STATIC_ASSERT(4 == sizeof(T)); +inline T Atomic::PlatformCmpxchg::operator()(T volatile* dest __attribute__((unused)), + T compare_value, + T exchange_value, + atomic_memory_order order) const { - T old_value; - long rc; +#ifndef FULL_COMPILER_ATOMIC_SUPPORT + STATIC_ASSERT(byte_size >= 4); +#endif + STATIC_ASSERT(byte_size == sizeof(T)); if (order != memory_order_relaxed) { FULL_MEM_BARRIER; } - __asm__ __volatile__ ( - "1: sext.w %1, %3 \n\t" // sign-extend compare_value - " lr.w %0, %2 \n\t" - " bne %0, %1, 2f \n\t" - " sc.w %1, %4, %2 \n\t" - " bnez %1, 1b \n\t" - "2: \n\t" - : /*%0*/"=&r" (old_value), /*%1*/"=&r" (rc), /*%2*/"+A" (*dest) - : /*%3*/"r" (compare_value), /*%4*/"r" (exchange_value) - : "memory" ); + __atomic_compare_exchange(dest, &compare_value, &exchange_value, /* weak */ false, + __ATOMIC_RELAXED, __ATOMIC_RELAXED); if (order != memory_order_relaxed) { FULL_MEM_BARRIER; } - return old_value; + return compare_value; } template @@ -148,4 +186,6 @@ struct Atomic::PlatformOrderedStore void operator()(volatile T* p, T v) const { release_store(p, v); OrderAccess::fence(); } }; +#undef FULL_COMPILER_ATOMIC_SUPPORT + #endif // OS_CPU_LINUX_RISCV_ATOMIC_LINUX_RISCV_HPP diff --git a/src/hotspot/share/gc/g1/g1CodeRootSet.cpp b/src/hotspot/share/gc/g1/g1CodeRootSet.cpp index bbb1f3b9006..8b32d3c2956 100644 --- a/src/hotspot/share/gc/g1/g1CodeRootSet.cpp +++ b/src/hotspot/share/gc/g1/g1CodeRootSet.cpp @@ -28,82 +28,258 @@ #include "code/nmethod.hpp" #include "gc/g1/g1CodeRootSet.hpp" #include "gc/g1/heapRegion.hpp" +#include "memory/allocation.hpp" #include "oops/oop.inline.hpp" +#include "runtime/atomic.hpp" +#include "utilities/concurrentHashTable.inline.hpp" +#include "utilities/concurrentHashTableTasks.inline.hpp" -void G1CodeRootSet::add(nmethod* nm) { - assert(_is_iterating == false, "should not mutate while iterating the table"); - bool added = false; - if (_table == nullptr) { - _table = new (mtGC) Table(SmallSize, LargeSize); +class G1CodeRootSetHashTableConfig : public StackObj { +public: + using Value = nmethod*; + + static uintx get_hash(Value const& value, bool* is_dead); + + static void* allocate_node(void* context, size_t size, Value const& value) { + return AllocateHeap(size, mtGC); } - added = _table->put(nm, nm); - if (added && _table->table_size() == SmallSize && length() == Threshold) { - _table->resize(LargeSize); + + static void free_node(void* context, void* memory, Value const& value) { + FreeHeap(memory); } +}; + +// Storage container for the code root set. +class G1CodeRootSetHashTable : public CHeapObj { + using HashTable = ConcurrentHashTable; + using HashTableScanTask = HashTable::ScanTask; + + // Default (log2) number of buckets; small since typically we do not expect many + // entries. + static const size_t Log2DefaultNumBuckets = 2; + static const uint BucketClaimSize = 16; + + HashTable _table; + HashTableScanTask _table_scanner; + + size_t volatile _num_entries; + + bool is_empty() const { return number_of_entries() == 0; } + + class HashTableLookUp : public StackObj { + nmethod* _nmethod; + + public: + explicit HashTableLookUp(nmethod* nmethod) : _nmethod(nmethod) { } + uintx get_hash() const; + bool equals(nmethod** value); + bool is_dead(nmethod** value) const { return false; } + }; + + class HashTableIgnore : public StackObj { + public: + HashTableIgnore() { } + void operator()(nmethod** value) { /* do nothing */ } + }; + +public: + G1CodeRootSetHashTable() : + _table(Log2DefaultNumBuckets, + HashTable::DEFAULT_MAX_SIZE_LOG2), + _table_scanner(&_table, BucketClaimSize), _num_entries(0) { + clear(); + } + + // Robert Jenkins 1996 & Thomas Wang 1997 + // http://web.archive.org/web/20071223173210/http://www.concentric.net/~Ttwang/tech/inthash.htm + static uint32_t hash(uint32_t key) { + key = ~key + (key << 15); + key = key ^ (key >> 12); + key = key + (key << 2); + key = key ^ (key >> 4); + key = key * 2057; + key = key ^ (key >> 16); + return key; + } + + static uintx get_hash(nmethod* nmethod) { + uintptr_t value = (uintptr_t)nmethod; + // The CHT only uses the bits smaller than HashTable::DEFAULT_MAX_SIZE_LOG2, so + // try to increase the randomness by incorporating the upper bits of the + // address too. + STATIC_ASSERT(HashTable::DEFAULT_MAX_SIZE_LOG2 <= sizeof(uint32_t) * BitsPerByte); +#ifdef _LP64 + return hash((uint32_t)value ^ (uint32_t(value >> 32))); +#else + return hash((uint32_t)value); +#endif + } + + void insert(nmethod* method) { + HashTableLookUp lookup(method); + bool grow_hint = false; + bool inserted = _table.insert(Thread::current(), lookup, method, &grow_hint); + if (inserted) { + Atomic::inc(&_num_entries); + } + if (grow_hint) { + _table.grow(Thread::current()); + } + } + + bool remove(nmethod* method) { + HashTableLookUp lookup(method); + bool removed = _table.remove(Thread::current(), lookup); + if (removed) { + Atomic::dec(&_num_entries); + } + return removed; + } + + bool contains(nmethod* method) { + HashTableLookUp lookup(method); + HashTableIgnore ignore; + return _table.get(Thread::current(), lookup, ignore); + } + + void clear() { + _table.unsafe_reset(); + Atomic::store(&_num_entries, (size_t)0); + } + + void iterate_at_safepoint(CodeBlobClosure* blk) { + assert_at_safepoint(); + // A lot of code root sets are typically empty. + if (is_empty()) { + return; + } + + auto do_value = + [&] (nmethod** value) { + blk->do_code_blob(*value); + return true; + }; + _table_scanner.do_safepoint_scan(do_value); + } + + // Removes entries as indicated by the given EVAL closure. + template + void clean(EVAL& eval) { + // A lot of code root sets are typically empty. + if (is_empty()) { + return; + } + + size_t num_deleted = 0; + auto do_delete = + [&] (nmethod** value) { + num_deleted++; + }; + bool succeeded = _table.try_bulk_delete(Thread::current(), eval, do_delete); + guarantee(succeeded, "unable to clean table"); + + if (num_deleted != 0) { + size_t current_size = Atomic::sub(&_num_entries, num_deleted); + shrink_to_match(current_size); + } + } + + // Calculate the log2 of the table size we want to shrink to. + size_t log2_target_shrink_size(size_t current_size) const { + // A table with the new size should be at most filled by this factor. Otherwise + // we would grow again quickly. + const float WantedLoadFactor = 0.5; + size_t min_expected_size = checked_cast(ceil(current_size / WantedLoadFactor)); + + size_t result = Log2DefaultNumBuckets; + if (min_expected_size != 0) { + size_t log2_bound = checked_cast(log2i_exact(round_up_power_of_2(min_expected_size))); + result = clamp(log2_bound, Log2DefaultNumBuckets, HashTable::DEFAULT_MAX_SIZE_LOG2); + } + return result; + } + + // Shrink to keep table size appropriate to the given number of entries. + void shrink_to_match(size_t current_size) { + size_t prev_log2size = _table.get_size_log2(Thread::current()); + size_t new_log2_table_size = log2_target_shrink_size(current_size); + if (new_log2_table_size < prev_log2size) { + _table.shrink(Thread::current(), new_log2_table_size); + } + } + + void reset_table_scanner() { + _table_scanner.set(&_table, BucketClaimSize); + } + + size_t mem_size() { return sizeof(*this) + _table.get_mem_size(Thread::current()); } + + size_t number_of_entries() const { return Atomic::load(&_num_entries); } +}; + +uintx G1CodeRootSetHashTable::HashTableLookUp::get_hash() const { + return G1CodeRootSetHashTable::get_hash(_nmethod); +} + +bool G1CodeRootSetHashTable::HashTableLookUp::equals(nmethod** value) { + return *value == _nmethod; +} + +uintx G1CodeRootSetHashTableConfig::get_hash(Value const& value, bool* is_dead) { + *is_dead = false; + return G1CodeRootSetHashTable::get_hash(value); } +size_t G1CodeRootSet::length() const { return _table->number_of_entries(); } + +void G1CodeRootSet::add(nmethod* method) { + if (!contains(method)) { + assert(!_is_iterating, "must be"); + _table->insert(method); + } +} + +G1CodeRootSet::G1CodeRootSet() : + _table(new G1CodeRootSetHashTable()) + DEBUG_ONLY(COMMA _is_iterating(false)) { } + G1CodeRootSet::~G1CodeRootSet() { delete _table; } bool G1CodeRootSet::remove(nmethod* method) { - assert(_is_iterating == false, "should not mutate while iterating the table"); - bool removed = false; - if (_table != nullptr) { - removed = _table->remove(method); - } - if (removed) { - if (length() == 0) { - clear(); - } - } - return removed; + assert(!_is_iterating, "should not mutate while iterating the table"); + return _table->remove(method); } bool G1CodeRootSet::contains(nmethod* method) { - if (_table != nullptr) { - return _table->contains(method); - } - return false; + return _table->contains(method); } void G1CodeRootSet::clear() { - assert(_is_iterating == false, "should not mutate while iterating the table"); - delete _table; - _table = nullptr; + assert(!_is_iterating, "should not mutate while iterating the table"); + _table->clear(); } size_t G1CodeRootSet::mem_size() { - return (_table == nullptr) - ? sizeof(*this) - : sizeof(*this) + _table->mem_size(); + return sizeof(*this) + _table->mem_size(); +} + +void G1CodeRootSet::reset_table_scanner() { + _table->reset_table_scanner(); } void G1CodeRootSet::nmethods_do(CodeBlobClosure* blk) const { DEBUG_ONLY(_is_iterating = true;) - if (_table != nullptr) { - _table->iterate_all([&](nmethod* nm, nmethod* _) { - blk->do_code_blob(nm); - }); - } + _table->iterate_at_safepoint(blk); DEBUG_ONLY(_is_iterating = false;) } class CleanCallback : public StackObj { NONCOPYABLE(CleanCallback); // can not copy, _blobs will point to old copy + class PointsIntoHRDetectionClosure : public OopClosure { HeapRegion* _hr; - public: - bool _points_into; - PointsIntoHRDetectionClosure(HeapRegion* hr) : _hr(hr), _points_into(false) {} - - void do_oop(narrowOop* o) { - do_oop_work(o); - } - - void do_oop(oop* o) { - do_oop_work(o); - } template void do_oop_work(T* p) { @@ -111,6 +287,14 @@ class CleanCallback : public StackObj { _points_into = true; } } + + public: + bool _points_into; + PointsIntoHRDetectionClosure(HeapRegion* hr) : _hr(hr), _points_into(false) {} + + void do_oop(narrowOop* o) { do_oop_work(o); } + + void do_oop(oop* o) { do_oop_work(o); } }; PointsIntoHRDetectionClosure _detector; @@ -119,20 +303,16 @@ class CleanCallback : public StackObj { public: CleanCallback(HeapRegion* hr) : _detector(hr), _blobs(&_detector, !CodeBlobToOopClosure::FixRelocations) {} - bool do_entry(nmethod* nm, nmethod* _) { + bool operator()(nmethod** value) { _detector._points_into = false; - _blobs.do_code_blob(nm); + _blobs.do_code_blob(*value); return !_detector._points_into; } }; void G1CodeRootSet::clean(HeapRegion* owner) { - assert(_is_iterating == false, "should not mutate while iterating the table"); - CleanCallback should_clean(owner); - if (_table != nullptr) { - _table->unlink(&should_clean); - } - if (length() == 0) { - clear(); - } + assert(!_is_iterating, "should not mutate while iterating the table"); + + CleanCallback eval(owner); + _table->clean(eval); } diff --git a/src/hotspot/share/gc/g1/g1CodeRootSet.hpp b/src/hotspot/share/gc/g1/g1CodeRootSet.hpp index 087c3635cf0..d1051e6e42e 100644 --- a/src/hotspot/share/gc/g1/g1CodeRootSet.hpp +++ b/src/hotspot/share/gc/g1/g1CodeRootSet.hpp @@ -27,43 +27,37 @@ #include "code/codeCache.hpp" #include "utilities/globalDefinitions.hpp" -#include "utilities/resizeableResourceHash.hpp" +class G1CodeRootSetHashTable; class HeapRegion; class nmethod; // Implements storage for a set of code roots. -// This class is not thread safe, locks are needed. +// This class is thread safe. class G1CodeRootSet { - friend class G1CodeRootSetTest; - friend class G1CodeRootSetTest_g1_code_cache_rem_set_vm_Test; - - private: - const static size_t SmallSize = 32; - const static size_t Threshold = 24; - const static size_t LargeSize = 512; - - using Table = ResizeableResourceHashtable; - Table* _table; + G1CodeRootSetHashTable* _table; DEBUG_ONLY(mutable bool _is_iterating;) public: - G1CodeRootSet() : _table(nullptr) DEBUG_ONLY(COMMA _is_iterating(false)) {} + G1CodeRootSet(); ~G1CodeRootSet(); void add(nmethod* method); bool remove(nmethod* method); bool contains(nmethod* method); void clear(); + + // Prepare for MT iteration. Must be called before nmethods_do. + void reset_table_scanner(); void nmethods_do(CodeBlobClosure* blk) const; - // Remove all nmethods which no longer contain pointers into our "owner" region + // Remove all nmethods which no longer contain pointers into our "owner" region. void clean(HeapRegion* owner); bool is_empty() { return length() == 0;} // Length in elements - size_t length() const { return _table == nullptr ? 0 : _table->number_of_entries(); } + size_t length() const; // Memory size in bytes taken by this set. size_t mem_size(); diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index dc97b715b91..ba657e650a0 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -3009,8 +3009,7 @@ class RegisterNMethodOopClosure: public OopClosure { " starting at " HR_FORMAT, p2i(_nm), HR_FORMAT_PARAMS(hr), HR_FORMAT_PARAMS(hr->humongous_start_region())); - // HeapRegion::add_code_root_locked() avoids adding duplicate entries. - hr->add_code_root_locked(_nm); + hr->add_code_root(_nm); } } diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 33e8ad79754..13736b891e2 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -1345,7 +1345,7 @@ void G1Policy::abandon_collection_set_candidates() { // Clear remembered sets of remaining candidate regions and the actual candidate // set. for (HeapRegion* r : *candidates()) { - r->rem_set()->clear_locked(true /* only_cardset */); + r->rem_set()->clear(true /* only_cardset */); } _collection_set->abandon_all_candidates(); } diff --git a/src/hotspot/share/gc/g1/g1RemSet.cpp b/src/hotspot/share/gc/g1/g1RemSet.cpp index 09b5f4ee80e..f620b9778a4 100644 --- a/src/hotspot/share/gc/g1/g1RemSet.cpp +++ b/src/hotspot/share/gc/g1/g1RemSet.cpp @@ -257,7 +257,6 @@ class G1RemSetScanState : public CHeapObj { public: G1RemSetScanState() : _max_reserved_regions(0), - _collection_set_iter_state(nullptr), _card_table_scan_state(nullptr), _scan_chunks_per_region(G1CollectedHeap::get_chunks_per_region()), _log_scan_chunks_per_region(log2i(_scan_chunks_per_region)), @@ -270,16 +269,14 @@ class G1RemSetScanState : public CHeapObj { } ~G1RemSetScanState() { - FREE_C_HEAP_ARRAY(G1RemsetIterState, _collection_set_iter_state); FREE_C_HEAP_ARRAY(uint, _card_table_scan_state); FREE_C_HEAP_ARRAY(bool, _region_scan_chunks); FREE_C_HEAP_ARRAY(HeapWord*, _scan_top); } void initialize(size_t max_reserved_regions) { - assert(_collection_set_iter_state == nullptr, "Must not be initialized twice"); + assert(_card_table_scan_state == nullptr, "Must not be initialized twice"); _max_reserved_regions = max_reserved_regions; - _collection_set_iter_state = NEW_C_HEAP_ARRAY(G1RemsetIterState, max_reserved_regions, mtGC); _card_table_scan_state = NEW_C_HEAP_ARRAY(uint, max_reserved_regions, mtGC); _num_total_scan_chunks = max_reserved_regions * _scan_chunks_per_region; _region_scan_chunks = NEW_C_HEAP_ARRAY(bool, _num_total_scan_chunks, mtGC); @@ -294,7 +291,6 @@ class G1RemSetScanState : public CHeapObj { // become used during the collection these values must be valid // for those regions as well. for (size_t i = 0; i < _max_reserved_regions; i++) { - reset_region_claim((uint)i); clear_scan_top((uint)i); } @@ -399,20 +395,6 @@ class G1RemSetScanState : public CHeapObj { } while (cur != start_pos); } - void reset_region_claim(uint region_idx) { - _collection_set_iter_state[region_idx] = false; - } - - // Attempt to claim the given region in the collection set for iteration. Returns true - // if this call caused the transition from Unclaimed to Claimed. - inline bool claim_collection_set_region(uint region) { - assert(region < _max_reserved_regions, "Tried to access invalid region %u", region); - if (_collection_set_iter_state[region]) { - return false; - } - return !Atomic::cmpxchg(&_collection_set_iter_state[region], false, true); - } - bool has_cards_to_scan(uint region) { assert(region < _max_reserved_regions, "Tried to access invalid region %u", region); return _card_table_scan_state[region] < HeapRegion::CardsPerRegion; @@ -829,8 +811,6 @@ class G1ScanCollectionSetRegionClosure : public HeapRegionClosure { _rem_set_opt_trim_partially_time() { } bool do_heap_region(HeapRegion* r) { - uint const region_idx = r->hrm_index(); - // The individual references for the optional remembered set are per-worker, so we // always need to scan them. if (r->has_index_in_opt_cset()) { @@ -841,7 +821,8 @@ class G1ScanCollectionSetRegionClosure : public HeapRegionClosure { event.commit(GCId::current(), _worker_id, G1GCPhaseTimes::phase_name(_scan_phase)); } - if (_scan_state->claim_collection_set_region(region_idx)) { + // Scan code root remembered sets. + { EventGCPhaseParallel event; G1EvacPhaseWithTrimTimeTracker timer(_pss, _code_root_scan_time, _code_trim_partially_time); G1ScanAndCountCodeBlobClosure cl(_pss->closures()->weak_codeblobs()); @@ -1212,7 +1193,7 @@ class G1MergeHeapRootsTask : public WorkerTask { // implicitly rebuild anything else during eager reclaim. Note that at the moment // (and probably never) we do not enter this path if there are other kind of // remembered sets for this region. - r->rem_set()->clear_locked(true /* only_cardset */); + r->rem_set()->clear(true /* only_cardset */); // Clear_locked() above sets the state to Empty. However we want to continue // collecting remembered set entries for humongous regions that were not // reclaimed. diff --git a/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.cpp b/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.cpp index dc467107136..6c084a72d94 100644 --- a/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.cpp +++ b/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.cpp @@ -142,7 +142,7 @@ void G1RemSetTrackingPolicy::update_after_rebuild(HeapRegion* r) { [&] (HeapRegion* r) { assert(!r->is_continues_humongous() || r->rem_set()->is_empty(), "Continues humongous region %u remset should be empty", r->hrm_index()); - r->rem_set()->clear_locked(true /* only_cardset */); + r->rem_set()->clear(true /* only_cardset */); }); } G1ConcurrentMark* cm = G1CollectedHeap::heap()->concurrent_mark(); diff --git a/src/hotspot/share/gc/g1/heapRegion.cpp b/src/hotspot/share/gc/g1/heapRegion.cpp index bddff5bc44e..da11963317e 100644 --- a/src/hotspot/share/gc/g1/heapRegion.cpp +++ b/src/hotspot/share/gc/g1/heapRegion.cpp @@ -105,7 +105,7 @@ void HeapRegion::handle_evacuation_failure(bool retain) { move_to_old(); _rem_set->clean_code_roots(this); - _rem_set->clear_locked(true /* only_cardset */, retain /* keep_tracked */); + _rem_set->clear(true /* only_cardset */, retain /* keep_tracked */); } void HeapRegion::unlink_from_list() { @@ -122,7 +122,7 @@ void HeapRegion::hr_clear(bool clear_space) { set_free(); reset_pre_dummy_top(); - rem_set()->clear_locked(); + rem_set()->clear(); init_top_at_mark_start(); if (clear_space) clear(SpaceDecorator::Mangle); @@ -205,7 +205,7 @@ void HeapRegion::clear_humongous() { } void HeapRegion::prepare_remset_for_scan() { - return _rem_set->reset_table_scanner(); + _rem_set->reset_table_scanner(); } HeapRegion::HeapRegion(uint hrm_index, @@ -275,24 +275,15 @@ void HeapRegion::note_self_forward_chunk_done(size_t garbage_bytes) { // Code roots support void HeapRegion::add_code_root(nmethod* nm) { - HeapRegionRemSet* hrrs = rem_set(); - hrrs->add_code_root(nm); -} - -void HeapRegion::add_code_root_locked(nmethod* nm) { - assert_locked_or_safepoint(CodeCache_lock); - HeapRegionRemSet* hrrs = rem_set(); - hrrs->add_code_root_locked(nm); + rem_set()->add_code_root(nm); } void HeapRegion::remove_code_root(nmethod* nm) { - HeapRegionRemSet* hrrs = rem_set(); - hrrs->remove_code_root(nm); + rem_set()->remove_code_root(nm); } void HeapRegion::code_roots_do(CodeBlobClosure* blk) const { - HeapRegionRemSet* hrrs = rem_set(); - hrrs->code_roots_do(blk); + rem_set()->code_roots_do(blk); } class VerifyCodeRootOopClosure: public OopClosure { diff --git a/src/hotspot/share/gc/g1/heapRegion.hpp b/src/hotspot/share/gc/g1/heapRegion.hpp index ae4aec73386..a424680e202 100644 --- a/src/hotspot/share/gc/g1/heapRegion.hpp +++ b/src/hotspot/share/gc/g1/heapRegion.hpp @@ -544,7 +544,6 @@ class HeapRegion : public CHeapObj { // Routines for managing a list of code roots (attached to the // this region's RSet) that point into this heap region. void add_code_root(nmethod* nm); - void add_code_root_locked(nmethod* nm); void remove_code_root(nmethod* nm); // Applies blk->do_code_blob() to each of the entries in diff --git a/src/hotspot/share/gc/g1/heapRegionRemSet.cpp b/src/hotspot/share/gc/g1/heapRegionRemSet.cpp index 083780ff3fc..41cce2fa661 100644 --- a/src/hotspot/share/gc/g1/heapRegionRemSet.cpp +++ b/src/hotspot/share/gc/g1/heapRegionRemSet.cpp @@ -57,7 +57,6 @@ void HeapRegionRemSet::initialize(MemRegion reserved) { HeapRegionRemSet::HeapRegionRemSet(HeapRegion* hr, G1CardSetConfiguration* config) : - _m(Mutex::service - 1, FormatBuffer<128>("HeapRegionRemSet#%u_lock", hr->hrm_index())), _code_roots(), _card_set_mm(config, G1CollectedHeap::heap()->card_set_freelist_pool()), _card_set(config, &_card_set_mm), @@ -68,12 +67,7 @@ void HeapRegionRemSet::clear_fcc() { G1FromCardCache::clear(_hr->hrm_index()); } -void HeapRegionRemSet::clear(bool only_cardset) { - MutexLocker x(&_m, Mutex::_no_safepoint_check_flag); - clear_locked(only_cardset); -} - -void HeapRegionRemSet::clear_locked(bool only_cardset, bool keep_tracked) { +void HeapRegionRemSet::clear(bool only_cardset, bool keep_tracked) { if (!only_cardset) { _code_roots.clear(); } @@ -88,6 +82,7 @@ void HeapRegionRemSet::clear_locked(bool only_cardset, bool keep_tracked) { } void HeapRegionRemSet::reset_table_scanner() { + _code_roots.reset_table_scanner(); _card_set.reset_table_scanner(); } @@ -108,33 +103,12 @@ void HeapRegionRemSet::print_static_mem_size(outputStream* out) { void HeapRegionRemSet::add_code_root(nmethod* nm) { assert(nm != nullptr, "sanity"); - assert((!CodeCache_lock->owned_by_self() || SafepointSynchronize::is_at_safepoint()), - "should call add_code_root_locked instead. CodeCache_lock->owned_by_self(): %s, is_at_safepoint(): %s", - BOOL_TO_STR(CodeCache_lock->owned_by_self()), BOOL_TO_STR(SafepointSynchronize::is_at_safepoint())); - - MutexLocker ml(&_m, Mutex::_no_safepoint_check_flag); - add_code_root_locked(nm); -} - -void HeapRegionRemSet::add_code_root_locked(nmethod* nm) { - assert(nm != nullptr, "sanity"); - assert((CodeCache_lock->owned_by_self() || - (SafepointSynchronize::is_at_safepoint() && - (_m.owned_by_self() || Thread::current()->is_VM_thread()))), - "not safely locked. CodeCache_lock->owned_by_self(): %s, is_at_safepoint(): %s, _m.owned_by_self(): %s, Thread::current()->is_VM_thread(): %s", - BOOL_TO_STR(CodeCache_lock->owned_by_self()), BOOL_TO_STR(SafepointSynchronize::is_at_safepoint()), - BOOL_TO_STR(_m.owned_by_self()), BOOL_TO_STR(Thread::current()->is_VM_thread())); - - if (!_code_roots.contains(nm)) { // with this test, we can assert that we do not modify the hash table while iterating over it - _code_roots.add(nm); - } + _code_roots.add(nm); } void HeapRegionRemSet::remove_code_root(nmethod* nm) { assert(nm != nullptr, "sanity"); - assert_locked_or_safepoint(CodeCache_lock); - ConditionalMutexLocker ml(&_m, !CodeCache_lock->owned_by_self(), Mutex::_no_safepoint_check_flag); _code_roots.remove(nm); // Check that there were no duplicates diff --git a/src/hotspot/share/gc/g1/heapRegionRemSet.hpp b/src/hotspot/share/gc/g1/heapRegionRemSet.hpp index 640e7315a18..960d0864a50 100644 --- a/src/hotspot/share/gc/g1/heapRegionRemSet.hpp +++ b/src/hotspot/share/gc/g1/heapRegionRemSet.hpp @@ -40,7 +40,6 @@ class outputStream; class HeapRegionRemSet : public CHeapObj { friend class VMStructs; - Mutex _m; // A set of code blobs (nmethods) whose code contains pointers into // the region that owns this RSet. G1CodeRootSet _code_roots; @@ -117,8 +116,7 @@ class HeapRegionRemSet : public CHeapObj { // The region is being reclaimed; clear its remset, and any mention of // entries for this region in other remsets. - void clear(bool only_cardset = false); - void clear_locked(bool only_cardset = false, bool keep_tracked = false); + void clear(bool only_cardset = false, bool keep_tracked = false); void reset_table_scanner(); @@ -167,7 +165,6 @@ class HeapRegionRemSet : public CHeapObj { // Returns true if the code roots contains the given // nmethod. bool code_roots_list_contains(nmethod* nm) { - MutexLocker ml(&_m, Mutex::_no_safepoint_check_flag); return _code_roots.contains(nm); } diff --git a/src/hotspot/share/opto/idealGraphPrinter.cpp b/src/hotspot/share/opto/idealGraphPrinter.cpp index bd2e7e36262..d5858b4bb3f 100644 --- a/src/hotspot/share/opto/idealGraphPrinter.cpp +++ b/src/hotspot/share/opto/idealGraphPrinter.cpp @@ -374,9 +374,28 @@ void IdealGraphPrinter::visit_node(Node *n, bool edges, VectorSet* temp_set) { #ifndef PRODUCT Compile::current()->_in_dump_cnt++; print_prop(NODE_NAME_PROPERTY, (const char *)node->Name()); + print_prop("idx", node->_idx); const Type *t = node->bottom_type(); print_prop("type", t->msg()); - print_prop("idx", node->_idx); + if (t->category() != Type::Category::Control && + t->category() != Type::Category::Memory) { + // Print detailed type information for nodes whose type is not trivial. + buffer[0] = 0; + stringStream bottom_type_stream(buffer, sizeof(buffer) - 1); + t->dump_on(&bottom_type_stream); + print_prop("bottom_type", buffer); + if (C->types() != nullptr && C->matcher() == nullptr) { + // Phase types maintained during optimization (GVN, IGVN, CCP) are + // available and valid (not in code generation phase). + const Type* pt = (*C->types())[node->_idx]; + if (pt != nullptr) { + buffer[0] = 0; + stringStream phase_type_stream(buffer, sizeof(buffer) - 1); + pt->dump_on(&phase_type_stream); + print_prop("phase_type", buffer); + } + } + } if (C->cfg() != nullptr) { Block* block = C->cfg()->get_block_for_node(node); diff --git a/src/hotspot/share/opto/idealGraphPrinter.hpp b/src/hotspot/share/opto/idealGraphPrinter.hpp index 091560d9231..aff139a82e3 100644 --- a/src/hotspot/share/opto/idealGraphPrinter.hpp +++ b/src/hotspot/share/opto/idealGraphPrinter.hpp @@ -89,7 +89,7 @@ class IdealGraphPrinter : public CHeapObj { outputStream *_output; ciMethod *_current_method; int _depth; - char buffer[128]; + char buffer[512]; bool _should_send_method; PhaseChaitin* _chaitin; bool _traverse_outs; diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 9d1e8dd9fd8..c151dece210 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -2285,6 +2285,8 @@ void PhaseIdealLoop::do_unroll(IdealLoopTree *loop, Node_List &old_new, bool adj // can edit it's inputs directly. Hammer in the new limit for the // minimum-trip guard. assert(opaq->outcnt() == 1, ""); + // Notify limit -> opaq -> CmpI, it may constant fold. + _igvn.add_users_to_worklist(opaq->in(1)); _igvn.replace_input_of(opaq, 1, new_limit); } diff --git a/src/hotspot/share/opto/phaseX.hpp b/src/hotspot/share/opto/phaseX.hpp index 4469c396528..535c476ab03 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -124,11 +124,10 @@ class Type_Array : public AnyObj { uint _max; const Type **_types; void grow( uint i ); // Grow array node to fit - const Type *operator[] ( uint i ) const // Lookup, or null for not mapped - { return (i<_max) ? _types[i] : (Type*)nullptr; } - friend class PhaseValues; public: Type_Array(Arena *a) : _a(a), _max(0), _types(0) {} + const Type *operator[] ( uint i ) const // Lookup, or null for not mapped + { return (i<_max) ? _types[i] : (Type*)nullptr; } const Type *fast_lookup(uint i) const{assert(i<_max,"oob");return _types[i];} // Extend the mapping: index i maps to Type *n. void map( uint i, const Type *n ) { if( i>=_max ) grow(i); _types[i] = n; } diff --git a/src/hotspot/share/runtime/mutexLocker.cpp b/src/hotspot/share/runtime/mutexLocker.cpp index 63418658e42..6d969738802 100644 --- a/src/hotspot/share/runtime/mutexLocker.cpp +++ b/src/hotspot/share/runtime/mutexLocker.cpp @@ -231,7 +231,7 @@ void mutex_init() { MUTEX_DEFN(StringDedupIntern_lock , PaddedMutex , nosafepoint); MUTEX_DEFN(RawMonitor_lock , PaddedMutex , nosafepoint-1); - MUTEX_DEFN(Metaspace_lock , PaddedMutex , nosafepoint-3); + MUTEX_DEFN(Metaspace_lock , PaddedMutex , nosafepoint-4); MUTEX_DEFN(MetaspaceCritical_lock , PaddedMonitor, nosafepoint-1); MUTEX_DEFN(Patching_lock , PaddedMutex , nosafepoint); // used for safepointing and code patching. @@ -302,9 +302,9 @@ void mutex_init() { MUTEX_DEFN(UnsafeJlong_lock , PaddedMutex , nosafepoint); #endif - MUTEX_DEFN(ContinuationRelativize_lock , PaddedMonitor, nosafepoint-3); + MUTEX_DEFN(ContinuationRelativize_lock , PaddedMonitor, nosafepoint-4); MUTEX_DEFN(CodeHeapStateAnalytics_lock , PaddedMutex , safepoint); - MUTEX_DEFN(ThreadsSMRDelete_lock , PaddedMonitor, nosafepoint-3); // Holds ConcurrentHashTableResize_lock + MUTEX_DEFN(ThreadsSMRDelete_lock , PaddedMonitor, nosafepoint-4); // Holds ConcurrentHashTableResize_lock MUTEX_DEFN(ThreadIdTableCreate_lock , PaddedMutex , safepoint); MUTEX_DEFN(SharedDecoder_lock , PaddedMutex , tty-1); MUTEX_DEFN(DCmdFactory_lock , PaddedMutex , nosafepoint); diff --git a/src/hotspot/share/utilities/concurrentHashTable.inline.hpp b/src/hotspot/share/utilities/concurrentHashTable.inline.hpp index b222d379b72..733aaaa077f 100644 --- a/src/hotspot/share/utilities/concurrentHashTable.inline.hpp +++ b/src/hotspot/share/utilities/concurrentHashTable.inline.hpp @@ -1024,7 +1024,7 @@ ConcurrentHashTable(size_t log2size, size_t log2size_limit, size_t grow_hint, bo _stats_rate = nullptr; } _resize_lock = - new Mutex(Mutex::nosafepoint-2, "ConcurrentHashTableResize_lock"); + new Mutex(Mutex::nosafepoint-3, "ConcurrentHashTableResize_lock"); _table = new InternalTable(log2size); assert(log2size_limit >= log2size, "bad ergo"); _size_limit_reached = _table->_log2_size == _log2_size_limit; diff --git a/src/java.base/share/classes/java/text/ChoiceFormat.java b/src/java.base/share/classes/java/text/ChoiceFormat.java index a75aad0a303..ed49b11bd45 100644 --- a/src/java.base/share/classes/java/text/ChoiceFormat.java +++ b/src/java.base/share/classes/java/text/ChoiceFormat.java @@ -572,7 +572,11 @@ public Object clone() } /** - * Generates a hash code for the message format object. + * {@return the hash code for this {@code ChoiceFormat}} + * + * @implSpec This method calculates the hash code value using the values returned by + * {@link #getFormats()} and {@link #getLimits()}. + * @see Object#hashCode() */ @Override public int hashCode() { @@ -585,7 +589,17 @@ public int hashCode() { } /** - * Equality comparison between two + * Compares the specified object with this {@code ChoiceFormat} for equality. + * Returns true if the object is also a {@code ChoiceFormat} and the + * two formats would format any value the same. + * + * @implSpec This method performs an equality check with a notion of class + * identity based on {@code getClass()}, rather than {@code instanceof}. + * Therefore, in the equals methods in subclasses, no instance of this class + * should compare as equal to an instance of a subclass. + * @param obj object to be compared for equality + * @return {@code true} if the specified object is equal to this {@code ChoiceFormat} + * @see Object#equals(Object) */ @Override public boolean equals(Object obj) { diff --git a/src/java.base/share/classes/java/text/CompactNumberFormat.java b/src/java.base/share/classes/java/text/CompactNumberFormat.java index 14df610621f..3926a807b41 100644 --- a/src/java.base/share/classes/java/text/CompactNumberFormat.java +++ b/src/java.base/share/classes/java/text/CompactNumberFormat.java @@ -2343,13 +2343,17 @@ public void setParseBigDecimal(boolean newValue) { } /** - * Checks if this {@code CompactNumberFormat} is equal to the - * specified {@code obj}. The objects of type {@code CompactNumberFormat} - * are compared, other types return false; obeys the general contract of - * {@link java.lang.Object#equals(java.lang.Object) Object.equals}. + * Compares the specified object with this {@code CompactNumberFormat} for equality. + * Returns true if the object is also a {@code CompactNumberFormat} and the + * two formats would format any value the same. * + * @implSpec This method performs an equality check with a notion of class + * identity based on {@code getClass()}, rather than {@code instanceof}. + * Therefore, in the equals methods in subclasses, no instance of this class + * should compare as equal to an instance of a subclass. * @param obj the object to compare with * @return true if this is equal to the other {@code CompactNumberFormat} + * @see Object#hashCode() */ @Override public boolean equals(Object obj) { @@ -2373,7 +2377,11 @@ public boolean equals(Object obj) { } /** - * {@return the hash code for this {@code CompactNumberFormat} instance} + * {@return the hash code for this {@code CompactNumberFormat}} + * + * @implSpec Non-transient instance fields of this class are used to calculate + * a hash code value which adheres to the contract defined in {@link Objects#hashCode} + * @see Object#hashCode() */ @Override public int hashCode() { diff --git a/src/java.base/share/classes/java/text/DateFormat.java b/src/java.base/share/classes/java/text/DateFormat.java index 162a89d7915..d5083f11965 100644 --- a/src/java.base/share/classes/java/text/DateFormat.java +++ b/src/java.base/share/classes/java/text/DateFormat.java @@ -767,7 +767,11 @@ public boolean isLenient() } /** - * Overrides hashCode + * {@return the hash code for this {@code DateFormat}} + * + * @implSpec This method calculates the hash code value using the value returned by + * {@link #getNumberFormat()}. + * @see Object#hashCode() */ public int hashCode() { return numberFormat.hashCode(); @@ -775,7 +779,17 @@ public int hashCode() { } /** - * Overrides equals + * Compares the specified object with this {@code DateFormat} for equality. + * Returns true if the object is also a {@code DateFormat} and the + * two formats would format any value the same. + * + * @implSpec This method performs an equality check with a notion of class + * identity based on {@code getClass()}, rather than {@code instanceof}. + * Therefore, in the equals methods in subclasses, no instance of this class + * should compare as equal to an instance of a subclass. + * @param obj object to be compared for equality + * @return {@code true} if the specified object is equal to this {@code DateFormat} + * @see Object#equals(Object) */ public boolean equals(Object obj) { if (this == obj) return true; diff --git a/src/java.base/share/classes/java/text/DateFormatSymbols.java b/src/java.base/share/classes/java/text/DateFormatSymbols.java index d89c11f2c5a..372752bf737 100644 --- a/src/java.base/share/classes/java/text/DateFormatSymbols.java +++ b/src/java.base/share/classes/java/text/DateFormatSymbols.java @@ -657,8 +657,11 @@ public Object clone() } /** - * Override hashCode. - * Generates a hash code for the DateFormatSymbols object. + * {@return the hash code for this {@code DateFormatSymbols}} + * + * @implSpec Non-transient instance fields of this class are used to calculate + * a hash code value which adheres to the contract defined in {@link Objects#hashCode}. + * @see Object#hashCode() */ @Override public int hashCode() { @@ -682,7 +685,17 @@ public int hashCode() { } /** - * Override equals + * Compares the specified object with this {@code DateFormatSymbols} for equality. + * Returns true if the object is also a {@code DateFormatSymbols} and the two + * {@code DateFormatSymbols} objects represent the same date-time formatting data. + * + * @implSpec This method performs an equality check with a notion of class + * identity based on {@code getClass()}, rather than {@code instanceof}. + * Therefore, in the equals methods in subclasses, no instance of this class + * should compare as equal to an instance of a subclass. + * @param obj object to be compared for equality + * @return {@code true} if the specified object is equal to this {@code DateFormatSymbols} + * @see Object#equals(Object) */ @Override public boolean equals(Object obj) diff --git a/src/java.base/share/classes/java/text/DecimalFormat.java b/src/java.base/share/classes/java/text/DecimalFormat.java index 0cb7d430c8a..23c20266f41 100644 --- a/src/java.base/share/classes/java/text/DecimalFormat.java +++ b/src/java.base/share/classes/java/text/DecimalFormat.java @@ -2928,7 +2928,17 @@ public Object clone() { } /** - * Overrides equals + * Compares the specified object with this {@code DecimalFormat} for equality. + * Returns true if the object is also a {@code DecimalFormat} and the + * two formats would format any value the same. + * + * @implSpec This method performs an equality check with a notion of class + * identity based on {@code getClass()}, rather than {@code instanceof}. + * Therefore, in the equals methods in subclasses, no instance of this class + * should compare as equal to an instance of a subclass. + * @param obj object to be compared for equality + * @return {@code true} if the specified object is equal to this {@code DecimalFormat} + * @see Object#equals(Object) */ @Override public boolean equals(Object obj) @@ -2973,7 +2983,12 @@ public boolean equals(Object obj) } /** - * Overrides hashCode + * {@return the hash code for this {@code DecimalFormat}} + * + * @implSpec This method calculates the hash code value using the values returned from + * {@link #getPositivePrefix()} and {@link NumberFormat#hashCode()}. + * @see Object#hashCode() + * @see NumberFormat#hashCode() */ @Override public int hashCode() { diff --git a/src/java.base/share/classes/java/text/DecimalFormatSymbols.java b/src/java.base/share/classes/java/text/DecimalFormatSymbols.java index b5f2b404f78..eb47763faa5 100644 --- a/src/java.base/share/classes/java/text/DecimalFormatSymbols.java +++ b/src/java.base/share/classes/java/text/DecimalFormatSymbols.java @@ -737,7 +737,17 @@ public Object clone() { } /** - * Override equals. + * Compares the specified object with this {@code DecimalFormatSymbols} for equality. + * Returns true if the object is also a {@code DecimalFormatSymbols} and the two + * {@code DecimalFormatSymbols} objects represent the same set of symbols. + * + * @implSpec This method performs an equality check with a notion of class + * identity based on {@code getClass()}, rather than {@code instanceof}. + * Therefore, in the equals methods in subclasses, no instance of this class + * should compare as equal to an instance of a subclass. + * @param obj object to be compared for equality + * @return {@code true} if the specified object is equal to this {@code DecimalFormatSymbols} + * @see Object#equals(Object) */ @Override public boolean equals(Object obj) { @@ -767,7 +777,11 @@ public boolean equals(Object obj) { } /** - * Override hashCode. + * {@return the hash code for this {@code DecimalFormatSymbols}} + * + * @implSpec Non-transient instance fields of this class are used to calculate + * a hash code value which adheres to the contract defined in {@link Objects#hashCode}. + * @see Object#hashCode() */ @Override public int hashCode() { diff --git a/src/java.base/share/classes/java/text/MessageFormat.java b/src/java.base/share/classes/java/text/MessageFormat.java index 76237df2972..89acc14cdb1 100644 --- a/src/java.base/share/classes/java/text/MessageFormat.java +++ b/src/java.base/share/classes/java/text/MessageFormat.java @@ -1141,7 +1141,17 @@ public Object clone() { } /** - * Equality comparison between two message format objects + * Compares the specified object with this {@code MessageFormat} for equality. + * Returns true if the object is also a {@code MessageFormat} and the + * two formats would format any value the same. + * + * @implSpec This method performs an equality check with a notion of class + * identity based on {@code getClass()}, rather than {@code instanceof}. + * Therefore, in the equals methods in subclasses, no instance of this class + * should compare as equal to an instance of a subclass. + * @param obj object to be compared for equality + * @return {@code true} if the specified object is equal to this {@code MessageFormat} + * @see Object#equals(Object) */ @Override public boolean equals(Object obj) { @@ -1159,7 +1169,11 @@ public boolean equals(Object obj) { } /** - * Generates a hash code for the message format object. + * {@return the hash code value for this {@code MessageFormat}} + * + * @implSpec This method calculates the hash code value using the value returned by + * {@link #toPattern()}. + * @see Object#hashCode() */ @Override public int hashCode() { diff --git a/src/java.base/share/classes/java/text/NumberFormat.java b/src/java.base/share/classes/java/text/NumberFormat.java index 19d4e2b6ee2..86f56f9dd94 100644 --- a/src/java.base/share/classes/java/text/NumberFormat.java +++ b/src/java.base/share/classes/java/text/NumberFormat.java @@ -698,7 +698,11 @@ public static Locale[] getAvailableLocales() { } /** - * Overrides hashCode. + * {@return the hash code for this {@code NumberFormat}} + * + * @implSpec This method calculates the hash code value using the values returned by + * {@link #getMaximumIntegerDigits()} and {@link #getMaximumFractionDigits()}. + * @see Object#hashCode() */ @Override public int hashCode() { @@ -707,7 +711,17 @@ public int hashCode() { } /** - * Overrides equals. + * Compares the specified object with this {@code NumberFormat} for equality. + * Returns true if the object is also a {@code NumberFormat} and the + * two formats would format any value the same. + * + * @implSpec This method performs an equality check with a notion of class + * identity based on {@code getClass()}, rather than {@code instanceof}. + * Therefore, in the equals methods in subclasses, no instance of this class + * should compare as equal to an instance of a subclass. + * @param obj object to be compared for equality + * @return {@code true} if the specified object is equal to this {@code NumberFormat} + * @see Object#equals(Object) */ @Override public boolean equals(Object obj) { diff --git a/src/java.base/share/classes/java/text/SimpleDateFormat.java b/src/java.base/share/classes/java/text/SimpleDateFormat.java index b4bb3e3f31e..ee6ff005c49 100644 --- a/src/java.base/share/classes/java/text/SimpleDateFormat.java +++ b/src/java.base/share/classes/java/text/SimpleDateFormat.java @@ -2409,7 +2409,11 @@ public Object clone() { } /** - * {@return the hash code value for this {@code SimpleDateFormat} object} + * {@return the hash code value for this {@code SimpleDateFormat}} + * + * @implSpec This method calculates the hash code value using the value returned by + * {@link #toPattern()}. + * @see Object#hashCode() */ @Override public int hashCode() @@ -2419,11 +2423,17 @@ public int hashCode() } /** - * Compares the given object with this {@code SimpleDateFormat} for - * equality. + * Compares the specified object with this {@code SimpleDateFormat} for equality. + * Returns true if the object is also a {@code SimpleDateFormat} and the + * two formats would format any value the same. * - * @return true if the given object is equal to this - * {@code SimpleDateFormat} + * @implSpec This method performs an equality check with a notion of class + * identity based on {@code getClass()}, rather than {@code instanceof}. + * Therefore, in the equals methods in subclasses, no instance of this class + * should compare as equal to an instance of a subclass. + * @param obj object to be compared for equality + * @return {@code true} if the specified object is equal to this {@code SimpleDateFormat} + * @see Object#equals(Object) */ @Override public boolean equals(Object obj) diff --git a/src/utils/IdealGraphVisualizer/Filter/src/main/java/com/sun/hotspot/igv/filter/EditPropertyFilter.java b/src/utils/IdealGraphVisualizer/Filter/src/main/java/com/sun/hotspot/igv/filter/EditPropertyFilter.java index b26e34049c8..aaab56e3241 100644 --- a/src/utils/IdealGraphVisualizer/Filter/src/main/java/com/sun/hotspot/igv/filter/EditPropertyFilter.java +++ b/src/utils/IdealGraphVisualizer/Filter/src/main/java/com/sun/hotspot/igv/filter/EditPropertyFilter.java @@ -26,23 +26,23 @@ import com.sun.hotspot.igv.graph.Diagram; import com.sun.hotspot.igv.graph.Figure; import com.sun.hotspot.igv.graph.Selector; -import java.util.function.UnaryOperator; +import java.util.function.Function; import java.util.List; public class EditPropertyFilter extends AbstractFilter { private String name; private Selector selector; - private final String inputPropertyName; + private final String[] inputPropertyNames; private final String outputPropertyName; - private final UnaryOperator editFunction; + private final Function editFunction; public EditPropertyFilter(String name, Selector selector, - String inputPropertyName, String outputPropertyName, - UnaryOperator editFunction) { + String[] inputPropertyNames, String outputPropertyName, + Function editFunction) { this.name = name; this.selector = selector; - this.inputPropertyName = inputPropertyName; + this.inputPropertyNames = inputPropertyNames; this.outputPropertyName = outputPropertyName; this.editFunction = editFunction; } @@ -55,9 +55,12 @@ public String getName() { @Override public void apply(Diagram diagram) { List
list = selector.selected(diagram); + String[] inputVals = new String[inputPropertyNames.length]; for (Figure f : list) { - String inputVal = f.getProperties().get(inputPropertyName); - String outputVal = editFunction.apply(inputVal); + for (int i = 0; i < inputPropertyNames.length; i++) { + inputVals[i] = f.getProperties().get(inputPropertyNames[i]); + } + String outputVal = editFunction.apply(inputVals); f.getProperties().setProperty(outputPropertyName, outputVal); } } diff --git a/src/utils/IdealGraphVisualizer/Filter/src/main/resources/com/sun/hotspot/igv/filter/helper.js b/src/utils/IdealGraphVisualizer/Filter/src/main/resources/com/sun/hotspot/igv/filter/helper.js index 92b3c3ab5eb..78c71c0fdb0 100644 --- a/src/utils/IdealGraphVisualizer/Filter/src/main/resources/com/sun/hotspot/igv/filter/helper.js +++ b/src/utils/IdealGraphVisualizer/Filter/src/main/resources/com/sun/hotspot/igv/filter/helper.js @@ -190,15 +190,15 @@ var white = Color.white; // function that takes as input the old property value and returns the new // property value. function editSameProperty(selector, propertyName, editFunction) { - var f = new EditPropertyFilter("", selector, propertyName, propertyName, editFunction); + var f = new EditPropertyFilter("", selector, [propertyName], propertyName, editFunction); f.apply(graph); } // Update the value of the given property ('outputPropertyName') in the selected -// nodes according to a function that takes as input the value of a possibly -// different property ('inputPropertyName') and returns the new property value. -function editProperty(selector, inputPropertyName, outputPropertyName, editFunction) { - var f = new EditPropertyFilter("", selector, inputPropertyName, outputPropertyName, editFunction); +// nodes according to a function that takes as input the values of multiple +// properties ('inputPropertyNames') and returns the new property value. +function editProperty(selector, inputPropertyNames, outputPropertyName, editFunction) { + var f = new EditPropertyFilter("", selector, inputPropertyNames, outputPropertyName, editFunction); f.apply(graph); } diff --git a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/condenseGraph.filter b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/condenseGraph.filter index 02232647a47..6919f06da00 100644 --- a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/condenseGraph.filter +++ b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/condenseGraph.filter @@ -3,7 +3,8 @@ // combination with "Simplify graph". // Pretty-print Bool nodes to be shown as output slots. -function replaceComparisonWithSign(dump_spec) { +function replaceComparisonWithSign(propertyValues) { + dump_spec = propertyValues[0] var comparison = dump_spec.replace('[','').replace(']','') switch (comparison) { case "eq": return "="; @@ -23,21 +24,22 @@ editSameProperty(and([matches("name", "ConP|ConN"), matches("dump_spec", "#.*NUL function(t) {return "null";}); // Pretty-print CatchProj nodes. -function catchProjShortText(con) { +function catchProjShortText(propertyValues) { + con = propertyValues[0] switch (con) { case "0": return "F"; // fall-through case "1": return "T"; // throw default: return "?"; } } -editProperty(matches("name", "CatchProj"), "con", "short_name", catchProjShortText); +editProperty(matches("name", "CatchProj"), ["con"], "short_name", catchProjShortText); // Add short text to inlined Mach data parameters. editProperty(and([matches("name", "MachProj"), matches("category", "data"), successorOf(matches("name", "Start"))]), - "dump_spec", "short_name", - function(dump_spec) {return dump_spec;}); + ["dump_spec"], "short_name", + function(dump_spec) {return dump_spec[0];}); // Condense inputs in all nodes. var anyNode = matches("name", ".*"); diff --git a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/customNodeInfo.filter b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/customNodeInfo.filter index a272b7420ca..3f7956dc1ee 100644 --- a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/customNodeInfo.filter +++ b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/customNodeInfo.filter @@ -14,10 +14,10 @@ function callJavaInfo(dump_spec, regularPos, trapPos) { } return "trap: " + tm[2]; } -editProperty(matches("name", "CallStaticJava|CallDynamicJava|CallJava"), "dump_spec", "extra_label", - function(dump_spec) {return callJavaInfo(dump_spec, 2, 2);}); -editProperty(matches("name", "CallStaticJavaDirect|CallDynamicJavaDirect"), "dump_spec", "extra_label", - function(dump_spec) {return callJavaInfo(dump_spec, 1, 3);}); +editProperty(matches("name", "CallStaticJava|CallDynamicJava|CallJava"), ["dump_spec"], "extra_label", + function(dump_spec) {return callJavaInfo(dump_spec[0], 2, 2);}); +editProperty(matches("name", "CallStaticJavaDirect|CallDynamicJavaDirect"), ["dump_spec"], "extra_label", + function(dump_spec) {return callJavaInfo(dump_spec[0], 1, 3);}); function callLeafInfo(dump_spec, pos) { dump_components = split_string(dump_spec); @@ -26,21 +26,7 @@ function callLeafInfo(dump_spec, pos) { } return dump_components[pos]; } -editProperty(matches("name", "CallLeaf|CallLeafNoFP"), "dump_spec", "extra_label", - function(dump_spec) {return callLeafInfo(dump_spec, 1);}); -editProperty(matches("name", "CallLeafDirect|CallLeafDirectVector|CallLeafNoFPDirect"), "dump_spec", "extra_label", - function(dump_spec) {return callLeafInfo(dump_spec, 0);}); - -// Add extra line to exception creation nodes with the name of the exception. -function exceptionInfo(dump_spec) { - dump_spec2 = dump_spec.replace('#','') - dump_components = split_string(dump_spec2); - if (dump_components.length < 1) { - return null; - } - // dump_components[0] has a form like e.g. java/lang/NumberFormatException:NotNull, - // we want to return only the simple class name ("NumberFormatException"). - simple_classname = dump_components[0].split("/").pop(); - return simple_classname.split(":")[0]; -} -editProperty(matches("name", "CreateEx|CreateException"), "dump_spec", "extra_label", exceptionInfo); +editProperty(matches("name", "CallLeaf|CallLeafNoFP"), ["dump_spec"], "extra_label", + function(dump_spec) {return callLeafInfo(dump_spec[0], 1);}); +editProperty(matches("name", "CallLeafDirect|CallLeafDirectVector|CallLeafNoFPDirect"), ["dump_spec"], "extra_label", + function(dump_spec) {return callLeafInfo(dump_spec[0], 0);}); diff --git a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showTypes.filter b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showTypes.filter new file mode 100644 index 00000000000..16360d78c18 --- /dev/null +++ b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showTypes.filter @@ -0,0 +1,72 @@ +// This filter appends simplified type information to the (possibly already +// existing) extra-label line. +// If the phase type is available, show it. If the bottom type is available and +// differs from the bottom type, show it too (prefixed with 'B:'). + +// Simplify a reference type of the form +// "[5:]my/package/Class (package1/Class1,package2/Class2,..)" +// into +// "[5:]Class" +function simplify_reference_type(type) { + // Clean up interface lists in reference types. + var m = /(.*)\(.*\)(.*)/.exec(type); + if (m != null && typeof m[1] != 'undefined' && typeof m[2] != 'undefined') { + type = m[1] + m[2]; + } + // Remove package name in reference types. + var m2 = /(\d+:)?.*\/(.*)/.exec(type); + if (m2 != null && typeof m2[2] != 'undefined') { + type = (typeof m2[1] != 'undefined' ? m2[1] : "") + m2[2]; + } + return type; +} + +// Remove fixed input types for calls and simplify references. +function simplifyType(type) { + var callTypeStart = "{0:control, 1:abIO, 2:memory, 3:rawptr:BotPTR, 4:return_address"; + if (type.startsWith(callTypeStart)) { + // Exclude types of the first five outputs of call-like nodes. + type = type.replace(callTypeStart, "").replace("}", ""); + prefix = ", "; + if (type.startsWith(prefix)) { + type = type.slice(prefix.length); + } + components = type.split(", "); + for (i = 0; i < components.length; i++) { + components[i] = simplify_reference_type(components[i]); + } + type = "{" + components.join(", ") + "}"; + } else { + type = simplify_reference_type(type); + } + type = type.replace(">=", "≥").replace("<=", "≤"); + return type; +} + +// Merge a possibly existing extra label, bottom type, and phase type into a +// new, single extra label. +function mergeAndAppendTypeInfo(extra_label, bottom_type, phase_type) { + if (phase_type == null && bottom_type == null) { + return extra_label; + } + type = ""; + // Always show phase type, if available. + if (phase_type != null) { + type += simplifyType(phase_type); + } + // Show bottom type, if available and different from phase type. + if (bottom_type != null && bottom_type != phase_type) { + if (phase_type != null) { + type += " | "; + } + type += "B: "; + type += simplifyType(bottom_type); + } + new_extra_label = extra_label == null ? "" : (extra_label + " "); + return new_extra_label + type; +} + +editProperty(not(or([matches("bottom_type", "bottom"), + matches("bottom_type", "abIO")])), + ["extra_label", "bottom_type", "phase_type"], "extra_label", + function(propertyValues) {return mergeAndAppendTypeInfo(propertyValues[0], propertyValues[1], propertyValues[2]);}); diff --git a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/layer.xml b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/layer.xml index 6372b5a1e84..24533b75af0 100644 --- a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/layer.xml +++ b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/layer.xml @@ -15,12 +15,16 @@ - + - + + + + + diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java index 8bbc562a7d2..7ac76fdefba 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2023, 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 @@ -84,6 +84,16 @@ public boolean isHitAt(Point localLocation) { return middleWidget.isHitAt(localLocation); } + private void formatExtraLabel(boolean selected) { + // If the figure contains an extra label, use a light italic font to + // differentiate it from the regular label. + if (getFigure().getProperties().get("extra_label") != null) { + LabelWidget extraLabelWidget = labelWidgets.get(labelWidgets.size() - 1); + extraLabelWidget.setFont(Diagram.FONT.deriveFont(Font.ITALIC)); + extraLabelWidget.setForeground(selected ? getTextColor() : Color.DARK_GRAY); + } + } + public FigureWidget(final Figure f, DiagramScene scene) { super(scene); @@ -139,6 +149,7 @@ public FigureWidget(final Figure f, DiagramScene scene) { lw.setBorder(BorderFactory.createEmptyBorder()); lw.setCheckClipping(false); } + formatExtraLabel(false); if (getFigure().getWarning() != null) { ImageWidget warningWidget = new ImageWidget(scene, warningSign); @@ -194,6 +205,7 @@ protected void notifyStateChanged(ObjectState previousState, ObjectState state) for (LabelWidget labelWidget : labelWidgets) { labelWidget.setFont(font); } + formatExtraLabel(state.isSelected()); repaint(); } diff --git a/test/hotspot/gtest/gc/g1/test_g1CodeRootSet.cpp b/test/hotspot/gtest/gc/g1/test_g1CodeRootSet.cpp index 8ab1c60954d..2a8f52ec0d1 100644 --- a/test/hotspot/gtest/gc/g1/test_g1CodeRootSet.cpp +++ b/test/hotspot/gtest/gc/g1/test_g1CodeRootSet.cpp @@ -25,15 +25,7 @@ #include "gc/g1/g1CodeRootSet.hpp" #include "unittest.hpp" -class G1CodeRootSetTest : public ::testing::Test { - public: - - size_t threshold() { - return G1CodeRootSet::Threshold; - } -}; - -TEST_VM_F(G1CodeRootSetTest, g1_code_cache_rem_set) { +TEST_VM(G1CodeRootSet, g1_code_cache_rem_set) { G1CodeRootSet root_set; ASSERT_TRUE(root_set.is_empty()) << "Code root set must be initially empty " @@ -43,7 +35,7 @@ TEST_VM_F(G1CodeRootSetTest, g1_code_cache_rem_set) { ASSERT_EQ(root_set.length(), (size_t) 1) << "Added exactly one element, but" " set contains " << root_set.length() << " elements"; - const size_t num_to_add = (size_t) threshold() + 1; + const size_t num_to_add = 1000; for (size_t i = 1; i <= num_to_add; i++) { root_set.add((nmethod*) 1); @@ -60,9 +52,6 @@ TEST_VM_F(G1CodeRootSetTest, g1_code_cache_rem_set) { << "After adding in total " << num_to_add << " distinct code roots, " "they need to be in the set, but there are only " << root_set.length(); - ASSERT_EQ(root_set._table->table_size(), 512u) - << "should have grown to large hashtable"; - size_t num_popped = 0; for (size_t i = 1; i <= num_to_add; i++) { bool removed = root_set.remove((nmethod*) i); @@ -76,5 +65,5 @@ TEST_VM_F(G1CodeRootSetTest, g1_code_cache_rem_set) { << "Managed to pop " << num_popped << " code roots, but only " << num_to_add << " were added"; ASSERT_EQ(root_set.length(), 0u) - << "should have grown to large hashtable"; + << "should be empty"; } diff --git a/test/hotspot/gtest/runtime/test_atomic.cpp b/test/hotspot/gtest/runtime/test_atomic.cpp index 44448e52d29..35b415319ce 100644 --- a/test/hotspot/gtest/runtime/test_atomic.cpp +++ b/test/hotspot/gtest/runtime/test_atomic.cpp @@ -146,6 +146,51 @@ TEST(AtomicCmpxchgTest, int64) { Support().test(); } +struct AtomicCmpxchg1ByteStressSupport { + char _default_val; + int _base; + char _array[7+32+7]; + + AtomicCmpxchg1ByteStressSupport() : _default_val(0x7a), _base(7), _array{} {} + + void validate(char val, char val2, int index) { + for (int i = 0; i < 7; i++) { + EXPECT_EQ(_array[i], _default_val); + } + for (int i = 7; i < (7+32); i++) { + if (i == index) { + EXPECT_EQ(_array[i], val2); + } else { + EXPECT_EQ(_array[i], val); + } + } + for (int i = 0; i < 7; i++) { + EXPECT_EQ(_array[i], _default_val); + } + } + + void test_index(int index) { + char one = 1; + Atomic::cmpxchg(&_array[index], _default_val, one); + validate(_default_val, one, index); + + Atomic::cmpxchg(&_array[index], one, _default_val); + validate(_default_val, _default_val, index); + } + + void test() { + memset(_array, _default_val, sizeof(_array)); + for (int i = _base; i < (_base+32); i++) { + test_index(i); + } + } +}; + +TEST(AtomicCmpxchg1Byte, stress) { + AtomicCmpxchg1ByteStressSupport support; + support.test(); +} + template struct AtomicEnumTestSupport { volatile T _test_value; diff --git a/test/hotspot/jtreg/compiler/loopopts/TestNotifyOpaqueZeroTripGuardToCmpI.java b/test/hotspot/jtreg/compiler/loopopts/TestNotifyOpaqueZeroTripGuardToCmpI.java new file mode 100644 index 00000000000..c00dea51e32 --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/TestNotifyOpaqueZeroTripGuardToCmpI.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8316361 + * @summary Test that if input to OpaqueZeroTripGuard changes, we notify down to CmpI. + * @run main/othervm -Xcomp -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=10 + * -XX:CompileCommand=compileonly,compiler.loopopts.TestNotifyOpaqueZeroTripGuardToCmpI::test + * compiler.loopopts.TestNotifyOpaqueZeroTripGuardToCmpI + */ +package compiler.loopopts; + +public class TestNotifyOpaqueZeroTripGuardToCmpI { + static int x, y; + + public static void main(String[] strArr) { + test(); + } + + static void test() { + for (int i = 6; i < 43; i++) { + for (int j = i; j < 11; j++) { + x = y; + } + } + } +}