1 //===-- Utility condition variable class ------------------------*- C++ -*-===// 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 #ifndef LLVM_LIBC_SRC_THREADS_LINUX_CNDVAR_H 10 #define LLVM_LIBC_SRC_THREADS_LINUX_CNDVAR_H 11 12 #include "include/sys/syscall.h" // For syscall numbers. 13 #include "include/threads.h" // For values like thrd_success etc. 14 #include "src/__support/CPP/atomic.h" 15 #include "src/__support/OSUtil/syscall.h" // For syscall functions. 16 #include "src/__support/threads/mutex.h" 17 18 #include <linux/futex.h> // For futex operations. 19 #include <stdint.h> 20 21 namespace __llvm_libc { 22 23 struct CndVar { 24 enum CndWaiterStatus : uint32_t { 25 WS_Waiting = 0xE, 26 WS_Signalled = 0x5, 27 }; 28 29 struct CndWaiter { 30 cpp::Atomic<uint32_t> futex_word = WS_Waiting; 31 CndWaiter *next = nullptr; 32 }; 33 34 CndWaiter *waitq_front; 35 CndWaiter *waitq_back; 36 Mutex qmtx; 37 38 static int init(CndVar *cv) { 39 cv->waitq_front = cv->waitq_back = nullptr; 40 auto err = Mutex::init(&cv->qmtx, false, false, false); 41 return err == MutexError::NONE ? thrd_success : thrd_error; 42 } 43 44 static void destroy(CndVar *cv) { 45 cv->waitq_front = cv->waitq_back = nullptr; 46 } 47 48 int wait(Mutex *m) { 49 // The goal is to perform "unlock |m| and wait" in an 50 // atomic operation. However, it is not possible to do it 51 // in the true sense so we do it in spirit. Before unlocking 52 // |m|, a new waiter object is added to the waiter queue with 53 // the waiter queue locked. Iff a signalling thread signals 54 // the waiter before the waiter actually starts waiting, the 55 // wait operation will not begin at all and the waiter immediately 56 // returns. 57 58 CndWaiter waiter; 59 { 60 MutexLock ml(&qmtx); 61 CndWaiter *old_back = nullptr; 62 if (waitq_front == nullptr) { 63 waitq_front = waitq_back = &waiter; 64 } else { 65 old_back = waitq_back; 66 waitq_back->next = &waiter; 67 waitq_back = &waiter; 68 } 69 70 if (m->unlock() != MutexError::NONE) { 71 // If we do not remove the queued up waiter before returning, 72 // then another thread can potentially signal a non-existing 73 // waiter. Note also that we do this with |qmtx| locked. This 74 // ensures that another thread will not signal the withdrawing 75 // waiter. 76 waitq_back = old_back; 77 if (waitq_back == nullptr) 78 waitq_front = nullptr; 79 else 80 waitq_back->next = nullptr; 81 82 return thrd_error; 83 } 84 } 85 86 __llvm_libc::syscall(SYS_futex, &waiter.futex_word.val, FUTEX_WAIT, 87 WS_Waiting, 0, 0, 0); 88 89 // At this point, if locking |m| fails, we can simply return as the 90 // queued up waiter would have been removed from the queue. 91 auto err = m->lock(); 92 return err == MutexError::NONE ? thrd_success : thrd_error; 93 } 94 95 int notify_one() { 96 // We don't use an RAII locker in this method as we want to unlock 97 // |qmtx| and signal the waiter using a single FUTEX_WAKE_OP signal. 98 qmtx.lock(); 99 if (waitq_front == nullptr) { 100 qmtx.unlock(); 101 return thrd_success; 102 } 103 104 CndWaiter *first = waitq_front; 105 waitq_front = waitq_front->next; 106 if (waitq_front == nullptr) 107 waitq_back = nullptr; 108 109 qmtx.futex_word = Mutex::FutexWordType(Mutex::LockState::Free); 110 111 __llvm_libc::syscall( 112 SYS_futex, &qmtx.futex_word.val, FUTEX_WAKE_OP, 1, 1, 113 &first->futex_word.val, 114 FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting)); 115 return thrd_success; 116 } 117 118 int broadcast() { 119 MutexLock ml(&qmtx); 120 uint32_t dummy_futex_word; 121 CndWaiter *waiter = waitq_front; 122 waitq_front = waitq_back = nullptr; 123 while (waiter != nullptr) { 124 // FUTEX_WAKE_OP is used instead of just FUTEX_WAKE as it allows us to 125 // atomically update the waiter status to WS_Signalled before waking 126 // up the waiter. A dummy location is used for the other futex of 127 // FUTEX_WAKE_OP. 128 __llvm_libc::syscall( 129 SYS_futex, &dummy_futex_word, FUTEX_WAKE_OP, 1, 1, 130 &waiter->futex_word.val, 131 FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting)); 132 waiter = waiter->next; 133 } 134 return thrd_success; 135 } 136 }; 137 138 static_assert(sizeof(CndVar) == sizeof(cnd_t), 139 "Mismatch in the size of the " 140 "internal representation of condition variable and the public " 141 "cnd_t type."); 142 143 } // namespace __llvm_libc 144 145 #endif // LLVM_LIBC_SRC_THREADS_LINUX_CNDVAR_H 146