• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (C) 2014 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #pragma once
16 
17 #include "base/Compiler.h"
18 
19 #include <atomic>
20 
21 #ifdef _WIN32
22 #define WIN32_LEAN_AND_MEAN 1
23 #include <windows.h>
24 #else
25 #include <pthread.h>
26 #endif
27 
28 #include <assert.h>
29 
30 namespace android {
31 namespace base {
32 
33 class AutoLock;
34 class AutoWriteLock;
35 class AutoReadLock;
36 
37 // A wrapper class for mutexes only suitable for using in static context,
38 // where it's OK to leak the underlying system object. Use Lock for scoped or
39 // member locks.
40 class StaticLock {
41 public:
42     using AutoLock = android::base::AutoLock;
43 
44     constexpr StaticLock() = default;
45 
46     // Acquire the lock.
lock()47     void lock() {
48 #ifdef _WIN32
49         ::AcquireSRWLockExclusive(&mLock);
50 #else
51         ::pthread_mutex_lock(&mLock);
52 #endif
53     }
54 
tryLock()55     bool tryLock() {
56         bool ret = false;
57 #ifdef _WIN32
58         ret = ::TryAcquireSRWLockExclusive(&mLock);
59 #else
60         ret = ::pthread_mutex_trylock(&mLock) == 0;
61 #endif
62         return ret;
63     }
64 
65     // Release the lock.
unlock()66     void unlock() {
67 #ifdef _WIN32
68         ::ReleaseSRWLockExclusive(&mLock);
69 #else
70         ::pthread_mutex_unlock(&mLock);
71 #endif
72     }
73 
74 protected:
75     friend class ConditionVariable;
76 
77 #ifdef _WIN32
78     // Benchmarks show that on Windows SRWLOCK performs a little bit better than
79     // CRITICAL_SECTION for uncontended mode and much better in case of
80     // contention.
81     SRWLOCK mLock = SRWLOCK_INIT;
82 #else
83     pthread_mutex_t mLock = PTHREAD_MUTEX_INITIALIZER;
84 #endif
85     // Both POSIX threads and WinAPI don't allow move (undefined behavior).
86     DISALLOW_COPY_ASSIGN_AND_MOVE(StaticLock);
87 };
88 
89 // Simple wrapper class for mutexes used in non-static context.
90 class Lock : public StaticLock {
91 public:
92     using StaticLock::AutoLock;
93 
94     constexpr Lock() = default;
95 #ifndef _WIN32
96     // The only difference is that POSIX requires a deallocation function call
97     // for its mutexes.
~Lock()98     ~Lock() { ::pthread_mutex_destroy(&mLock); }
99 #endif
100 };
101 
102 class ReadWriteLock {
103 public:
104     using AutoWriteLock = android::base::AutoWriteLock;
105     using AutoReadLock = android::base::AutoReadLock;
106 
107 #ifdef _WIN32
108     constexpr ReadWriteLock() = default;
109     ~ReadWriteLock() = default;
lockRead()110     void lockRead() { ::AcquireSRWLockShared(&mLock); }
unlockRead()111     void unlockRead() { ::ReleaseSRWLockShared(&mLock); }
lockWrite()112     void lockWrite() { ::AcquireSRWLockExclusive(&mLock); }
unlockWrite()113     void unlockWrite() { ::ReleaseSRWLockExclusive(&mLock); }
114 
115 private:
116     SRWLOCK mLock = SRWLOCK_INIT;
117 #else   // !_WIN32
ReadWriteLock()118     ReadWriteLock() { ::pthread_rwlock_init(&mLock, NULL); }
~ReadWriteLock()119     ~ReadWriteLock() { ::pthread_rwlock_destroy(&mLock); }
lockRead()120     void lockRead() { ::pthread_rwlock_rdlock(&mLock); }
unlockRead()121     void unlockRead() { ::pthread_rwlock_unlock(&mLock); }
lockWrite()122     void lockWrite() { ::pthread_rwlock_wrlock(&mLock); }
unlockWrite()123     void unlockWrite() { ::pthread_rwlock_unlock(&mLock); }
124 
125 private:
126     pthread_rwlock_t mLock;
127 #endif  // !_WIN32
128 
129     friend class ConditionVariable;
130     DISALLOW_COPY_ASSIGN_AND_MOVE(ReadWriteLock);
131 };
132 
133 // Helper class to lock / unlock a mutex automatically on scope
134 // entry and exit.
135 // NB: not thread-safe (as opposed to the Lock class)
136 class AutoLock {
137 public:
AutoLock(StaticLock & lock)138     AutoLock(StaticLock& lock) : mLock(lock) { mLock.lock(); }
139 
AutoLock(AutoLock && other)140     AutoLock(AutoLock&& other) : mLock(other.mLock), mLocked(other.mLocked) {
141         other.mLocked = false;
142     }
143 
lock()144     void lock() {
145         assert(!mLocked);
146         mLock.lock();
147         mLocked = true;
148     }
149 
unlock()150     void unlock() {
151         assert(mLocked);
152         mLock.unlock();
153         mLocked = false;
154     }
155 
isLocked()156     bool isLocked() const { return mLocked; }
157 
~AutoLock()158     ~AutoLock() {
159         if (mLocked) {
160             mLock.unlock();
161         }
162     }
163 
164 private:
165     StaticLock& mLock;
166     bool mLocked = true;
167 
168     friend class ConditionVariable;
169     // Don't allow move because this class has a non-movable object.
170     DISALLOW_COPY_AND_ASSIGN(AutoLock);
171 };
172 
173 class AutoWriteLock {
174 public:
AutoWriteLock(ReadWriteLock & lock)175     AutoWriteLock(ReadWriteLock& lock) : mLock(lock) { mLock.lockWrite(); }
176 
lockWrite()177     void lockWrite() {
178         assert(!mWriteLocked);
179         mLock.lockWrite();
180         mWriteLocked = true;
181     }
182 
unlockWrite()183     void unlockWrite() {
184         assert(mWriteLocked);
185         mLock.unlockWrite();
186         mWriteLocked = false;
187     }
188 
~AutoWriteLock()189     ~AutoWriteLock() {
190         if (mWriteLocked) {
191             mLock.unlockWrite();
192         }
193     }
194 
195 private:
196     ReadWriteLock& mLock;
197     bool mWriteLocked = true;
198     // This class has a non-movable object.
199     DISALLOW_COPY_ASSIGN_AND_MOVE(AutoWriteLock);
200 };
201 
202 class AutoReadLock {
203 public:
AutoReadLock(ReadWriteLock & lock)204     AutoReadLock(ReadWriteLock& lock) : mLock(lock) { mLock.lockRead(); }
205 
lockRead()206     void lockRead() {
207         assert(!mReadLocked);
208         mLock.lockRead();
209         mReadLocked = true;
210     }
211 
unlockRead()212     void unlockRead() {
213         assert(mReadLocked);
214         mLock.unlockRead();
215         mReadLocked = false;
216     }
217 
~AutoReadLock()218     ~AutoReadLock() {
219         if (mReadLocked) {
220             mLock.unlockRead();
221         }
222     }
223 
224 private:
225     ReadWriteLock& mLock;
226     bool mReadLocked = true;
227     // This class has a non-movable object.
228     DISALLOW_COPY_ASSIGN_AND_MOVE(AutoReadLock);
229 };
230 
231 // Seqlock (cross platform)
232 // Based on:
233 // https://lwn.net/Articles/21812/
234 // https://github.com/rigtorp/Seqlock
235 //
236 // A seqlock is meant to address performance issues with using reader/writer
237 // locks to protect data structures where the time spent performing operations
238 // while the lock is held is very short or even comparable to the time spent
239 // locking/unlocking in the first place. This is very common in situations
240 // where we have some globally accessible array of objects and multiple threads
241 // performing short little read/write operations on them (i.e., pretty much
242 // anything that uses entity component system architecture that needs to be
243 // accessed by multiple threads).
244 //
245 // The basic idea of a seqlock is to store a sequence number (like a version
246 // number) that writers increment, but readers only read. When beginning write
247 // access, the sequence number is incremented, and after write access ends, the
248 // sequence number is incremented again. This way, when a reader is trying to
249 // read and it notices a change in the sequence number (or, as an optimization,
250 // that the number is odd (because writes should always end up incrementing the
251 // sequence number by 2 if they complete)), it can try again until there is no
252 // change.
253 //
254 // The problem, however, is that we need to be very careful about how we set
255 // and compare the sequence numbers, because compilers/hardware easily reorder
256 // instructions involving what seems to be just simple integer arithmetic.
257 // (see https://www.hpl.hp.com/techreports/2012/HPL-2012-68.pdf) Atomic
258 // primitives need to be used for all accesses to the sequence number.
259 //
260 // In particular, the atomic updates to the sequence number and the actual
261 // non-atomic data accesses are allowed to be reordered by the compiler, which
262 // introduces problems when accessing the data (still allowing reads of an
263 // update in progress); we need smp_rmb.
264 // https://elixir.bootlin.com/linux/latest/source/tools/arch/arm64/include/asm/barrier.h#L25
265 //
266 // arm64: memory barrier instruction
267 // asm volatile("dmb ishld" ::: "memory")
268 // x86: compiler barrier
269 // std::atomic_signal_fence(std::memory_order_acq_rel);
270 //
271 // This smp_rmb needs to be added before and after the read operation.
272 //
273 // On the write side, we use
274 // arm64: memory barrier instruction
275 // asm volatile("dmb ishst" ::: "memory")
276 // x86: compiler barrier
277 // std::atomic_signal_fence(std::memory_order_acq_rel);
278 //
279 // https://github.com/rigtorp/Seqlock has a version that seems to address these issues, while
280 // https://elixir.bootlin.com/linux/latest/source/include/linux/seqlock.h shows how to implement in the kernel.
281 //
SmpWmb()282 static inline __attribute__((always_inline)) void SmpWmb() {
283 #if defined(__aarch64__)
284         asm volatile("dmb ishst" ::: "memory");
285 #elif defined(__x86_64__)
286         std::atomic_thread_fence(std::memory_order_release);
287 #else
288 #error "Unimplemented SmpWmb for current CPU architecture"
289 #endif
290 }
291 
SmpRmb()292 static inline __attribute__((always_inline)) void SmpRmb() {
293 #if defined(__aarch64__)
294         asm volatile("dmb ishld" ::: "memory");
295 #elif defined(__x86_64__)
296         std::atomic_thread_fence(std::memory_order_acquire);
297 #else
298 #error "Unimplemented SmpRmb for current CPU architecture"
299 #endif
300 }
301 
302 class SeqLock {
303 public:
beginWrite()304     void beginWrite() {
305         mWriteLock.lock();
306         mSeq.fetch_add(1, std::memory_order_release);
307         SmpWmb();
308     }
309 
endWrite()310     void endWrite() {
311         SmpWmb();
312         mSeq.fetch_add(1, std::memory_order_release);
313         mWriteLock.unlock();
314     }
315 
316 #ifdef __cplusplus
317 #   define SEQLOCK_LIKELY( exp )    (__builtin_expect( !!(exp), true ))
318 #   define SEQLOCK_UNLIKELY( exp )  (__builtin_expect( !!(exp), false ))
319 #else
320 #   define SEQLOCK_LIKELY( exp )    (__builtin_expect( !!(exp), 1 ))
321 #   define SEQLOCK_UNLIKELY( exp )  (__builtin_expect( !!(exp), 0 ))
322 #endif
323 
beginRead()324     uint32_t beginRead() {
325         uint32_t res;
326 
327         // see https://elixir.bootlin.com/linux/latest/source/include/linux/seqlock.h#L128; if odd we definitely know there's a write in progress, and shouldn't proceed any further.
328 repeat:
329         res = mSeq.load(std::memory_order_acquire);
330         if (SEQLOCK_UNLIKELY(res & 1)) {
331             goto repeat;
332         }
333 
334         SmpRmb();
335         return res;
336     }
337 
shouldRetryRead(uint32_t prevSeq)338     bool shouldRetryRead(uint32_t prevSeq) {
339         SmpRmb();
340         uint32_t res = mSeq.load(std::memory_order_acquire);
341         return (res != prevSeq);
342     }
343 
344     // Convenience class for write
345     class ScopedWrite {
346     public:
ScopedWrite(SeqLock * lock)347         ScopedWrite(SeqLock* lock) : mLock(lock) {
348             mLock->beginWrite();
349         }
~ScopedWrite()350         ~ScopedWrite() {
351             mLock->endWrite();
352         }
353     private:
354         SeqLock* mLock;
355     };
356 
357     // Convenience macro for read (no std::function due to its considerable overhead)
358 #define AEMU_SEQLOCK_READ_WITH_RETRY(lock, readStuff) { uint32_t aemu_seqlock_curr_seq; do { \
359     aemu_seqlock_curr_seq = (lock)->beginRead(); \
360     readStuff; \
361     } while ((lock)->shouldRetryRead(aemu_seqlock_curr_seq)); }
362 
363 private:
364     std::atomic<uint32_t> mSeq { 0 }; // The sequence number
365     Lock mWriteLock; // Just use a normal mutex to protect writes
366 };
367 
368 }  // namespace base
369 }  // namespace android
370