1 //===----------------------------------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
10 // UNSUPPORTED: no-threads
11 // UNSUPPORTED: no-exceptions
13 #define TESTING_CXA_GUARD
14 #include "../src/cxa_guard_impl.h"
15 #include <unordered_map>
23 #include "make_test_thread.h"
24 #include "test_macros.h"
27 using namespace __cxxabiv1
;
29 // Misc test configuration. It's used to tune the flakyness of the test.
30 // ThreadsPerTest - The number of threads used
31 constexpr int ThreadsPerTest
= 10;
32 // The number of instances of a test to run concurrently.
33 constexpr int ConcurrentRunsPerTest
= 10;
34 // The number of times to rerun each test.
35 constexpr int TestSamples
= 50;
40 std::this_thread::yield();
43 void YieldAfterBarrier() {
44 std::this_thread::sleep_for(std::chrono::nanoseconds(10));
45 std::this_thread::yield();
49 explicit Barrier(int n
) : m_threads(n
), m_remaining(n
) { }
50 Barrier(Barrier
const&) = delete;
51 Barrier
& operator=(Barrier
const&) = delete;
53 void arrive_and_wait() const {
55 while (m_remaining
.load()) {
60 void arrive_and_drop() const {
64 void wait_for_threads(int n
) const {
65 while ((m_threads
- m_remaining
.load()) < n
) {
66 std::this_thread::yield();
72 mutable std::atomic
<int> m_remaining
;
76 enum class InitResult
{
82 constexpr InitResult COMPLETE
= InitResult::COMPLETE
;
83 constexpr InitResult PERFORMED
= InitResult::PERFORMED
;
84 constexpr InitResult WAITED
= InitResult::WAITED
;
85 constexpr InitResult ABORTED
= InitResult::ABORTED
;
88 template <class Impl
, class GuardType
, class Init
>
89 InitResult
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) {
93 if (impl
.cxa_guard_acquire() == INIT_IS_PENDING
) {
94 #ifndef TEST_HAS_NO_EXCEPTIONS
98 impl
.cxa_guard_release();
100 #ifndef TEST_HAS_NO_EXCEPTIONS
102 impl
.cxa_guard_abort();
113 template <class GuardType
, class Impl
>
114 struct FunctionLocalStatic
{
115 FunctionLocalStatic() {}
116 FunctionLocalStatic(FunctionLocalStatic
const&) = delete;
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
)];
125 template <class InitFn
>
126 struct AccessCallback
{
127 void operator()() const { this_obj
->access(init
); }
129 FunctionLocalStatic
*this_obj
;
133 template <class InitFn
, class Callback
= AccessCallback
< InitFn
> >
134 Callback
access_callback(InitFn init
) {
135 return Callback
{this, init
};
138 int get_count(InitResult I
) const {
139 return result_counts
[static_cast<int>(I
)].load();
142 int num_completed() const {
143 return get_count(COMPLETE
) + get_count(PERFORMED
) + get_count(WAITED
);
146 int num_waiting() const {
147 return waiting_threads
.load();
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");
158 ThreadGroup() = default;
159 ThreadGroup(ThreadGroup
const&) = delete;
161 template <class ...Args
>
162 void Create(Args
&& ...args
) {
163 threads
.emplace_back(std::forward
<Args
>(args
)...);
166 template <class Callback
>
167 void CreateThreadsWithBarrier(int N
, Callback cb
) {
168 auto start
= std::make_shared
<Barrier
>(N
+ 1);
169 for (int I
=0; I
< N
; ++I
) {
170 Create([start
, cb
]() {
171 start
->arrive_and_wait();
175 start
->arrive_and_wait();
179 for (auto& t
: threads
) {
185 std::vector
<std::thread
> threads
;
189 template <class GuardType
, class Impl
>
190 void test_free_for_all(int num_waiters
) {
191 FunctionLocalStatic
<GuardType
, Impl
> test_obj
;
195 bool already_init
= false;
196 threads
.CreateThreadsWithBarrier(num_waiters
,
197 test_obj
.access_callback([&]() {
198 assert(!already_init
);
203 // wait for the other threads to finish initialization.
206 assert(test_obj
.get_count(PERFORMED
) == 1);
207 assert(test_obj
.get_count(COMPLETE
) + test_obj
.get_count(WAITED
) == num_waiters
- 1);
210 template <class GuardType
, class Impl
>
211 void test_waiting_for_init(int num_waiters
) {
212 FunctionLocalStatic
<GuardType
, Impl
> test_obj
;
216 Barrier
start_init(2);
217 threads
.Create(test_obj
.access_callback(
219 start_init
.arrive_and_wait();
220 // Take our sweet time completing the initialization...
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.
229 start_init
.wait_for_threads(1);
231 threads
.CreateThreadsWithBarrier(num_waiters
,
232 test_obj
.access_callback([]() { assert(false); })
234 // unblock the initializing thread
235 start_init
.arrive_and_drop();
237 // wait for the other threads to finish initialization.
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
);
246 template <class GuardType
, class Impl
>
247 void test_aborted_init(int num_waiters
) {
248 FunctionLocalStatic
<GuardType
, Impl
> test_obj
;
250 Barrier
start_init(2);
252 threads
.Create(test_obj
.access_callback(
254 start_init
.arrive_and_wait();
259 start_init
.wait_for_threads(1);
261 bool already_init
= false;
262 threads
.CreateThreadsWithBarrier(num_waiters
,
263 test_obj
.access_callback([&]() {
264 assert(!already_init
);
268 // unblock the initializing thread
269 start_init
.arrive_and_drop();
271 // wait for the other threads to finish initialization.
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);
280 template <class GuardType
, class Impl
>
281 void test_completed_init(int num_waiters
) {
283 FunctionLocalStatic
<GuardType
, Impl
> test_obj
;
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);
291 threads
.CreateThreadsWithBarrier(num_waiters
,
292 test_obj
.access_callback([]() { assert(false); })
294 // wait for the other threads to finish initialization.
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
);
303 template <class 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
>
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
);
324 test_threads
.JoinAll();
328 void test_all_impls() {
329 using MutexImpl
= SelectImplementation
<Implementation::GlobalMutex
>::type
;
331 // Attempt to test the Futex based implementation if it's supported on the
333 using RealFutexImpl
= SelectImplementation
<Implementation::Futex
>::type
;
334 using FutexImpl
= typename
std::conditional
<
335 PlatformSupportsFutex(),
340 test_impl
<MutexImpl
>();
341 if (PlatformSupportsFutex())
342 test_impl
<FutexImpl
>();
346 template <bool Dummy
= true>
347 void test_futex_syscall() {
348 if (!PlatformSupportsFutex())
353 std::thread waiter1
= support::make_test_thread([&]() {
355 PlatformFutexWait(&lock1
, expect
);
358 std::thread waiter2
= support::make_test_thread([&]() {
360 PlatformFutexWait(&lock2
, expect
);
363 std::thread waiter3
= support::make_test_thread([&]() {
364 int expect
= 42; // not the value
365 PlatformFutexWait(&lock3
, expect
); // doesn't block
367 std::thread waker
= support::make_test_thread([&]() {
369 PlatformFutexWake(&lock1
);
371 PlatformFutexWake(&lock2
);
379 int main(int, char**) {
380 // Test each multi-threaded implementation with real threads.
382 // Test the basic sanity of the futex syscall wrappers.
383 test_futex_syscall();