387 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9// UNSUPPORTED: c++0310// UNSUPPORTED: no-threads11// UNSUPPORTED: no-exceptions12 13#define TESTING_CXA_GUARD14#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 used31constexpr 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(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 ABORTED81};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_EXCEPTIONS95 try {96#endif97 init();98 impl.cxa_guard_release();99 return PERFORMED;100#ifndef TEST_HAS_NO_EXCEPTIONS101 } catch (...) {102 impl.cxa_guard_abort();103 return ABORTED;104 }105#endif106 }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(COMPLETE) + get_count(PERFORMED) + get_count(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>(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 the223 // start_init barrier, and them actually hitting the cxa guard.224 // But we're trying to test the waiting logic, we want as many225 // threads to enter the waiting loop as possible.226 YieldAfterBarrier();227 }228 ));229 start_init.wait_for_threads(1);230 231 threads.CreateThreadsWithBarrier(num_waiters,232 test_obj.access_callback([]() { assert(false); })233 );234 // unblock the initializing thread235 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(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 thread269 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 object286 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 the332 // target platform.333 using RealFutexImpl = SelectImplementation<Implementation::Futex>::type;334 using FutexImpl = typename std::conditional<335 PlatformSupportsFutex(),336 RealFutexImpl,337 MutexImpl338 >::type;339 340 test_impl<MutexImpl>();341 if (PlatformSupportsFutex())342 test_impl<FutexImpl>();343}344 345// A dummy346template <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 value365 PlatformFutexWait(&lock3, expect); // doesn't block366 });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