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