Skip to content

Commit ab3a9e7

Browse files
[libc] clean up futex usage (#91163)
# Motivation Futex syscalls are widely used in our codebase as synchronization mechanism. Hence, it may be worthy to abstract out the most common routines (wait and wake). On the other hand, C++20 also provides `std::atomic_notify_one/std::atomic_wait/std::atomic_notify_all` which align with such functionalities. This PR introduces `Futex` as a subtype of `cpp::Atomic<FutexWordType>` with additional `notify_one/notify_all/wait` operations. Providing such wrappers also make future porting easier. For example, FreeBSD's `_umtx_op` and Darwin's `ulock` can be wrapped in a similar manner. ### Similar Examples 1. [bionic futex](https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/bionic/bionic_futex.cpp) 2. [futex in Rust's std](https://github.com/rust-lang/rust/blob/8cef37dbb67e9c80702925f19cf298c4203991e4/library/std/src/sys/pal/unix/futex.rs#L21)
1 parent 099417d commit ab3a9e7

File tree

10 files changed

+136
-69
lines changed

10 files changed

+136
-69
lines changed

libc/src/__support/threads/linux/CMakeLists.txt

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,25 @@ if(NOT TARGET libc.src.__support.OSUtil.osutil)
99
endif()
1010

1111
add_header_library(
12-
mutex
12+
futex_utils
1313
HDRS
14-
mutex.h
14+
futex_utils.h
1515
DEPENDS
1616
.futex_word_type
1717
libc.include.sys_syscall
18-
libc.src.__support.CPP.atomic
1918
libc.src.__support.OSUtil.osutil
19+
libc.src.__support.CPP.atomic
20+
libc.src.__support.CPP.limits
21+
libc.src.__support.CPP.optional
22+
libc.hdr.types.struct_timespec
23+
)
24+
25+
add_header_library(
26+
mutex
27+
HDRS
28+
mutex.h
29+
DEPENDS
30+
.futex
2031
libc.src.__support.threads.mutex_common
2132
)
2233

@@ -25,7 +36,7 @@ add_object_library(
2536
SRCS
2637
thread.cpp
2738
DEPENDS
28-
.futex_word_type
39+
.futex_utils
2940
libc.config.linux.app_h
3041
libc.include.sys_syscall
3142
libc.src.errno.errno
@@ -50,8 +61,5 @@ add_object_library(
5061
HDRS
5162
../callonce.h
5263
DEPENDS
53-
libc.include.sys_syscall
54-
libc.src.__support.CPP.atomic
55-
libc.src.__support.CPP.limits
56-
libc.src.__support.OSUtil.osutil
64+
.futex_utils
5765
)

libc/src/__support/threads/linux/callonce.cpp

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,8 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9-
#include "futex_word.h"
10-
11-
#include "src/__support/CPP/atomic.h"
12-
#include "src/__support/CPP/limits.h" // INT_MAX
13-
#include "src/__support/OSUtil/syscall.h" // For syscall functions.
149
#include "src/__support/threads/callonce.h"
15-
16-
#include <linux/futex.h>
17-
#include <sys/syscall.h> // For syscall numbers.
10+
#include "src/__support/threads/linux/futex_utils.h"
1811

1912
namespace LIBC_NAMESPACE {
2013

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

2619
int callonce(CallOnceFlag *flag, CallOnceCallback *func) {
27-
auto *futex_word = reinterpret_cast<cpp::Atomic<FutexWordType> *>(flag);
20+
auto *futex_word = reinterpret_cast<Futex *>(flag);
2821

2922
FutexWordType not_called = NOT_CALLED;
3023

@@ -33,22 +26,15 @@ int callonce(CallOnceFlag *flag, CallOnceCallback *func) {
3326
if (futex_word->compare_exchange_strong(not_called, START)) {
3427
func();
3528
auto status = futex_word->exchange(FINISH);
36-
if (status == WAITING) {
37-
LIBC_NAMESPACE::syscall_impl<long>(FUTEX_SYSCALL_ID, &futex_word->val,
38-
FUTEX_WAKE_PRIVATE,
39-
INT_MAX, // Wake all waiters.
40-
0, 0, 0);
41-
}
29+
if (status == WAITING)
30+
futex_word->notify_all();
4231
return 0;
4332
}
4433

4534
FutexWordType status = START;
4635
if (futex_word->compare_exchange_strong(status, WAITING) ||
4736
status == WAITING) {
48-
LIBC_NAMESPACE::syscall_impl<long>(
49-
FUTEX_SYSCALL_ID, &futex_word->val, FUTEX_WAIT_PRIVATE,
50-
WAITING, // Block only if status is still |WAITING|.
51-
0, 0, 0);
37+
futex_word->wait(WAITING);
5238
}
5339

5440
return 0;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//===--- Futex Wrapper ------------------------------------------*- 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_THREADS_LINUX_FUTEX_UTILS_H
10+
#define LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_FUTEX_UTILS_H
11+
12+
#include "hdr/types/struct_timespec.h"
13+
#include "src/__support/CPP/atomic.h"
14+
#include "src/__support/CPP/limits.h"
15+
#include "src/__support/CPP/optional.h"
16+
#include "src/__support/OSUtil/syscall.h"
17+
#include "src/__support/macros/attributes.h"
18+
#include "src/__support/threads/linux/futex_word.h"
19+
#include <linux/errno.h>
20+
#include <linux/futex.h>
21+
22+
namespace LIBC_NAMESPACE {
23+
class Futex : public cpp::Atomic<FutexWordType> {
24+
public:
25+
struct Timeout {
26+
timespec abs_time;
27+
bool is_realtime;
28+
};
29+
LIBC_INLINE constexpr Futex(FutexWordType value)
30+
: cpp::Atomic<FutexWordType>(value) {}
31+
LIBC_INLINE Futex &operator=(FutexWordType value) {
32+
cpp::Atomic<FutexWordType>::store(value);
33+
return *this;
34+
}
35+
LIBC_INLINE long wait(FutexWordType expected,
36+
cpp::optional<Timeout> timeout = cpp::nullopt,
37+
bool is_shared = false) {
38+
// use bitset variants to enforce abs_time
39+
uint32_t op = is_shared ? FUTEX_WAIT_BITSET : FUTEX_WAIT_BITSET_PRIVATE;
40+
if (timeout && timeout->is_realtime) {
41+
op |= FUTEX_CLOCK_REALTIME;
42+
}
43+
for (;;) {
44+
if (this->load(cpp::MemoryOrder::RELAXED) != expected)
45+
return 0;
46+
47+
long ret = syscall_impl<long>(
48+
/* syscall number */ FUTEX_SYSCALL_ID,
49+
/* futex address */ this,
50+
/* futex operation */ op,
51+
/* expected value */ expected,
52+
/* timeout */ timeout ? &timeout->abs_time : nullptr,
53+
/* ignored */ nullptr,
54+
/* bitset */ FUTEX_BITSET_MATCH_ANY);
55+
56+
// continue waiting if interrupted; otherwise return the result
57+
// which should normally be 0 or -ETIMEOUT
58+
if (ret == -EINTR)
59+
continue;
60+
61+
return ret;
62+
}
63+
}
64+
LIBC_INLINE long notify_one(bool is_shared = false) {
65+
return syscall_impl<long>(
66+
/* syscall number */ FUTEX_SYSCALL_ID,
67+
/* futex address */ this,
68+
/* futex operation */ is_shared ? FUTEX_WAKE : FUTEX_WAKE_PRIVATE,
69+
/* wake up limit */ 1,
70+
/* ignored */ nullptr,
71+
/* ignored */ nullptr,
72+
/* ignored */ 0);
73+
}
74+
LIBC_INLINE long notify_all(bool is_shared = false) {
75+
return syscall_impl<long>(
76+
/* syscall number */ FUTEX_SYSCALL_ID,
77+
/* futex address */ this,
78+
/* futex operation */ is_shared ? FUTEX_WAKE : FUTEX_WAKE_PRIVATE,
79+
/* wake up limit */ cpp::numeric_limits<int>::max(),
80+
/* ignored */ nullptr,
81+
/* ignored */ nullptr,
82+
/* ignored */ 0);
83+
}
84+
};
85+
86+
static_assert(__is_standard_layout(Futex),
87+
"Futex must be a standard layout type.");
88+
} // namespace LIBC_NAMESPACE
89+
90+
#endif // LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_FUTEX_UTILS_H

libc/src/__support/threads/linux/futex_word.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
#include <stdint.h>
1313
#include <sys/syscall.h>
14-
1514
namespace LIBC_NAMESPACE {
1615

1716
// Futexes are 32 bits in size on all platforms, including 64-bit platforms.

libc/src/__support/threads/linux/mutex.h

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,10 @@
99
#ifndef LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_MUTEX_H
1010
#define LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_MUTEX_H
1111

12-
#include "src/__support/CPP/atomic.h"
13-
#include "src/__support/OSUtil/syscall.h" // For syscall functions.
14-
#include "src/__support/threads/linux/futex_word.h"
12+
#include "src/__support/threads/linux/futex_utils.h"
1513
#include "src/__support/threads/mutex_common.h"
1614

17-
#include <linux/futex.h>
18-
#include <stdint.h>
19-
#include <sys/syscall.h> // For syscall numbers.
20-
2115
namespace LIBC_NAMESPACE {
22-
2316
struct Mutex {
2417
unsigned char timed;
2518
unsigned char recursive;
@@ -28,7 +21,7 @@ struct Mutex {
2821
void *owner;
2922
unsigned long long lock_count;
3023

31-
cpp::Atomic<FutexWordType> futex_word;
24+
Futex futex_word;
3225

3326
enum class LockState : FutexWordType {
3427
Free,
@@ -76,9 +69,7 @@ struct Mutex {
7669
// futex syscall will block if the futex data is still
7770
// `LockState::Waiting` (the 4th argument to the syscall function
7871
// below.)
79-
LIBC_NAMESPACE::syscall_impl<long>(
80-
FUTEX_SYSCALL_ID, &futex_word.val, FUTEX_WAIT_PRIVATE,
81-
FutexWordType(LockState::Waiting), 0, 0, 0);
72+
futex_word.wait(FutexWordType(LockState::Waiting));
8273
was_waiting = true;
8374
// Once woken up/unblocked, try everything all over.
8475
continue;
@@ -91,9 +82,7 @@ struct Mutex {
9182
// we will wait for the futex to be woken up. Note again that the
9283
// following syscall will block only if the futex data is still
9384
// `LockState::Waiting`.
94-
LIBC_NAMESPACE::syscall_impl<long>(
95-
FUTEX_SYSCALL_ID, &futex_word, FUTEX_WAIT_PRIVATE,
96-
FutexWordType(LockState::Waiting), 0, 0, 0);
85+
futex_word.wait(FutexWordType(LockState::Waiting));
9786
was_waiting = true;
9887
}
9988
continue;
@@ -110,8 +99,7 @@ struct Mutex {
11099
if (futex_word.compare_exchange_strong(mutex_status,
111100
FutexWordType(LockState::Free))) {
112101
// If any thread is waiting to be woken up, then do it.
113-
LIBC_NAMESPACE::syscall_impl<long>(FUTEX_SYSCALL_ID, &futex_word,
114-
FUTEX_WAKE_PRIVATE, 1, 0, 0, 0);
102+
futex_word.notify_one();
115103
return MutexError::NONE;
116104
}
117105

libc/src/__support/threads/linux/thread.cpp

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@
1414
#include "src/__support/OSUtil/syscall.h" // For syscall functions.
1515
#include "src/__support/common.h"
1616
#include "src/__support/error_or.h"
17-
#include "src/__support/threads/linux/futex_word.h" // For FutexWordType
18-
#include "src/errno/libc_errno.h" // For error macros
17+
#include "src/__support/threads/linux/futex_utils.h" // For FutexWordType
18+
#include "src/errno/libc_errno.h" // For error macros
1919

2020
#ifdef LIBC_TARGET_ARCH_IS_AARCH64
2121
#include <arm_acle.h>
2222
#endif
2323

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

249248
static constexpr size_t INTERNAL_STACK_DATA_SIZE =
250-
sizeof(StartArgs) + sizeof(ThreadAttributes) +
251-
sizeof(cpp::Atomic<FutexWordType>);
249+
sizeof(StartArgs) + sizeof(ThreadAttributes) + sizeof(Futex);
252250

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

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

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

387382
bool Thread::operator==(const Thread &thread) const {

libc/src/__support/threads/mutex.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838
// want the constructors of the Mutex classes to be constexprs.
3939

4040
#if defined(__linux__)
41-
#include "linux/mutex.h"
41+
#include "src/__support/threads/linux/mutex.h"
4242
#elif defined(LIBC_TARGET_ARCH_IS_GPU)
43-
#include "gpu/mutex.h"
43+
#include "src/__support/threads/gpu/mutex.h"
4444
#endif // __linux__
4545

4646
namespace LIBC_NAMESPACE {

libc/src/__support/threads/thread.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9-
#include "thread.h"
10-
#include "mutex.h"
9+
#include "src/__support/threads/thread.h"
10+
#include "src/__support/threads/mutex.h"
1111

1212
#include "src/__support/CPP/array.h"
1313
#include "src/__support/CPP/optional.h"

libc/src/threads/linux/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ add_header_library(
99
libc.src.__support.CPP.atomic
1010
libc.src.__support.OSUtil.osutil
1111
libc.src.__support.threads.mutex
12-
libc.src.__support.threads.linux.futex_word_type
12+
libc.src.__support.threads.linux.futex_utils
1313
)
1414

1515
add_entrypoint_object(

libc/src/threads/linux/CndVar.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
#define LLVM_LIBC_SRC_THREADS_LINUX_CNDVAR_H
1111

1212
#include "src/__support/CPP/atomic.h"
13+
#include "src/__support/CPP/optional.h"
1314
#include "src/__support/OSUtil/syscall.h" // For syscall functions.
14-
#include "src/__support/threads/linux/futex_word.h"
15+
#include "src/__support/threads/linux/futex_utils.h"
1516
#include "src/__support/threads/mutex.h"
1617

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

3031
struct CndWaiter {
31-
cpp::Atomic<uint32_t> futex_word = WS_Waiting;
32+
Futex futex_word = WS_Waiting;
3233
CndWaiter *next = nullptr;
3334
};
3435

@@ -84,8 +85,7 @@ struct CndVar {
8485
}
8586
}
8687

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

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

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

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

0 commit comments

Comments
 (0)