blob: c8adc9aab981d21241da6af1dd01b245acc9cfcb [file]
//===--- Futex Wrapper ------------------------------------------*- 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_THREADS_LINUX_FUTEX_UTILS_H
#define LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_FUTEX_UTILS_H
#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/limits.h"
#include "src/__support/CPP/optional.h"
#include "src/__support/OSUtil/syscall.h"
#include "src/__support/error_or.h"
#include "src/__support/macros/attributes.h"
#include "src/__support/macros/config.h"
#include "src/__support/threads/linux/futex_word.h"
#include "src/__support/time/abs_timeout.h"
#include <linux/errno.h>
#include <linux/futex.h>
#if defined(SYS_futex_time64)
#include <linux/time_types.h>
#endif
namespace LIBC_NAMESPACE_DECL {
#if !defined(SYS_futex_time64) && defined(SYS_futex)
static_assert(
sizeof(timespec::tv_nsec) == sizeof(long),
"This legacy syscall fallback is only safe on platforms where tv_nsec "
"matches the register size (long). It is unsafe on 32-bit platforms "
"with 64-bit tv_nsec.");
#endif
class Futex : public cpp::Atomic<FutexWordType> {
public:
using Timeout = internal::AbsTimeout;
LIBC_INLINE constexpr Futex(FutexWordType value)
: cpp::Atomic<FutexWordType>(value) {}
LIBC_INLINE Futex &operator=(FutexWordType value) {
cpp::Atomic<FutexWordType>::store(value);
return *this;
}
LIBC_INLINE ErrorOr<int> wait(FutexWordType expected,
cpp::optional<Timeout> timeout = cpp::nullopt,
bool is_shared = false) {
// use bitset variants to enforce abs_time
uint32_t op = is_shared ? FUTEX_WAIT_BITSET : FUTEX_WAIT_BITSET_PRIVATE;
if (timeout && timeout->is_realtime()) {
op |= FUTEX_CLOCK_REALTIME;
}
for (;;) {
if (this->load(cpp::MemoryOrder::RELAXED) != expected)
return 0;
void *timeout_ptr = nullptr;
#if defined(SYS_futex_time64)
__kernel_timespec ts64;
if (timeout) {
// If timespec has the same size and layout as __kernel_timespec, we can
// pass a pointer to it directly.
if constexpr (sizeof(timespec) == sizeof(__kernel_timespec)) {
timeout_ptr = const_cast<timespec *>(&timeout->get_timespec());
} else {
// Otherwise (e.g. 32-bit platforms with 32-bit time_t), the 64-bit
// futex syscall (SYS_futex_time64) still expects a 64-bit timespec.
// We must convert it to avoid passing a mismatching structure size.
ts64.tv_sec = timeout->get_timespec().tv_sec;
ts64.tv_nsec = timeout->get_timespec().tv_nsec;
timeout_ptr = &ts64;
}
}
#else
timespec ts;
if (timeout) {
ts = timeout->get_timespec();
timeout_ptr = &ts;
}
#endif
int ret = syscall_impl<int>(
/*syscall_number=*/FUTEX_SYSCALL_ID,
/*futex_addr=*/this,
/*op=*/op,
/*expected=*/expected,
/*timeout=*/timeout_ptr,
/*ignored=*/nullptr,
/*bitset=*/FUTEX_BITSET_MATCH_ANY);
// continue waiting if interrupted; otherwise return the result
// which should normally be 0 or -ETIMEOUT.
if (ret == -EINTR)
continue;
// EAGAIN and EWOULDBLOCK will be treated as a normal finish, as the value
// has changed.
if (ret == -EAGAIN || ret == -EWOULDBLOCK)
return 0;
if (ret < 0)
return cpp::unexpected(-ret);
return ret;
}
}
LIBC_INLINE ErrorOr<int> notify_one(bool is_shared = false) {
int ret = syscall_impl<int>(
/*syscall_number=*/FUTEX_SYSCALL_ID,
/*futex_addr=*/this,
/*op=*/is_shared ? FUTEX_WAKE : FUTEX_WAKE_PRIVATE,
/*wake_limit=*/1,
/*ignored=*/nullptr,
/*ignored=*/nullptr,
/* ignored */ 0);
if (ret < 0)
return cpp::unexpected(-ret);
return ret;
}
LIBC_INLINE ErrorOr<int> notify_all(bool is_shared = false) {
int ret = syscall_impl<int>(
/*syscall_number=*/FUTEX_SYSCALL_ID,
/*futex_addr=*/this,
/*op=*/is_shared ? FUTEX_WAKE : FUTEX_WAKE_PRIVATE,
/*wake_limit=*/cpp::numeric_limits<int>::max(),
/*ignored=*/nullptr,
/*ignored=*/nullptr,
/*ignored=*/0);
if (ret < 0)
return cpp::unexpected(-ret);
return ret;
}
LIBC_INLINE ErrorOr<int> requeue_to(Futex &other,
cpp::optional<FutexWordType> oldval,
int wake_limit, int requeue_limit,
bool is_shared = false) {
int ret;
if (oldval)
ret = syscall_impl<int>(
/*syscall_number=*/FUTEX_SYSCALL_ID,
/*futex_addr=*/this,
/*op=*/
is_shared ? FUTEX_CMP_REQUEUE : FUTEX_CMP_REQUEUE_PRIVATE,
/*wake_limit=*/wake_limit,
/*requeue_limit=*/requeue_limit,
/*requeue_addr=*/&other, *oldval);
else
ret = syscall_impl<int>(
/*syscall_number=*/FUTEX_SYSCALL_ID,
/*futex_addr=*/this,
/*op=*/is_shared ? FUTEX_REQUEUE : FUTEX_REQUEUE_PRIVATE,
/*wake_limit=*/wake_limit,
/*requeue_limit=*/requeue_limit,
/*requeue_addr=*/&other);
if (ret < 0)
return cpp::unexpected<int>(-ret);
return static_cast<int>(ret);
}
};
static_assert(__is_standard_layout(Futex),
"Futex must be a standard layout type.");
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_FUTEX_UTILS_H