15442e15aSNick Desaulniers (paternity leave) //===-- Utility condition variable class ------------------------*- C++ -*-===// 25442e15aSNick Desaulniers (paternity leave) // 35442e15aSNick Desaulniers (paternity leave) // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 45442e15aSNick Desaulniers (paternity leave) // See https://llvm.org/LICENSE.txt for license information. 55442e15aSNick Desaulniers (paternity leave) // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 65442e15aSNick Desaulniers (paternity leave) // 75442e15aSNick Desaulniers (paternity leave) //===----------------------------------------------------------------------===// 85442e15aSNick Desaulniers (paternity leave) 95442e15aSNick Desaulniers (paternity leave) #include "src/__support/threads/CndVar.h" 100694552cSSchrodinger ZHU Yifan #include "src/__support/CPP/mutex.h" 115442e15aSNick Desaulniers (paternity leave) #include "src/__support/OSUtil/syscall.h" // syscall_impl 12*5ff3ff33SPetr Hosek #include "src/__support/macros/config.h" 135442e15aSNick Desaulniers (paternity leave) #include "src/__support/threads/linux/futex_word.h" // FutexWordType 14142afde0SSchrodinger ZHU Yifan #include "src/__support/threads/linux/raw_mutex.h" // RawMutex 150694552cSSchrodinger ZHU Yifan #include "src/__support/threads/mutex.h" // Mutex 165442e15aSNick Desaulniers (paternity leave) 175442e15aSNick Desaulniers (paternity leave) #include <sys/syscall.h> // For syscall numbers. 185442e15aSNick Desaulniers (paternity leave) 19*5ff3ff33SPetr Hosek namespace LIBC_NAMESPACE_DECL { 205442e15aSNick Desaulniers (paternity leave) 215442e15aSNick Desaulniers (paternity leave) int CndVar::wait(Mutex *m) { 225442e15aSNick Desaulniers (paternity leave) // The goal is to perform "unlock |m| and wait" in an 235442e15aSNick Desaulniers (paternity leave) // atomic operation. However, it is not possible to do it 245442e15aSNick Desaulniers (paternity leave) // in the true sense so we do it in spirit. Before unlocking 255442e15aSNick Desaulniers (paternity leave) // |m|, a new waiter object is added to the waiter queue with 265442e15aSNick Desaulniers (paternity leave) // the waiter queue locked. Iff a signalling thread signals 275442e15aSNick Desaulniers (paternity leave) // the waiter before the waiter actually starts waiting, the 285442e15aSNick Desaulniers (paternity leave) // wait operation will not begin at all and the waiter immediately 295442e15aSNick Desaulniers (paternity leave) // returns. 305442e15aSNick Desaulniers (paternity leave) 315442e15aSNick Desaulniers (paternity leave) CndWaiter waiter; 325442e15aSNick Desaulniers (paternity leave) { 330694552cSSchrodinger ZHU Yifan cpp::lock_guard ml(qmtx); 345442e15aSNick Desaulniers (paternity leave) CndWaiter *old_back = nullptr; 355442e15aSNick Desaulniers (paternity leave) if (waitq_front == nullptr) { 365442e15aSNick Desaulniers (paternity leave) waitq_front = waitq_back = &waiter; 375442e15aSNick Desaulniers (paternity leave) } else { 385442e15aSNick Desaulniers (paternity leave) old_back = waitq_back; 395442e15aSNick Desaulniers (paternity leave) waitq_back->next = &waiter; 405442e15aSNick Desaulniers (paternity leave) waitq_back = &waiter; 415442e15aSNick Desaulniers (paternity leave) } 425442e15aSNick Desaulniers (paternity leave) 435442e15aSNick Desaulniers (paternity leave) if (m->unlock() != MutexError::NONE) { 445442e15aSNick Desaulniers (paternity leave) // If we do not remove the queued up waiter before returning, 455442e15aSNick Desaulniers (paternity leave) // then another thread can potentially signal a non-existing 465442e15aSNick Desaulniers (paternity leave) // waiter. Note also that we do this with |qmtx| locked. This 475442e15aSNick Desaulniers (paternity leave) // ensures that another thread will not signal the withdrawing 485442e15aSNick Desaulniers (paternity leave) // waiter. 495442e15aSNick Desaulniers (paternity leave) waitq_back = old_back; 505442e15aSNick Desaulniers (paternity leave) if (waitq_back == nullptr) 515442e15aSNick Desaulniers (paternity leave) waitq_front = nullptr; 525442e15aSNick Desaulniers (paternity leave) else 535442e15aSNick Desaulniers (paternity leave) waitq_back->next = nullptr; 545442e15aSNick Desaulniers (paternity leave) 555442e15aSNick Desaulniers (paternity leave) return -1; 565442e15aSNick Desaulniers (paternity leave) } 575442e15aSNick Desaulniers (paternity leave) } 585442e15aSNick Desaulniers (paternity leave) 595442e15aSNick Desaulniers (paternity leave) waiter.futex_word.wait(WS_Waiting, cpp::nullopt, true); 605442e15aSNick Desaulniers (paternity leave) 615442e15aSNick Desaulniers (paternity leave) // At this point, if locking |m| fails, we can simply return as the 625442e15aSNick Desaulniers (paternity leave) // queued up waiter would have been removed from the queue. 635442e15aSNick Desaulniers (paternity leave) auto err = m->lock(); 645442e15aSNick Desaulniers (paternity leave) return err == MutexError::NONE ? 0 : -1; 655442e15aSNick Desaulniers (paternity leave) } 665442e15aSNick Desaulniers (paternity leave) 675442e15aSNick Desaulniers (paternity leave) void CndVar::notify_one() { 685442e15aSNick Desaulniers (paternity leave) // We don't use an RAII locker in this method as we want to unlock 695442e15aSNick Desaulniers (paternity leave) // |qmtx| and signal the waiter using a single FUTEX_WAKE_OP signal. 705442e15aSNick Desaulniers (paternity leave) qmtx.lock(); 715442e15aSNick Desaulniers (paternity leave) if (waitq_front == nullptr) 725442e15aSNick Desaulniers (paternity leave) qmtx.unlock(); 735442e15aSNick Desaulniers (paternity leave) 745442e15aSNick Desaulniers (paternity leave) CndWaiter *first = waitq_front; 755442e15aSNick Desaulniers (paternity leave) waitq_front = waitq_front->next; 765442e15aSNick Desaulniers (paternity leave) if (waitq_front == nullptr) 775442e15aSNick Desaulniers (paternity leave) waitq_back = nullptr; 785442e15aSNick Desaulniers (paternity leave) 79142afde0SSchrodinger ZHU Yifan qmtx.reset(); 805442e15aSNick Desaulniers (paternity leave) 815442e15aSNick Desaulniers (paternity leave) // this is a special WAKE_OP, so we use syscall directly 825442e15aSNick Desaulniers (paternity leave) LIBC_NAMESPACE::syscall_impl<long>( 83142afde0SSchrodinger ZHU Yifan FUTEX_SYSCALL_ID, &qmtx.get_raw_futex(), FUTEX_WAKE_OP, 1, 1, 845442e15aSNick Desaulniers (paternity leave) &first->futex_word.val, 855442e15aSNick Desaulniers (paternity leave) FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting)); 865442e15aSNick Desaulniers (paternity leave) } 875442e15aSNick Desaulniers (paternity leave) 885442e15aSNick Desaulniers (paternity leave) void CndVar::broadcast() { 890694552cSSchrodinger ZHU Yifan cpp::lock_guard ml(qmtx); 905442e15aSNick Desaulniers (paternity leave) uint32_t dummy_futex_word; 915442e15aSNick Desaulniers (paternity leave) CndWaiter *waiter = waitq_front; 925442e15aSNick Desaulniers (paternity leave) waitq_front = waitq_back = nullptr; 935442e15aSNick Desaulniers (paternity leave) while (waiter != nullptr) { 945442e15aSNick Desaulniers (paternity leave) // FUTEX_WAKE_OP is used instead of just FUTEX_WAKE as it allows us to 955442e15aSNick Desaulniers (paternity leave) // atomically update the waiter status to WS_Signalled before waking 965442e15aSNick Desaulniers (paternity leave) // up the waiter. A dummy location is used for the other futex of 975442e15aSNick Desaulniers (paternity leave) // FUTEX_WAKE_OP. 985442e15aSNick Desaulniers (paternity leave) LIBC_NAMESPACE::syscall_impl<long>( 995442e15aSNick Desaulniers (paternity leave) FUTEX_SYSCALL_ID, &dummy_futex_word, FUTEX_WAKE_OP, 1, 1, 1005442e15aSNick Desaulniers (paternity leave) &waiter->futex_word.val, 1015442e15aSNick Desaulniers (paternity leave) FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting)); 1025442e15aSNick Desaulniers (paternity leave) waiter = waiter->next; 1035442e15aSNick Desaulniers (paternity leave) } 1045442e15aSNick Desaulniers (paternity leave) } 1055442e15aSNick Desaulniers (paternity leave) 106*5ff3ff33SPetr Hosek } // namespace LIBC_NAMESPACE_DECL 107