1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "gtest/gtest.h"
18
19 #include <atomic>
20 #include <cstdint>
21 #include <thread>
22
23 namespace {
24
25 template <typename UIntType>
ReleaseStore(uint32_t * x,std::atomic<UIntType> * y,std::atomic<uint32_t> * thread_cnt)26 void ReleaseStore(uint32_t* x, std::atomic<UIntType>* y, std::atomic<uint32_t>* thread_cnt) {
27 thread_cnt->fetch_add(1U);
28 while (thread_cnt->load() != 2U)
29 ;
30
31 *x = 1U;
32 y->store(1U, std::memory_order_release);
33 }
34
35 template <typename UIntType>
AcquireLoad(uint32_t * x,std::atomic<UIntType> * y,std::atomic<uint32_t> * thread_cnt,bool * success)36 void AcquireLoad(uint32_t* x,
37 std::atomic<UIntType>* y,
38 std::atomic<uint32_t>* thread_cnt,
39 bool* success) {
40 thread_cnt->fetch_add(1U);
41 while (thread_cnt->load() != 2U)
42 ;
43
44 UIntType y_value = 0U;
45 while (!(y_value = y->load(std::memory_order_acquire)))
46 ;
47 *success = (*x == 1U) && (y_value == 1U);
48 }
49
50 template <typename UIntType>
ReleaseAcquireTest()51 bool ReleaseAcquireTest() {
52 uint32_t x = 0U;
53 std::atomic<UIntType> y = {0U};
54 std::atomic<uint32_t> thread_cnt = {0U};
55 bool success = false;
56
57 // All threads wait for this counter to be sure they start more or less simultaneously, so that
58 // test is more likely to observe different memory orderings.
59 std::thread t1(ReleaseStore<UIntType>, &x, &y, &thread_cnt);
60 std::thread t2(AcquireLoad<UIntType>, &x, &y, &thread_cnt, &success);
61 t1.join();
62 t2.join();
63
64 return success;
65 }
66
WriteX(std::atomic<uint32_t> * x,std::atomic<uint32_t> * thread_cnt)67 void WriteX(std::atomic<uint32_t>* x, std::atomic<uint32_t>* thread_cnt) {
68 thread_cnt->fetch_add(1U);
69 while (thread_cnt->load() != 4U)
70 ;
71
72 x->store(1U, std::memory_order_seq_cst);
73 }
74
WriteY(std::atomic<uint32_t> * y,std::atomic<uint32_t> * thread_cnt)75 void WriteY(std::atomic<uint32_t>* y, std::atomic<uint32_t>* thread_cnt) {
76 thread_cnt->fetch_add(1U);
77 while (thread_cnt->load() != 4U)
78 ;
79
80 y->store(1U, std::memory_order_seq_cst);
81 }
82
83 template <typename UIntType>
ReadXAndY(std::atomic<uint32_t> * x,std::atomic<uint32_t> * y,std::atomic<UIntType> * z,std::atomic<uint32_t> * thread_cnt)84 void ReadXAndY(std::atomic<uint32_t>* x,
85 std::atomic<uint32_t>* y,
86 std::atomic<UIntType>* z,
87 std::atomic<uint32_t>* thread_cnt) {
88 thread_cnt->fetch_add(1U);
89 while (thread_cnt->load() != 4U)
90 ;
91
92 while (!x->load(std::memory_order_seq_cst))
93 ;
94 if (y->load(std::memory_order_seq_cst)) {
95 z->fetch_add(1u, std::memory_order_seq_cst);
96 }
97 }
98
99 template <typename UIntType>
ReadYAndX(std::atomic<uint32_t> * x,std::atomic<uint32_t> * y,std::atomic<UIntType> * z,std::atomic<uint32_t> * thread_cnt)100 void ReadYAndX(std::atomic<uint32_t>* x,
101 std::atomic<uint32_t>* y,
102 std::atomic<UIntType>* z,
103 std::atomic<uint32_t>* thread_cnt) {
104 thread_cnt->fetch_add(1U);
105 while (thread_cnt->load() != 4U)
106 ;
107
108 while (!y->load(std::memory_order_seq_cst))
109 ;
110 if (x->load(std::memory_order_seq_cst)) {
111 z->fetch_add(1U, std::memory_order_seq_cst);
112 }
113 }
114
115 template <typename UIntType>
SequentiallyConsistentTest()116 bool SequentiallyConsistentTest() {
117 std::atomic<uint32_t> x = {0U};
118 std::atomic<uint32_t> y = {0U};
119 std::atomic<UIntType> z = {0U};
120 std::atomic<uint32_t> thread_cnt = {0U};
121
122 // All threads wait for this counter to be sure they start more or less simultaneously, so that
123 // test is more likely to observe different memory orderings.
124 std::thread t1(WriteX, &x, &thread_cnt);
125 std::thread t2(WriteY, &y, &thread_cnt);
126 std::thread t3(ReadXAndY<UIntType>, &x, &y, &z, &thread_cnt);
127 std::thread t4(ReadYAndX<UIntType>, &x, &y, &z, &thread_cnt);
128 t1.join();
129 t2.join();
130 t3.join();
131 t4.join();
132
133 UIntType z_value = z.load();
134 return (z_value == 1U) || (z_value == 2U);
135 }
136
137 } // namespace
138
139 // Warning: We tried to create threads once and synchronize threads between tests through
140 // self-defined functions. However, we found the interpretations of atomicity and memory ordering
141 // related instructions are so SLOW that interpreting series of instructions for synchronization is
142 // MORE EXPENSIVE than creating threads.
143
TEST(MemoryOrder,ReleaseAcquire)144 TEST(MemoryOrder, ReleaseAcquire) {
145 for (int i = 0; i < 100; i++) {
146 ASSERT_TRUE(ReleaseAcquireTest<uint8_t>());
147 ASSERT_TRUE(ReleaseAcquireTest<uint16_t>());
148 ASSERT_TRUE(ReleaseAcquireTest<uint32_t>());
149 ASSERT_TRUE(ReleaseAcquireTest<uint64_t>());
150 }
151 }
152
TEST(MemoryOrder,SequentiallyConsistent)153 TEST(MemoryOrder, SequentiallyConsistent) {
154 for (int i = 0; i < 100; i++) {
155 ASSERT_TRUE(SequentiallyConsistentTest<uint8_t>());
156 ASSERT_TRUE(SequentiallyConsistentTest<uint16_t>());
157 ASSERT_TRUE(SequentiallyConsistentTest<uint32_t>());
158 ASSERT_TRUE(SequentiallyConsistentTest<uint64_t>());
159 }
160 }
161