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