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