170ebeabfSEric Fiselier //===----------------------------------------------------------------------===//
270ebeabfSEric Fiselier //
370ebeabfSEric Fiselier // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
470ebeabfSEric Fiselier // See https://llvm.org/LICENSE.txt for license information.
570ebeabfSEric Fiselier // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
670ebeabfSEric Fiselier //
770ebeabfSEric Fiselier //===----------------------------------------------------------------------===//
870ebeabfSEric Fiselier
931cbe0f2SLouis Dionne // UNSUPPORTED: c++03
10*a7f9895cSLouis Dionne // UNSUPPORTED: no-threads
118c61114cSLouis Dionne // UNSUPPORTED: no-exceptions
1270ebeabfSEric Fiselier
1370ebeabfSEric Fiselier #define TESTING_CXA_GUARD
1470ebeabfSEric Fiselier #include "../src/cxa_guard_impl.h"
1570ebeabfSEric Fiselier #include <unordered_map>
1670ebeabfSEric Fiselier #include <thread>
1770ebeabfSEric Fiselier #include <atomic>
1870ebeabfSEric Fiselier #include <array>
1970ebeabfSEric Fiselier #include <cassert>
2070ebeabfSEric Fiselier #include <memory>
2170ebeabfSEric Fiselier #include <vector>
2270ebeabfSEric Fiselier
2356462801SLouis Dionne #include "make_test_thread.h"
24e6d94f4bSLouis Dionne #include "test_macros.h"
25e6d94f4bSLouis Dionne
2670ebeabfSEric Fiselier
2770ebeabfSEric Fiselier using namespace __cxxabiv1;
2870ebeabfSEric Fiselier
29a4939d35SEric Fiselier // Misc test configuration. It's used to tune the flakyness of the test.
30a4939d35SEric Fiselier // ThreadsPerTest - The number of threads used
31a4939d35SEric Fiselier constexpr int ThreadsPerTest = 10;
32a4939d35SEric Fiselier // The number of instances of a test to run concurrently.
33a4939d35SEric Fiselier constexpr int ConcurrentRunsPerTest = 10;
34a4939d35SEric Fiselier // The number of times to rerun each test.
35a4939d35SEric Fiselier constexpr int TestSamples = 50;
36a4939d35SEric Fiselier
37a4939d35SEric Fiselier
38a4939d35SEric Fiselier
BusyWait()39a4939d35SEric Fiselier void BusyWait() {
40a4939d35SEric Fiselier std::this_thread::yield();
41a4939d35SEric Fiselier }
42a4939d35SEric Fiselier
YieldAfterBarrier()43a4939d35SEric Fiselier void YieldAfterBarrier() {
44a4939d35SEric Fiselier std::this_thread::sleep_for(std::chrono::nanoseconds(10));
45a4939d35SEric Fiselier std::this_thread::yield();
46a4939d35SEric Fiselier }
47a4939d35SEric Fiselier
48a4939d35SEric Fiselier struct Barrier {
BarrierBarrier49a4939d35SEric Fiselier explicit Barrier(int n) : m_threads(n), m_remaining(n) { }
50a4939d35SEric Fiselier Barrier(Barrier const&) = delete;
51a4939d35SEric Fiselier Barrier& operator=(Barrier const&) = delete;
52a4939d35SEric Fiselier
arrive_and_waitBarrier53a4939d35SEric Fiselier void arrive_and_wait() const {
54a4939d35SEric Fiselier --m_remaining;
55a4939d35SEric Fiselier while (m_remaining.load()) {
56a4939d35SEric Fiselier BusyWait();
57a4939d35SEric Fiselier }
58a4939d35SEric Fiselier }
59a4939d35SEric Fiselier
arrive_and_dropBarrier60a4939d35SEric Fiselier void arrive_and_drop() const {
61a4939d35SEric Fiselier --m_remaining;
62a4939d35SEric Fiselier }
63a4939d35SEric Fiselier
wait_for_threadsBarrier64a4939d35SEric Fiselier void wait_for_threads(int n) const {
65a4939d35SEric Fiselier while ((m_threads - m_remaining.load()) < n) {
66a4939d35SEric Fiselier std::this_thread::yield();
67a4939d35SEric Fiselier }
68a4939d35SEric Fiselier }
69a4939d35SEric Fiselier
70a4939d35SEric Fiselier private:
71a4939d35SEric Fiselier const int m_threads;
72a4939d35SEric Fiselier mutable std::atomic<int> m_remaining;
73a4939d35SEric Fiselier };
74a4939d35SEric Fiselier
75a4939d35SEric Fiselier
7670ebeabfSEric Fiselier enum class InitResult {
7770ebeabfSEric Fiselier COMPLETE,
7870ebeabfSEric Fiselier PERFORMED,
7970ebeabfSEric Fiselier WAITED,
8070ebeabfSEric Fiselier ABORTED
8170ebeabfSEric Fiselier };
8270ebeabfSEric Fiselier constexpr InitResult COMPLETE = InitResult::COMPLETE;
8370ebeabfSEric Fiselier constexpr InitResult PERFORMED = InitResult::PERFORMED;
8470ebeabfSEric Fiselier constexpr InitResult WAITED = InitResult::WAITED;
8570ebeabfSEric Fiselier constexpr InitResult ABORTED = InitResult::ABORTED;
8670ebeabfSEric Fiselier
8770ebeabfSEric Fiselier
8870ebeabfSEric Fiselier template <class Impl, class GuardType, class Init>
check_guard(GuardType * g,Init init)8970ebeabfSEric Fiselier InitResult check_guard(GuardType *g, Init init) {
9070ebeabfSEric Fiselier uint8_t *first_byte = reinterpret_cast<uint8_t*>(g);
9170ebeabfSEric Fiselier if (std::__libcpp_atomic_load(first_byte, std::_AO_Acquire) == 0) {
9270ebeabfSEric Fiselier Impl impl(g);
9370ebeabfSEric Fiselier if (impl.cxa_guard_acquire() == INIT_IS_PENDING) {
94e6d94f4bSLouis Dionne #ifndef TEST_HAS_NO_EXCEPTIONS
9570ebeabfSEric Fiselier try {
9670ebeabfSEric Fiselier #endif
9770ebeabfSEric Fiselier init();
9870ebeabfSEric Fiselier impl.cxa_guard_release();
9970ebeabfSEric Fiselier return PERFORMED;
100e6d94f4bSLouis Dionne #ifndef TEST_HAS_NO_EXCEPTIONS
10170ebeabfSEric Fiselier } catch (...) {
10270ebeabfSEric Fiselier impl.cxa_guard_abort();
10370ebeabfSEric Fiselier return ABORTED;
10470ebeabfSEric Fiselier }
10570ebeabfSEric Fiselier #endif
10670ebeabfSEric Fiselier }
10770ebeabfSEric Fiselier return WAITED;
10870ebeabfSEric Fiselier }
10970ebeabfSEric Fiselier return COMPLETE;
11070ebeabfSEric Fiselier }
11170ebeabfSEric Fiselier
11270ebeabfSEric Fiselier
11370ebeabfSEric Fiselier template <class GuardType, class Impl>
11470ebeabfSEric Fiselier struct FunctionLocalStatic {
FunctionLocalStaticFunctionLocalStatic115a4939d35SEric Fiselier FunctionLocalStatic() {}
11670ebeabfSEric Fiselier FunctionLocalStatic(FunctionLocalStatic const&) = delete;
11770ebeabfSEric Fiselier
11870ebeabfSEric Fiselier template <class InitFunc>
accessFunctionLocalStatic11970ebeabfSEric Fiselier InitResult access(InitFunc&& init) {
12070ebeabfSEric Fiselier auto res = check_guard<Impl>(&guard_object, init);
12170ebeabfSEric Fiselier ++result_counts[static_cast<int>(res)];
12270ebeabfSEric Fiselier return res;
12370ebeabfSEric Fiselier }
12470ebeabfSEric Fiselier
12570ebeabfSEric Fiselier template <class InitFn>
126a4939d35SEric Fiselier struct AccessCallback {
operator ()FunctionLocalStatic::AccessCallback127a4939d35SEric Fiselier void operator()() const { this_obj->access(init); }
128a4939d35SEric Fiselier
12970ebeabfSEric Fiselier FunctionLocalStatic *this_obj;
130a4939d35SEric Fiselier InitFn init;
13170ebeabfSEric Fiselier };
13270ebeabfSEric Fiselier
133a4939d35SEric Fiselier template <class InitFn, class Callback = AccessCallback< InitFn > >
access_callbackFunctionLocalStatic134a4939d35SEric Fiselier Callback access_callback(InitFn init) {
135a4939d35SEric Fiselier return Callback{this, init};
13670ebeabfSEric Fiselier }
13770ebeabfSEric Fiselier
get_countFunctionLocalStatic13870ebeabfSEric Fiselier int get_count(InitResult I) const {
13970ebeabfSEric Fiselier return result_counts[static_cast<int>(I)].load();
14070ebeabfSEric Fiselier }
141a4939d35SEric Fiselier
num_completedFunctionLocalStatic14270ebeabfSEric Fiselier int num_completed() const {
14370ebeabfSEric Fiselier return get_count(COMPLETE) + get_count(PERFORMED) + get_count(WAITED);
14470ebeabfSEric Fiselier }
145a4939d35SEric Fiselier
num_waitingFunctionLocalStatic14670ebeabfSEric Fiselier int num_waiting() const {
14770ebeabfSEric Fiselier return waiting_threads.load();
14870ebeabfSEric Fiselier }
14970ebeabfSEric Fiselier
15070ebeabfSEric Fiselier private:
151a4939d35SEric Fiselier GuardType guard_object = {};
152a4939d35SEric Fiselier std::atomic<int> waiting_threads{0};
153a4939d35SEric Fiselier std::array<std::atomic<int>, 4> result_counts{};
15470ebeabfSEric Fiselier static_assert(static_cast<int>(ABORTED) == 3, "only 4 result kinds expected");
15570ebeabfSEric Fiselier };
15670ebeabfSEric Fiselier
15770ebeabfSEric Fiselier struct ThreadGroup {
15870ebeabfSEric Fiselier ThreadGroup() = default;
15970ebeabfSEric Fiselier ThreadGroup(ThreadGroup const&) = delete;
16070ebeabfSEric Fiselier
16170ebeabfSEric Fiselier template <class ...Args>
CreateThreadGroup16270ebeabfSEric Fiselier void Create(Args&& ...args) {
16370ebeabfSEric Fiselier threads.emplace_back(std::forward<Args>(args)...);
16470ebeabfSEric Fiselier }
16570ebeabfSEric Fiselier
166a4939d35SEric Fiselier template <class Callback>
CreateThreadsWithBarrierThreadGroup167a4939d35SEric Fiselier void CreateThreadsWithBarrier(int N, Callback cb) {
168a4939d35SEric Fiselier auto start = std::make_shared<Barrier>(N + 1);
169a4939d35SEric Fiselier for (int I=0; I < N; ++I) {
170a4939d35SEric Fiselier Create([start, cb]() {
171a4939d35SEric Fiselier start->arrive_and_wait();
172a4939d35SEric Fiselier cb();
173a4939d35SEric Fiselier });
174a4939d35SEric Fiselier }
175a4939d35SEric Fiselier start->arrive_and_wait();
176a4939d35SEric Fiselier }
177a4939d35SEric Fiselier
JoinAllThreadGroup17870ebeabfSEric Fiselier void JoinAll() {
17970ebeabfSEric Fiselier for (auto& t : threads) {
18070ebeabfSEric Fiselier t.join();
18170ebeabfSEric Fiselier }
18270ebeabfSEric Fiselier }
18370ebeabfSEric Fiselier
18470ebeabfSEric Fiselier private:
18570ebeabfSEric Fiselier std::vector<std::thread> threads;
18670ebeabfSEric Fiselier };
18770ebeabfSEric Fiselier
18870ebeabfSEric Fiselier
18970ebeabfSEric Fiselier template <class GuardType, class Impl>
test_free_for_all(int num_waiters)190a4939d35SEric Fiselier void test_free_for_all(int num_waiters) {
19170ebeabfSEric Fiselier FunctionLocalStatic<GuardType, Impl> test_obj;
19270ebeabfSEric Fiselier
19370ebeabfSEric Fiselier ThreadGroup threads;
194a4939d35SEric Fiselier
195a4939d35SEric Fiselier bool already_init = false;
196a4939d35SEric Fiselier threads.CreateThreadsWithBarrier(num_waiters,
197a4939d35SEric Fiselier test_obj.access_callback([&]() {
19870ebeabfSEric Fiselier assert(!already_init);
19970ebeabfSEric Fiselier already_init = true;
200a4939d35SEric Fiselier })
20170ebeabfSEric Fiselier );
20270ebeabfSEric Fiselier
20370ebeabfSEric Fiselier // wait for the other threads to finish initialization.
20470ebeabfSEric Fiselier threads.JoinAll();
20570ebeabfSEric Fiselier
20670ebeabfSEric Fiselier assert(test_obj.get_count(PERFORMED) == 1);
207a4939d35SEric Fiselier assert(test_obj.get_count(COMPLETE) + test_obj.get_count(WAITED) == num_waiters - 1);
208a4939d35SEric Fiselier }
209a4939d35SEric Fiselier
210a4939d35SEric Fiselier template <class GuardType, class Impl>
test_waiting_for_init(int num_waiters)211a4939d35SEric Fiselier void test_waiting_for_init(int num_waiters) {
212a4939d35SEric Fiselier FunctionLocalStatic<GuardType, Impl> test_obj;
213a4939d35SEric Fiselier
214a4939d35SEric Fiselier ThreadGroup threads;
215a4939d35SEric Fiselier
216a4939d35SEric Fiselier Barrier start_init(2);
217a4939d35SEric Fiselier threads.Create(test_obj.access_callback(
218a4939d35SEric Fiselier [&]() {
219a4939d35SEric Fiselier start_init.arrive_and_wait();
220a4939d35SEric Fiselier // Take our sweet time completing the initialization...
221a4939d35SEric Fiselier //
222a4939d35SEric Fiselier // There's a race condition between the other threads reaching the
223a4939d35SEric Fiselier // start_init barrier, and them actually hitting the cxa guard.
224a4939d35SEric Fiselier // But we're trying to test the waiting logic, we want as many
225a4939d35SEric Fiselier // threads to enter the waiting loop as possible.
226a4939d35SEric Fiselier YieldAfterBarrier();
227a4939d35SEric Fiselier }
228a4939d35SEric Fiselier ));
229a4939d35SEric Fiselier start_init.wait_for_threads(1);
230a4939d35SEric Fiselier
231a4939d35SEric Fiselier threads.CreateThreadsWithBarrier(num_waiters,
232a4939d35SEric Fiselier test_obj.access_callback([]() { assert(false); })
233a4939d35SEric Fiselier );
234a4939d35SEric Fiselier // unblock the initializing thread
235a4939d35SEric Fiselier start_init.arrive_and_drop();
236a4939d35SEric Fiselier
237a4939d35SEric Fiselier // wait for the other threads to finish initialization.
238a4939d35SEric Fiselier threads.JoinAll();
239a4939d35SEric Fiselier
240a4939d35SEric Fiselier assert(test_obj.get_count(PERFORMED) == 1);
241a4939d35SEric Fiselier assert(test_obj.get_count(ABORTED) == 0);
242a4939d35SEric Fiselier assert(test_obj.get_count(COMPLETE) + test_obj.get_count(WAITED) == num_waiters);
24370ebeabfSEric Fiselier }
24470ebeabfSEric Fiselier
24570ebeabfSEric Fiselier
24670ebeabfSEric Fiselier template <class GuardType, class Impl>
test_aborted_init(int num_waiters)247a4939d35SEric Fiselier void test_aborted_init(int num_waiters) {
24870ebeabfSEric Fiselier FunctionLocalStatic<GuardType, Impl> test_obj;
24970ebeabfSEric Fiselier
250a4939d35SEric Fiselier Barrier start_init(2);
25170ebeabfSEric Fiselier ThreadGroup threads;
252a4939d35SEric Fiselier threads.Create(test_obj.access_callback(
25370ebeabfSEric Fiselier [&]() {
254a4939d35SEric Fiselier start_init.arrive_and_wait();
255a4939d35SEric Fiselier YieldAfterBarrier();
25670ebeabfSEric Fiselier throw 42;
257a4939d35SEric Fiselier })
25870ebeabfSEric Fiselier );
259a4939d35SEric Fiselier start_init.wait_for_threads(1);
26070ebeabfSEric Fiselier
26170ebeabfSEric Fiselier bool already_init = false;
262a4939d35SEric Fiselier threads.CreateThreadsWithBarrier(num_waiters,
263a4939d35SEric Fiselier test_obj.access_callback([&]() {
26470ebeabfSEric Fiselier assert(!already_init);
26570ebeabfSEric Fiselier already_init = true;
266a4939d35SEric Fiselier })
267a4939d35SEric Fiselier );
26870ebeabfSEric Fiselier // unblock the initializing thread
269a4939d35SEric Fiselier start_init.arrive_and_drop();
27070ebeabfSEric Fiselier
27170ebeabfSEric Fiselier // wait for the other threads to finish initialization.
27270ebeabfSEric Fiselier threads.JoinAll();
27370ebeabfSEric Fiselier
27470ebeabfSEric Fiselier assert(test_obj.get_count(ABORTED) == 1);
27570ebeabfSEric Fiselier assert(test_obj.get_count(PERFORMED) == 1);
276a4939d35SEric Fiselier assert(test_obj.get_count(WAITED) + test_obj.get_count(COMPLETE) == num_waiters - 1);
27770ebeabfSEric Fiselier }
27870ebeabfSEric Fiselier
27970ebeabfSEric Fiselier
28070ebeabfSEric Fiselier template <class GuardType, class Impl>
test_completed_init(int num_waiters)281a4939d35SEric Fiselier void test_completed_init(int num_waiters) {
28270ebeabfSEric Fiselier
28370ebeabfSEric Fiselier FunctionLocalStatic<GuardType, Impl> test_obj;
28470ebeabfSEric Fiselier
285a4939d35SEric Fiselier test_obj.access([]() {}); // initialize the object
28670ebeabfSEric Fiselier assert(test_obj.num_waiting() == 0);
28770ebeabfSEric Fiselier assert(test_obj.num_completed() == 1);
28870ebeabfSEric Fiselier assert(test_obj.get_count(PERFORMED) == 1);
28970ebeabfSEric Fiselier
29070ebeabfSEric Fiselier ThreadGroup threads;
291a4939d35SEric Fiselier threads.CreateThreadsWithBarrier(num_waiters,
292a4939d35SEric Fiselier test_obj.access_callback([]() { assert(false); })
293a4939d35SEric Fiselier );
29470ebeabfSEric Fiselier // wait for the other threads to finish initialization.
29570ebeabfSEric Fiselier threads.JoinAll();
29670ebeabfSEric Fiselier
29770ebeabfSEric Fiselier assert(test_obj.get_count(ABORTED) == 0);
29870ebeabfSEric Fiselier assert(test_obj.get_count(PERFORMED) == 1);
29970ebeabfSEric Fiselier assert(test_obj.get_count(WAITED) == 0);
300a4939d35SEric Fiselier assert(test_obj.get_count(COMPLETE) == num_waiters);
30170ebeabfSEric Fiselier }
30270ebeabfSEric Fiselier
30370ebeabfSEric Fiselier template <class Impl>
test_impl()30470ebeabfSEric Fiselier void test_impl() {
305a4939d35SEric Fiselier using TestFn = void(*)(int);
306a4939d35SEric Fiselier TestFn TestList[] = {
307a4939d35SEric Fiselier test_free_for_all<uint32_t, Impl>,
308a4939d35SEric Fiselier test_free_for_all<uint32_t, Impl>,
309a4939d35SEric Fiselier test_waiting_for_init<uint32_t, Impl>,
310a4939d35SEric Fiselier test_waiting_for_init<uint64_t, Impl>,
311a4939d35SEric Fiselier test_aborted_init<uint32_t, Impl>,
312a4939d35SEric Fiselier test_aborted_init<uint64_t, Impl>,
313a4939d35SEric Fiselier test_completed_init<uint32_t, Impl>,
314a4939d35SEric Fiselier test_completed_init<uint64_t, Impl>
315a4939d35SEric Fiselier };
316a4939d35SEric Fiselier
317a4939d35SEric Fiselier for (auto test_func : TestList) {
318a4939d35SEric Fiselier ThreadGroup test_threads;
319a4939d35SEric Fiselier test_threads.CreateThreadsWithBarrier(ConcurrentRunsPerTest, [=]() {
320a4939d35SEric Fiselier for (int I = 0; I < TestSamples; ++I) {
321a4939d35SEric Fiselier test_func(ThreadsPerTest);
32270ebeabfSEric Fiselier }
323a4939d35SEric Fiselier });
324a4939d35SEric Fiselier test_threads.JoinAll();
32570ebeabfSEric Fiselier }
32670ebeabfSEric Fiselier }
32770ebeabfSEric Fiselier
test_all_impls()3285a235865SEric Fiselier void test_all_impls() {
329e42eeb88SDaniel McIntosh using MutexImpl = SelectImplementation<Implementation::GlobalMutex>::type;
33070ebeabfSEric Fiselier
33170ebeabfSEric Fiselier // Attempt to test the Futex based implementation if it's supported on the
33270ebeabfSEric Fiselier // target platform.
33370ebeabfSEric Fiselier using RealFutexImpl = SelectImplementation<Implementation::Futex>::type;
33470ebeabfSEric Fiselier using FutexImpl = typename std::conditional<
3355a235865SEric Fiselier PlatformSupportsFutex(),
33670ebeabfSEric Fiselier RealFutexImpl,
33770ebeabfSEric Fiselier MutexImpl
33870ebeabfSEric Fiselier >::type;
33970ebeabfSEric Fiselier
34070ebeabfSEric Fiselier test_impl<MutexImpl>();
3415a235865SEric Fiselier if (PlatformSupportsFutex())
34270ebeabfSEric Fiselier test_impl<FutexImpl>();
34370ebeabfSEric Fiselier }
3445a235865SEric Fiselier
3455a235865SEric Fiselier // A dummy
3465a235865SEric Fiselier template <bool Dummy = true>
test_futex_syscall()3475a235865SEric Fiselier void test_futex_syscall() {
3485a235865SEric Fiselier if (!PlatformSupportsFutex())
3495a235865SEric Fiselier return;
3505a235865SEric Fiselier int lock1 = 0;
3515a235865SEric Fiselier int lock2 = 0;
3525a235865SEric Fiselier int lock3 = 0;
35356462801SLouis Dionne std::thread waiter1 = support::make_test_thread([&]() {
3545a235865SEric Fiselier int expect = 0;
3555a235865SEric Fiselier PlatformFutexWait(&lock1, expect);
3565a235865SEric Fiselier assert(lock1 == 1);
3575a235865SEric Fiselier });
35856462801SLouis Dionne std::thread waiter2 = support::make_test_thread([&]() {
3595a235865SEric Fiselier int expect = 0;
3605a235865SEric Fiselier PlatformFutexWait(&lock2, expect);
3615a235865SEric Fiselier assert(lock2 == 2);
3625a235865SEric Fiselier });
36356462801SLouis Dionne std::thread waiter3 = support::make_test_thread([&]() {
3645a235865SEric Fiselier int expect = 42; // not the value
3655a235865SEric Fiselier PlatformFutexWait(&lock3, expect); // doesn't block
3665a235865SEric Fiselier });
36756462801SLouis Dionne std::thread waker = support::make_test_thread([&]() {
3685a235865SEric Fiselier lock1 = 1;
3695a235865SEric Fiselier PlatformFutexWake(&lock1);
3705a235865SEric Fiselier lock2 = 2;
3715a235865SEric Fiselier PlatformFutexWake(&lock2);
3725a235865SEric Fiselier });
3735a235865SEric Fiselier waiter1.join();
3745a235865SEric Fiselier waiter2.join();
3755a235865SEric Fiselier waiter3.join();
3765a235865SEric Fiselier waker.join();
3775a235865SEric Fiselier }
3785a235865SEric Fiselier
main(int,char **)379504bc07dSLouis Dionne int main(int, char**) {
3805a235865SEric Fiselier // Test each multi-threaded implementation with real threads.
3815a235865SEric Fiselier test_all_impls();
3825a235865SEric Fiselier // Test the basic sanity of the futex syscall wrappers.
3835a235865SEric Fiselier test_futex_syscall();
384504bc07dSLouis Dionne
385504bc07dSLouis Dionne return 0;
3865a235865SEric Fiselier }
387