xref: /aosp_15_r20/external/angle/src/common/SimpleMutex.h (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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