// // Copyright 2024 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // SimpleMutex.h: // A simple non-recursive mutex that only supports lock and unlock operations. As such, it can be // implemented more efficiently than a generic mutex such as std::mutex. In the uncontended // paths, the implementation boils down to basically an inlined atomic operation and an untaken // branch. The implementation in this file is inspired by Mesa's src/util/simple_mtx.h, which in // turn is based on "mutex3" in: // // "Futexes Are Tricky" // http://www.akkadia.org/drepper/futex.pdf // // Given that std::condition_variable only interacts with std::mutex, SimpleMutex cannot be used // with condition variables. // #ifndef COMMON_SIMPLEMUTEX_H_ #define COMMON_SIMPLEMUTEX_H_ #include "common/log_utils.h" #include "common/platform.h" #include #include // Enable futexes on: // // - Linux and derivatives (Android, ChromeOS, etc) // - Windows 8+ // // There is no TSAN support for futex currently, so it is disabled in that case #if !defined(ANGLE_WITH_TSAN) # if defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID) // Linux has had futexes for a very long time. Assume support. # define ANGLE_USE_FUTEX 1 # elif defined(ANGLE_PLATFORM_WINDOWS) && !defined(ANGLE_ENABLE_WINDOWS_UWP) && \ !defined(ANGLE_WINDOWS_NO_FUTEX) // Windows has futexes since version 8, which is already end of life (let alone older versions). // Assume support. # define ANGLE_USE_FUTEX 1 # endif // defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID) #endif // !defined(ANGLE_WITH_TSAN) namespace angle { namespace priv { #if ANGLE_USE_FUTEX class MutexOnFutex { public: void lock() { uint32_t oldState = kUnlocked; const bool lockTaken = mState.compare_exchange_strong(oldState, kLocked); // In uncontended cases, the lock is acquired and there's nothing to do if (ANGLE_UNLIKELY(!lockTaken)) { ASSERT(oldState == kLocked || oldState == kBlocked); // If not already marked as such, signal that the mutex is contended. if (oldState != kBlocked) { oldState = mState.exchange(kBlocked, std::memory_order_acq_rel); } // Wait until the lock is acquired while (oldState != kUnlocked) { futexWait(); oldState = mState.exchange(kBlocked, std::memory_order_acq_rel); } } } void unlock() { // Unlock the mutex const uint32_t oldState = mState.fetch_add(-1, std::memory_order_acq_rel); // If another thread is waiting on this mutex, wake it up if (ANGLE_UNLIKELY(oldState != kLocked)) { mState.store(kUnlocked, std::memory_order_relaxed); futexWake(); } } void assertLocked() { ASSERT(mState.load(std::memory_order_relaxed) != kUnlocked); } private: void futexWait(); void futexWake(); // Note: the ordering of these values is important due to |unlock()|'s atomic decrement. static constexpr uint32_t kUnlocked = 0; static constexpr uint32_t kLocked = 1; static constexpr uint32_t kBlocked = 2; std::atomic_uint32_t mState = 0; }; #else // !ANGLE_USE_FUTEX class MutexOnStd { public: void lock() { mutex.lock(); } void unlock() { mutex.unlock(); } void assertLocked() { ASSERT(isLocked()); } private: bool isLocked() { // This works because angle::SimpleMutex does not support recursion const bool acquiredLock = mutex.try_lock(); if (acquiredLock) { mutex.unlock(); } return !acquiredLock; } std::mutex mutex; }; #endif // ANGLE_USE_FUTEX } // namespace priv #if ANGLE_USE_FUTEX using SimpleMutex = priv::MutexOnFutex; #else using SimpleMutex = priv::MutexOnStd; #endif // A no-op mutex to replace SimpleMutex where a lock is not needed. struct NoOpMutex { void lock() {} void unlock() {} bool try_lock() { return true; } }; } // namespace angle #endif // COMMON_SIMPLEMUTEX_H_