| /* | |
| * Copyright (c) 2021, 2024, 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. | |
| * | |
| */ | |
| #ifndef SHARE_GC_G1_G1CARDSETCONTAINERS_INLINE_HPP | |
| #define SHARE_GC_G1_G1CARDSETCONTAINERS_INLINE_HPP | |
| #include "gc/g1/g1CardSetContainers.hpp" | |
| #include "gc/g1/g1GCPhaseTimes.hpp" | |
| #include "utilities/bitMap.inline.hpp" | |
| #include "utilities/checkedCast.hpp" | |
| #include "utilities/globalDefinitions.hpp" | |
| #include "utilities/spinYield.hpp" | |
| inline G1CardSetInlinePtr::ContainerPtr G1CardSetInlinePtr::merge(ContainerPtr orig_value, uint card_in_region, uint idx, uint bits_per_card) { | |
| assert((idx & (SizeFieldMask >> SizeFieldPos)) == idx, "Index %u too large to fit into size field", idx); | |
| assert(card_in_region < ((uint)1 << bits_per_card), "Card %u too large to fit into card value field", card_in_region); | |
| uint card_pos = card_pos_for(idx, bits_per_card); | |
| assert(card_pos + bits_per_card < BitsInValue, "Putting card at pos %u with %u bits would extend beyond pointer", card_pos, bits_per_card); | |
| // Check that we do not touch any fields we do not own. | |
| uintptr_t mask = ((((uintptr_t)1 << bits_per_card) - 1) << card_pos); | |
| assert(((uintptr_t)orig_value & mask) == 0, "The bits in the new range should be empty; orig_value " PTR_FORMAT " mask " PTR_FORMAT, p2i(orig_value), mask); | |
| uintptr_t value = ((uintptr_t)(idx + 1) << SizeFieldPos) | ((uintptr_t)card_in_region << card_pos); | |
| uintptr_t res = (((uintptr_t)orig_value & ~SizeFieldMask) | value); | |
| return (ContainerPtr)res; | |
| } | |
| inline G1AddCardResult G1CardSetInlinePtr::add(uint card_idx, uint bits_per_card, uint max_cards_in_inline_ptr) { | |
| assert(_value_addr != nullptr, "No value address available, cannot add to set."); | |
| uint cur_idx = 0; | |
| while (true) { | |
| uint num_cards = num_cards_in(_value); | |
| if (num_cards > 0) { | |
| cur_idx = find(card_idx, bits_per_card, cur_idx, num_cards); | |
| } | |
| // Check if the card is already stored in the pointer. | |
| if (cur_idx < num_cards) { | |
| return Found; | |
| } | |
| // Check if there is actually enough space. | |
| if (num_cards >= max_cards_in_inline_ptr) { | |
| return Overflow; | |
| } | |
| ContainerPtr new_value = merge(_value, card_idx, num_cards, bits_per_card); | |
| ContainerPtr old_value = Atomic::cmpxchg(_value_addr, _value, new_value, memory_order_relaxed); | |
| if (_value == old_value) { | |
| return Added; | |
| } | |
| // Update values and retry. | |
| _value = old_value; | |
| // The value of the pointer may have changed to something different than | |
| // an inline card set. Exit then instead of overwriting. | |
| if (G1CardSet::container_type(_value) != G1CardSet::ContainerInlinePtr) { | |
| return Overflow; | |
| } | |
| } | |
| } | |
| inline uint G1CardSetInlinePtr::find(uint card_idx, uint bits_per_card, uint start_at, uint num_cards) { | |
| assert(start_at < num_cards, "Precondition!"); | |
| uintptr_t const card_mask = (1 << bits_per_card) - 1; | |
| uintptr_t value = ((uintptr_t)_value) >> card_pos_for(start_at, bits_per_card); | |
| // Check if the card is already stored in the pointer. | |
| for (uint cur_idx = start_at; cur_idx < num_cards; cur_idx++) { | |
| if ((value & card_mask) == card_idx) { | |
| return cur_idx; | |
| } | |
| value >>= bits_per_card; | |
| } | |
| return num_cards; | |
| } | |
| inline bool G1CardSetInlinePtr::contains(uint card_idx, uint bits_per_card) { | |
| uint num_cards = num_cards_in(_value); | |
| if (num_cards == 0) { | |
| return false; | |
| } | |
| uint cur_idx = find(card_idx, bits_per_card, 0, num_cards); | |
| return cur_idx < num_cards; | |
| } | |
| template <class CardVisitor> | |
| inline void G1CardSetInlinePtr::iterate(CardVisitor& found, uint bits_per_card) { | |
| uint const num_cards = num_cards_in(_value); | |
| uintptr_t const card_mask = (1 << bits_per_card) - 1; | |
| uintptr_t value = ((uintptr_t)_value) >> card_pos_for(0, bits_per_card); | |
| for (uint cur_idx = 0; cur_idx < num_cards; cur_idx++) { | |
| found(value & card_mask); | |
| value >>= bits_per_card; | |
| } | |
| } | |
| inline bool G1CardSetContainer::try_increment_refcount() { | |
| uintptr_t old_value = refcount(); | |
| while (true) { | |
| if (old_value < 3 || (old_value & 0x1) == 0) { // reclaimed, reference counts are odd numbers starting at 3 | |
| return false; // dead, can't revive. | |
| } | |
| uintptr_t new_value = old_value + 2; | |
| uintptr_t ref_count = Atomic::cmpxchg(&_ref_count, old_value, new_value); | |
| if (ref_count == old_value) { | |
| return true; | |
| } | |
| old_value = ref_count; | |
| } | |
| } | |
| inline uintptr_t G1CardSetContainer::decrement_refcount() { | |
| uintptr_t old_value = refcount(); | |
| assert((old_value & 0x1) != 0 && old_value >= 3, "precondition"); | |
| return Atomic::sub(&_ref_count, 2u); | |
| } | |
| inline G1CardSetArray::G1CardSetArray(uint card_in_region, EntryCountType num_cards) : | |
| G1CardSetContainer(), | |
| _size(num_cards), | |
| _num_entries(1) { | |
| assert(_size > 0, "CardSetArray of size 0 not supported."); | |
| assert(_size < LockBitMask, "Only support CardSetArray of size %u or smaller.", LockBitMask - 1); | |
| *entry_addr(0) = checked_cast<EntryDataType>(card_in_region); | |
| } | |
| inline G1CardSetArray::G1CardSetArrayLocker::G1CardSetArrayLocker(EntryCountType volatile* num_entries_addr) : | |
| _num_entries_addr(num_entries_addr) { | |
| SpinYield s; | |
| EntryCountType num_entries = Atomic::load(_num_entries_addr) & EntryMask; | |
| while (true) { | |
| EntryCountType old_value = Atomic::cmpxchg(_num_entries_addr, | |
| num_entries, | |
| (EntryCountType)(num_entries | LockBitMask)); | |
| if (old_value == num_entries) { | |
| // Succeeded locking the array. | |
| _local_num_entries = num_entries; | |
| break; | |
| } | |
| // Failed. Retry (with the lock bit stripped again). | |
| num_entries = old_value & EntryMask; | |
| s.wait(); | |
| } | |
| } | |
| inline G1CardSetArray::EntryDataType const* G1CardSetArray::base_addr() const { | |
| const void* ptr = reinterpret_cast<const char*>(this) + header_size_in_bytes(); | |
| return reinterpret_cast<EntryDataType const*>(ptr); | |
| } | |
| inline G1CardSetArray::EntryDataType const* G1CardSetArray::entry_addr(EntryCountType index) const { | |
| assert(index < _num_entries, "precondition"); | |
| return base_addr() + index; | |
| } | |
| inline G1CardSetArray::EntryDataType* G1CardSetArray::entry_addr(EntryCountType index) { | |
| return const_cast<EntryDataType*>(const_cast<const G1CardSetArray*>(this)->entry_addr(index)); | |
| } | |
| inline G1CardSetArray::EntryDataType G1CardSetArray::at(EntryCountType index) const { | |
| return *entry_addr(index); | |
| } | |
| inline G1AddCardResult G1CardSetArray::add(uint card_idx) { | |
| assert(card_idx < (1u << (sizeof(EntryDataType) * BitsPerByte)), | |
| "Card index %u does not fit allowed card value range.", card_idx); | |
| EntryCountType num_entries = Atomic::load_acquire(&_num_entries) & EntryMask; | |
| EntryCountType idx = 0; | |
| for (; idx < num_entries; idx++) { | |
| if (at(idx) == card_idx) { | |
| return Found; | |
| } | |
| } | |
| // Since we did not find the card, lock. | |
| G1CardSetArrayLocker x(&_num_entries); | |
| // Reload number of entries from the G1CardSetArrayLocker as it might have changed. | |
| // It already read the actual value with the necessary synchronization. | |
| num_entries = x.num_entries(); | |
| // Look if the cards added while waiting for the lock are the same as our card. | |
| for (; idx < num_entries; idx++) { | |
| if (at(idx) == card_idx) { | |
| return Found; | |
| } | |
| } | |
| // Check if there is space left. | |
| if (num_entries == _size) { | |
| return Overflow; | |
| } | |
| *entry_addr(num_entries) = checked_cast<EntryDataType>(card_idx); | |
| x.inc_num_entries(); | |
| return Added; | |
| } | |
| inline bool G1CardSetArray::contains(uint card_idx) { | |
| EntryCountType num_entries = Atomic::load_acquire(&_num_entries) & EntryMask; | |
| for (EntryCountType idx = 0; idx < num_entries; idx++) { | |
| if (at(idx) == card_idx) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| template <class CardVisitor> | |
| void G1CardSetArray::iterate(CardVisitor& found) { | |
| EntryCountType num_entries = Atomic::load_acquire(&_num_entries) & EntryMask; | |
| for (EntryCountType idx = 0; idx < num_entries; idx++) { | |
| found(at(idx)); | |
| } | |
| } | |
| inline size_t G1CardSetArray::header_size_in_bytes() { | |
| return offset_of(G1CardSetArray, _data); | |
| } | |
| inline G1CardSetBitMap::G1CardSetBitMap(uint card_in_region, uint size_in_bits) : | |
| G1CardSetContainer(), _num_bits_set(1) { | |
| assert(size_in_bits % (sizeof(_bits[0]) * BitsPerByte) == 0, | |
| "Size %u should be aligned to bitmap word size.", size_in_bits); | |
| BitMapView bm(_bits, size_in_bits); | |
| bm.clear(); | |
| bm.set_bit(card_in_region); | |
| } | |
| inline G1AddCardResult G1CardSetBitMap::add(uint card_idx, size_t threshold, size_t size_in_bits) { | |
| BitMapView bm(_bits, size_in_bits); | |
| if (_num_bits_set >= threshold) { | |
| return bm.at(card_idx) ? Found : Overflow; | |
| } | |
| if (bm.par_set_bit(card_idx)) { | |
| Atomic::inc(&_num_bits_set, memory_order_relaxed); | |
| return Added; | |
| } | |
| return Found; | |
| } | |
| template <class CardVisitor> | |
| inline void G1CardSetBitMap::iterate(CardVisitor& found, size_t size_in_bits, uint offset) { | |
| BitMapView bm(_bits, size_in_bits); | |
| bm.iterate([&](BitMap::idx_t idx) { found(offset | (uint)idx); }); | |
| } | |
| inline size_t G1CardSetBitMap::header_size_in_bytes() { | |
| return offset_of(G1CardSetBitMap, _bits); | |
| } | |
| inline G1CardSetHowl::ContainerPtr const* G1CardSetHowl::container_addr(EntryCountType index) const { | |
| assert(index < _num_entries, "precondition"); | |
| return buckets() + index; | |
| } | |
| inline G1CardSetHowl::ContainerPtr* G1CardSetHowl::container_addr(EntryCountType index) { | |
| return const_cast<ContainerPtr*>(const_cast<const G1CardSetHowl*>(this)->container_addr(index)); | |
| } | |
| inline G1CardSetHowl::ContainerPtr G1CardSetHowl::at(EntryCountType index) const { | |
| return *container_addr(index); | |
| } | |
| inline G1CardSetHowl::ContainerPtr const* G1CardSetHowl::buckets() const { | |
| const void* ptr = reinterpret_cast<const char*>(this) + header_size_in_bytes(); | |
| return reinterpret_cast<ContainerPtr const*>(ptr); | |
| } | |
| inline G1CardSetHowl::G1CardSetHowl(EntryCountType card_in_region, G1CardSetConfiguration* config) : | |
| G1CardSetContainer(), | |
| _num_entries((config->max_cards_in_array() + 1)) /* Card Transfer will not increment _num_entries */ { | |
| EntryCountType num_buckets = config->num_buckets_in_howl(); | |
| EntryCountType bucket = config->howl_bucket_index(card_in_region); | |
| for (uint i = 0; i < num_buckets; ++i) { | |
| *container_addr(i) = G1CardSetInlinePtr(); | |
| if (i == bucket) { | |
| G1CardSetInlinePtr value(container_addr(i), at(i)); | |
| value.add(card_in_region, config->inline_ptr_bits_per_card(), config->max_cards_in_inline_ptr()); | |
| } | |
| } | |
| } | |
| inline bool G1CardSetHowl::contains(uint card_idx, G1CardSetConfiguration* config) { | |
| EntryCountType bucket = config->howl_bucket_index(card_idx); | |
| ContainerPtr* array_entry = container_addr(bucket); | |
| ContainerPtr container = Atomic::load_acquire(array_entry); | |
| switch (G1CardSet::container_type(container)) { | |
| case G1CardSet::ContainerArrayOfCards: { | |
| return G1CardSet::container_ptr<G1CardSetArray>(container)->contains(card_idx); | |
| } | |
| case G1CardSet::ContainerBitMap: { | |
| uint card_offset = config->howl_bitmap_offset(card_idx); | |
| return G1CardSet::container_ptr<G1CardSetBitMap>(container)->contains(card_offset, config->max_cards_in_howl_bitmap()); | |
| } | |
| case G1CardSet::ContainerInlinePtr: { | |
| G1CardSetInlinePtr ptr(container); | |
| return ptr.contains(card_idx, config->inline_ptr_bits_per_card()); | |
| } | |
| case G1CardSet::ContainerHowl: {// Fullcard set entry | |
| assert(container == G1CardSet::FullCardSet, "Must be"); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| template <class CardOrRangeVisitor> | |
| inline void G1CardSetHowl::iterate(CardOrRangeVisitor& found, G1CardSetConfiguration* config) { | |
| for (uint i = 0; i < config->num_buckets_in_howl(); ++i) { | |
| iterate_cardset(at(i), i, found, config); | |
| } | |
| } | |
| template <class ContainerPtrVisitor> | |
| inline void G1CardSetHowl::iterate(ContainerPtrVisitor& found, uint num_card_sets) { | |
| for (uint i = 0; i < num_card_sets; ++i) { | |
| found(container_addr(i)); | |
| } | |
| } | |
| template <class CardOrRangeVisitor> | |
| inline void G1CardSetHowl::iterate_cardset(ContainerPtr const container, uint index, CardOrRangeVisitor& found, G1CardSetConfiguration* config) { | |
| switch (G1CardSet::container_type(container)) { | |
| case G1CardSet::ContainerInlinePtr: { | |
| if (found.start_iterate(G1GCPhaseTimes::MergeRSHowlInline)) { | |
| G1CardSetInlinePtr ptr(container); | |
| ptr.iterate(found, config->inline_ptr_bits_per_card()); | |
| } | |
| return; | |
| } | |
| case G1CardSet::ContainerArrayOfCards: { | |
| if (found.start_iterate(G1GCPhaseTimes::MergeRSHowlArrayOfCards)) { | |
| G1CardSet::container_ptr<G1CardSetArray>(container)->iterate(found); | |
| } | |
| return; | |
| } | |
| case G1CardSet::ContainerBitMap: { | |
| if (found.start_iterate(G1GCPhaseTimes::MergeRSHowlBitmap)) { | |
| uint offset = index << config->log2_max_cards_in_howl_bitmap(); | |
| G1CardSet::container_ptr<G1CardSetBitMap>(container)->iterate(found, config->max_cards_in_howl_bitmap(), offset); | |
| } | |
| return; | |
| } | |
| case G1CardSet::ContainerHowl: { // actually FullCardSet | |
| assert(container == G1CardSet::FullCardSet, "Must be"); | |
| if (found.start_iterate(G1GCPhaseTimes::MergeRSHowlFull)) { | |
| uint offset = index << config->log2_max_cards_in_howl_bitmap(); | |
| found(offset, config->max_cards_in_howl_bitmap()); | |
| } | |
| return; | |
| } | |
| } | |
| } | |
| inline G1CardSetHowl::EntryCountType G1CardSetHowl::num_buckets(size_t size_in_bits, size_t max_cards_in_array, size_t max_num_buckets) { | |
| size_t size_bitmap_bytes = BitMap::calc_size_in_words(size_in_bits) * BytesPerWord; | |
| // Ensure that in the worst case arrays consume half the memory size | |
| // of storing the entire bitmap | |
| size_t max_size_arrays_bytes = size_bitmap_bytes / 2; | |
| size_t size_array_bytes = max_cards_in_array * sizeof(G1CardSetArray::EntryDataType); | |
| size_t num_arrays = max_size_arrays_bytes / size_array_bytes; | |
| // We use shifts and masks for indexing the array. So round down to the next | |
| // power of two to not use more than expected memory. | |
| num_arrays = round_down_power_of_2(MAX2((size_t)1, MIN2(num_arrays, max_num_buckets))); | |
| return (EntryCountType)num_arrays; | |
| } | |
| inline size_t G1CardSetHowl::header_size_in_bytes() { | |
| return offset_of(G1CardSetHowl, _buckets); | |
| } | |
| #endif // SHARE_GC_G1_G1CARDSETCONTAINERS_INLINE_HPP |