blob: e46af564b7037f75407a21f31002d655a803b27d [file] [log] [blame]
Eric Fiselier867c4b02019-04-24 01:47:30 +00001//===----------------------------------------------------------------------===//
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, libcxxabi-no-exceptions
11
12#define TESTING_CXA_GUARD
13#include "../src/cxa_guard_impl.h"
14#include <unordered_map>
15#include <thread>
16#include <atomic>
17#include <array>
18#include <cassert>
19#include <memory>
20#include <vector>
21
22
23using namespace __cxxabiv1;
24
25enum class InitResult {
26 COMPLETE,
27 PERFORMED,
28 WAITED,
29 ABORTED
30};
31constexpr InitResult COMPLETE = InitResult::COMPLETE;
32constexpr InitResult PERFORMED = InitResult::PERFORMED;
33constexpr InitResult WAITED = InitResult::WAITED;
34constexpr InitResult ABORTED = InitResult::ABORTED;
35
36
37template <class Impl, class GuardType, class Init>
38InitResult check_guard(GuardType *g, Init init) {
39 uint8_t *first_byte = reinterpret_cast<uint8_t*>(g);
40 if (std::__libcpp_atomic_load(first_byte, std::_AO_Acquire) == 0) {
41 Impl impl(g);
42 if (impl.cxa_guard_acquire() == INIT_IS_PENDING) {
43#ifndef LIBCXXABI_HAS_NO_EXCEPTIONS
44 try {
45#endif
46 init();
47 impl.cxa_guard_release();
48 return PERFORMED;
49#ifndef LIBCXXABI_HAS_NO_EXCEPTIONS
50 } catch (...) {
51 impl.cxa_guard_abort();
52 return ABORTED;
53 }
54#endif
55 }
56 return WAITED;
57 }
58 return COMPLETE;
59}
60
61
62template <class GuardType, class Impl>
63struct FunctionLocalStatic {
64 FunctionLocalStatic() { reset(); }
65 FunctionLocalStatic(FunctionLocalStatic const&) = delete;
66
67 template <class InitFunc>
68 InitResult access(InitFunc&& init) {
69 ++waiting_threads;
70 auto res = check_guard<Impl>(&guard_object, init);
71 --waiting_threads;
72 ++result_counts[static_cast<int>(res)];
73 return res;
74 }
75
76 struct Accessor {
77 explicit Accessor(FunctionLocalStatic& obj) : this_obj(&obj) {}
78
79 template <class InitFn>
80 void operator()(InitFn && fn) const {
81 this_obj->access(std::forward<InitFn>(fn));
82 }
83 private:
84 FunctionLocalStatic *this_obj;
85 };
86
87 Accessor get_access() {
88 return Accessor(*this);
89 }
90
91 void reset() {
92 guard_object = 0;
93 waiting_threads.store(0);
94 for (auto& counter : result_counts) {
95 counter.store(0);
96 }
97 }
98
99 int get_count(InitResult I) const {
100 return result_counts[static_cast<int>(I)].load();
101 }
102 int num_completed() const {
103 return get_count(COMPLETE) + get_count(PERFORMED) + get_count(WAITED);
104 }
105 int num_waiting() const {
106 return waiting_threads.load();
107 }
108
109private:
110 GuardType guard_object;
111 std::atomic<int> waiting_threads;
112 std::array<std::atomic<int>, 4> result_counts;
113 static_assert(static_cast<int>(ABORTED) == 3, "only 4 result kinds expected");
114};
115
116struct ThreadGroup {
117 ThreadGroup() = default;
118 ThreadGroup(ThreadGroup const&) = delete;
119
120 template <class ...Args>
121 void Create(Args&& ...args) {
122 threads.emplace_back(std::forward<Args>(args)...);
123 }
124
125 void JoinAll() {
126 for (auto& t : threads) {
127 t.join();
128 }
129 }
130
131private:
132 std::vector<std::thread> threads;
133};
134
135struct Barrier {
136 explicit Barrier(int n) : m_wait_for(n) { reset(); }
137 Barrier(Barrier const&) = delete;
138
139 void wait() {
140 ++m_entered;
141 while (m_entered.load() < m_wait_for) {
142 std::this_thread::yield();
143 }
144 assert(m_entered.load() == m_wait_for);
145 ++m_exited;
146 }
147
148 int num_waiting() const {
149 return m_entered.load() - m_exited.load();
150 }
151
152 void reset() {
153 m_entered.store(0);
154 m_exited.store(0);
155 }
156private:
157 const int m_wait_for;
158 std::atomic<int> m_entered;
159 std::atomic<int> m_exited;
160};
161
162struct Notification {
163 Notification() { reset(); }
164 Notification(Notification const&) = delete;
165
166 int num_waiting() const {
167 return m_waiting.load();
168 }
169
170 void wait() {
171 if (m_cond.load())
172 return;
173 ++m_waiting;
174 while (!m_cond.load()) {
175 std::this_thread::yield();
176 }
177 --m_waiting;
178 }
179
180 void notify() {
181 m_cond.store(true);
182 }
183
184 template <class Cond>
185 void notify_when(Cond &&c) {
186 if (m_cond.load())
187 return;
188 while (!c()) {
189 std::this_thread::yield();
190 }
191 m_cond.store(true);
192 }
193
194 void reset() {
195 m_cond.store(0);
196 m_waiting.store(0);
197 }
198private:
199 std::atomic<bool> m_cond;
200 std::atomic<int> m_waiting;
201};
202
203
204template <class GuardType, class Impl>
205void test_free_for_all() {
206 const int num_waiting_threads = 10; // one initializing thread, 10 waiters.
207
208 FunctionLocalStatic<GuardType, Impl> test_obj;
209
210 Barrier start_init_barrier(num_waiting_threads);
211 bool already_init = false;
212 ThreadGroup threads;
213 for (int i=0; i < num_waiting_threads; ++i) {
214 threads.Create([&]() {
215 start_init_barrier.wait();
216 test_obj.access([&]() {
217 assert(!already_init);
218 already_init = true;
219 });
220 });
221 }
222
223 // wait for the other threads to finish initialization.
224 threads.JoinAll();
225
226 assert(test_obj.get_count(PERFORMED) == 1);
227 assert(test_obj.get_count(COMPLETE) + test_obj.get_count(WAITED) == 9);
228}
229
230template <class GuardType, class Impl>
231void test_waiting_for_init() {
232 const int num_waiting_threads = 10; // one initializing thread, 10 waiters.
233
234 Notification init_pending;
235 Notification init_barrier;
236 FunctionLocalStatic<GuardType, Impl> test_obj;
237 auto access_fn = test_obj.get_access();
238
239 ThreadGroup threads;
240 threads.Create(access_fn,
241 [&]() {
242 init_pending.notify();
243 init_barrier.wait();
244 }
245 );
246 init_pending.wait();
247
248 assert(test_obj.num_waiting() == 1);
249
250 for (int i=0; i < num_waiting_threads; ++i) {
251 threads.Create(access_fn, []() { assert(false); });
252 }
253 // unblock the initializing thread
254 init_barrier.notify_when([&]() {
255 return test_obj.num_waiting() == num_waiting_threads + 1;
256 });
257
258 // wait for the other threads to finish initialization.
259 threads.JoinAll();
260
261 assert(test_obj.get_count(PERFORMED) == 1);
262 assert(test_obj.get_count(WAITED) == 10);
263 assert(test_obj.get_count(COMPLETE) == 0);
264}
265
266
267template <class GuardType, class Impl>
268void test_aborted_init() {
269 const int num_waiting_threads = 10; // one initializing thread, 10 waiters.
270
271 Notification init_pending;
272 Notification init_barrier;
273 FunctionLocalStatic<GuardType, Impl> test_obj;
274 auto access_fn = test_obj.get_access();
275
276 ThreadGroup threads;
277 threads.Create(access_fn,
278 [&]() {
279 init_pending.notify();
280 init_barrier.wait();
281 throw 42;
282 }
283 );
284 init_pending.wait();
285
286 assert(test_obj.num_waiting() == 1);
287
288 bool already_init = false;
289 for (int i=0; i < num_waiting_threads; ++i) {
290 threads.Create(access_fn, [&]() {
291 assert(!already_init);
292 already_init = true;
293 });
294 }
295 // unblock the initializing thread
296 init_barrier.notify_when([&]() {
297 return test_obj.num_waiting() == num_waiting_threads + 1;
298 });
299
300 // wait for the other threads to finish initialization.
301 threads.JoinAll();
302
303 assert(test_obj.get_count(ABORTED) == 1);
304 assert(test_obj.get_count(PERFORMED) == 1);
305 assert(test_obj.get_count(WAITED) == 9);
306 assert(test_obj.get_count(COMPLETE) == 0);
307}
308
309
310template <class GuardType, class Impl>
311void test_completed_init() {
312 const int num_waiting_threads = 10; // one initializing thread, 10 waiters.
313
314 Notification init_barrier;
315 FunctionLocalStatic<GuardType, Impl> test_obj;
316
317 test_obj.access([]() {});
318 assert(test_obj.num_waiting() == 0);
319 assert(test_obj.num_completed() == 1);
320 assert(test_obj.get_count(PERFORMED) == 1);
321
322 auto access_fn = test_obj.get_access();
323 ThreadGroup threads;
324 for (int i=0; i < num_waiting_threads; ++i) {
325 threads.Create(access_fn, []() {
326 assert(false);
327 });
328 }
329
330 // wait for the other threads to finish initialization.
331 threads.JoinAll();
332
333 assert(test_obj.get_count(ABORTED) == 0);
334 assert(test_obj.get_count(PERFORMED) == 1);
335 assert(test_obj.get_count(WAITED) == 0);
336 assert(test_obj.get_count(COMPLETE) == 10);
337}
338
339template <class Impl>
340void test_impl() {
341 {
342 test_free_for_all<uint32_t, Impl>();
343 test_free_for_all<uint32_t, Impl>();
344 }
345 {
346 test_waiting_for_init<uint32_t, Impl>();
347 test_waiting_for_init<uint64_t, Impl>();
348 }
349 {
350 test_aborted_init<uint32_t, Impl>();
351 test_aborted_init<uint64_t, Impl>();
352 }
353 {
354 test_completed_init<uint32_t, Impl>();
355 test_completed_init<uint64_t, Impl>();
356 }
357}
358
Eric Fiselier6eae8a42019-04-24 04:21:05 +0000359void test_all_impls() {
Eric Fiselier867c4b02019-04-24 01:47:30 +0000360 using MutexImpl = SelectImplementation<Implementation::GlobalLock>::type;
361
362 // Attempt to test the Futex based implementation if it's supported on the
363 // target platform.
364 using RealFutexImpl = SelectImplementation<Implementation::Futex>::type;
365 using FutexImpl = typename std::conditional<
Eric Fiselier6eae8a42019-04-24 04:21:05 +0000366 PlatformSupportsFutex(),
Eric Fiselier867c4b02019-04-24 01:47:30 +0000367 RealFutexImpl,
368 MutexImpl
369 >::type;
370
371 // Run each test 5 times to help TSAN catch bugs.
372 const int num_runs = 5;
373 for (int i=0; i < num_runs; ++i) {
374 test_impl<MutexImpl>();
Eric Fiselier6eae8a42019-04-24 04:21:05 +0000375 if (PlatformSupportsFutex())
Eric Fiselier867c4b02019-04-24 01:47:30 +0000376 test_impl<FutexImpl>();
377 }
378}
Eric Fiselier6eae8a42019-04-24 04:21:05 +0000379
380// A dummy
381template <bool Dummy = true>
382void test_futex_syscall() {
383 if (!PlatformSupportsFutex())
384 return;
385 int lock1 = 0;
386 int lock2 = 0;
387 int lock3 = 0;
388 std::thread waiter1([&]() {
389 int expect = 0;
390 PlatformFutexWait(&lock1, expect);
391 assert(lock1 == 1);
392 });
393 std::thread waiter2([&]() {
394 int expect = 0;
395 PlatformFutexWait(&lock2, expect);
396 assert(lock2 == 2);
397 });
398 std::thread waiter3([&]() {
399 int expect = 42; // not the value
400 PlatformFutexWait(&lock3, expect); // doesn't block
401 });
402 std::thread waker([&]() {
403 lock1 = 1;
404 PlatformFutexWake(&lock1);
405 lock2 = 2;
406 PlatformFutexWake(&lock2);
407 });
408 waiter1.join();
409 waiter2.join();
410 waiter3.join();
411 waker.join();
412}
413
414int main() {
415 // Test each multi-threaded implementation with real threads.
416 test_all_impls();
417 // Test the basic sanity of the futex syscall wrappers.
418 test_futex_syscall();
419}