Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qatomicwait.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 Intel Corporation.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include <qendian.h>
8#include "qfutex_p.h"
10
11#include <array>
12#include <condition_variable>
13#include <mutex>
14
15QT_BEGIN_NAMESPACE
16
17/*
18 * Implementation details:
19 *
20 * Normally, a call from atomic_notify_one() or atomic_notify_all() corresponds
21 * to a call to cond.notify_one() or cond.notify_all(). This simple
22 * implementation would require that we keep a distinct wait condition variable
23 * per atomic variable, of which there could be an arbitrary number.
24 *
25 * Instead, we have a limited set of std::condition_variable, which are
26 * selected based on the address of the variable being waited/notified on.
27 * Because multiple, distinct variables could be sharing the same condition
28 * variable, we need to perform a cond.notify_all() call in case the lock is
29 * contended by waiters to more than one address, as we can't be sure which one
30 * would be woken up by the cond.notify_one() call.
31 *
32 * We recover some of the performance of notifying a single waker by also
33 * storing the address of the variable that was being waited on. If it matches
34 * the address of the variable being notified, we can perform a notify_one()
35 * call. This also allows us to avoid any system call in case no waiter has yet
36 * joined the queue. In case of contention, we store a sentinel address
37 * instead, indicating there are waiters to multiple variables. The last waiter
38 * to leave a wait queue is responsible for resetting the watched address back
39 * to nullptr.
40 *
41 * Performance details:
42 *
43 * This implementation is designed for systems where neither the Standard
44 * Library's own std::atomic_wait nor operating system futexes are available
45 * (read: usually, not the systems Qt is used most frequently on). Therefore,
46 * it's designed for simplicity, not performance. Simplifications applied
47 * include:
48 * - the total number of possible condition variables
49 * - the hashing algorithm to select a condition variable (simple XOR)
50 * - no attempt to wait for a variable change before calling cond.wait()
51 * (no spinning, no HW-assisted wait)
52 *
53 * Other limitations:
54 *
55 * We only support 8-, 16-, 32- and 64-bit variables and we require bit-exact
56 * comparisons for all bits. This means we do not support objects with padding
57 * bits or without unique representations.
58 */
59
60namespace {
62{
63 // Sentinel address used to indicate this Lock is being used by waiters to
64 // multiple addresses, implying we must make a notify_all() call.
65 static void *contendedWatchAddress() { return reinterpret_cast<void *>(-1); }
66
67 struct Lock {
68 alignas(QtPrivate::IdealMutexAlignment) std::mutex mutex;
69 alignas(QtPrivate::IdealMutexAlignment) std::condition_variable cond;
70
71 // Can assume values:
72 // - nullptr: no waiter is waiting
73 // - contendedWatchAddress(): waiters to distinct addresses
74 // - any other value: all waiters are waiting on the same address
75 const void *watchedAddress = nullptr;
77 };
78
79 static constexpr int LockCount = 16;
81
82 int indexFor(const void *ptr)
83 {
84 static_assert((LockCount & (LockCount - 1)) == 0, "LockCount is not a power of two");
85
86 quintptr value = quintptr(ptr) / sizeof(int);
87 quintptr idx = value % LockCount;
88#ifndef QATOMICWAIT_USE_FALLBACK
89 // XOR some higher bits too to avoid hashing collisions
90 // (we don't do it in the unit test because we *want* those collisions)
91 idx ^= (value / LockCount) % LockCount;
92 idx ^= (value / LockCount / LockCount) % LockCount;
93#endif
94 return int(idx);
95 }
96
97 Lock &lockFor(const void *ptr) { return locks[indexFor(ptr)]; }
98};
99} // unnamed namespace
100
101static inline void checkFutexUse()
102{
103#if !defined(QATOMICWAIT_USE_FALLBACK)
104 if (QtFutex::futexAvailable()) {
105 // This will disable the code and data on systems where futexes are
106 // always available (currently: FreeBSD, Linux, Windows).
107 qFatal("Implementation should have used futex!");
108 }
109#endif
110}
111
113{
115
116 static QAtomicWaitLocks global {};
117 return global;
118}
119
120template <typename T> static bool isEqual(const void *address, const void *old)
121{
122 auto atomic = static_cast<const std::atomic<T> *>(address);
123 auto expected = static_cast<const T *>(old);
124 return atomic->load(std::memory_order_relaxed) == *expected;
125}
126
127static bool isEqual(const void *address, const void *old, qsizetype size) noexcept
128{
129 switch (size) {
130 case 1: return isEqual<quint8 >(address, old);
131 case 2: return isEqual<quint16>(address, old);
132 case 4: return isEqual<quint32>(address, old);
133 case 8: return isEqual<quint64>(address, old);
134 }
135 Q_UNREACHABLE_RETURN(false);
136}
137
138void QtPrivate::_q_atomicWait(const void *address, const void *old, qsizetype size) noexcept
139{
140 auto &locker = atomicLocks().lockFor(address);
141
142 // NOT noexcept; we'll terminate if locking the mutex fails
143 std::unique_lock lock(locker.mutex);
144
145 // is the value still current?
146 if (!isEqual(address, old, size))
147 return; // no, failed to wait
148
149 if (locker.watchedAddress && locker.watchedAddress != address)
151 else
152 locker.watchedAddress = address;
153 ++locker.watcherCount;
154
155 do {
156 locker.cond.wait(lock);
157 } while (isEqual(address, old, size));
158
159 if (--locker.watcherCount == 0)
160 locker.watchedAddress = nullptr;
161}
162
163void QtPrivate::_q_atomicWake(void *address, WakeMode mode) noexcept
164{
165 auto &locker = atomicLocks().lockFor(address);
166
167 // NOT noexcept; we'll terminate if locking the mutex fails
168 std::unique_lock lock(locker.mutex);
169
170 // can we wake just one?
171 if (mode == WakeMode::One && locker.watchedAddress == address)
172 locker.cond.notify_one();
173 else if (locker.watchedAddress != nullptr)
174 locker.cond.notify_all();
175 else
176 qt_noop(); // no one was waiting
177}
178
179QT_END_NAMESPACE
constexpr bool futexAvailable()
Definition qfutex_p.h:25
Q_ATOMICWAIT_EXPORT void _q_atomicWake(void *address, WakeMode) noexcept
Q_ATOMICWAIT_EXPORT void _q_atomicWait(const void *address, const void *old, qsizetype size) noexcept
static bool isEqual(const void *address, const void *old)
static bool isEqual(const void *address, const void *old, qsizetype size) noexcept
static void checkFutexUse()
static QAtomicWaitLocks & atomicLocks() noexcept
std::array< Lock, LockCount > locks
Lock & lockFor(const void *ptr)