1 | //===--- Implementation of a Linux mutex class ------------------*- 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 LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_MUTEX_H |
10 | #define LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_MUTEX_H |
11 | |
12 | #include "src/__support/CPP/atomic.h" |
13 | #include "src/__support/OSUtil/syscall.h" // For syscall functions. |
14 | #include "src/__support/threads/linux/futex_word.h" |
15 | #include "src/__support/threads/mutex_common.h" |
16 | |
17 | #include <linux/futex.h> |
18 | #include <stdint.h> |
19 | #include <sys/syscall.h> // For syscall numbers. |
20 | |
21 | namespace LIBC_NAMESPACE { |
22 | |
23 | struct Mutex { |
24 | unsigned char timed; |
25 | unsigned char recursive; |
26 | unsigned char robust; |
27 | |
28 | void *owner; |
29 | unsigned long long lock_count; |
30 | |
31 | cpp::Atomic<FutexWordType> futex_word; |
32 | |
33 | enum class LockState : FutexWordType { |
34 | Free, |
35 | Locked, |
36 | Waiting, |
37 | }; |
38 | |
39 | public: |
40 | constexpr Mutex(bool istimed, bool isrecursive, bool isrobust) |
41 | : timed(istimed), recursive(isrecursive), robust(isrobust), |
42 | owner(nullptr), lock_count(0), |
43 | futex_word(FutexWordType(LockState::Free)) {} |
44 | |
45 | static MutexError init(Mutex *mutex, bool istimed, bool isrecur, |
46 | bool isrobust) { |
47 | mutex->timed = istimed; |
48 | mutex->recursive = isrecur; |
49 | mutex->robust = isrobust; |
50 | mutex->owner = nullptr; |
51 | mutex->lock_count = 0; |
52 | mutex->futex_word.set(FutexWordType(LockState::Free)); |
53 | return MutexError::NONE; |
54 | } |
55 | |
56 | static MutexError destroy(Mutex *) { return MutexError::NONE; } |
57 | |
58 | MutexError reset(); |
59 | |
60 | MutexError lock() { |
61 | bool was_waiting = false; |
62 | while (true) { |
63 | FutexWordType mutex_status = FutexWordType(LockState::Free); |
64 | FutexWordType locked_status = FutexWordType(LockState::Locked); |
65 | |
66 | if (futex_word.compare_exchange_strong( |
67 | expected&: mutex_status, desired: FutexWordType(LockState::Locked))) { |
68 | if (was_waiting) |
69 | futex_word = FutexWordType(LockState::Waiting); |
70 | return MutexError::NONE; |
71 | } |
72 | |
73 | switch (LockState(mutex_status)) { |
74 | case LockState::Waiting: |
75 | // If other threads are waiting already, then join them. Note that the |
76 | // futex syscall will block if the futex data is still |
77 | // `LockState::Waiting` (the 4th argument to the syscall function |
78 | // below.) |
79 | LIBC_NAMESPACE::syscall_impl<long>( |
80 | number: FUTEX_SYSCALL_ID, ts: &futex_word.val, FUTEX_WAIT_PRIVATE, |
81 | ts: FutexWordType(LockState::Waiting), ts: 0, ts: 0, ts: 0); |
82 | was_waiting = true; |
83 | // Once woken up/unblocked, try everything all over. |
84 | continue; |
85 | case LockState::Locked: |
86 | // Mutex has been locked by another thread so set the status to |
87 | // LockState::Waiting. |
88 | if (futex_word.compare_exchange_strong( |
89 | expected&: locked_status, desired: FutexWordType(LockState::Waiting))) { |
90 | // If we are able to set the futex data to `LockState::Waiting`, then |
91 | // we will wait for the futex to be woken up. Note again that the |
92 | // following syscall will block only if the futex data is still |
93 | // `LockState::Waiting`. |
94 | LIBC_NAMESPACE::syscall_impl<long>( |
95 | number: FUTEX_SYSCALL_ID, ts: &futex_word, FUTEX_WAIT_PRIVATE, |
96 | ts: FutexWordType(LockState::Waiting), ts: 0, ts: 0, ts: 0); |
97 | was_waiting = true; |
98 | } |
99 | continue; |
100 | case LockState::Free: |
101 | // If it was LockState::Free, we shouldn't be here at all. |
102 | return MutexError::BAD_LOCK_STATE; |
103 | } |
104 | } |
105 | } |
106 | |
107 | MutexError unlock() { |
108 | while (true) { |
109 | FutexWordType mutex_status = FutexWordType(LockState::Waiting); |
110 | if (futex_word.compare_exchange_strong(expected&: mutex_status, |
111 | desired: FutexWordType(LockState::Free))) { |
112 | // If any thread is waiting to be woken up, then do it. |
113 | LIBC_NAMESPACE::syscall_impl<long>(number: FUTEX_SYSCALL_ID, ts: &futex_word, |
114 | FUTEX_WAKE_PRIVATE, ts: 1, ts: 0, ts: 0, ts: 0); |
115 | return MutexError::NONE; |
116 | } |
117 | |
118 | if (mutex_status == FutexWordType(LockState::Locked)) { |
119 | // If nobody was waiting at this point, just free it. |
120 | if (futex_word.compare_exchange_strong(expected&: mutex_status, |
121 | desired: FutexWordType(LockState::Free))) |
122 | return MutexError::NONE; |
123 | } else { |
124 | // This can happen, for example if some thread tries to unlock an |
125 | // already free mutex. |
126 | return MutexError::UNLOCK_WITHOUT_LOCK; |
127 | } |
128 | } |
129 | } |
130 | |
131 | MutexError trylock(); |
132 | }; |
133 | |
134 | } // namespace LIBC_NAMESPACE |
135 | |
136 | #endif // LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_MUTEX_H |
137 | |