Skip to content

Commit dd6c235

Browse files
committed
[Runtime] Use os_unfair_lock for Mutex and StaticMutex on Darwin.
os_unfair_lock is much smaller than pthread_mutex_t (4 bytes versus 64) and a bit faster. However, it doesn't support condition variables. Most of our uses of Mutex don't use condition variables, but a few do. Introduce ConditionMutex and StaticConditionMutex, which allow condition variables and continue to use pthread_mutex_t. On all other platforms, we continue to use the same backing mutex type for both Mutex and ConditionMutex. rdar://problem/45412121
1 parent 12d1147 commit dd6c235

File tree

8 files changed

+282
-41
lines changed

8 files changed

+282
-41
lines changed

include/swift/Runtime/Mutex.h

Lines changed: 147 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ class ScopedNotifyAllT {
7575
/// A ConditionVariable that works with Mutex to allow -- as an example --
7676
/// multi-threaded producers and consumers to signal each other in a safe way.
7777
class ConditionVariable {
78-
friend class Mutex;
79-
friend class StaticMutex;
78+
friend class ConditionMutex;
79+
friend class StaticConditionMutex;
8080

8181
ConditionVariable(const ConditionVariable &) = delete;
8282
ConditionVariable &operator=(const ConditionVariable &) = delete;
@@ -137,21 +137,27 @@ template <typename T, bool Inverted> class ScopedLockT {
137137
};
138138

139139
class Mutex;
140+
class ConditionMutex;
140141
class StaticMutex;
142+
class StaticConditionMutex;
141143

142144
/// A stack based object that locks the supplied mutex on construction
143145
/// and unlocks it on destruction.
144146
///
145147
/// Precondition: Mutex unlocked by this thread, undefined otherwise.
146148
typedef ScopedLockT<Mutex, false> ScopedLock;
149+
typedef ScopedLockT<ConditionMutex, false> ConditionScopedLock;
147150
typedef ScopedLockT<StaticMutex, false> StaticScopedLock;
151+
typedef ScopedLockT<StaticConditionMutex, false> StaticConditionScopedLock;
148152

149153
/// A stack based object that unlocks the supplied mutex on construction
150154
/// and relocks it on destruction.
151155
///
152156
/// Precondition: Mutex locked by this thread, undefined otherwise.
153157
typedef ScopedLockT<Mutex, true> ScopedUnlock;
158+
typedef ScopedLockT<ConditionMutex, true> ConditionScopedUnlock;
154159
typedef ScopedLockT<StaticMutex, true> StaticScopedUnlock;
160+
typedef ScopedLockT<StaticConditionMutex, true> StaticConditionScopedUnlock;
155161

156162
/// A Mutex object that supports `BasicLockable` and `Lockable` C++ concepts.
157163
/// See http://en.cppreference.com/w/cpp/concept/BasicLockable
@@ -211,6 +217,85 @@ class Mutex {
211217
/// - Does not throw exceptions but will halt on error (fatalError).
212218
bool try_lock() { return MutexPlatformHelper::try_lock(Handle); }
213219

220+
/// Acquires lock before calling the supplied critical section and releases
221+
/// lock on return from critical section.
222+
///
223+
/// This call can block while waiting for the lock to become available.
224+
///
225+
/// For example the following mutates value while holding the mutex lock.
226+
///
227+
/// ```
228+
/// mutex.lock([&value] { value++; });
229+
/// ```
230+
///
231+
/// Precondition: Mutex not held by this thread, undefined otherwise.
232+
template <typename CriticalSection>
233+
auto withLock(CriticalSection criticalSection)
234+
-> decltype(criticalSection()) {
235+
ScopedLock guard(*this);
236+
return criticalSection();
237+
}
238+
239+
private:
240+
MutexHandle Handle;
241+
};
242+
243+
/// A Mutex object that also supports ConditionVariables.
244+
///
245+
/// This is NOT a recursive mutex.
246+
class ConditionMutex {
247+
248+
ConditionMutex(const ConditionMutex &) = delete;
249+
ConditionMutex &operator=(const ConditionMutex &) = delete;
250+
ConditionMutex(ConditionMutex &&) = delete;
251+
ConditionMutex &operator=(ConditionMutex &&) = delete;
252+
253+
public:
254+
/// Constructs a non-recursive mutex.
255+
///
256+
/// If `checked` is true the mutex will attempt to check for misuse and
257+
/// fatalError when detected. If `checked` is false (the default) the
258+
/// mutex will make little to no effort to check for misuse (more efficient).
259+
explicit ConditionMutex(bool checked = false) {
260+
MutexPlatformHelper::init(Handle, checked);
261+
}
262+
~ConditionMutex() { MutexPlatformHelper::destroy(Handle); }
263+
264+
/// The lock() method has the following properties:
265+
/// - Behaves as an atomic operation.
266+
/// - Blocks the calling thread until exclusive ownership of the mutex
267+
/// can be obtained.
268+
/// - Prior m.unlock() operations on the same mutex synchronize-with
269+
/// this lock operation.
270+
/// - The behavior is undefined if the calling thread already owns
271+
/// the mutex (likely a deadlock).
272+
/// - Does not throw exceptions but will halt on error (fatalError).
273+
void lock() { MutexPlatformHelper::lock(Handle); }
274+
275+
/// The unlock() method has the following properties:
276+
/// - Behaves as an atomic operation.
277+
/// - Releases the calling thread's ownership of the mutex and
278+
/// synchronizes-with the subsequent successful lock operations on
279+
/// the same object.
280+
/// - The behavior is undefined if the calling thread does not own
281+
/// the mutex.
282+
/// - Does not throw exceptions but will halt on error (fatalError).
283+
void unlock() { MutexPlatformHelper::unlock(Handle); }
284+
285+
/// The try_lock() method has the following properties:
286+
/// - Behaves as an atomic operation.
287+
/// - Attempts to obtain exclusive ownership of the mutex for the calling
288+
/// thread without blocking. If ownership is not obtained, returns
289+
/// immediately. The function is allowed to spuriously fail and return
290+
/// even if the mutex is not currently owned by another thread.
291+
/// - If try_lock() succeeds, prior unlock() operations on the same object
292+
/// synchronize-with this operation. lock() does not synchronize with a
293+
/// failed try_lock()
294+
/// - The behavior is undefined if the calling thread already owns
295+
/// the mutex (likely a deadlock)?
296+
/// - Does not throw exceptions but will halt on error (fatalError).
297+
bool try_lock() { return MutexPlatformHelper::try_lock(Handle); }
298+
214299
/// Releases lock, waits on supplied condition, and relocks before returning.
215300
///
216301
/// Precondition: Mutex held by this thread, undefined otherwise.
@@ -232,7 +317,7 @@ class Mutex {
232317
/// Precondition: Mutex not held by this thread, undefined otherwise.
233318
template <typename CriticalSection>
234319
auto withLock(CriticalSection criticalSection) -> decltype(criticalSection()){
235-
ScopedLock guard(*this);
320+
ConditionScopedLock guard(*this);
236321
return criticalSection();
237322
}
238323

@@ -318,7 +403,7 @@ class Mutex {
318403
}
319404

320405
private:
321-
MutexHandle Handle;
406+
ConditionMutexHandle Handle;
322407
};
323408

324409
/// Compile time adjusted stack based object that locks/unlocks the supplied
@@ -557,7 +642,7 @@ class ReadWriteLock {
557642
///
558643
/// Use ConditionVariable instead unless you need static allocation.
559644
class StaticConditionVariable {
560-
friend class StaticMutex;
645+
friend class StaticConditionMutex;
561646

562647
StaticConditionVariable(const StaticConditionVariable &) = delete;
563648
StaticConditionVariable &operator=(const StaticConditionVariable &) = delete;
@@ -612,6 +697,45 @@ class StaticMutex {
612697
/// See Mutex::try_lock
613698
bool try_lock() { return MutexPlatformHelper::try_lock(Handle); }
614699

700+
/// See Mutex::lock
701+
template <typename CriticalSection>
702+
auto withLock(CriticalSection criticalSection)
703+
-> decltype(criticalSection()) {
704+
StaticScopedLock guard(*this);
705+
return criticalSection();
706+
}
707+
708+
private:
709+
MutexHandle Handle;
710+
};
711+
712+
/// A static allocation variant of ConditionMutex.
713+
///
714+
/// Use ConditionMutex instead unless you need static allocation.
715+
class StaticConditionMutex {
716+
717+
StaticConditionMutex(const StaticMutex &) = delete;
718+
StaticConditionMutex &operator=(const StaticMutex &) = delete;
719+
StaticConditionMutex(StaticMutex &&) = delete;
720+
StaticConditionMutex &operator=(StaticMutex &&) = delete;
721+
722+
public:
723+
#if SWIFT_MUTEX_SUPPORTS_CONSTEXPR
724+
constexpr
725+
#endif
726+
StaticConditionMutex()
727+
: Handle(MutexPlatformHelper::conditionStaticInit()) {
728+
}
729+
730+
/// See Mutex::lock
731+
void lock() { MutexPlatformHelper::lock(Handle); }
732+
733+
/// See Mutex::unlock
734+
void unlock() { MutexPlatformHelper::unlock(Handle); }
735+
736+
/// See Mutex::try_lock
737+
bool try_lock() { return MutexPlatformHelper::try_lock(Handle); }
738+
615739
/// See Mutex::wait
616740
void wait(StaticConditionVariable &condition) {
617741
ConditionPlatformHelper::wait(condition.Handle, Handle);
@@ -623,7 +747,7 @@ class StaticMutex {
623747
/// See Mutex::lock
624748
template <typename CriticalSection>
625749
auto withLock(CriticalSection criticalSection) -> decltype(criticalSection()){
626-
StaticScopedLock guard(*this);
750+
StaticConditionScopedLock guard(*this);
627751
return criticalSection();
628752
}
629753

@@ -661,7 +785,7 @@ class StaticMutex {
661785
}
662786

663787
private:
664-
MutexHandle Handle;
788+
ConditionMutexHandle Handle;
665789
};
666790

667791
/// A static allocation variant of ReadWriteLock.
@@ -772,21 +896,18 @@ class StaticUnsafeMutex {
772896
MutexHandle Handle;
773897
};
774898

775-
/// A "small" variant of a Mutex. This allocates the mutex on the heap, for
776-
/// places where having the mutex inline takes up too much space.
777-
///
778-
/// TODO: On OSes that provide a smaller mutex type (e.g. os_unfair_lock on
779-
/// Darwin), make SmallMutex use that and store it inline, or make Mutex use it
780-
/// and this can become a typedef there.
781-
class SmallMutex {
782-
SmallMutex(const SmallMutex &) = delete;
783-
SmallMutex &operator=(const SmallMutex &) = delete;
784-
SmallMutex(SmallMutex &&) = delete;
785-
SmallMutex &operator=(SmallMutex &&) = delete;
899+
/// An indirect variant of a Mutex. This allocates the mutex on the heap, for
900+
/// places where having the mutex inline takes up too much space. Used for
901+
/// SmallMutex on platforms where Mutex is large.
902+
class IndirectMutex {
903+
IndirectMutex(const IndirectMutex &) = delete;
904+
IndirectMutex &operator=(const IndirectMutex &) = delete;
905+
IndirectMutex(IndirectMutex &&) = delete;
906+
IndirectMutex &operator=(IndirectMutex &&) = delete;
786907

787908
public:
788-
explicit SmallMutex(bool checked = false) { Ptr = new Mutex(checked); }
789-
~SmallMutex() { delete Ptr; }
909+
explicit IndirectMutex(bool checked = false) { Ptr = new Mutex(checked); }
910+
~IndirectMutex() { delete Ptr; }
790911

791912
void lock() { Ptr->lock(); }
792913

@@ -798,6 +919,12 @@ class SmallMutex {
798919
Mutex *Ptr;
799920
};
800921

922+
/// A "small" mutex, which is pointer sized or smaller, for places where the
923+
/// mutex is stored inline with limited storage space. This uses a normal Mutex
924+
/// when that is small, and otherwise uses IndirectMutex.
925+
using SmallMutex =
926+
std::conditional_t<sizeof(Mutex) <= sizeof(void *), Mutex, IndirectMutex>;
927+
801928
// Enforce literal requirements for static variants.
802929
#if SWIFT_MUTEX_SUPPORTS_CONSTEXPR
803930
static_assert(std::is_literal_type<StaticMutex>::value,

include/swift/Runtime/MutexPThread.h

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,23 @@
2020

2121
#include <pthread.h>
2222

23+
#if defined(__APPLE__) && defined(__MACH__)
24+
#include <os/lock.h>
25+
#define HAS_OS_UNFAIR_LOCK 1
26+
#endif
27+
2328
namespace swift {
2429

2530
typedef pthread_cond_t ConditionHandle;
26-
typedef pthread_mutex_t MutexHandle;
31+
typedef pthread_mutex_t ConditionMutexHandle;
2732
typedef pthread_rwlock_t ReadWriteLockHandle;
2833

34+
#if HAS_OS_UNFAIR_LOCK
35+
typedef os_unfair_lock MutexHandle;
36+
#else
37+
typedef pthread_mutex_t MutexHandle;
38+
#endif
39+
2940
#if defined(__CYGWIN__) || defined(__ANDROID__) || defined(__HAIKU__) || defined(__wasi__)
3041
// At the moment CYGWIN pthreads implementation doesn't support the use of
3142
// constexpr for static allocation versions. The way they define things
@@ -59,7 +70,7 @@ struct ConditionPlatformHelper {
5970
static void destroy(ConditionHandle &condition);
6071
static void notifyOne(ConditionHandle &condition);
6172
static void notifyAll(ConditionHandle &condition);
62-
static void wait(ConditionHandle &condition, MutexHandle &mutex);
73+
static void wait(ConditionHandle &condition, ConditionMutexHandle &mutex);
6374
};
6475

6576
/// PThread low-level implementation that supports Mutex
@@ -72,21 +83,48 @@ struct MutexPlatformHelper {
7283
#else
7384
static
7485
#endif
75-
MutexHandle
76-
staticInit() {
86+
ConditionMutexHandle
87+
conditionStaticInit() {
7788
return PTHREAD_MUTEX_INITIALIZER;
7889
};
90+
#if SWIFT_MUTEX_SUPPORTS_CONSTEXPR
91+
static constexpr
92+
#else
93+
static
94+
#endif
95+
MutexHandle
96+
staticInit() {
97+
#if HAS_OS_UNFAIR_LOCK
98+
return OS_UNFAIR_LOCK_INIT;
99+
#else
100+
return PTHREAD_MUTEX_INITIALIZER;
101+
#endif
102+
}
103+
static void init(ConditionMutexHandle &mutex, bool checked = false);
104+
static void destroy(ConditionMutexHandle &mutex);
105+
static void lock(ConditionMutexHandle &mutex);
106+
static void unlock(ConditionMutexHandle &mutex);
107+
static bool try_lock(ConditionMutexHandle &mutex);
108+
109+
// The ConditionMutexHandle versions handle everything on-Apple platforms.
110+
#if HAS_OS_UNFAIR_LOCK
111+
79112
static void init(MutexHandle &mutex, bool checked = false);
80113
static void destroy(MutexHandle &mutex);
81114
static void lock(MutexHandle &mutex);
82115
static void unlock(MutexHandle &mutex);
83116
static bool try_lock(MutexHandle &mutex);
84117

118+
// os_unfair_lock always checks for errors, so just call through.
119+
static void unsafeLock(MutexHandle &mutex) { lock(mutex); }
120+
static void unsafeUnlock(MutexHandle &mutex) { unlock(mutex); }
121+
#endif
122+
85123
// The unsafe versions don't do error checking.
86-
static void unsafeLock(MutexHandle &mutex) {
124+
static void unsafeLock(ConditionMutexHandle &mutex) {
87125
(void)pthread_mutex_lock(&mutex);
88126
}
89-
static void unsafeUnlock(MutexHandle &mutex) {
127+
static void unsafeUnlock(ConditionMutexHandle &mutex) {
90128
(void)pthread_mutex_unlock(&mutex);
91129
}
92130
};

include/swift/Runtime/MutexSingleThreaded.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
namespace swift {
2323

2424
typedef void* ConditionHandle;
25+
typedef void* ConditionMutexHandle;
2526
typedef void* MutexHandle;
2627
typedef void* ReadWriteLockHandle;
2728

@@ -44,6 +45,9 @@ struct ConditionPlatformHelper {
4445

4546
struct MutexPlatformHelper {
4647
static constexpr MutexHandle staticInit() { return nullptr; }
48+
static constexpr ConditionMutexHandle conditionStaticInit() {
49+
return nullptr;
50+
}
4751
static void init(MutexHandle &mutex, bool checked = false) {}
4852
static void destroy(MutexHandle &mutex) {}
4953
static void lock(MutexHandle &mutex) {}

include/swift/Runtime/MutexWin32.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
namespace swift {
2626

2727
typedef CONDITION_VARIABLE ConditionHandle;
28+
typedef SRWLOCK ConditionMutexHandle;
2829
typedef SRWLOCK MutexHandle;
2930
typedef SRWLOCK ReadWriteLockHandle;
3031

@@ -51,6 +52,9 @@ struct ConditionPlatformHelper {
5152

5253
struct MutexPlatformHelper {
5354
static constexpr MutexHandle staticInit() { return SRWLOCK_INIT; }
55+
static constexpr ConditionMutexHandle conditionStaticInit() {
56+
return SRWLOCK_INIT;
57+
}
5458
static void init(MutexHandle &mutex, bool checked = false) {
5559
InitializeSRWLock(&mutex);
5660
}

0 commit comments

Comments
 (0)