1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 // UNSUPPORTED: c++03
10 // UNSUPPORTED: libcxxabi-no-threads
11 // UNSUPPORTED: no-exceptions
12 
13 #define TESTING_CXA_GUARD
14 #include "../src/cxa_guard_impl.h"
15 #include <unordered_map>
16 #include <thread>
17 #include <atomic>
18 #include <array>
19 #include <cassert>
20 #include <memory>
21 #include <vector>
22 
23 #include "test_macros.h"
24 
25 
26 using namespace __cxxabiv1;
27 
28 // Misc test configuration. It's used to tune the flakyness of the test.
29 // ThreadsPerTest - The number of threads used
30 constexpr int ThreadsPerTest = 10;
31 // The number of instances of a test to run concurrently.
32 constexpr int ConcurrentRunsPerTest = 10;
33 // The number of times to rerun each test.
34 constexpr int TestSamples = 50;
35 
36 
37 
38 void BusyWait() {
39     std::this_thread::yield();
40 }
41 
42 void YieldAfterBarrier() {
43   std::this_thread::sleep_for(std::chrono::nanoseconds(10));
44   std::this_thread::yield();
45 }
46 
47 struct Barrier {
48   explicit Barrier(int n) : m_threads(n), m_remaining(n) { }
49   Barrier(Barrier const&) = delete;
50   Barrier& operator=(Barrier const&) = delete;
51 
52   void arrive_and_wait() const {
53     --m_remaining;
54     while (m_remaining.load()) {
55       BusyWait();
56     }
57   }
58 
59   void arrive_and_drop()  const {
60     --m_remaining;
61   }
62 
63   void wait_for_threads(int n) const {
64     while ((m_threads - m_remaining.load()) < n) {
65       std::this_thread::yield();
66     }
67   }
68 
69 private:
70   const int m_threads;
71   mutable std::atomic<int> m_remaining;
72 };
73 
74 
75 enum class InitResult {
76   COMPLETE,
77   PERFORMED,
78   WAITED,
79   ABORTED
80 };
81 constexpr InitResult COMPLETE = InitResult::COMPLETE;
82 constexpr InitResult PERFORMED = InitResult::PERFORMED;
83 constexpr InitResult WAITED = InitResult::WAITED;
84 constexpr InitResult ABORTED = InitResult::ABORTED;
85 
86 
87 template <class Impl, class GuardType, class Init>
88 InitResult check_guard(GuardType *g, Init init) {
89   uint8_t *first_byte = reinterpret_cast<uint8_t*>(g);
90   if (std::__libcpp_atomic_load(first_byte, std::_AO_Acquire) == 0) {
91     Impl impl(g);
92     if (impl.cxa_guard_acquire() == INIT_IS_PENDING) {
93 #ifndef TEST_HAS_NO_EXCEPTIONS
94       try {
95 #endif
96         init();
97         impl.cxa_guard_release();
98         return PERFORMED;
99 #ifndef TEST_HAS_NO_EXCEPTIONS
100       } catch (...) {
101         impl.cxa_guard_abort();
102         return ABORTED;
103       }
104 #endif
105     }
106     return WAITED;
107   }
108   return COMPLETE;
109 }
110 
111 
112 template <class GuardType, class Impl>
113 struct FunctionLocalStatic {
114   FunctionLocalStatic() {}
115   FunctionLocalStatic(FunctionLocalStatic const&) = delete;
116 
117   template <class InitFunc>
118   InitResult access(InitFunc&& init) {
119     auto res = check_guard<Impl>(&guard_object, init);
120     ++result_counts[static_cast<int>(res)];
121     return res;
122   }
123 
124   template <class InitFn>
125   struct AccessCallback {
126     void operator()() const { this_obj->access(init); }
127 
128     FunctionLocalStatic *this_obj;
129     InitFn init;
130   };
131 
132   template <class InitFn, class Callback = AccessCallback< InitFn >  >
133   Callback access_callback(InitFn init) {
134     return Callback{this, init};
135   }
136 
137   int get_count(InitResult I) const {
138     return result_counts[static_cast<int>(I)].load();
139   }
140 
141   int num_completed() const {
142     return get_count(COMPLETE) + get_count(PERFORMED) + get_count(WAITED);
143   }
144 
145   int num_waiting() const {
146     return waiting_threads.load();
147   }
148 
149 private:
150   GuardType guard_object = {};
151   std::atomic<int> waiting_threads{0};
152   std::array<std::atomic<int>, 4> result_counts{};
153   static_assert(static_cast<int>(ABORTED) == 3, "only 4 result kinds expected");
154 };
155 
156 struct ThreadGroup {
157   ThreadGroup() = default;
158   ThreadGroup(ThreadGroup const&) = delete;
159 
160   template <class ...Args>
161   void Create(Args&& ...args) {
162     threads.emplace_back(std::forward<Args>(args)...);
163   }
164 
165   template <class Callback>
166   void CreateThreadsWithBarrier(int N, Callback cb) {
167     auto start = std::make_shared<Barrier>(N + 1);
168     for (int I=0; I < N; ++I) {
169       Create([start, cb]() {
170         start->arrive_and_wait();
171         cb();
172       });
173     }
174     start->arrive_and_wait();
175   }
176 
177   void JoinAll() {
178     for (auto& t : threads) {
179       t.join();
180     }
181   }
182 
183 private:
184   std::vector<std::thread> threads;
185 };
186 
187 
188 template <class GuardType, class Impl>
189 void test_free_for_all(int num_waiters) {
190   FunctionLocalStatic<GuardType, Impl> test_obj;
191 
192   ThreadGroup threads;
193 
194   bool already_init = false;
195   threads.CreateThreadsWithBarrier(num_waiters,
196     test_obj.access_callback([&]() {
197       assert(!already_init);
198       already_init = true;
199     })
200   );
201 
202   // wait for the other threads to finish initialization.
203   threads.JoinAll();
204 
205   assert(test_obj.get_count(PERFORMED) == 1);
206   assert(test_obj.get_count(COMPLETE) + test_obj.get_count(WAITED) == num_waiters - 1);
207 }
208 
209 template <class GuardType, class Impl>
210 void test_waiting_for_init(int num_waiters) {
211     FunctionLocalStatic<GuardType, Impl> test_obj;
212 
213     ThreadGroup threads;
214 
215     Barrier start_init(2);
216     threads.Create(test_obj.access_callback(
217       [&]() {
218         start_init.arrive_and_wait();
219         // Take our sweet time completing the initialization...
220         //
221         // There's a race condition between the other threads reaching the
222         // start_init barrier, and them actually hitting the cxa guard.
223         // But we're trying to test the waiting logic, we want as many
224         // threads to enter the waiting loop as possible.
225         YieldAfterBarrier();
226       }
227     ));
228     start_init.wait_for_threads(1);
229 
230     threads.CreateThreadsWithBarrier(num_waiters,
231         test_obj.access_callback([]() { assert(false); })
232     );
233     // unblock the initializing thread
234     start_init.arrive_and_drop();
235 
236     // wait for the other threads to finish initialization.
237     threads.JoinAll();
238 
239     assert(test_obj.get_count(PERFORMED) == 1);
240     assert(test_obj.get_count(ABORTED) == 0);
241     assert(test_obj.get_count(COMPLETE) + test_obj.get_count(WAITED) == num_waiters);
242 }
243 
244 
245 template <class GuardType, class Impl>
246 void test_aborted_init(int num_waiters) {
247   FunctionLocalStatic<GuardType, Impl> test_obj;
248 
249   Barrier start_init(2);
250   ThreadGroup threads;
251   threads.Create(test_obj.access_callback(
252     [&]() {
253       start_init.arrive_and_wait();
254       YieldAfterBarrier();
255       throw 42;
256     })
257   );
258   start_init.wait_for_threads(1);
259 
260   bool already_init = false;
261   threads.CreateThreadsWithBarrier(num_waiters,
262       test_obj.access_callback([&]() {
263         assert(!already_init);
264         already_init = true;
265       })
266     );
267   // unblock the initializing thread
268   start_init.arrive_and_drop();
269 
270   // wait for the other threads to finish initialization.
271   threads.JoinAll();
272 
273   assert(test_obj.get_count(ABORTED) == 1);
274   assert(test_obj.get_count(PERFORMED) == 1);
275   assert(test_obj.get_count(WAITED) + test_obj.get_count(COMPLETE) == num_waiters - 1);
276 }
277 
278 
279 template <class GuardType, class Impl>
280 void test_completed_init(int num_waiters) {
281 
282   FunctionLocalStatic<GuardType, Impl> test_obj;
283 
284   test_obj.access([]() {}); // initialize the object
285   assert(test_obj.num_waiting() == 0);
286   assert(test_obj.num_completed() == 1);
287   assert(test_obj.get_count(PERFORMED) == 1);
288 
289   ThreadGroup threads;
290   threads.CreateThreadsWithBarrier(num_waiters,
291       test_obj.access_callback([]() { assert(false); })
292   );
293   // wait for the other threads to finish initialization.
294   threads.JoinAll();
295 
296   assert(test_obj.get_count(ABORTED) == 0);
297   assert(test_obj.get_count(PERFORMED) == 1);
298   assert(test_obj.get_count(WAITED) == 0);
299   assert(test_obj.get_count(COMPLETE) == num_waiters);
300 }
301 
302 template <class Impl>
303 void test_impl() {
304   using TestFn = void(*)(int);
305   TestFn TestList[] = {
306     test_free_for_all<uint32_t, Impl>,
307     test_free_for_all<uint32_t, Impl>,
308     test_waiting_for_init<uint32_t, Impl>,
309     test_waiting_for_init<uint64_t, Impl>,
310     test_aborted_init<uint32_t, Impl>,
311     test_aborted_init<uint64_t, Impl>,
312     test_completed_init<uint32_t, Impl>,
313     test_completed_init<uint64_t, Impl>
314   };
315 
316   for (auto test_func : TestList) {
317       ThreadGroup test_threads;
318       test_threads.CreateThreadsWithBarrier(ConcurrentRunsPerTest, [=]() {
319         for (int I = 0; I < TestSamples; ++I) {
320           test_func(ThreadsPerTest);
321         }
322       });
323       test_threads.JoinAll();
324     }
325   }
326 
327 void test_all_impls() {
328   using MutexImpl = SelectImplementation<Implementation::GlobalLock>::type;
329 
330   // Attempt to test the Futex based implementation if it's supported on the
331   // target platform.
332   using RealFutexImpl = SelectImplementation<Implementation::Futex>::type;
333   using FutexImpl = typename std::conditional<
334       PlatformSupportsFutex(),
335       RealFutexImpl,
336       MutexImpl
337   >::type;
338 
339   test_impl<MutexImpl>();
340   if (PlatformSupportsFutex())
341     test_impl<FutexImpl>();
342 }
343 
344 // A dummy
345 template <bool Dummy = true>
346 void test_futex_syscall() {
347   if (!PlatformSupportsFutex())
348     return;
349   int lock1 = 0;
350   int lock2 = 0;
351   int lock3 = 0;
352   std::thread waiter1([&]() {
353     int expect = 0;
354     PlatformFutexWait(&lock1, expect);
355     assert(lock1 == 1);
356   });
357   std::thread waiter2([&]() {
358     int expect = 0;
359     PlatformFutexWait(&lock2, expect);
360     assert(lock2 == 2);
361   });
362   std::thread waiter3([&]() {
363     int expect = 42; // not the value
364     PlatformFutexWait(&lock3, expect); // doesn't block
365   });
366   std::thread waker([&]() {
367     lock1 = 1;
368     PlatformFutexWake(&lock1);
369     lock2 = 2;
370     PlatformFutexWake(&lock2);
371   });
372   waiter1.join();
373   waiter2.join();
374   waiter3.join();
375   waker.join();
376 }
377 
378 int main() {
379   // Test each multi-threaded implementation with real threads.
380   test_all_impls();
381   // Test the basic sanity of the futex syscall wrappers.
382   test_futex_syscall();
383 }
384