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