| //===--- Implementation of a Linux mutex class ------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LLVM_LIBC_SRC_SUPPORT_THREAD_LINUX_MUTEX_H |
| #define LLVM_LIBC_SRC_SUPPORT_THREAD_LINUX_MUTEX_H |
| |
| #include "src/__support/CPP/atomic.h" |
| #include "src/__support/OSUtil/syscall.h" // For syscall functions. |
| #include "src/__support/threads/linux/futex_word.h" |
| #include "src/__support/threads/mutex_common.h" |
| |
| #include <linux/futex.h> |
| #include <stdint.h> |
| #include <sys/syscall.h> // For syscall numbers. |
| |
| namespace __llvm_libc { |
| |
| struct Mutex { |
| unsigned char timed; |
| unsigned char recursive; |
| unsigned char robust; |
| |
| void *owner; |
| unsigned long long lock_count; |
| |
| cpp::Atomic<FutexWordType> futex_word; |
| |
| enum class LockState : FutexWordType { |
| Free, |
| Locked, |
| Waiting, |
| }; |
| |
| public: |
| constexpr Mutex(bool istimed, bool isrecursive, bool isrobust) |
| : timed(istimed), recursive(isrecursive), robust(isrobust), |
| owner(nullptr), lock_count(0), |
| futex_word(FutexWordType(LockState::Free)) {} |
| |
| static MutexError init(Mutex *mutex, bool istimed, bool isrecur, |
| bool isrobust) { |
| mutex->timed = istimed; |
| mutex->recursive = isrecur; |
| mutex->robust = isrobust; |
| mutex->owner = nullptr; |
| mutex->lock_count = 0; |
| mutex->futex_word.set(FutexWordType(LockState::Free)); |
| return MutexError::NONE; |
| } |
| |
| static MutexError destroy(Mutex *) { return MutexError::NONE; } |
| |
| MutexError reset(); |
| |
| MutexError lock() { |
| bool was_waiting = false; |
| while (true) { |
| FutexWordType mutex_status = FutexWordType(LockState::Free); |
| FutexWordType locked_status = FutexWordType(LockState::Locked); |
| |
| if (futex_word.compare_exchange_strong( |
| mutex_status, FutexWordType(LockState::Locked))) { |
| if (was_waiting) |
| futex_word = FutexWordType(LockState::Waiting); |
| return MutexError::NONE; |
| } |
| |
| switch (LockState(mutex_status)) { |
| case LockState::Waiting: |
| // If other threads are waiting already, then join them. Note that the |
| // futex syscall will block if the futex data is still |
| // `LockState::Waiting` (the 4th argument to the syscall function |
| // below.) |
| __llvm_libc::syscall(SYS_futex, &futex_word.val, FUTEX_WAIT_PRIVATE, |
| FutexWordType(LockState::Waiting), 0, 0, 0); |
| was_waiting = true; |
| // Once woken up/unblocked, try everything all over. |
| continue; |
| case LockState::Locked: |
| // Mutex has been locked by another thread so set the status to |
| // LockState::Waiting. |
| if (futex_word.compare_exchange_strong( |
| locked_status, FutexWordType(LockState::Waiting))) { |
| // If we are able to set the futex data to `LockState::Waiting`, then |
| // we will wait for the futex to be woken up. Note again that the |
| // following syscall will block only if the futex data is still |
| // `LockState::Waiting`. |
| __llvm_libc::syscall(SYS_futex, &futex_word, FUTEX_WAIT_PRIVATE, |
| FutexWordType(LockState::Waiting), 0, 0, 0); |
| was_waiting = true; |
| } |
| continue; |
| case LockState::Free: |
| // If it was LockState::Free, we shouldn't be here at all. |
| return MutexError::BAD_LOCK_STATE; |
| } |
| } |
| } |
| |
| MutexError unlock() { |
| while (true) { |
| FutexWordType mutex_status = FutexWordType(LockState::Waiting); |
| if (futex_word.compare_exchange_strong(mutex_status, |
| FutexWordType(LockState::Free))) { |
| // If any thread is waiting to be woken up, then do it. |
| __llvm_libc::syscall(SYS_futex, &futex_word, FUTEX_WAKE_PRIVATE, 1, 0, |
| 0, 0); |
| return MutexError::NONE; |
| } |
| |
| if (mutex_status == FutexWordType(LockState::Locked)) { |
| // If nobody was waiting at this point, just free it. |
| if (futex_word.compare_exchange_strong(mutex_status, |
| FutexWordType(LockState::Free))) |
| return MutexError::NONE; |
| } else { |
| // This can happen, for example if some thread tries to unlock an |
| // already free mutex. |
| return MutexError::UNLOCK_WITHOUT_LOCK; |
| } |
| } |
| } |
| |
| MutexError trylock(); |
| }; |
| |
| } // namespace __llvm_libc |
| |
| #endif // LLVM_LIBC_SRC_SUPPORT_THREAD_LINUX_MUTEX_H |