Skip to content

Commit 9c6dd66

Browse files
committed
[libc++] Add testing for atomic_ref store/load for cv-qualified types
1 parent 69d92fa commit 9c6dd66

File tree

6 files changed

+146
-57
lines changed

6 files changed

+146
-57
lines changed

libcxx/test/std/atomics/atomics.ref/assign.pass.cpp

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,42 @@
2020
#include "test_helper.h"
2121
#include "test_macros.h"
2222

23-
template <typename T>
23+
template <typename U>
2424
struct TestAssign {
2525
void operator()() const {
26-
{
27-
T x(T(1));
28-
std::atomic_ref<T> const a(x);
26+
static_assert(std::is_nothrow_assignable_v<std::atomic_ref<U>, U>);
27+
do_test<U>();
28+
do_test_atomic<U>();
29+
static_assert(!std::is_assignable_v<std::atomic_ref<U const>, U>);
30+
if constexpr (std::atomic_ref<U>::is_always_lock_free) {
31+
static_assert(std::is_nothrow_assignable_v<std::atomic_ref<U volatile>, U>);
32+
do_test<U volatile>();
33+
do_test_atomic<U volatile>();
34+
static_assert(!std::is_assignable_v<std::atomic_ref<U const volatile>, U>);
35+
}
36+
}
2937

30-
std::same_as<T> decltype(auto) y = (a = T(2));
31-
assert(y == T(2));
32-
assert(x == T(2));
38+
template <typename T>
39+
void do_test() const {
40+
T x(T(1));
41+
std::atomic_ref<T> const a(x);
3342

34-
ASSERT_NOEXCEPT(a = T(0));
35-
static_assert(std::is_nothrow_assignable_v<std::atomic_ref<T>, T>);
43+
std::same_as<std::remove_cv_t<T>> decltype(auto) y = (a = T(2));
44+
assert(y == std::remove_cv_t<T>(2));
45+
assert(const_cast<std::remove_cv_t<T> const&>(x) == std::remove_cv_t<T>(2));
3646

37-
static_assert(!std::is_copy_assignable_v<std::atomic_ref<T>>);
38-
}
47+
ASSERT_NOEXCEPT(a = T(0));
48+
49+
static_assert(!std::is_copy_assignable_v<std::atomic_ref<T>>);
50+
}
3951

52+
template <typename T>
53+
void do_test_atomic() const {
4054
{
41-
auto assign = [](std::atomic_ref<T> const& y, T, T new_val) { y = new_val; };
42-
auto load = [](std::atomic_ref<T> const& y) { return y.load(); };
55+
auto assign = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
56+
y = const_cast<std::remove_cv_t<T> const&>(new_val);
57+
};
58+
auto load = [](std::atomic_ref<T> const& y) { return y.load(); };
4359
test_seq_cst<T>(assign, load);
4460
}
4561
}

libcxx/test/std/atomics/atomics.ref/convert.pass.cpp

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,42 @@
1919
#include "test_helper.h"
2020
#include "test_macros.h"
2121

22-
template <typename T>
22+
template <typename U>
2323
struct TestConvert {
2424
void operator()() const {
25+
static_assert(std::is_nothrow_convertible_v<std::atomic_ref<U>, U>);
26+
do_test<U>();
27+
do_test_atomic<U>();
28+
static_assert(std::is_nothrow_convertible_v<std::atomic_ref<U const>, U>);
29+
do_test<U const>();
30+
if constexpr (std::atomic_ref<U>::is_always_lock_free) {
31+
static_assert(std::is_nothrow_convertible_v<std::atomic_ref<U volatile>, U>);
32+
do_test<U volatile>();
33+
do_test_atomic<U volatile>();
34+
static_assert(std::is_nothrow_convertible_v<std::atomic_ref<U const volatile>, U>);
35+
do_test<U const volatile>();
36+
}
37+
}
38+
39+
template <class T>
40+
void do_test() const {
2541
T x(T(1));
2642

27-
T copy = x;
43+
T copy = const_cast<std::remove_cv_t<T> const&>(x);
2844
std::atomic_ref<T> const a(copy);
2945

30-
T converted = a;
31-
assert(converted == x);
46+
std::remove_cv_t<T> converted = a;
47+
assert(converted == const_cast<std::remove_cv_t<T> const&>(x));
3248

3349
ASSERT_NOEXCEPT(T(a));
34-
static_assert(std::is_nothrow_convertible_v<std::atomic_ref<T>, T>);
50+
}
3551

36-
auto store = [](std::atomic_ref<T> const& y, T, T new_val) { y.store(new_val); };
37-
auto load = [](std::atomic_ref<T> const& y) { return static_cast<T>(y); };
52+
template <class T>
53+
void do_test_atomic() const {
54+
auto store = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
55+
y.store(const_cast<std::remove_cv_t<T> const&>(new_val));
56+
};
57+
auto load = [](std::atomic_ref<T> const& y) { return static_cast<std::remove_cv_t<T>>(y); };
3858
test_seq_cst<T>(store, load);
3959
}
4060
};

libcxx/test/std/atomics/atomics.ref/deduction.pass.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,18 @@
2121
template <typename T>
2222
struct TestDeduction {
2323
void operator()() const {
24-
T x(T(0));
24+
do_test<T>();
25+
do_test<T const>();
26+
if constexpr (std::atomic_ref<T>::is_always_lock_free) {
27+
do_test<T volatile>();
28+
do_test<T const volatile>();
29+
}
30+
}
31+
template <class U>
32+
void do_test() const {
33+
U x(U(0));
2534
std::atomic_ref a(x);
26-
ASSERT_SAME_TYPE(decltype(a), std::atomic_ref<T>);
35+
ASSERT_SAME_TYPE(decltype(a), std::atomic_ref<U>);
2736
}
2837
};
2938

libcxx/test/std/atomics/atomics.ref/load.pass.cpp

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,44 @@
2020
#include "test_helper.h"
2121
#include "test_macros.h"
2222

23-
template <typename T>
23+
template <typename U>
2424
struct TestLoad {
2525
void operator()() const {
26+
do_test<U>();
27+
do_test_atomic<U>();
28+
do_test<U const>();
29+
if constexpr (std::atomic_ref<U>::is_always_lock_free) {
30+
do_test<U volatile>();
31+
do_test_atomic<U volatile>();
32+
do_test<U const volatile>();
33+
}
34+
}
35+
36+
template <class T>
37+
void do_test() const {
2638
T x(T(1));
2739
std::atomic_ref<T> const a(x);
2840

2941
{
30-
std::same_as<T> decltype(auto) y = a.load();
31-
assert(y == T(1));
42+
std::same_as<std::remove_cv_t<T>> decltype(auto) y = a.load();
43+
assert(y == std::remove_cv_t<T>(1));
3244
ASSERT_NOEXCEPT(a.load());
3345
}
3446

3547
{
36-
std::same_as<T> decltype(auto) y = a.load(std::memory_order_seq_cst);
37-
assert(y == T(1));
48+
std::same_as<std::remove_cv_t<T>> decltype(auto) y = a.load(std::memory_order_seq_cst);
49+
assert(y == std::remove_cv_t<T>(1));
3850
ASSERT_NOEXCEPT(a.load(std::memory_order_seq_cst));
3951
}
52+
}
4053

54+
template <class T>
55+
void do_test_atomic() const {
4156
// memory_order::seq_cst
4257
{
43-
auto store = [](std::atomic_ref<T> const& y, T, T new_val) { y.store(new_val); };
58+
auto store = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
59+
y.store(const_cast<std::remove_cv_t<T> const&>(new_val));
60+
};
4461
auto load_no_arg = [](std::atomic_ref<T> const& y) { return y.load(); };
4562
auto load_with_order = [](std::atomic_ref<T> const& y) { return y.load(std::memory_order::seq_cst); };
4663
test_seq_cst<T>(store, load_no_arg);
@@ -49,8 +66,10 @@ struct TestLoad {
4966

5067
// memory_order::release
5168
{
52-
auto store = [](std::atomic_ref<T> const& y, T, T new_val) { y.store(new_val, std::memory_order::release); };
53-
auto load = [](std::atomic_ref<T> const& y) { return y.load(std::memory_order::acquire); };
69+
auto store = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
70+
y.store(const_cast<std::remove_cv_t<T> const&>(new_val), std::memory_order::release);
71+
};
72+
auto load = [](std::atomic_ref<T> const& y) { return y.load(std::memory_order::acquire); };
5473
test_acquire_release<T>(store, load);
5574
}
5675
}

libcxx/test/std/atomics/atomics.ref/store.pass.cpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,49 @@
1919
#include "test_helper.h"
2020
#include "test_macros.h"
2121

22-
template <typename T>
22+
template <typename T, typename U>
23+
concept has_store = requires { std::declval<T>().store(std::declval<U>()); };
24+
25+
template <typename U>
2326
struct TestStore {
2427
void operator()() const {
28+
static_assert(has_store<std::atomic_ref<U>, U>);
29+
do_test<U>();
30+
do_test_atomic<U>();
31+
static_assert(!has_store<std::atomic_ref<U const>, U>);
32+
if constexpr (std::atomic_ref<U>::is_always_lock_free) {
33+
static_assert(has_store<std::atomic_ref<U volatile>, U>);
34+
do_test<U volatile>();
35+
do_test_atomic<U volatile>();
36+
static_assert(!has_store<std::atomic_ref<U const volatile>, U>);
37+
}
38+
}
39+
40+
template <typename T>
41+
void do_test() const {
2542
T x(T(1));
2643
std::atomic_ref<T> const a(x);
2744

2845
a.store(T(2));
29-
assert(x == T(2));
46+
assert(const_cast<std::remove_cv_t<T> const&>(x) == std::remove_cv_t<T>(2));
3047
ASSERT_NOEXCEPT(a.store(T(1)));
3148

3249
a.store(T(3), std::memory_order_seq_cst);
33-
assert(x == T(3));
50+
assert(const_cast<std::remove_cv_t<T> const&>(x) == std::remove_cv_t<T>(3));
3451
ASSERT_NOEXCEPT(a.store(T(0), std::memory_order_seq_cst));
52+
}
3553

54+
template <typename T>
55+
void do_test_atomic() const {
3656
// TODO memory_order::relaxed
3757

3858
// memory_order::seq_cst
3959
{
40-
auto store_no_arg = [](std::atomic_ref<T> const& y, T, T new_val) { y.store(new_val); };
41-
auto store_with_order = [](std::atomic_ref<T> const& y, T, T new_val) {
42-
y.store(new_val, std::memory_order::seq_cst);
60+
auto store_no_arg = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
61+
y.store(const_cast<std::remove_cv_t<T> const&>(new_val));
62+
};
63+
auto store_with_order = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
64+
y.store(const_cast<std::remove_cv_t<T> const&>(new_val), std::memory_order::seq_cst);
4365
};
4466
auto load = [](std::atomic_ref<T> const& y) { return y.load(); };
4567
test_seq_cst<T>(store_no_arg, load);
@@ -48,8 +70,10 @@ struct TestStore {
4870

4971
// memory_order::release
5072
{
51-
auto store = [](std::atomic_ref<T> const& y, T, T new_val) { y.store(new_val, std::memory_order::release); };
52-
auto load = [](std::atomic_ref<T> const& y) { return y.load(std::memory_order::acquire); };
73+
auto store = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
74+
y.store(const_cast<std::remove_cv_t<T> const&>(new_val), std::memory_order::release);
75+
};
76+
auto load = [](std::atomic_ref<T> const& y) { return y.load(std::memory_order::acquire); };
5377
test_acquire_release<T>(store, load);
5478
}
5579
}

libcxx/test/std/atomics/atomics.ref/test_helper.h

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ template <class T, class StoreOp, class LoadOp>
4545
void test_seq_cst(StoreOp store_op, LoadOp load_op) {
4646
#ifndef TEST_HAS_NO_THREADS
4747
for (int i = 0; i < 100; ++i) {
48-
T old_value(make_value<T>(0));
49-
T new_value(make_value<T>(1));
48+
T old_value(make_value<std::remove_cv_t<T>>(0));
49+
T new_value(make_value<std::remove_cv_t<T>>(1));
5050

51-
T copy_x = old_value;
51+
T copy_x = const_cast<std::remove_cv_t<T> const&>(old_value);
5252
std::atomic_ref<T> const x(copy_x);
53-
T copy_y = old_value;
53+
T copy_y = const_cast<std::remove_cv_t<T> const&>(old_value);
5454
std::atomic_ref<T> const y(copy_y);
5555

5656
std::atomic_bool x_updated_first(false);
@@ -61,19 +61,19 @@ void test_seq_cst(StoreOp store_op, LoadOp load_op) {
6161
auto t2 = support::make_test_thread([&] { store_op(y, old_value, new_value); });
6262

6363
auto t3 = support::make_test_thread([&] {
64-
while (!equals(load_op(x), new_value)) {
64+
while (!equals(load_op(x), const_cast<std::remove_cv_t<T> const&>(new_value))) {
6565
std::this_thread::yield();
6666
}
67-
if (!equals(load_op(y), new_value)) {
67+
if (!equals(load_op(y), const_cast<std::remove_cv_t<T> const&>(new_value))) {
6868
x_updated_first.store(true, std::memory_order_relaxed);
6969
}
7070
});
7171

7272
auto t4 = support::make_test_thread([&] {
73-
while (!equals(load_op(y), new_value)) {
73+
while (!equals(load_op(y), const_cast<std::remove_cv_t<T> const&>(new_value))) {
7474
std::this_thread::yield();
7575
}
76-
if (!equals(load_op(x), new_value)) {
76+
if (!equals(load_op(x), const_cast<std::remove_cv_t<T> const&>(new_value))) {
7777
y_updated_first.store(true, std::memory_order_relaxed);
7878
}
7979
});
@@ -98,10 +98,10 @@ template <class T, class StoreOp, class LoadOp>
9898
void test_acquire_release(StoreOp store_op, LoadOp load_op) {
9999
#ifndef TEST_HAS_NO_THREADS
100100
for (auto i = 0; i < 100; ++i) {
101-
T old_value(make_value<T>(0));
102-
T new_value(make_value<T>(1));
101+
T old_value(make_value<std::remove_cv_t<T>>(0));
102+
T new_value(make_value<std::remove_cv_t<T>>(1));
103103

104-
T copy = old_value;
104+
T copy = const_cast<std::remove_cv_t<T> const&>(old_value);
105105
std::atomic_ref<T> const at(copy);
106106
int non_atomic = 5;
107107

@@ -110,14 +110,15 @@ void test_acquire_release(StoreOp store_op, LoadOp load_op) {
110110
threads.reserve(number_of_threads);
111111

112112
for (auto j = 0; j < number_of_threads; ++j) {
113-
threads.push_back(support::make_test_thread([&at, &non_atomic, load_op, new_value] {
114-
while (!equals(load_op(at), new_value)) {
115-
std::this_thread::yield();
116-
}
117-
// Other thread's writes before the release store are visible
118-
// in this thread's read after the acquire load
119-
assert(non_atomic == 6);
120-
}));
113+
threads.push_back(support::make_test_thread(
114+
[&at, &non_atomic, load_op, new_value = const_cast<std::remove_cv_t<T> const&>(new_value)] {
115+
while (!equals(load_op(at), new_value)) {
116+
std::this_thread::yield();
117+
}
118+
// Other thread's writes before the release store are visible
119+
// in this thread's read after the acquire load
120+
assert(non_atomic == 6);
121+
}));
121122
}
122123

123124
non_atomic = 6;

0 commit comments

Comments
 (0)