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

source code of libcxxabi/test/guard_threaded_test.pass.cpp