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