1 // 2 // Copyright 2024 The ANGLE Project Authors. All rights reserved. 3 // Use of this source code is governed by a BSD-style license that can be 4 // found in the LICENSE file. 5 // 6 // SimpleMutex.h: 7 // A simple non-recursive mutex that only supports lock and unlock operations. As such, it can be 8 // implemented more efficiently than a generic mutex such as std::mutex. In the uncontended 9 // paths, the implementation boils down to basically an inlined atomic operation and an untaken 10 // branch. The implementation in this file is inspired by Mesa's src/util/simple_mtx.h, which in 11 // turn is based on "mutex3" in: 12 // 13 // "Futexes Are Tricky" 14 // http://www.akkadia.org/drepper/futex.pdf 15 // 16 // Given that std::condition_variable only interacts with std::mutex, SimpleMutex cannot be used 17 // with condition variables. 18 // 19 20 #ifndef COMMON_SIMPLEMUTEX_H_ 21 #define COMMON_SIMPLEMUTEX_H_ 22 23 #include "common/log_utils.h" 24 #include "common/platform.h" 25 26 #include <atomic> 27 #include <mutex> 28 29 // Enable futexes on: 30 // 31 // - Linux and derivatives (Android, ChromeOS, etc) 32 // - Windows 8+ 33 // 34 // There is no TSAN support for futex currently, so it is disabled in that case 35 #if !defined(ANGLE_WITH_TSAN) 36 # if defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID) 37 // Linux has had futexes for a very long time. Assume support. 38 # define ANGLE_USE_FUTEX 1 39 # elif defined(ANGLE_PLATFORM_WINDOWS) && !defined(ANGLE_ENABLE_WINDOWS_UWP) && \ 40 !defined(ANGLE_WINDOWS_NO_FUTEX) 41 // Windows has futexes since version 8, which is already end of life (let alone older versions). 42 // Assume support. 43 # define ANGLE_USE_FUTEX 1 44 # endif // defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID) 45 #endif // !defined(ANGLE_WITH_TSAN) 46 47 namespace angle 48 { 49 namespace priv 50 { 51 #if ANGLE_USE_FUTEX 52 class MutexOnFutex 53 { 54 public: lock()55 void lock() 56 { 57 uint32_t oldState = kUnlocked; 58 const bool lockTaken = mState.compare_exchange_strong(oldState, kLocked); 59 60 // In uncontended cases, the lock is acquired and there's nothing to do 61 if (ANGLE_UNLIKELY(!lockTaken)) 62 { 63 ASSERT(oldState == kLocked || oldState == kBlocked); 64 65 // If not already marked as such, signal that the mutex is contended. 66 if (oldState != kBlocked) 67 { 68 oldState = mState.exchange(kBlocked, std::memory_order_acq_rel); 69 } 70 // Wait until the lock is acquired 71 while (oldState != kUnlocked) 72 { 73 futexWait(); 74 oldState = mState.exchange(kBlocked, std::memory_order_acq_rel); 75 } 76 } 77 } unlock()78 void unlock() 79 { 80 // Unlock the mutex 81 const uint32_t oldState = mState.fetch_add(-1, std::memory_order_acq_rel); 82 83 // If another thread is waiting on this mutex, wake it up 84 if (ANGLE_UNLIKELY(oldState != kLocked)) 85 { 86 mState.store(kUnlocked, std::memory_order_relaxed); 87 futexWake(); 88 } 89 } assertLocked()90 void assertLocked() { ASSERT(mState.load(std::memory_order_relaxed) != kUnlocked); } 91 92 private: 93 void futexWait(); 94 void futexWake(); 95 96 // Note: the ordering of these values is important due to |unlock()|'s atomic decrement. 97 static constexpr uint32_t kUnlocked = 0; 98 static constexpr uint32_t kLocked = 1; 99 static constexpr uint32_t kBlocked = 2; 100 101 std::atomic_uint32_t mState = 0; 102 }; 103 #else // !ANGLE_USE_FUTEX 104 class MutexOnStd 105 { 106 public: 107 void lock() { mutex.lock(); } 108 void unlock() { mutex.unlock(); } 109 void assertLocked() { ASSERT(isLocked()); } 110 111 private: 112 bool isLocked() 113 { 114 // This works because angle::SimpleMutex does not support recursion 115 const bool acquiredLock = mutex.try_lock(); 116 if (acquiredLock) 117 { 118 mutex.unlock(); 119 } 120 121 return !acquiredLock; 122 } 123 124 std::mutex mutex; 125 }; 126 #endif // ANGLE_USE_FUTEX 127 } // namespace priv 128 129 #if ANGLE_USE_FUTEX 130 using SimpleMutex = priv::MutexOnFutex; 131 #else 132 using SimpleMutex = priv::MutexOnStd; 133 #endif 134 135 // A no-op mutex to replace SimpleMutex where a lock is not needed. 136 struct NoOpMutex 137 { lockNoOpMutex138 void lock() {} unlockNoOpMutex139 void unlock() {} try_lockNoOpMutex140 bool try_lock() { return true; } 141 }; 142 } // namespace angle 143 144 #endif // COMMON_SIMPLEMUTEX_H_ 145