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 #include "src/__support/threads/CndVar.h"
10 #include "src/__support/CPP/mutex.h"
11 #include "src/__support/OSUtil/syscall.h" // syscall_impl
12 #include "src/__support/threads/linux/futex_word.h" // FutexWordType
13 #include "src/__support/threads/linux/raw_mutex.h" // RawMutex
14 #include "src/__support/threads/mutex.h" // Mutex
15
16 #include <sys/syscall.h> // For syscall numbers.
17
18 namespace LIBC_NAMESPACE {
19
wait(Mutex * m)20 int CndVar::wait(Mutex *m) {
21 // The goal is to perform "unlock |m| and wait" in an
22 // atomic operation. However, it is not possible to do it
23 // in the true sense so we do it in spirit. Before unlocking
24 // |m|, a new waiter object is added to the waiter queue with
25 // the waiter queue locked. Iff a signalling thread signals
26 // the waiter before the waiter actually starts waiting, the
27 // wait operation will not begin at all and the waiter immediately
28 // returns.
29
30 CndWaiter waiter;
31 {
32 cpp::lock_guard ml(qmtx);
33 CndWaiter *old_back = nullptr;
34 if (waitq_front == nullptr) {
35 waitq_front = waitq_back = &waiter;
36 } else {
37 old_back = waitq_back;
38 waitq_back->next = &waiter;
39 waitq_back = &waiter;
40 }
41
42 if (m->unlock() != MutexError::NONE) {
43 // If we do not remove the queued up waiter before returning,
44 // then another thread can potentially signal a non-existing
45 // waiter. Note also that we do this with |qmtx| locked. This
46 // ensures that another thread will not signal the withdrawing
47 // waiter.
48 waitq_back = old_back;
49 if (waitq_back == nullptr)
50 waitq_front = nullptr;
51 else
52 waitq_back->next = nullptr;
53
54 return -1;
55 }
56 }
57
58 waiter.futex_word.wait(WS_Waiting, cpp::nullopt, true);
59
60 // At this point, if locking |m| fails, we can simply return as the
61 // queued up waiter would have been removed from the queue.
62 auto err = m->lock();
63 return err == MutexError::NONE ? 0 : -1;
64 }
65
notify_one()66 void CndVar::notify_one() {
67 // We don't use an RAII locker in this method as we want to unlock
68 // |qmtx| and signal the waiter using a single FUTEX_WAKE_OP signal.
69 qmtx.lock();
70 if (waitq_front == nullptr)
71 qmtx.unlock();
72
73 CndWaiter *first = waitq_front;
74 waitq_front = waitq_front->next;
75 if (waitq_front == nullptr)
76 waitq_back = nullptr;
77
78 qmtx.reset();
79
80 // this is a special WAKE_OP, so we use syscall directly
81 LIBC_NAMESPACE::syscall_impl<long>(
82 FUTEX_SYSCALL_ID, &qmtx.get_raw_futex(), FUTEX_WAKE_OP, 1, 1,
83 &first->futex_word.val,
84 FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting));
85 }
86
broadcast()87 void CndVar::broadcast() {
88 cpp::lock_guard ml(qmtx);
89 uint32_t dummy_futex_word;
90 CndWaiter *waiter = waitq_front;
91 waitq_front = waitq_back = nullptr;
92 while (waiter != nullptr) {
93 // FUTEX_WAKE_OP is used instead of just FUTEX_WAKE as it allows us to
94 // atomically update the waiter status to WS_Signalled before waking
95 // up the waiter. A dummy location is used for the other futex of
96 // FUTEX_WAKE_OP.
97 LIBC_NAMESPACE::syscall_impl<long>(
98 FUTEX_SYSCALL_ID, &dummy_futex_word, FUTEX_WAKE_OP, 1, 1,
99 &waiter->futex_word.val,
100 FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting));
101 waiter = waiter->next;
102 }
103 }
104
105 } // namespace LIBC_NAMESPACE
106