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_THREAD_LINUX_MUTEX_H
10 #define LLVM_LIBC_SRC_SUPPORT_THREAD_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 __llvm_libc {
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:
MutexMutex40   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 
initMutex45   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 
destroyMutex56   static MutexError destroy(Mutex *) { return MutexError::NONE; }
57 
58   MutexError reset();
59 
lockMutex60   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               mutex_status, 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         __llvm_libc::syscall(SYS_futex, &futex_word.val, FUTEX_WAIT_PRIVATE,
80                              FutexWordType(LockState::Waiting), 0, 0, 0);
81         was_waiting = true;
82         // Once woken up/unblocked, try everything all over.
83         continue;
84       case LockState::Locked:
85         // Mutex has been locked by another thread so set the status to
86         // LockState::Waiting.
87         if (futex_word.compare_exchange_strong(
88                 locked_status, FutexWordType(LockState::Waiting))) {
89           // If we are able to set the futex data to `LockState::Waiting`, then
90           // we will wait for the futex to be woken up. Note again that the
91           // following syscall will block only if the futex data is still
92           // `LockState::Waiting`.
93           __llvm_libc::syscall(SYS_futex, &futex_word, FUTEX_WAIT_PRIVATE,
94                                FutexWordType(LockState::Waiting), 0, 0, 0);
95           was_waiting = true;
96         }
97         continue;
98       case LockState::Free:
99         // If it was LockState::Free, we shouldn't be here at all.
100         return MutexError::BAD_LOCK_STATE;
101       }
102     }
103   }
104 
unlockMutex105   MutexError unlock() {
106     while (true) {
107       FutexWordType mutex_status = FutexWordType(LockState::Waiting);
108       if (futex_word.compare_exchange_strong(mutex_status,
109                                              FutexWordType(LockState::Free))) {
110         // If any thread is waiting to be woken up, then do it.
111         __llvm_libc::syscall(SYS_futex, &futex_word, FUTEX_WAKE_PRIVATE, 1, 0,
112                              0, 0);
113         return MutexError::NONE;
114       }
115 
116       if (mutex_status == FutexWordType(LockState::Locked)) {
117         // If nobody was waiting at this point, just free it.
118         if (futex_word.compare_exchange_strong(mutex_status,
119                                                FutexWordType(LockState::Free)))
120           return MutexError::NONE;
121       } else {
122         // This can happen, for example if some thread tries to unlock an
123         // already free mutex.
124         return MutexError::UNLOCK_WITHOUT_LOCK;
125       }
126     }
127   }
128 
129   MutexError trylock();
130 };
131 
132 } // namespace __llvm_libc
133 
134 #endif // LLVM_LIBC_SRC_SUPPORT_THREAD_LINUX_MUTEX_H
135