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