1//===-- mutex.h -------------------------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#ifndef SCUDO_MUTEX_H_
10#define SCUDO_MUTEX_H_
11
12#include "atomic_helpers.h"
13#include "common.h"
14#include "thread_annotations.h"
15
16#include <string.h>
17
18#if SCUDO_FUCHSIA
19#include <lib/sync/mutex.h> // for sync_mutex_t
20#endif
21
22namespace scudo {
23
24class CAPABILITY("mutex") HybridMutex {
25public:
26 bool tryLock() TRY_ACQUIRE(true);
27 NOINLINE void lock() ACQUIRE() {
28 if (LIKELY(tryLock()))
29 return;
30 // The compiler may try to fully unroll the loop, ending up in a
31 // NumberOfTries*NumberOfYields block of pauses mixed with tryLocks. This
32 // is large, ugly and unneeded, a compact loop is better for our purpose
33 // here. Use a pragma to tell the compiler not to unroll the loop.
34#ifdef __clang__
35#pragma nounroll
36#endif
37 for (u8 I = 0U; I < NumberOfTries; I++) {
38 delayLoop();
39 if (tryLock())
40 return;
41 }
42 lockSlow();
43 }
44 void unlock() RELEASE();
45
46 // TODO(chiahungduan): In general, we may want to assert the owner of lock as
47 // well. Given the current uses of HybridMutex, it's acceptable without
48 // asserting the owner. Re-evaluate this when we have certain scenarios which
49 // requires a more fine-grained lock granularity.
50 ALWAYS_INLINE void assertHeld() ASSERT_CAPABILITY(this) {
51 if (SCUDO_DEBUG)
52 assertHeldImpl();
53 }
54
55private:
56 void delayLoop() {
57 // The value comes from the average time spent in accessing caches (which
58 // are the fastest operations) so that we are unlikely to wait too long for
59 // fast operations.
60 constexpr u32 SpinTimes = 16;
61 volatile u32 V = 0;
62 for (u32 I = 0; I < SpinTimes; ++I) {
63 u32 Tmp = V + 1;
64 V = Tmp;
65 }
66 }
67
68 void assertHeldImpl();
69
70 // TODO(chiahungduan): Adapt this value based on scenarios. E.g., primary and
71 // secondary allocator have different allocation times.
72 static constexpr u8 NumberOfTries = 32U;
73
74#if SCUDO_LINUX
75 atomic_u32 M = {};
76#elif SCUDO_FUCHSIA
77 sync_mutex_t M = {};
78#endif
79
80 void lockSlow() ACQUIRE();
81};
82
83class SCOPED_CAPABILITY ScopedLock {
84public:
85 explicit ScopedLock(HybridMutex &M) ACQUIRE(M) : Mutex(M) { Mutex.lock(); }
86 ~ScopedLock() RELEASE() { Mutex.unlock(); }
87
88private:
89 HybridMutex &Mutex;
90
91 ScopedLock(const ScopedLock &) = delete;
92 void operator=(const ScopedLock &) = delete;
93};
94
95} // namespace scudo
96
97#endif // SCUDO_MUTEX_H_
98