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 27 using namespace __cxxabiv1; 28 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; 36 37 38 39 void BusyWait() { 40 std::this_thread::yield(); 41 } 42 43 void YieldAfterBarrier() { 44 std::this_thread::sleep_for(std::chrono::nanoseconds(10)); 45 std::this_thread::yield(); 46 } 47 48 struct 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 70 private: 71 const int m_threads; 72 mutable std::atomic<int> m_remaining; 73 }; 74 75 76 enum class InitResult { 77 COMPLETE, 78 PERFORMED, 79 WAITED, 80 ABORTED 81 }; 82 constexpr InitResult COMPLETE = InitResult::COMPLETE; 83 constexpr InitResult PERFORMED = InitResult::PERFORMED; 84 constexpr InitResult WAITED = InitResult::WAITED; 85 constexpr InitResult ABORTED = InitResult::ABORTED; 86 87 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) { 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 113 template <class GuardType, class Impl> 114 struct 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 150 private: 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 157 struct 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 184 private: 185 std::vector<std::thread> threads; 186 }; 187 188 189 template <class GuardType, class Impl> 190 void 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 210 template <class GuardType, class Impl> 211 void 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(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 246 template <class GuardType, class Impl> 247 void 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 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 280 template <class GuardType, class Impl> 281 void 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 303 template <class Impl> 304 void 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 328 void 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 346 template <bool Dummy = true> 347 void 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 379 int 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