| /* | |
| * Copyright (c) 2001, 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_TASKQUEUE_HPP | |
| #define SHARE_GC_SHARED_TASKQUEUE_HPP | |
| #include "memory/allocation.hpp" | |
| #include "memory/padded.hpp" | |
| #include "oops/oopsHierarchy.hpp" | |
| #include "runtime/atomic.hpp" | |
| #include "utilities/debug.hpp" | |
| #include "utilities/globalDefinitions.hpp" | |
| #include "utilities/ostream.hpp" | |
| #include "utilities/stack.hpp" | |
| #if TASKQUEUE_STATS | |
| class TaskQueueStats { | |
| public: | |
| enum StatId { | |
| push, // number of taskqueue pushes | |
| pop, // number of taskqueue pops | |
| pop_slow, // subset of taskqueue pops that were done slow-path | |
| steal_attempt, // number of taskqueue steal attempts | |
| steal_empty, // number of empty taskqueues | |
| steal_contended, // number of contended steals | |
| steal_success, // number of successful steals | |
| steal_max_contended_in_a_row, // maximum number of contended steals in a row | |
| steal_bias_drop, // number of times the bias has been dropped | |
| overflow, // number of overflow pushes | |
| overflow_max_len, // max length of overflow stack | |
| last_stat_id | |
| }; | |
| public: | |
| inline TaskQueueStats() { reset(); } | |
| inline void record_push() { ++_stats[push]; } | |
| inline void record_pop() { ++_stats[pop]; } | |
| inline void record_pop_slow() { record_pop(); ++_stats[pop_slow]; } | |
| inline void record_steal_attempt(uint kind) { | |
| ++_stats[steal_attempt]; | |
| ++_stats[steal_empty + kind]; | |
| } | |
| inline void record_contended_in_a_row(uint in_a_row) { | |
| if (_stats[steal_max_contended_in_a_row] < in_a_row) { | |
| _stats[steal_max_contended_in_a_row] = in_a_row; | |
| } | |
| } | |
| inline void record_bias_drop() { ++_stats[steal_bias_drop]; } | |
| inline void record_overflow(size_t new_length); | |
| TaskQueueStats & operator +=(const TaskQueueStats & addend); | |
| inline size_t get(StatId id) const { return _stats[id]; } | |
| inline const size_t* get() const { return _stats; } | |
| inline void reset(); | |
| // Print the specified line of the header (does not include a line separator). | |
| static void print_header(unsigned int line, outputStream* const stream = tty, | |
| unsigned int width = 11); | |
| // Print the statistics (does not include a line separator). | |
| void print(outputStream* const stream = tty, unsigned int width = 11) const; | |
| DEBUG_ONLY(void verify() const;) | |
| private: | |
| size_t _stats[last_stat_id]; | |
| static const char * const _names[last_stat_id]; | |
| }; | |
| void TaskQueueStats::record_overflow(size_t new_len) { | |
| ++_stats[overflow]; | |
| if (new_len > _stats[overflow_max_len]) _stats[overflow_max_len] = new_len; | |
| } | |
| void TaskQueueStats::reset() { | |
| memset(_stats, 0, sizeof(_stats)); | |
| } | |
| #endif // TASKQUEUE_STATS | |
| // TaskQueueSuper collects functionality common to all GenericTaskQueue instances. | |
| template <unsigned int N, MemTag MT> | |
| class TaskQueueSuper: public CHeapObj<MT> { | |
| protected: | |
| // Internal type for indexing the queue; also used for the tag. | |
| typedef NOT_LP64(uint16_t) LP64_ONLY(uint32_t) idx_t; | |
| STATIC_ASSERT(N == idx_t(N)); // Ensure N fits in an idx_t. | |
| // N must be a power of 2 for computing modulo via masking. | |
| // N must be >= 2 for the algorithm to work at all, though larger is better. | |
| STATIC_ASSERT(N >= 2); | |
| STATIC_ASSERT(is_power_of_2(N)); | |
| static const uint MOD_N_MASK = N - 1; | |
| class Age { | |
| friend class TaskQueueSuper; | |
| public: | |
| explicit Age(size_t data = 0) : _data(data) {} | |
| Age(idx_t top, idx_t tag) { _fields._top = top; _fields._tag = tag; } | |
| idx_t top() const { return _fields._top; } | |
| idx_t tag() const { return _fields._tag; } | |
| bool operator ==(const Age& other) const { return _data == other._data; } | |
| private: | |
| struct fields { | |
| idx_t _top; | |
| idx_t _tag; | |
| }; | |
| union { | |
| size_t _data; | |
| fields _fields; | |
| }; | |
| STATIC_ASSERT(sizeof(size_t) >= sizeof(fields)); | |
| }; | |
| uint bottom_relaxed() const { | |
| return Atomic::load(&_bottom); | |
| } | |
| uint bottom_acquire() const { | |
| return Atomic::load_acquire(&_bottom); | |
| } | |
| void set_bottom_relaxed(uint new_bottom) { | |
| Atomic::store(&_bottom, new_bottom); | |
| } | |
| void release_set_bottom(uint new_bottom) { | |
| Atomic::release_store(&_bottom, new_bottom); | |
| } | |
| Age age_relaxed() const { | |
| return Age(Atomic::load(&_age._data)); | |
| } | |
| void set_age_relaxed(Age new_age) { | |
| Atomic::store(&_age._data, new_age._data); | |
| } | |
| Age cmpxchg_age(Age old_age, Age new_age) { | |
| return Age(Atomic::cmpxchg(&_age._data, old_age._data, new_age._data)); | |
| } | |
| idx_t age_top_relaxed() const { | |
| // Atomically accessing a subfield of an "atomic" member. | |
| return Atomic::load(&_age._fields._top); | |
| } | |
| // These both operate mod N. | |
| static uint increment_index(uint ind) { | |
| return (ind + 1) & MOD_N_MASK; | |
| } | |
| static uint decrement_index(uint ind) { | |
| return (ind - 1) & MOD_N_MASK; | |
| } | |
| // Returns a number in the range [0..N). If the result is "N-1", it should be | |
| // interpreted as 0. | |
| uint dirty_size(uint bot, uint top) const { | |
| return (bot - top) & MOD_N_MASK; | |
| } | |
| // Returns the size corresponding to the given "bot" and "top". | |
| uint clean_size(uint bot, uint top) const { | |
| uint sz = dirty_size(bot, top); | |
| // Has the queue "wrapped", so that bottom is less than top? There's a | |
| // complicated special case here. A pair of threads could perform pop_local | |
| // and pop_global operations concurrently, starting from a state in which | |
| // _bottom == _top+1. The pop_local could succeed in decrementing _bottom, | |
| // and the pop_global in incrementing _top (in which case the pop_global | |
| // will be awarded the contested queue element.) The resulting state must | |
| // be interpreted as an empty queue. (We only need to worry about one such | |
| // event: only the queue owner performs pop_local's, and several concurrent | |
| // threads attempting to perform the pop_global will all perform the same | |
| // CAS, and only one can succeed.) Any stealing thread that reads after | |
| // either the increment or decrement will see an empty queue, and will not | |
| // join the competitors. The "sz == -1" / "sz == N-1" state will not be | |
| // modified by concurrent threads, so the owner thread can reset the state | |
| // to _bottom == top so subsequent pushes will be performed normally. | |
| return (sz == N - 1) ? 0 : sz; | |
| } | |
| // Assert that we're not in the underflow state where bottom has | |
| // been decremented past top, so that _bottom+1 mod N == top. See | |
| // the discussion in clean_size. | |
| void assert_not_underflow(uint bot, uint top) const { | |
| assert_not_underflow(dirty_size(bot, top)); | |
| } | |
| void assert_not_underflow(uint dirty_size) const { | |
| assert(dirty_size != N - 1, "invariant"); | |
| } | |
| private: | |
| DEFINE_PAD_MINUS_SIZE(0, DEFAULT_PADDING_SIZE, 0); | |
| // Index of the first free element after the last one pushed (mod N). | |
| volatile uint _bottom; | |
| DEFINE_PAD_MINUS_SIZE(1, DEFAULT_PADDING_SIZE, sizeof(uint)); | |
| // top() is the index of the oldest pushed element (mod N), and tag() | |
| // is the associated epoch, to distinguish different modifications of | |
| // the age. There is no available element if top() == _bottom or | |
| // (_bottom - top()) mod N == N-1; the latter indicates underflow | |
| // during concurrent pop_local/pop_global. | |
| volatile Age _age; | |
| DEFINE_PAD_MINUS_SIZE(2, DEFAULT_PADDING_SIZE, sizeof(Age)); | |
| NONCOPYABLE(TaskQueueSuper); | |
| public: | |
| TaskQueueSuper() : _bottom(0), _age() {} | |
| // Assert the queue is empty. | |
| // Unreliable if there are concurrent pushes or pops. | |
| void assert_empty() const { | |
| assert(bottom_relaxed() == age_top_relaxed(), "not empty"); | |
| } | |
| bool is_empty() const { | |
| return size() == 0; | |
| } | |
| // Return an estimate of the number of elements in the queue. | |
| // Treats pop_local/pop_global race that underflows as empty. | |
| uint size() const { | |
| return clean_size(bottom_relaxed(), age_top_relaxed()); | |
| } | |
| // Discard the contents of the queue. | |
| void set_empty() { | |
| set_bottom_relaxed(0); | |
| set_age_relaxed(Age()); | |
| } | |
| // Maximum number of elements allowed in the queue. This is two less | |
| // than the actual queue size, so that a full queue can be distinguished | |
| // from underflow involving pop_local and concurrent pop_global operations | |
| // in GenericTaskQueue. | |
| uint max_elems() const { return N - 2; } | |
| // The result of a pop_global operation. The value order of this must correspond | |
| // to the order in the corresponding TaskQueueStats StatId. | |
| enum class PopResult : uint { | |
| Empty = 0, // Queue has been empty. t is undefined. | |
| Contended = 1, // Contention prevented successful retrieval, queue most likely contains elements. t is undefined. | |
| Success = 2 // Successfully retrieved an element, t contains it. | |
| }; | |
| TASKQUEUE_STATS_ONLY(void record_steal_attempt(PopResult kind) { stats.record_steal_attempt((uint)kind); }) | |
| TASKQUEUE_STATS_ONLY(TaskQueueStats stats;) | |
| }; | |
| // | |
| // GenericTaskQueue implements an ABP, Aurora-Blumofe-Plaxton, double- | |
| // ended-queue (deque), intended for use in work stealing. Queue operations | |
| // are non-blocking. | |
| // | |
| // A queue owner thread performs push() and pop_local() operations on one end | |
| // of the queue, while other threads may steal work using the pop_global() | |
| // method. | |
| // | |
| // The main difference to the original algorithm is that this | |
| // implementation allows wrap-around at the end of its allocated | |
| // storage, which is an array. | |
| // | |
| // The original paper is: | |
| // | |
| // Arora, N. S., Blumofe, R. D., and Plaxton, C. G. | |
| // Thread scheduling for multiprogrammed multiprocessors. | |
| // Theory of Computing Systems 34, 2 (2001), 115-144. | |
| // | |
| // The following paper provides an correctness proof and an | |
| // implementation for weakly ordered memory models including (pseudo-) | |
| // code containing memory barriers for a Chase-Lev deque. Chase-Lev is | |
| // similar to ABP, with the main difference that it allows resizing of the | |
| // underlying storage: | |
| // | |
| // Le, N. M., Pop, A., Cohen A., and Nardell, F. Z. | |
| // Correct and efficient work-stealing for weak memory models | |
| // Proceedings of the 18th ACM SIGPLAN symposium on Principles and | |
| // practice of parallel programming (PPoPP 2013), 69-80 | |
| // | |
| template <class E, MemTag MT, unsigned int N = TASKQUEUE_SIZE> | |
| class GenericTaskQueue: public TaskQueueSuper<N, MT> { | |
| protected: | |
| typedef typename TaskQueueSuper<N, MT>::Age Age; | |
| typedef typename TaskQueueSuper<N, MT>::idx_t idx_t; | |
| using TaskQueueSuper<N, MT>::MOD_N_MASK; | |
| using TaskQueueSuper<N, MT>::bottom_relaxed; | |
| using TaskQueueSuper<N, MT>::bottom_acquire; | |
| using TaskQueueSuper<N, MT>::set_bottom_relaxed; | |
| using TaskQueueSuper<N, MT>::release_set_bottom; | |
| using TaskQueueSuper<N, MT>::age_relaxed; | |
| using TaskQueueSuper<N, MT>::set_age_relaxed; | |
| using TaskQueueSuper<N, MT>::cmpxchg_age; | |
| using TaskQueueSuper<N, MT>::age_top_relaxed; | |
| using TaskQueueSuper<N, MT>::increment_index; | |
| using TaskQueueSuper<N, MT>::decrement_index; | |
| using TaskQueueSuper<N, MT>::dirty_size; | |
| using TaskQueueSuper<N, MT>::clean_size; | |
| using TaskQueueSuper<N, MT>::assert_not_underflow; | |
| public: | |
| typedef typename TaskQueueSuper<N, MT>::PopResult PopResult; | |
| using TaskQueueSuper<N, MT>::max_elems; | |
| using TaskQueueSuper<N, MT>::size; | |
| #if TASKQUEUE_STATS | |
| using TaskQueueSuper<N, MT>::stats; | |
| #endif | |
| private: | |
| // Slow path for pop_local, dealing with possible conflict with pop_global. | |
| bool pop_local_slow(uint localBot, Age oldAge); | |
| public: | |
| typedef E element_type; | |
| // Initializes the queue to empty. | |
| GenericTaskQueue(); | |
| // Push the task "t" on the queue. Returns "false" iff the queue is full. | |
| inline bool push(E t); | |
| // Attempts to claim a task from the "local" end of the queue (the most | |
| // recently pushed) as long as the number of entries exceeds the threshold. | |
| // If successfully claims a task, returns true and sets t to the task; | |
| // otherwise, returns false and t is unspecified. May fail and return | |
| // false because of a successful steal by pop_global. | |
| inline bool pop_local(E& t, uint threshold = 0); | |
| // Like pop_local(), but uses the "global" end of the queue (the least | |
| // recently pushed). | |
| PopResult pop_global(E& t); | |
| // Delete any resource associated with the queue. | |
| ~GenericTaskQueue(); | |
| // Apply fn to each element in the task queue. The queue must not | |
| // be modified while iterating. | |
| template<typename Fn> void iterate(Fn fn); | |
| private: | |
| // Base class has trailing padding. | |
| // Element array. | |
| E* _elems; | |
| DEFINE_PAD_MINUS_SIZE(1, DEFAULT_PADDING_SIZE, sizeof(E*)); | |
| // Queue owner local variables. Not to be accessed by other threads. | |
| static const uint InvalidQueueId = uint(-1); | |
| uint _last_stolen_queue_id; // The id of the queue we last stole from | |
| int _seed; // Current random seed used for selecting a random queue during stealing. | |
| DEFINE_PAD_MINUS_SIZE(2, DEFAULT_PADDING_SIZE, sizeof(uint) + sizeof(int)); | |
| public: | |
| int next_random_queue_id(); | |
| void set_last_stolen_queue_id(uint id) { _last_stolen_queue_id = id; } | |
| uint last_stolen_queue_id() const { return _last_stolen_queue_id; } | |
| bool is_last_stolen_queue_id_valid() const { return _last_stolen_queue_id != InvalidQueueId; } | |
| void invalidate_last_stolen_queue_id() { | |
| TASKQUEUE_STATS_ONLY(stats.record_bias_drop();) | |
| _last_stolen_queue_id = InvalidQueueId; | |
| } | |
| }; | |
| // OverflowTaskQueue is a TaskQueue that also includes an overflow stack for | |
| // elements that do not fit in the TaskQueue. | |
| // | |
| // This class hides two methods from super classes: | |
| // | |
| // push() - push onto the task queue or, if that fails, onto the overflow stack | |
| // is_empty() - return true if both the TaskQueue and overflow stack are empty | |
| // | |
| // Note that size() is not hidden--it returns the number of elements in the | |
| // TaskQueue, and does not include the size of the overflow stack. This | |
| // simplifies replacement of GenericTaskQueues with OverflowTaskQueues. | |
| template<class E, MemTag MT, unsigned int N = TASKQUEUE_SIZE> | |
| class OverflowTaskQueue: public GenericTaskQueue<E, MT, N> | |
| { | |
| public: | |
| typedef Stack<E, MT> overflow_t; | |
| typedef GenericTaskQueue<E, MT, N> taskqueue_t; | |
| TASKQUEUE_STATS_ONLY(using taskqueue_t::stats;) | |
| // Push task t onto the queue or onto the overflow stack. Return true. | |
| inline bool push(E t); | |
| // Try to push task t onto the queue only. Returns true if successful, false otherwise. | |
| inline bool try_push_to_taskqueue(E t); | |
| // Attempt to pop from the overflow stack; return true if anything was popped. | |
| inline bool pop_overflow(E& t); | |
| inline overflow_t* overflow_stack() { return &_overflow_stack; } | |
| inline bool taskqueue_empty() const { return taskqueue_t::is_empty(); } | |
| inline bool overflow_empty() const { return _overflow_stack.is_empty(); } | |
| inline bool is_empty() const { | |
| return taskqueue_empty() && overflow_empty(); | |
| } | |
| private: | |
| overflow_t _overflow_stack; | |
| }; | |
| class TaskQueueSetSuper { | |
| public: | |
| // Assert all queues in the set are empty. | |
| NOT_DEBUG(void assert_empty() const {}) | |
| DEBUG_ONLY(virtual void assert_empty() const = 0;) | |
| // Tasks in queue | |
| virtual uint tasks() const = 0; | |
| }; | |
| template <MemTag MT> class TaskQueueSetSuperImpl: public CHeapObj<MT>, public TaskQueueSetSuper { | |
| }; | |
| template<class T, MemTag MT> | |
| class GenericTaskQueueSet: public TaskQueueSetSuperImpl<MT> { | |
| public: | |
| typedef typename T::element_type E; | |
| typedef typename T::PopResult PopResult; | |
| private: | |
| uint _n; | |
| T** _queues; | |
| // Attempts to steal an element from a foreign queue (!= queue_num), setting | |
| // the result in t. Validity of this value and the return value is the same | |
| // as for the last pop_global() operation. | |
| PopResult steal_best_of_2(uint queue_num, E& t); | |
| public: | |
| GenericTaskQueueSet(uint n); | |
| ~GenericTaskQueueSet(); | |
| // Set the i'th queue to the provided queue. | |
| // Does not transfer ownership of the queue to this queue set. | |
| void register_queue(uint i, T* q); | |
| T* queue(uint n); | |
| // Try to steal a task from some other queue than queue_num. It may perform several attempts at doing so. | |
| // Returns if stealing succeeds, and sets "t" to the stolen task. | |
| bool steal(uint queue_num, E& t); | |
| DEBUG_ONLY(virtual void assert_empty() const;) | |
| virtual uint tasks() const; | |
| uint size() const { return _n; } | |
| #if TASKQUEUE_STATS | |
| private: | |
| static void print_taskqueue_stats_hdr(outputStream* const st, const char* label); | |
| public: | |
| void print_taskqueue_stats(outputStream* const st, const char* label); | |
| void reset_taskqueue_stats(); | |
| // Prints taskqueue set statistics into gc+task+stats=trace and resets | |
| // its statistics. | |
| void print_and_reset_taskqueue_stats(const char* label); | |
| #endif // TASKQUEUE_STATS | |
| }; | |
| template<class T, MemTag MT> void | |
| GenericTaskQueueSet<T, MT>::register_queue(uint i, T* q) { | |
| assert(i < _n, "index out of range."); | |
| _queues[i] = q; | |
| } | |
| template<class T, MemTag MT> T* | |
| GenericTaskQueueSet<T, MT>::queue(uint i) { | |
| assert(i < _n, "index out of range."); | |
| return _queues[i]; | |
| } | |
| #ifdef ASSERT | |
| template<class T, MemTag MT> | |
| void GenericTaskQueueSet<T, MT>::assert_empty() const { | |
| for (uint j = 0; j < _n; j++) { | |
| _queues[j]->assert_empty(); | |
| } | |
| } | |
| #endif // ASSERT | |
| template<class T, MemTag MT> | |
| uint GenericTaskQueueSet<T, MT>::tasks() const { | |
| uint n = 0; | |
| for (uint j = 0; j < _n; j++) { | |
| n += _queues[j]->size(); | |
| } | |
| return n; | |
| } | |
| // When to terminate from the termination protocol. | |
| class TerminatorTerminator: public CHeapObj<mtInternal> { | |
| public: | |
| virtual bool should_exit_termination() = 0; | |
| }; | |
| class ObjArrayTask | |
| { | |
| public: | |
| ObjArrayTask(oop o = nullptr, int idx = 0): _obj(o), _index(idx) { } | |
| ObjArrayTask(oop o, size_t idx): _obj(o), _index(int(idx)) { | |
| assert(idx <= size_t(max_jint), "too big"); | |
| } | |
| // Trivially copyable, for use in GenericTaskQueue. | |
| inline oop obj() const { return _obj; } | |
| inline int index() const { return _index; } | |
| DEBUG_ONLY(bool is_valid() const); // Tasks to be pushed/popped must be valid. | |
| private: | |
| oop _obj; | |
| int _index; | |
| }; | |
| class PartialArrayState; | |
| // Discriminated union over oop/oop*, narrowOop*, and PartialArrayState. | |
| // Uses a low tag in the associated pointer to identify the category. | |
| // Oop/oop* are overloaded using the same tag because they can not appear at the | |
| // same time. | |
| // Used as a task queue element type. | |
| class ScannerTask { | |
| void* _p; | |
| static const uintptr_t OopTag = 0; | |
| static const uintptr_t NarrowOopTag = 1; | |
| static const uintptr_t PartialArrayTag = 2; | |
| static const uintptr_t TagSize = 2; | |
| static const uintptr_t TagAlignment = 1 << TagSize; | |
| static const uintptr_t TagMask = TagAlignment - 1; | |
| static void* encode(void* p, uintptr_t tag) { | |
| assert(is_aligned(p, TagAlignment), "misaligned: " PTR_FORMAT, p2i(p)); | |
| return static_cast<char*>(p) + tag; | |
| } | |
| uintptr_t raw_value() const { | |
| return reinterpret_cast<uintptr_t>(_p); | |
| } | |
| bool has_tag(uintptr_t tag) const { | |
| return (raw_value() & TagMask) == tag; | |
| } | |
| void* decode(uintptr_t tag) const { | |
| assert(has_tag(tag), "precondition"); | |
| return static_cast<char*>(_p) - tag; | |
| } | |
| public: | |
| ScannerTask() : _p(nullptr) {} | |
| explicit ScannerTask(oop p) : _p(encode(p, OopTag)) {} | |
| explicit ScannerTask(oop* p) : _p(encode(p, OopTag)) {} | |
| explicit ScannerTask(narrowOop* p) : _p(encode(p, NarrowOopTag)) {} | |
| explicit ScannerTask(PartialArrayState* state) : | |
| _p(encode(state, PartialArrayTag)) {} | |
| // Trivially copyable. | |
| // Predicate implementations assume OopTag == 0, others are powers of 2. | |
| bool is_oop_ptr() const { | |
| return (raw_value() & (NarrowOopTag | PartialArrayTag)) == 0; | |
| } | |
| bool is_narrow_oop_ptr() const { | |
| return (raw_value() & NarrowOopTag) != 0; | |
| } | |
| bool is_partial_array_state() const { | |
| return (raw_value() & PartialArrayTag) != 0; | |
| } | |
| oop* to_oop_ptr() const { | |
| return static_cast<oop*>(decode(OopTag)); | |
| } | |
| oop to_oop() const { | |
| return cast_to_oop(decode(OopTag)); | |
| } | |
| narrowOop* to_narrow_oop_ptr() const { | |
| return static_cast<narrowOop*>(decode(NarrowOopTag)); | |
| } | |
| PartialArrayState* to_partial_array_state() const { | |
| return static_cast<PartialArrayState*>(decode(PartialArrayTag)); | |
| } | |
| }; | |
| #endif // SHARE_GC_SHARED_TASKQUEUE_HPP |