Skip to content

Commit 6f1c573

Browse files
author
Kim Barrett
committed
8371923: Update LockFreeStack for Atomic<T>
Reviewed-by: iwalulya, dholmes
1 parent 223cc64 commit 6f1c573

File tree

2 files changed

+53
-32
lines changed

2 files changed

+53
-32
lines changed

src/hotspot/share/utilities/lockFreeStack.hpp

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#ifndef SHARE_UTILITIES_LOCKFREESTACK_HPP
2626
#define SHARE_UTILITIES_LOCKFREESTACK_HPP
2727

28+
#include "runtime/atomic.hpp"
2829
#include "runtime/atomicAccess.hpp"
2930
#include "utilities/debug.hpp"
3031
#include "utilities/globalDefinitions.hpp"
@@ -34,11 +35,14 @@
3435
// a result, there is no allocation involved in adding objects to the stack
3536
// or removing them from the stack.
3637
//
37-
// To be used in a LockFreeStack of objects of type T, an object of
38-
// type T must have a list entry member of type T* volatile, with an
39-
// non-member accessor function returning a pointer to that member. A
40-
// LockFreeStack is associated with the class of its elements and an
41-
// entry member from that class.
38+
// To be used in a LockFreeStack of objects of type T, an object of type T
39+
// must have a list entry member. A list entry member is a data member whose
40+
// type is either (1) Atomic<T*>, or (2) T* volatile. There must be a
41+
// non-member or static member function returning a pointer to that member,
42+
// which is used to provide access to it by a LockFreeStack. A LockFreeStack
43+
// is associated with the class of its elements and an entry member from that
44+
// class by being specialized on the element class and a pointer to the
45+
// function for accessing that entry member.
4246
//
4347
// An object can be in multiple stacks at the same time, so long as
4448
// each stack uses a different entry member. That is, the class of the
@@ -52,25 +56,34 @@
5256
//
5357
// \tparam T is the class of the elements in the stack.
5458
//
55-
// \tparam next_ptr is a function pointer. Applying this function to
59+
// \tparam next_accessor is a function pointer. Applying this function to
5660
// an object of type T must return a pointer to the list entry member
5761
// of the object associated with the LockFreeStack type.
58-
template<typename T, T* volatile* (*next_ptr)(T&)>
62+
template<typename T, auto next_accessor>
5963
class LockFreeStack {
60-
T* volatile _top;
64+
Atomic<T*> _top;
6165

6266
void prepend_impl(T* first, T* last) {
6367
T* cur = top();
6468
T* old;
6569
do {
6670
old = cur;
6771
set_next(*last, cur);
68-
cur = AtomicAccess::cmpxchg(&_top, cur, first);
72+
cur = _top.compare_exchange(cur, first);
6973
} while (old != cur);
7074
}
7175

7276
NONCOPYABLE(LockFreeStack);
7377

78+
template<typename NextAccessor>
79+
static constexpr void use_atomic_access_impl(NextAccessor) {
80+
static_assert(DependentAlwaysFalse<NextAccessor>, "Invalid next accessor");
81+
}
82+
static constexpr bool use_atomic_access_impl(T* volatile* (*)(T&)) { return true; }
83+
static constexpr bool use_atomic_access_impl(Atomic<T*>* (*)(T&)) { return false; }
84+
85+
static constexpr bool use_atomic_access = use_atomic_access_impl(next_accessor);
86+
7487
public:
7588
LockFreeStack() : _top(nullptr) {}
7689
~LockFreeStack() { assert(empty(), "stack not empty"); }
@@ -89,7 +102,7 @@ class LockFreeStack {
89102
new_top = next(*result);
90103
}
91104
// CAS even on empty pop, for consistent membar behavior.
92-
result = AtomicAccess::cmpxchg(&_top, result, new_top);
105+
result = _top.compare_exchange(result, new_top);
93106
} while (result != old);
94107
if (result != nullptr) {
95108
set_next(*result, nullptr);
@@ -101,7 +114,7 @@ class LockFreeStack {
101114
// list of elements. Acts as a full memory barrier.
102115
// postcondition: empty()
103116
T* pop_all() {
104-
return AtomicAccess::xchg(&_top, (T*)nullptr);
117+
return _top.exchange(nullptr);
105118
}
106119

107120
// Atomically adds value to the top of this stack. Acts as a full
@@ -143,9 +156,9 @@ class LockFreeStack {
143156
// Return true if the stack is empty.
144157
bool empty() const { return top() == nullptr; }
145158

146-
// Return the most recently pushed element, or nullptr if the stack is empty.
159+
// Return the most recently pushed element, or null if the stack is empty.
147160
// The returned element is not removed from the stack.
148-
T* top() const { return AtomicAccess::load(&_top); }
161+
T* top() const { return _top.load_relaxed(); }
149162

150163
// Return the number of objects in the stack. There must be no concurrent
151164
// pops while the length is being determined.
@@ -160,15 +173,23 @@ class LockFreeStack {
160173
// Return the entry following value in the list used by the
161174
// specialized LockFreeStack class.
162175
static T* next(const T& value) {
163-
return AtomicAccess::load(next_ptr(const_cast<T&>(value)));
176+
if constexpr (use_atomic_access) {
177+
return AtomicAccess::load(next_accessor(const_cast<T&>(value)));
178+
} else {
179+
return next_accessor(const_cast<T&>(value))->load_relaxed();
180+
}
164181
}
165182

166183
// Set the entry following value to new_next in the list used by the
167184
// specialized LockFreeStack class. Not thread-safe; in particular,
168185
// if value is in an instance of this specialization of LockFreeStack,
169186
// there must be no concurrent push or pop operations on that stack.
170187
static void set_next(T& value, T* new_next) {
171-
AtomicAccess::store(next_ptr(value), new_next);
188+
if constexpr (use_atomic_access) {
189+
AtomicAccess::store(next_accessor(value), new_next);
190+
} else {
191+
next_accessor(value)->store_relaxed(new_next);
192+
}
172193
}
173194
};
174195

test/hotspot/gtest/utilities/test_lockFreeStack.cpp

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*/
2323

2424
#include "memory/allocation.inline.hpp"
25-
#include "runtime/atomicAccess.hpp"
25+
#include "runtime/atomic.hpp"
2626
#include "utilities/globalDefinitions.hpp"
2727
#include "utilities/lockFreeStack.hpp"
2828
#include "threadHelper.inline.hpp"
@@ -33,12 +33,12 @@
3333
class LockFreeStackTestElement {
3434
typedef LockFreeStackTestElement Element;
3535

36-
Element* volatile _entry;
37-
Element* volatile _entry1;
36+
Atomic<Element*> _entry;
37+
Atomic<Element*> _entry1;
3838
size_t _id;
3939

40-
static Element* volatile* entry_ptr(Element& e) { return &e._entry; }
41-
static Element* volatile* entry1_ptr(Element& e) { return &e._entry1; }
40+
static Atomic<Element*>* entry_ptr(Element& e) { return &e._entry; }
41+
static Atomic<Element*>* entry1_ptr(Element& e) { return &e._entry1; }
4242

4343
public:
4444
LockFreeStackTestElement(size_t id = 0) : _entry(), _entry1(), _id(id) {}
@@ -202,17 +202,17 @@ class LockFreeStackTestThread : public JavaTestThread {
202202
uint _id;
203203
TestStack* _from;
204204
TestStack* _to;
205-
volatile size_t* _processed;
205+
Atomic<size_t>* _processed;
206206
size_t _process_limit;
207207
size_t _local_processed;
208-
volatile bool _ready;
208+
Atomic<bool> _ready;
209209

210210
public:
211211
LockFreeStackTestThread(Semaphore* post,
212212
uint id,
213213
TestStack* from,
214214
TestStack* to,
215-
volatile size_t* processed,
215+
Atomic<size_t>* processed,
216216
size_t process_limit) :
217217
JavaTestThread(post),
218218
_id(id),
@@ -225,21 +225,21 @@ class LockFreeStackTestThread : public JavaTestThread {
225225
{}
226226

227227
virtual void main_run() {
228-
AtomicAccess::release_store_fence(&_ready, true);
228+
_ready.release_store_fence(true);
229229
while (true) {
230230
Element* e = _from->pop();
231231
if (e != nullptr) {
232232
_to->push(*e);
233-
AtomicAccess::inc(_processed);
233+
_processed->fetch_then_add(1u);
234234
++_local_processed;
235-
} else if (AtomicAccess::load_acquire(_processed) == _process_limit) {
235+
} else if (_processed->load_acquire() == _process_limit) {
236236
tty->print_cr("thread %u processed %zu", _id, _local_processed);
237237
return;
238238
}
239239
}
240240
}
241241

242-
bool ready() const { return AtomicAccess::load_acquire(&_ready); }
242+
bool ready() const { return _ready.load_acquire(); }
243243
};
244244

245245
TEST_VM(LockFreeStackTest, stress) {
@@ -248,8 +248,8 @@ TEST_VM(LockFreeStackTest, stress) {
248248
TestStack start_stack;
249249
TestStack middle_stack;
250250
TestStack final_stack;
251-
volatile size_t stage1_processed = 0;
252-
volatile size_t stage2_processed = 0;
251+
Atomic<size_t> stage1_processed{0};
252+
Atomic<size_t> stage2_processed{0};
253253

254254
const size_t nelements = 10000;
255255
Element* elements = NEW_C_HEAP_ARRAY(Element, nelements, mtOther);
@@ -272,7 +272,7 @@ TEST_VM(LockFreeStackTest, stress) {
272272
for (uint i = 0; i < ARRAY_SIZE(threads); ++i) {
273273
TestStack* from = &start_stack;
274274
TestStack* to = &middle_stack;
275-
volatile size_t* processed = &stage1_processed;
275+
Atomic<size_t>* processed = &stage1_processed;
276276
if (i >= stage1_threads) {
277277
from = &middle_stack;
278278
to = &final_stack;
@@ -293,8 +293,8 @@ TEST_VM(LockFreeStackTest, stress) {
293293
}
294294

295295
// Verify expected state.
296-
ASSERT_EQ(nelements, stage1_processed);
297-
ASSERT_EQ(nelements, stage2_processed);
296+
ASSERT_EQ(nelements, stage1_processed.load_relaxed());
297+
ASSERT_EQ(nelements, stage2_processed.load_relaxed());
298298
ASSERT_EQ(0u, initial_stack.length());
299299
ASSERT_EQ(0u, start_stack.length());
300300
ASSERT_EQ(0u, middle_stack.length());

0 commit comments

Comments
 (0)