Skip to content

[libc] clean up futex usage #91163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions libc/src/__support/threads/linux/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,25 @@ if(NOT TARGET libc.src.__support.OSUtil.osutil)
endif()

add_header_library(
mutex
futex_utils
HDRS
mutex.h
futex_utils.h
DEPENDS
.futex_word_type
libc.include.sys_syscall
libc.src.__support.CPP.atomic
libc.src.__support.OSUtil.osutil
libc.src.__support.CPP.atomic
libc.src.__support.CPP.limits
libc.src.__support.CPP.optional
libc.hdr.types.struct_timespec
)

add_header_library(
mutex
HDRS
mutex.h
DEPENDS
.futex
libc.src.__support.threads.mutex_common
)

Expand All @@ -25,7 +36,7 @@ add_object_library(
SRCS
thread.cpp
DEPENDS
.futex_word_type
.futex_utils
libc.config.linux.app_h
libc.include.sys_syscall
libc.src.errno.errno
Expand All @@ -50,8 +61,5 @@ add_object_library(
HDRS
../callonce.h
DEPENDS
libc.include.sys_syscall
libc.src.__support.CPP.atomic
libc.src.__support.CPP.limits
libc.src.__support.OSUtil.osutil
.futex_utils
)
24 changes: 5 additions & 19 deletions libc/src/__support/threads/linux/callonce.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,8 @@
//
//===----------------------------------------------------------------------===//

#include "futex_word.h"

#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/limits.h" // INT_MAX
#include "src/__support/OSUtil/syscall.h" // For syscall functions.
#include "src/__support/threads/callonce.h"

#include <linux/futex.h>
#include <sys/syscall.h> // For syscall numbers.
#include "src/__support/threads/linux/futex_utils.h"

namespace LIBC_NAMESPACE {

Expand All @@ -24,7 +17,7 @@ static constexpr FutexWordType WAITING = 0x22;
static constexpr FutexWordType FINISH = 0x33;

int callonce(CallOnceFlag *flag, CallOnceCallback *func) {
auto *futex_word = reinterpret_cast<cpp::Atomic<FutexWordType> *>(flag);
auto *futex_word = reinterpret_cast<Futex *>(flag);

FutexWordType not_called = NOT_CALLED;

Expand All @@ -33,22 +26,15 @@ int callonce(CallOnceFlag *flag, CallOnceCallback *func) {
if (futex_word->compare_exchange_strong(not_called, START)) {
func();
auto status = futex_word->exchange(FINISH);
if (status == WAITING) {
LIBC_NAMESPACE::syscall_impl<long>(FUTEX_SYSCALL_ID, &futex_word->val,
FUTEX_WAKE_PRIVATE,
INT_MAX, // Wake all waiters.
0, 0, 0);
}
if (status == WAITING)
futex_word->notify_all();
return 0;
}

FutexWordType status = START;
if (futex_word->compare_exchange_strong(status, WAITING) ||
status == WAITING) {
LIBC_NAMESPACE::syscall_impl<long>(
FUTEX_SYSCALL_ID, &futex_word->val, FUTEX_WAIT_PRIVATE,
WAITING, // Block only if status is still |WAITING|.
0, 0, 0);
futex_word->wait(WAITING);
}

return 0;
Expand Down
90 changes: 90 additions & 0 deletions libc/src/__support/threads/linux/futex_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//===--- 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 "hdr/types/struct_timespec.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/macros/attributes.h"
#include "src/__support/threads/linux/futex_word.h"
#include <linux/errno.h>
#include <linux/futex.h>

namespace LIBC_NAMESPACE {
class Futex : public cpp::Atomic<FutexWordType> {
public:
struct Timeout {
timespec abs_time;
bool is_realtime;
};
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 long 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;

long ret = syscall_impl<long>(
/* syscall number */ FUTEX_SYSCALL_ID,
/* futex address */ this,
/* futex operation */ op,
/* expected value */ expected,
/* timeout */ timeout ? &timeout->abs_time : nullptr,
/* 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;

return ret;
}
}
LIBC_INLINE long notify_one(bool is_shared = false) {
return syscall_impl<long>(
/* syscall number */ FUTEX_SYSCALL_ID,
/* futex address */ this,
/* futex operation */ is_shared ? FUTEX_WAKE : FUTEX_WAKE_PRIVATE,
/* wake up limit */ 1,
/* ignored */ nullptr,
/* ignored */ nullptr,
/* ignored */ 0);
}
LIBC_INLINE long notify_all(bool is_shared = false) {
return syscall_impl<long>(
/* syscall number */ FUTEX_SYSCALL_ID,
/* futex address */ this,
/* futex operation */ is_shared ? FUTEX_WAKE : FUTEX_WAKE_PRIVATE,
/* wake up limit */ cpp::numeric_limits<int>::max(),
/* ignored */ nullptr,
/* ignored */ nullptr,
/* ignored */ 0);
}
};

static_assert(__is_standard_layout(Futex),
"Futex must be a standard layout type.");
} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_FUTEX_UTILS_H
1 change: 0 additions & 1 deletion libc/src/__support/threads/linux/futex_word.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

#include <stdint.h>
#include <sys/syscall.h>

namespace LIBC_NAMESPACE {

// Futexes are 32 bits in size on all platforms, including 64-bit platforms.
Expand Down
22 changes: 5 additions & 17 deletions libc/src/__support/threads/linux/mutex.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,10 @@
#ifndef LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_MUTEX_H
#define LLVM_LIBC_SRC___SUPPORT_THREADS_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/linux/futex_utils.h"
#include "src/__support/threads/mutex_common.h"

#include <linux/futex.h>
#include <stdint.h>
#include <sys/syscall.h> // For syscall numbers.

namespace LIBC_NAMESPACE {

struct Mutex {
unsigned char timed;
unsigned char recursive;
Expand All @@ -28,7 +21,7 @@ struct Mutex {
void *owner;
unsigned long long lock_count;

cpp::Atomic<FutexWordType> futex_word;
Futex futex_word;

enum class LockState : FutexWordType {
Free,
Expand Down Expand Up @@ -76,9 +69,7 @@ struct Mutex {
// futex syscall will block if the futex data is still
// `LockState::Waiting` (the 4th argument to the syscall function
// below.)
LIBC_NAMESPACE::syscall_impl<long>(
FUTEX_SYSCALL_ID, &futex_word.val, FUTEX_WAIT_PRIVATE,
FutexWordType(LockState::Waiting), 0, 0, 0);
futex_word.wait(FutexWordType(LockState::Waiting));
was_waiting = true;
// Once woken up/unblocked, try everything all over.
continue;
Expand All @@ -91,9 +82,7 @@ struct Mutex {
// 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`.
LIBC_NAMESPACE::syscall_impl<long>(
FUTEX_SYSCALL_ID, &futex_word, FUTEX_WAIT_PRIVATE,
FutexWordType(LockState::Waiting), 0, 0, 0);
futex_word.wait(FutexWordType(LockState::Waiting));
was_waiting = true;
}
continue;
Expand All @@ -110,8 +99,7 @@ struct Mutex {
if (futex_word.compare_exchange_strong(mutex_status,
FutexWordType(LockState::Free))) {
// If any thread is waiting to be woken up, then do it.
LIBC_NAMESPACE::syscall_impl<long>(FUTEX_SYSCALL_ID, &futex_word,
FUTEX_WAKE_PRIVATE, 1, 0, 0, 0);
futex_word.notify_one();
return MutexError::NONE;
}

Expand Down
25 changes: 10 additions & 15 deletions libc/src/__support/threads/linux/thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@
#include "src/__support/OSUtil/syscall.h" // For syscall functions.
#include "src/__support/common.h"
#include "src/__support/error_or.h"
#include "src/__support/threads/linux/futex_word.h" // For FutexWordType
#include "src/errno/libc_errno.h" // For error macros
#include "src/__support/threads/linux/futex_utils.h" // For FutexWordType
#include "src/errno/libc_errno.h" // For error macros

#ifdef LIBC_TARGET_ARCH_IS_AARCH64
#include <arm_acle.h>
#endif

#include <fcntl.h>
#include <linux/futex.h>
#include <linux/param.h> // For EXEC_PAGESIZE.
#include <linux/prctl.h> // For PR_SET_NAME
#include <linux/sched.h> // For CLONE_* flags.
Expand Down Expand Up @@ -247,8 +246,7 @@ int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
// stack memory.

static constexpr size_t INTERNAL_STACK_DATA_SIZE =
sizeof(StartArgs) + sizeof(ThreadAttributes) +
sizeof(cpp::Atomic<FutexWordType>);
sizeof(StartArgs) + sizeof(ThreadAttributes) + sizeof(Futex);

// This is pretty arbitrary, but at the moment we don't adjust user provided
// stacksize (or default) to account for this data as its assumed minimal. If
Expand Down Expand Up @@ -288,9 +286,9 @@ int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
start_args->runner = runner;
start_args->arg = arg;

auto clear_tid = reinterpret_cast<cpp::Atomic<FutexWordType> *>(
auto clear_tid = reinterpret_cast<Futex *>(
adjusted_stack + sizeof(StartArgs) + sizeof(ThreadAttributes));
clear_tid->val = CLEAR_TID_VALUE;
clear_tid->set(CLEAR_TID_VALUE);
attrib->platform_data = clear_tid;

// The clone syscall takes arguments in an architecture specific order.
Expand Down Expand Up @@ -374,14 +372,11 @@ void Thread::wait() {
// The kernel should set the value at the clear tid address to zero.
// If not, it is a spurious wake and we should continue to wait on
// the futex.
auto *clear_tid =
reinterpret_cast<cpp::Atomic<FutexWordType> *>(attrib->platform_data);
while (clear_tid->load() != 0) {
// We cannot do a FUTEX_WAIT_PRIVATE here as the kernel does a
// FUTEX_WAKE and not a FUTEX_WAKE_PRIVATE.
LIBC_NAMESPACE::syscall_impl<long>(FUTEX_SYSCALL_ID, &clear_tid->val,
FUTEX_WAIT, CLEAR_TID_VALUE, nullptr);
}
auto *clear_tid = reinterpret_cast<Futex *>(attrib->platform_data);
// We cannot do a FUTEX_WAIT_PRIVATE here as the kernel does a
// FUTEX_WAKE and not a FUTEX_WAKE_PRIVATE.
while (clear_tid->load() != 0)
clear_tid->wait(CLEAR_TID_VALUE, cpp::nullopt, true);
}

bool Thread::operator==(const Thread &thread) const {
Expand Down
4 changes: 2 additions & 2 deletions libc/src/__support/threads/mutex.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
// want the constructors of the Mutex classes to be constexprs.

#if defined(__linux__)
#include "linux/mutex.h"
#include "src/__support/threads/linux/mutex.h"
#elif defined(LIBC_TARGET_ARCH_IS_GPU)
#include "gpu/mutex.h"
#include "src/__support/threads/gpu/mutex.h"
#endif // __linux__

namespace LIBC_NAMESPACE {
Expand Down
4 changes: 2 additions & 2 deletions libc/src/__support/threads/thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
//
//===----------------------------------------------------------------------===//

#include "thread.h"
#include "mutex.h"
#include "src/__support/threads/thread.h"
#include "src/__support/threads/mutex.h"

#include "src/__support/CPP/array.h"
#include "src/__support/CPP/optional.h"
Expand Down
2 changes: 1 addition & 1 deletion libc/src/threads/linux/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ add_header_library(
libc.src.__support.CPP.atomic
libc.src.__support.OSUtil.osutil
libc.src.__support.threads.mutex
libc.src.__support.threads.linux.futex_word_type
libc.src.__support.threads.linux.futex_utils
)

add_entrypoint_object(
Expand Down
9 changes: 5 additions & 4 deletions libc/src/threads/linux/CndVar.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
#define LLVM_LIBC_SRC_THREADS_LINUX_CNDVAR_H

#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/optional.h"
#include "src/__support/OSUtil/syscall.h" // For syscall functions.
#include "src/__support/threads/linux/futex_word.h"
#include "src/__support/threads/linux/futex_utils.h"
#include "src/__support/threads/mutex.h"

#include <linux/futex.h> // For futex operations.
Expand All @@ -28,7 +29,7 @@ struct CndVar {
};

struct CndWaiter {
cpp::Atomic<uint32_t> futex_word = WS_Waiting;
Futex futex_word = WS_Waiting;
CndWaiter *next = nullptr;
};

Expand Down Expand Up @@ -84,8 +85,7 @@ struct CndVar {
}
}

LIBC_NAMESPACE::syscall_impl<long>(FUTEX_SYSCALL_ID, &waiter.futex_word.val,
FUTEX_WAIT, WS_Waiting, 0, 0, 0);
waiter.futex_word.wait(WS_Waiting, cpp::nullopt, true);

// At this point, if locking |m| fails, we can simply return as the
// queued up waiter would have been removed from the queue.
Expand All @@ -109,6 +109,7 @@ struct CndVar {

qmtx.futex_word = FutexWordType(Mutex::LockState::Free);

// this is a special WAKE_OP, so we use syscall directly
LIBC_NAMESPACE::syscall_impl<long>(
FUTEX_SYSCALL_ID, &qmtx.futex_word.val, FUTEX_WAKE_OP, 1, 1,
&first->futex_word.val,
Expand Down