xref: /freebsd-src/contrib/llvm-project/libcxx/include/__stop_token/atomic_unique_lock.h (revision 5f757f3ff9144b609b3c433dfd370cc6bdc191ad)
106c3fb27SDimitry Andric // -*- C++ -*-
206c3fb27SDimitry Andric //===----------------------------------------------------------------------===//
306c3fb27SDimitry Andric //
406c3fb27SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
506c3fb27SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
606c3fb27SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
706c3fb27SDimitry Andric //
806c3fb27SDimitry Andric //===----------------------------------------------------------------------===//
906c3fb27SDimitry Andric 
1006c3fb27SDimitry Andric #ifndef _LIBCPP___STOP_TOKEN_ATOMIC_UNIQUE_GUARD_H
1106c3fb27SDimitry Andric #define _LIBCPP___STOP_TOKEN_ATOMIC_UNIQUE_GUARD_H
1206c3fb27SDimitry Andric 
1306c3fb27SDimitry Andric #include <__bit/popcount.h>
1406c3fb27SDimitry Andric #include <__config>
1506c3fb27SDimitry Andric #include <atomic>
1606c3fb27SDimitry Andric 
1706c3fb27SDimitry Andric #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
1806c3fb27SDimitry Andric #  pragma GCC system_header
1906c3fb27SDimitry Andric #endif
2006c3fb27SDimitry Andric 
2106c3fb27SDimitry Andric _LIBCPP_BEGIN_NAMESPACE_STD
2206c3fb27SDimitry Andric 
2306c3fb27SDimitry Andric #if _LIBCPP_STD_VER >= 20
2406c3fb27SDimitry Andric 
2506c3fb27SDimitry Andric // This class implements an RAII unique_lock without a mutex.
2606c3fb27SDimitry Andric // It uses std::atomic<State>,
2706c3fb27SDimitry Andric // where State contains a lock bit and might contain other data,
2806c3fb27SDimitry Andric // and LockedBit is the value of State when the lock bit is set, e.g  1 << 2
2906c3fb27SDimitry Andric template <class _State, _State _LockedBit>
3006c3fb27SDimitry Andric class _LIBCPP_AVAILABILITY_SYNC __atomic_unique_lock {
31*5f757f3fSDimitry Andric   static_assert(std::__libcpp_popcount(static_cast<unsigned long long>(_LockedBit)) == 1,
32*5f757f3fSDimitry Andric                 "LockedBit must be an integer where only one bit is set");
3306c3fb27SDimitry Andric 
3406c3fb27SDimitry Andric   std::atomic<_State>& __state_;
3506c3fb27SDimitry Andric   bool __is_locked_;
3606c3fb27SDimitry Andric 
3706c3fb27SDimitry Andric public:
__atomic_unique_lock(std::atomic<_State> & __state)3806c3fb27SDimitry Andric   _LIBCPP_HIDE_FROM_ABI explicit __atomic_unique_lock(std::atomic<_State>& __state) noexcept
3906c3fb27SDimitry Andric       : __state_(__state), __is_locked_(true) {
4006c3fb27SDimitry Andric     __lock();
4106c3fb27SDimitry Andric   }
4206c3fb27SDimitry Andric 
4306c3fb27SDimitry Andric   template <class _Pred>
__atomic_unique_lock(std::atomic<_State> & __state,_Pred && __give_up_locking)4406c3fb27SDimitry Andric   _LIBCPP_HIDE_FROM_ABI __atomic_unique_lock(std::atomic<_State>& __state, _Pred&& __give_up_locking) noexcept
4506c3fb27SDimitry Andric       : __state_(__state), __is_locked_(false) {
4606c3fb27SDimitry Andric     __is_locked_ = __lock_impl(__give_up_locking, __set_locked_bit, std::memory_order_acquire);
4706c3fb27SDimitry Andric   }
4806c3fb27SDimitry Andric 
4906c3fb27SDimitry Andric   template <class _Pred, class _UnaryFunction>
__atomic_unique_lock(std::atomic<_State> & __state,_Pred && __give_up_locking,_UnaryFunction && __state_after_lock,std::memory_order __locked_ordering)5006c3fb27SDimitry Andric   _LIBCPP_HIDE_FROM_ABI __atomic_unique_lock(
5106c3fb27SDimitry Andric       std::atomic<_State>& __state,
5206c3fb27SDimitry Andric       _Pred&& __give_up_locking,
5306c3fb27SDimitry Andric       _UnaryFunction&& __state_after_lock,
5406c3fb27SDimitry Andric       std::memory_order __locked_ordering) noexcept
5506c3fb27SDimitry Andric       : __state_(__state), __is_locked_(false) {
5606c3fb27SDimitry Andric     __is_locked_ = __lock_impl(__give_up_locking, __state_after_lock, __locked_ordering);
5706c3fb27SDimitry Andric   }
5806c3fb27SDimitry Andric 
5906c3fb27SDimitry Andric   __atomic_unique_lock(const __atomic_unique_lock&)            = delete;
6006c3fb27SDimitry Andric   __atomic_unique_lock(__atomic_unique_lock&&)                 = delete;
6106c3fb27SDimitry Andric   __atomic_unique_lock& operator=(const __atomic_unique_lock&) = delete;
6206c3fb27SDimitry Andric   __atomic_unique_lock& operator=(__atomic_unique_lock&&)      = delete;
6306c3fb27SDimitry Andric 
~__atomic_unique_lock()6406c3fb27SDimitry Andric   _LIBCPP_HIDE_FROM_ABI ~__atomic_unique_lock() {
6506c3fb27SDimitry Andric     if (__is_locked_) {
6606c3fb27SDimitry Andric       __unlock();
6706c3fb27SDimitry Andric     }
6806c3fb27SDimitry Andric   }
6906c3fb27SDimitry Andric 
__owns_lock()7006c3fb27SDimitry Andric   _LIBCPP_HIDE_FROM_ABI bool __owns_lock() const noexcept { return __is_locked_; }
7106c3fb27SDimitry Andric 
__lock()7206c3fb27SDimitry Andric   _LIBCPP_HIDE_FROM_ABI void __lock() noexcept {
7306c3fb27SDimitry Andric     const auto __never_give_up_locking = [](_State) { return false; };
7406c3fb27SDimitry Andric     // std::memory_order_acquire because we'd like to make sure that all the read operations after the lock can read the
7506c3fb27SDimitry Andric     // up-to-date values.
7606c3fb27SDimitry Andric     __lock_impl(__never_give_up_locking, __set_locked_bit, std::memory_order_acquire);
7706c3fb27SDimitry Andric     __is_locked_ = true;
7806c3fb27SDimitry Andric   }
7906c3fb27SDimitry Andric 
__unlock()8006c3fb27SDimitry Andric   _LIBCPP_HIDE_FROM_ABI void __unlock() noexcept {
8106c3fb27SDimitry Andric     // unset the _LockedBit. `memory_order_release` because we need to make sure all the write operations before calling
8206c3fb27SDimitry Andric     // `__unlock` will be made visible to other threads
8306c3fb27SDimitry Andric     __state_.fetch_and(static_cast<_State>(~_LockedBit), std::memory_order_release);
8406c3fb27SDimitry Andric     __state_.notify_all();
8506c3fb27SDimitry Andric     __is_locked_ = false;
8606c3fb27SDimitry Andric   }
8706c3fb27SDimitry Andric 
8806c3fb27SDimitry Andric private:
8906c3fb27SDimitry Andric   template <class _Pred, class _UnaryFunction>
9006c3fb27SDimitry Andric   _LIBCPP_HIDE_FROM_ABI bool
__lock_impl(_Pred && __give_up_locking,_UnaryFunction && __state_after_lock,std::memory_order __locked_ordering)9106c3fb27SDimitry Andric   __lock_impl(_Pred&& __give_up_locking, // while trying to lock the state, if the predicate returns true, give up
9206c3fb27SDimitry Andric                                          // locking and return
9306c3fb27SDimitry Andric               _UnaryFunction&& __state_after_lock,
9406c3fb27SDimitry Andric               std::memory_order __locked_ordering) noexcept {
9506c3fb27SDimitry Andric     // At this stage, until we exit the inner while loop, other than the atomic state, we are not reading any order
9606c3fb27SDimitry Andric     // dependent values that is written on other threads, or writing anything that needs to be seen on other threads.
9706c3fb27SDimitry Andric     // Therefore `memory_order_relaxed` is enough.
9806c3fb27SDimitry Andric     _State __current_state = __state_.load(std::memory_order_relaxed);
9906c3fb27SDimitry Andric     do {
10006c3fb27SDimitry Andric       while (true) {
10106c3fb27SDimitry Andric         if (__give_up_locking(__current_state)) {
10206c3fb27SDimitry Andric           // user provided early return condition. fail to lock
10306c3fb27SDimitry Andric           return false;
10406c3fb27SDimitry Andric         } else if ((__current_state & _LockedBit) != 0) {
10506c3fb27SDimitry Andric           // another thread has locked the state, we need to wait
10606c3fb27SDimitry Andric           __state_.wait(__current_state, std::memory_order_relaxed);
10706c3fb27SDimitry Andric           // when it is woken up by notifyAll or spuriously, the __state_
10806c3fb27SDimitry Andric           // might have changed. reload the state
10906c3fb27SDimitry Andric           // Note that the new state's _LockedBit may or may not equal to 0
11006c3fb27SDimitry Andric           __current_state = __state_.load(std::memory_order_relaxed);
11106c3fb27SDimitry Andric         } else {
11206c3fb27SDimitry Andric           // at least for now, it is not locked. we can try `compare_exchange_weak` to lock it.
11306c3fb27SDimitry Andric           // Note that the variable `__current_state`'s lock bit has to be 0 at this point.
11406c3fb27SDimitry Andric           break;
11506c3fb27SDimitry Andric         }
11606c3fb27SDimitry Andric       }
11706c3fb27SDimitry Andric     } while (!__state_.compare_exchange_weak(
11806c3fb27SDimitry Andric         __current_state, // if __state_ has the same value of __current_state, lock bit must be zero before exchange and
11906c3fb27SDimitry Andric                          // we are good to lock/exchange and return. If _state has a different value, because other
12006c3fb27SDimitry Andric                          // threads locked it between the `break` statement above and this statement, exchange will fail
12106c3fb27SDimitry Andric                          // and go back to the inner while loop above.
12206c3fb27SDimitry Andric         __state_after_lock(__current_state), // state after lock. Usually it should be __current_state | _LockedBit.
12306c3fb27SDimitry Andric                                              // Some use cases need to set other bits at the same time as an atomic
12406c3fb27SDimitry Andric                                              // operation therefore we accept a function
12506c3fb27SDimitry Andric         __locked_ordering,        // sucessful exchange order. Usually it should be std::memory_order_acquire.
12606c3fb27SDimitry Andric                                   // Some use cases need more strict ordering therefore we accept it as a parameter
12706c3fb27SDimitry Andric         std::memory_order_relaxed // fail to exchange order. We don't need any ordering as we are going back to the
12806c3fb27SDimitry Andric                                   // inner while loop
12906c3fb27SDimitry Andric         ));
13006c3fb27SDimitry Andric     return true;
13106c3fb27SDimitry Andric   }
13206c3fb27SDimitry Andric 
13306c3fb27SDimitry Andric   _LIBCPP_HIDE_FROM_ABI static constexpr auto __set_locked_bit = [](_State __state) { return __state | _LockedBit; };
13406c3fb27SDimitry Andric };
13506c3fb27SDimitry Andric 
13606c3fb27SDimitry Andric #endif // _LIBCPP_STD_VER >= 20
13706c3fb27SDimitry Andric 
13806c3fb27SDimitry Andric _LIBCPP_END_NAMESPACE_STD
13906c3fb27SDimitry Andric 
14006c3fb27SDimitry Andric #endif // _LIBCPP___STOP_TOKEN_ATOMIC_UNIQUE_GUARD_H
141