blob: 0133b2069d84500b0a05974fbd607fbb511da022 [file] [log] [blame]
//===--- 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