| /* |
| * Copyright (c) 2018, 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_SHARED_OOPSTORAGE_INLINE_HPP |
| #define SHARE_GC_SHARED_OOPSTORAGE_INLINE_HPP |
| |
| #include "gc/shared/oopStorage.hpp" |
| |
| #include "memory/allocation.hpp" |
| #include "oops/oop.hpp" |
| #include "runtime/safepoint.hpp" |
| #include "utilities/align.hpp" |
| #include "utilities/count_trailing_zeros.hpp" |
| #include "utilities/debug.hpp" |
| #include "utilities/globalDefinitions.hpp" |
| |
| #include <type_traits> |
| |
| // Array of all active blocks. Refcounted for lock-free reclaim of |
| // old array when a new array is allocated for expansion. |
| class OopStorage::ActiveArray { |
| friend class OopStorage::TestAccess; |
| |
| size_t _size; |
| volatile size_t _block_count; |
| mutable volatile int _refcount; |
| // Block* _blocks[1]; // Pseudo flexible array member. |
| |
| ActiveArray(size_t size); |
| ~ActiveArray(); |
| |
| NONCOPYABLE(ActiveArray); |
| |
| static size_t blocks_offset(); |
| Block* const* base_ptr() const; |
| |
| Block* const* block_ptr(size_t index) const; |
| Block** block_ptr(size_t index); |
| |
| public: |
| static ActiveArray* create(size_t size, |
| MemTag mem_tag = mtGC, |
| AllocFailType alloc_fail = AllocFailStrategy::EXIT_OOM); |
| static void destroy(ActiveArray* ba); |
| |
| inline Block* at(size_t i) const; |
| |
| size_t size() const; |
| size_t block_count() const; |
| size_t block_count_acquire() const; |
| void increment_refcount() const; |
| bool decrement_refcount() const; // Return true if zero, otherwise false |
| |
| // Support for OopStorage::allocate. |
| // Add block to the end of the array. Updates block count at the |
| // end of the operation, with a release_store. Returns true if the |
| // block was added, false if there was no room available. |
| // precondition: owner's _allocation_mutex is locked, or at safepoint. |
| bool push(Block* block); |
| |
| // Support OopStorage::delete_empty_blocks_xxx operations. |
| // Remove block from the array. |
| // precondition: block must be present at its active_index element. |
| void remove(Block* block); |
| |
| void copy_from(const ActiveArray* from); |
| }; |
| |
| inline size_t OopStorage::ActiveArray::blocks_offset() { |
| return align_up(sizeof(ActiveArray), sizeof(Block*)); |
| } |
| |
| inline OopStorage::Block* const* OopStorage::ActiveArray::base_ptr() const { |
| const void* ptr = reinterpret_cast<const char*>(this) + blocks_offset(); |
| return reinterpret_cast<Block* const*>(ptr); |
| } |
| |
| inline OopStorage::Block* const* OopStorage::ActiveArray::block_ptr(size_t index) const { |
| return base_ptr() + index; |
| } |
| |
| inline OopStorage::Block** OopStorage::ActiveArray::block_ptr(size_t index) { |
| return const_cast<Block**>(base_ptr() + index); |
| } |
| |
| inline OopStorage::Block* OopStorage::ActiveArray::at(size_t index) const { |
| assert(index < _block_count, "precondition"); |
| return *block_ptr(index); |
| } |
| |
| // A Block has an embedded AllocationListEntry to provide the links between |
| // Blocks in an AllocationList. |
| class OopStorage::AllocationListEntry { |
| friend class OopStorage::AllocationList; |
| |
| // Members are mutable, and we deal exclusively with pointers to |
| // const, to make const blocks easier to use; a block being const |
| // doesn't prevent modifying its list state. |
| mutable const Block* _prev; |
| mutable const Block* _next; |
| |
| NONCOPYABLE(AllocationListEntry); |
| |
| public: |
| AllocationListEntry(); |
| ~AllocationListEntry(); |
| }; |
| |
| // Fixed-sized array of oops, plus bookkeeping data. |
| // All blocks are in the storage's _active_array, at the block's _active_index. |
| // Non-full blocks are in the storage's _allocation_list, linked through the |
| // block's _allocation_list_entry. Empty blocks are at the end of that list. |
| class OopStorage::Block /* No base class, to avoid messing up alignment. */ { |
| // _data must be the first non-static data member, for alignment. |
| oop _data[BitsPerWord]; |
| static const unsigned _data_pos = 0; // Position of _data. |
| |
| volatile uintx _allocated_bitmask; // One bit per _data element. |
| intptr_t _owner_address; |
| void* _memory; // Unaligned storage containing block. |
| size_t _active_index; |
| AllocationListEntry _allocation_list_entry; |
| Block* volatile _deferred_updates_next; |
| volatile uintx _release_refcount; |
| |
| Block(const OopStorage* owner, void* memory); |
| ~Block(); |
| |
| void check_index(unsigned index) const; |
| unsigned get_index(const oop* ptr) const; |
| void atomic_add_allocated(uintx add); |
| |
| template<typename F, typename BlockPtr> |
| static bool iterate_impl(F f, BlockPtr b); |
| |
| NONCOPYABLE(Block); |
| |
| public: |
| const AllocationListEntry& allocation_list_entry() const; |
| |
| static size_t allocation_size(); |
| static size_t allocation_alignment_shift(); |
| |
| oop* get_pointer(unsigned index); |
| const oop* get_pointer(unsigned index) const; |
| |
| uintx bitmask_for_index(unsigned index) const; |
| uintx bitmask_for_entry(const oop* ptr) const; |
| |
| // Allocation bitmask accessors are racy. |
| bool is_full() const; |
| bool is_empty() const; |
| uintx allocated_bitmask() const; |
| |
| bool is_safe_to_delete() const; |
| |
| Block* deferred_updates_next() const; |
| void set_deferred_updates_next(Block* new_next); |
| |
| bool contains(const oop* ptr) const; |
| |
| size_t active_index() const; |
| void set_active_index(size_t index); |
| static size_t active_index_safe(const Block* block); // Returns 0 if access fails. |
| |
| // Return block of owner containing ptr, if ptr is a valid entry of owner. |
| // If ptr is not a valid entry of owner then returns either null or a "false |
| // positive" pointer; see allocation_status. |
| // precondition: ptr != nullptr |
| static Block* block_for_ptr(const OopStorage* owner, const oop* ptr); |
| |
| oop* allocate(); |
| uintx allocate_all(); |
| static Block* new_block(const OopStorage* owner); |
| static void delete_block(const Block& block); |
| |
| void release_entries(uintx releasing, OopStorage* owner); |
| |
| template<typename F> bool iterate(F f); |
| template<typename F> bool iterate(F f) const; |
| |
| bool print_containing(const oop* addr, outputStream* st); |
| }; // class Block |
| |
| inline OopStorage::Block* OopStorage::AllocationList::head() { |
| return const_cast<Block*>(_head); |
| } |
| |
| inline OopStorage::Block* OopStorage::AllocationList::tail() { |
| return const_cast<Block*>(_tail); |
| } |
| |
| inline const OopStorage::Block* OopStorage::AllocationList::chead() const { |
| return _head; |
| } |
| |
| inline const OopStorage::Block* OopStorage::AllocationList::ctail() const { |
| return _tail; |
| } |
| |
| inline OopStorage::Block* OopStorage::AllocationList::prev(Block& block) { |
| return const_cast<Block*>(block.allocation_list_entry()._prev); |
| } |
| |
| inline OopStorage::Block* OopStorage::AllocationList::next(Block& block) { |
| return const_cast<Block*>(block.allocation_list_entry()._next); |
| } |
| |
| inline const OopStorage::Block* OopStorage::AllocationList::prev(const Block& block) const { |
| return block.allocation_list_entry()._prev; |
| } |
| |
| inline const OopStorage::Block* OopStorage::AllocationList::next(const Block& block) const { |
| return block.allocation_list_entry()._next; |
| } |
| |
| template<typename Closure> |
| class OopStorage::OopFn { |
| public: |
| explicit OopFn(Closure* cl) : _cl(cl) {} |
| |
| template<typename OopPtr> // [const] oop* |
| bool operator()(OopPtr ptr) const { |
| _cl->do_oop(ptr); |
| return true; |
| } |
| |
| private: |
| Closure* _cl; |
| }; |
| |
| template<typename Closure> |
| inline OopStorage::OopFn<Closure> OopStorage::oop_fn(Closure* cl) { |
| return OopFn<Closure>(cl); |
| } |
| |
| template<typename IsAlive, typename F> |
| class OopStorage::IfAliveFn { |
| public: |
| IfAliveFn(IsAlive* is_alive, F f) : _is_alive(is_alive), _f(f) {} |
| |
| bool operator()(oop* ptr) const { |
| bool result = true; |
| oop v = *ptr; |
| if (v != nullptr) { |
| if (_is_alive->do_object_b(v)) { |
| result = _f(ptr); |
| } else { |
| *ptr = nullptr; // Clear dead value. |
| } |
| } |
| return result; |
| } |
| |
| private: |
| IsAlive* _is_alive; |
| F _f; |
| }; |
| |
| template<typename IsAlive, typename F> |
| inline OopStorage::IfAliveFn<IsAlive, F> OopStorage::if_alive_fn(IsAlive* is_alive, F f) { |
| return IfAliveFn<IsAlive, F>(is_alive, f); |
| } |
| |
| template<typename F> |
| class OopStorage::SkipNullFn { |
| public: |
| SkipNullFn(F f) : _f(f) {} |
| |
| template<typename OopPtr> // [const] oop* |
| bool operator()(OopPtr ptr) const { |
| return (*ptr != nullptr) ? _f(ptr) : true; |
| } |
| |
| private: |
| F _f; |
| }; |
| |
| template<typename F> |
| inline OopStorage::SkipNullFn<F> OopStorage::skip_null_fn(F f) { |
| return SkipNullFn<F>(f); |
| } |
| |
| // Inline Block accesses for use in iteration loops. |
| |
| inline const OopStorage::AllocationListEntry& OopStorage::Block::allocation_list_entry() const { |
| return _allocation_list_entry; |
| } |
| |
| inline void OopStorage::Block::check_index(unsigned index) const { |
| assert(index < ARRAY_SIZE(_data), "Index out of bounds: %u", index); |
| } |
| |
| inline oop* OopStorage::Block::get_pointer(unsigned index) { |
| check_index(index); |
| return &_data[index]; |
| } |
| |
| inline const oop* OopStorage::Block::get_pointer(unsigned index) const { |
| check_index(index); |
| return &_data[index]; |
| } |
| |
| inline uintx OopStorage::Block::allocated_bitmask() const { |
| return _allocated_bitmask; |
| } |
| |
| inline uintx OopStorage::Block::bitmask_for_index(unsigned index) const { |
| check_index(index); |
| return uintx(1) << index; |
| } |
| |
| // Provide const or non-const iteration, depending on whether BlockPtr |
| // is const Block* or Block*, respectively. |
| template<typename F, typename BlockPtr> // BlockPtr := [const] Block* |
| inline bool OopStorage::Block::iterate_impl(F f, BlockPtr block) { |
| uintx bitmask = block->allocated_bitmask(); |
| while (bitmask != 0) { |
| unsigned index = count_trailing_zeros(bitmask); |
| bitmask ^= block->bitmask_for_index(index); |
| if (!f(block->get_pointer(index))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| template<typename F> |
| inline bool OopStorage::Block::iterate(F f) { |
| return iterate_impl(f, this); |
| } |
| |
| template<typename F> |
| inline bool OopStorage::Block::iterate(F f) const { |
| return iterate_impl(f, this); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Support for serial iteration, always at a safepoint. |
| |
| // Provide const or non-const iteration, depending on whether Storage is |
| // const OopStorage* or OopStorage*, respectively. |
| template<typename F, typename Storage> // Storage := [const] OopStorage |
| inline bool OopStorage::iterate_impl(F f, Storage* storage) { |
| assert_at_safepoint(); |
| // Propagate const/non-const iteration to the block layer, by using |
| // const or non-const blocks as corresponding to Storage. |
| using BlockPtr = std::conditional_t<std::is_const<Storage>::value, const Block*, Block*>; |
| ActiveArray* blocks = storage->_active_array; |
| size_t limit = blocks->block_count(); |
| for (size_t i = 0; i < limit; ++i) { |
| BlockPtr block = blocks->at(i); |
| if (!block->iterate(f)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| template<typename F> |
| inline bool OopStorage::iterate_safepoint(F f) { |
| return iterate_impl(f, this); |
| } |
| |
| template<typename F> |
| inline bool OopStorage::iterate_safepoint(F f) const { |
| return iterate_impl(f, this); |
| } |
| |
| template<typename Closure> |
| inline void OopStorage::oops_do(Closure* cl) { |
| iterate_safepoint(oop_fn(cl)); |
| } |
| |
| template<typename Closure> |
| inline void OopStorage::oops_do(Closure* cl) const { |
| iterate_safepoint(oop_fn(cl)); |
| } |
| |
| template<typename Closure> |
| inline void OopStorage::weak_oops_do(Closure* cl) { |
| iterate_safepoint(skip_null_fn(oop_fn(cl))); |
| } |
| |
| template<typename IsAliveClosure, typename Closure> |
| inline void OopStorage::weak_oops_do(IsAliveClosure* is_alive, Closure* cl) { |
| iterate_safepoint(if_alive_fn(is_alive, oop_fn(cl))); |
| } |
| |
| #endif // SHARE_GC_SHARED_OOPSTORAGE_INLINE_HPP |