blob: 6118429eef78c88a5ce1a7a0eb6b9a46104f7796 [file] [log] [blame]
Lingfeng Yangc02cb032020-10-26 14:21:25 -07001// 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
Lingfeng Yange83f6172021-01-06 14:57:40 -080019#include <atomic>
20
Lingfeng Yangc02cb032020-10-26 14:21:25 -070021#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
30namespace android {
31namespace base {
32
33class AutoLock;
34class AutoWriteLock;
35class 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.
40class StaticLock {
41public:
42 using AutoLock = android::base::AutoLock;
43
44 constexpr StaticLock() = default;
45
46 // Acquire the lock.
47 void lock() {
48#ifdef _WIN32
49 ::AcquireSRWLockExclusive(&mLock);
50#else
51 ::pthread_mutex_lock(&mLock);
52#endif
53 }
54
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.
66 void unlock() {
67#ifdef _WIN32
68 ::ReleaseSRWLockExclusive(&mLock);
69#else
70 ::pthread_mutex_unlock(&mLock);
71#endif
72 }
73
74protected:
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.
90class Lock : public StaticLock {
91public:
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.
98 ~Lock() { ::pthread_mutex_destroy(&mLock); }
99#endif
100};
101
102class ReadWriteLock {
103public:
104 using AutoWriteLock = android::base::AutoWriteLock;
105 using AutoReadLock = android::base::AutoReadLock;
106
107#ifdef _WIN32
108 constexpr ReadWriteLock() = default;
109 ~ReadWriteLock() = default;
110 void lockRead() { ::AcquireSRWLockShared(&mLock); }
111 void unlockRead() { ::ReleaseSRWLockShared(&mLock); }
112 void lockWrite() { ::AcquireSRWLockExclusive(&mLock); }
113 void unlockWrite() { ::ReleaseSRWLockExclusive(&mLock); }
114
115private:
116 SRWLOCK mLock = SRWLOCK_INIT;
117#else // !_WIN32
118 ReadWriteLock() { ::pthread_rwlock_init(&mLock, NULL); }
119 ~ReadWriteLock() { ::pthread_rwlock_destroy(&mLock); }
120 void lockRead() { ::pthread_rwlock_rdlock(&mLock); }
121 void unlockRead() { ::pthread_rwlock_unlock(&mLock); }
122 void lockWrite() { ::pthread_rwlock_wrlock(&mLock); }
123 void unlockWrite() { ::pthread_rwlock_unlock(&mLock); }
124
125private:
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)
136class AutoLock {
137public:
138 AutoLock(StaticLock& lock) : mLock(lock) { mLock.lock(); }
139
140 AutoLock(AutoLock&& other) : mLock(other.mLock), mLocked(other.mLocked) {
141 other.mLocked = false;
142 }
143
144 void lock() {
145 assert(!mLocked);
146 mLock.lock();
147 mLocked = true;
148 }
149
150 void unlock() {
151 assert(mLocked);
152 mLock.unlock();
153 mLocked = false;
154 }
155
156 bool isLocked() const { return mLocked; }
157
158 ~AutoLock() {
159 if (mLocked) {
160 mLock.unlock();
161 }
162 }
163
164private:
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
173class AutoWriteLock {
174public:
175 AutoWriteLock(ReadWriteLock& lock) : mLock(lock) { mLock.lockWrite(); }
176
177 void lockWrite() {
178 assert(!mWriteLocked);
179 mLock.lockWrite();
180 mWriteLocked = true;
181 }
182
183 void unlockWrite() {
184 assert(mWriteLocked);
185 mLock.unlockWrite();
186 mWriteLocked = false;
187 }
188
189 ~AutoWriteLock() {
190 if (mWriteLocked) {
191 mLock.unlockWrite();
192 }
193 }
194
195private:
196 ReadWriteLock& mLock;
197 bool mWriteLocked = true;
198 // This class has a non-movable object.
199 DISALLOW_COPY_ASSIGN_AND_MOVE(AutoWriteLock);
200};
201
202class AutoReadLock {
203public:
204 AutoReadLock(ReadWriteLock& lock) : mLock(lock) { mLock.lockRead(); }
205
206 void lockRead() {
207 assert(!mReadLocked);
208 mLock.lockRead();
209 mReadLocked = true;
210 }
211
212 void unlockRead() {
213 assert(mReadLocked);
214 mLock.unlockRead();
215 mReadLocked = false;
216 }
217
218 ~AutoReadLock() {
219 if (mReadLocked) {
220 mLock.unlockRead();
221 }
222 }
223
224private:
225 ReadWriteLock& mLock;
226 bool mReadLocked = true;
227 // This class has a non-movable object.
228 DISALLOW_COPY_ASSIGN_AND_MOVE(AutoReadLock);
229};
230
Lingfeng Yange83f6172021-01-06 14:57:40 -0800231// 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//
282static 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
292static 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
302class SeqLock {
303public:
304 void beginWrite() {
305 mWriteLock.lock();
306 mSeq.fetch_add(1, std::memory_order_release);
307 SmpWmb();
308 }
309
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
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.
328repeat:
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
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:
347 ScopedWrite(SeqLock* lock) : mLock(lock) {
348 mLock->beginWrite();
349 }
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
363private:
364 std::atomic<uint32_t> mSeq { 0 }; // The sequence number
365 Lock mWriteLock; // Just use a normal mutex to protect writes
366};
367
Lingfeng Yangc02cb032020-10-26 14:21:25 -0700368} // namespace base
369} // namespace android