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