Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 01bce29

Browse files
committedOct 17, 2024
Abstract the state type for futexes
In the same way that we expose SmallAtomic and SmallPrimitive to allow Windows to use a value other than an AtomicU32 for its futex state, this patch switches the primary futex state type from AtomicU32 to futex::Atomic. The futex::Atomic type should be usable as an atomic value with underlying primitive type equal to futex::Primitive. This allows supporting the futex API on systems where the underlying kernel futex implementation requires more state than simply an AtomicU32. All in-tree futex implementations simply define {Atomic,Primitive} directly as {AtomicU32,u32}.
1 parent 6c85d31 commit 01bce29

File tree

10 files changed

+83
-66
lines changed

10 files changed

+83
-66
lines changed
 

‎std/src/sys/pal/hermit/futex.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@ use crate::ptr::null;
33
use crate::sync::atomic::AtomicU32;
44
use crate::time::Duration;
55

6+
/// An atomic for use as a futex that is at least 32-bits but may be larger
7+
pub type Futex = AtomicU32;
8+
/// Must be the underlying type of Futex
9+
pub type Primitive = u32;
10+
611
/// An atomic for use as a futex that is at least 8-bits but may be larger.
7-
pub type SmallAtomic = AtomicU32;
8-
/// Must be the underlying type of SmallAtomic
12+
pub type SmallFutex = AtomicU32;
13+
/// Must be the underlying type of SmallFutex
914
pub type SmallPrimitive = u32;
1015

1116
pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {

‎std/src/sys/pal/unix/futex.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@
1111
use crate::sync::atomic::AtomicU32;
1212
use crate::time::Duration;
1313

14+
/// An atomic for use as a futex that is at least 32-bits but may be larger
15+
pub type Futex = AtomicU32;
16+
/// Must be the underlying type of Futex
17+
pub type Primitive = u32;
18+
1419
/// An atomic for use as a futex that is at least 8-bits but may be larger.
15-
pub type SmallAtomic = AtomicU32;
16-
/// Must be the underlying type of SmallAtomic
20+
pub type SmallFutex = AtomicU32;
21+
/// Must be the underlying type of SmallFutex
1722
pub type SmallPrimitive = u32;
1823

1924
/// Waits for a `futex_wake` operation to wake us.

‎std/src/sys/pal/wasm/atomics/futex.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ use core::arch::wasm64 as wasm;
66
use crate::sync::atomic::AtomicU32;
77
use crate::time::Duration;
88

9+
/// An atomic for use as a futex that is at least 32-bits but may be larger
10+
pub type Futex = AtomicU32;
11+
/// Must be the underlying type of Futex
12+
pub type Primitive = u32;
13+
914
/// An atomic for use as a futex that is at least 8-bits but may be larger.
10-
pub type SmallAtomic = AtomicU32;
11-
/// Must be the underlying type of SmallAtomic
15+
pub type SmallFutex = AtomicU32;
16+
/// Must be the underlying type of SmallFutex
1217
pub type SmallPrimitive = u32;
1318

1419
/// Wait for a futex_wake operation to wake us.

‎std/src/sys/pal/windows/futex.rs

+20-15
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,27 @@ use core::{mem, ptr};
99
use super::api::{self, WinError};
1010
use crate::sys::{c, dur2timeout};
1111

12+
/// An atomic for use as a futex that is at least 32-bits but may be larger
13+
pub type Futex = AtomicU32;
14+
/// Must be the underlying type of Futex
15+
pub type Primitive = u32;
16+
1217
/// An atomic for use as a futex that is at least 8-bits but may be larger.
13-
pub type SmallAtomic = AtomicU8;
14-
/// Must be the underlying type of SmallAtomic
18+
pub type SmallFutex = AtomicU8;
19+
/// Must be the underlying type of SmallFutex
1520
pub type SmallPrimitive = u8;
1621

17-
pub unsafe trait Futex {}
22+
pub unsafe trait Futexable {}
1823
pub unsafe trait Waitable {
19-
type Atomic;
24+
type Futex;
2025
}
2126
macro_rules! unsafe_waitable_int {
2227
($(($int:ty, $atomic:ty)),*$(,)?) => {
2328
$(
2429
unsafe impl Waitable for $int {
25-
type Atomic = $atomic;
30+
type Futex = $atomic;
2631
}
27-
unsafe impl Futex for $atomic {}
32+
unsafe impl Futexable for $atomic {}
2833
)*
2934
};
3035
}
@@ -42,15 +47,15 @@ unsafe_waitable_int! {
4247
(usize, AtomicUsize),
4348
}
4449
unsafe impl<T> Waitable for *const T {
45-
type Atomic = AtomicPtr<T>;
50+
type Futex = AtomicPtr<T>;
4651
}
4752
unsafe impl<T> Waitable for *mut T {
48-
type Atomic = AtomicPtr<T>;
53+
type Futex = AtomicPtr<T>;
4954
}
50-
unsafe impl<T> Futex for AtomicPtr<T> {}
55+
unsafe impl<T> Futexable for AtomicPtr<T> {}
5156

5257
pub fn wait_on_address<W: Waitable>(
53-
address: &W::Atomic,
58+
address: &W::Futex,
5459
compare: W,
5560
timeout: Option<Duration>,
5661
) -> bool {
@@ -63,30 +68,30 @@ pub fn wait_on_address<W: Waitable>(
6368
}
6469
}
6570

66-
pub fn wake_by_address_single<T: Futex>(address: &T) {
71+
pub fn wake_by_address_single<T: Futexable>(address: &T) {
6772
unsafe {
6873
let addr = ptr::from_ref(address).cast::<c_void>();
6974
c::WakeByAddressSingle(addr);
7075
}
7176
}
7277

73-
pub fn wake_by_address_all<T: Futex>(address: &T) {
78+
pub fn wake_by_address_all<T: Futexable>(address: &T) {
7479
unsafe {
7580
let addr = ptr::from_ref(address).cast::<c_void>();
7681
c::WakeByAddressAll(addr);
7782
}
7883
}
7984

80-
pub fn futex_wait<W: Waitable>(futex: &W::Atomic, expected: W, timeout: Option<Duration>) -> bool {
85+
pub fn futex_wait<W: Waitable>(futex: &W::Futex, expected: W, timeout: Option<Duration>) -> bool {
8186
// return false only on timeout
8287
wait_on_address(futex, expected, timeout) || api::get_last_error() != WinError::TIMEOUT
8388
}
8489

85-
pub fn futex_wake<T: Futex>(futex: &T) -> bool {
90+
pub fn futex_wake<T: Futexable>(futex: &T) -> bool {
8691
wake_by_address_single(futex);
8792
false
8893
}
8994

90-
pub fn futex_wake_all<T: Futex>(futex: &T) {
95+
pub fn futex_wake_all<T: Futexable>(futex: &T) {
9196
wake_by_address_all(futex)
9297
}

‎std/src/sys/sync/condvar/futex.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
use crate::sync::atomic::AtomicU32;
21
use crate::sync::atomic::Ordering::Relaxed;
3-
use crate::sys::futex::{futex_wait, futex_wake, futex_wake_all};
2+
use crate::sys::futex::{Futex, futex_wait, futex_wake, futex_wake_all};
43
use crate::sys::sync::Mutex;
54
use crate::time::Duration;
65

76
pub struct Condvar {
87
// The value of this atomic is simply incremented on every notification.
98
// This is used by `.wait()` to not miss any notifications after
109
// unlocking the mutex and before waiting for notifications.
11-
futex: AtomicU32,
10+
futex: Futex,
1211
}
1312

1413
impl Condvar {
1514
#[inline]
1615
pub const fn new() -> Self {
17-
Self { futex: AtomicU32::new(0) }
16+
Self { futex: Futex::new(0) }
1817
}
1918

2019
// All the memory orderings here are `Relaxed`,

‎std/src/sys/sync/mutex/futex.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
22
use crate::sys::futex::{self, futex_wait, futex_wake};
33

4-
type Atomic = futex::SmallAtomic;
4+
type Futex = futex::SmallFutex;
55
type State = futex::SmallPrimitive;
66

77
pub struct Mutex {
8-
futex: Atomic,
8+
futex: Futex,
99
}
1010

1111
const UNLOCKED: State = 0;
@@ -15,7 +15,7 @@ const CONTENDED: State = 2; // locked, and other threads waiting (contended)
1515
impl Mutex {
1616
#[inline]
1717
pub const fn new() -> Self {
18-
Self { futex: Atomic::new(UNLOCKED) }
18+
Self { futex: Futex::new(UNLOCKED) }
1919
}
2020

2121
#[inline]

‎std/src/sys/sync/once/futex.rs

+12-13
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,38 @@
11
use crate::cell::Cell;
22
use crate::sync as public;
3-
use crate::sync::atomic::AtomicU32;
43
use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
54
use crate::sync::once::ExclusiveState;
6-
use crate::sys::futex::{futex_wait, futex_wake_all};
5+
use crate::sys::futex::{Futex, Primitive, futex_wait, futex_wake_all};
76

87
// On some platforms, the OS is very nice and handles the waiter queue for us.
98
// This means we only need one atomic value with 4 states:
109

1110
/// No initialization has run yet, and no thread is currently using the Once.
12-
const INCOMPLETE: u32 = 0;
11+
const INCOMPLETE: Primitive = 0;
1312
/// Some thread has previously attempted to initialize the Once, but it panicked,
1413
/// so the Once is now poisoned. There are no other threads currently accessing
1514
/// this Once.
16-
const POISONED: u32 = 1;
15+
const POISONED: Primitive = 1;
1716
/// Some thread is currently attempting to run initialization. It may succeed,
1817
/// so all future threads need to wait for it to finish.
19-
const RUNNING: u32 = 2;
18+
const RUNNING: Primitive = 2;
2019
/// Initialization has completed and all future calls should finish immediately.
21-
const COMPLETE: u32 = 3;
20+
const COMPLETE: Primitive = 3;
2221

2322
// An additional bit indicates whether there are waiting threads:
2423

2524
/// May only be set if the state is not COMPLETE.
26-
const QUEUED: u32 = 4;
25+
const QUEUED: Primitive = 4;
2726

2827
// Threads wait by setting the QUEUED bit and calling `futex_wait` on the state
2928
// variable. When the running thread finishes, it will wake all waiting threads using
3029
// `futex_wake_all`.
3130

32-
const STATE_MASK: u32 = 0b11;
31+
const STATE_MASK: Primitive = 0b11;
3332

3433
pub struct OnceState {
3534
poisoned: bool,
36-
set_state_to: Cell<u32>,
35+
set_state_to: Cell<Primitive>,
3736
}
3837

3938
impl OnceState {
@@ -49,8 +48,8 @@ impl OnceState {
4948
}
5049

5150
struct CompletionGuard<'a> {
52-
state_and_queued: &'a AtomicU32,
53-
set_state_on_drop_to: u32,
51+
state_and_queued: &'a Futex,
52+
set_state_on_drop_to: Primitive,
5453
}
5554

5655
impl<'a> Drop for CompletionGuard<'a> {
@@ -65,13 +64,13 @@ impl<'a> Drop for CompletionGuard<'a> {
6564
}
6665

6766
pub struct Once {
68-
state_and_queued: AtomicU32,
67+
state_and_queued: Futex,
6968
}
7069

7170
impl Once {
7271
#[inline]
7372
pub const fn new() -> Once {
74-
Once { state_and_queued: AtomicU32::new(INCOMPLETE) }
73+
Once { state_and_queued: Futex::new(INCOMPLETE) }
7574
}
7675

7776
#[inline]

‎std/src/sys/sync/once/queue.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
// You'll find a few more details in the implementation, but that's the gist of
2424
// it!
2525
//
26-
// Atomic orderings:
26+
// Futex orderings:
2727
// When running `Once` we deal with multiple atomics:
2828
// `Once.state_and_queue` and an unknown number of `Waiter.signaled`.
2929
// * `state_and_queue` is used (1) as a state flag, (2) for synchronizing the

‎std/src/sys/sync/rwlock/futex.rs

+20-21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use crate::sync::atomic::AtomicU32;
21
use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
3-
use crate::sys::futex::{futex_wait, futex_wake, futex_wake_all};
2+
use crate::sys::futex::{Futex, Primitive, futex_wait, futex_wake, futex_wake_all};
43

54
pub struct RwLock {
65
// The state consists of a 30-bit reader counter, a 'readers waiting' flag, and a 'writers waiting' flag.
@@ -10,41 +9,41 @@ pub struct RwLock {
109
// 0x3FFF_FFFF: Write locked
1110
// Bit 30: Readers are waiting on this futex.
1211
// Bit 31: Writers are waiting on the writer_notify futex.
13-
state: AtomicU32,
12+
state: Futex,
1413
// The 'condition variable' to notify writers through.
1514
// Incremented on every signal.
16-
writer_notify: AtomicU32,
15+
writer_notify: Futex,
1716
}
1817

19-
const READ_LOCKED: u32 = 1;
20-
const MASK: u32 = (1 << 30) - 1;
21-
const WRITE_LOCKED: u32 = MASK;
22-
const MAX_READERS: u32 = MASK - 1;
23-
const READERS_WAITING: u32 = 1 << 30;
24-
const WRITERS_WAITING: u32 = 1 << 31;
18+
const READ_LOCKED: Primitive = 1;
19+
const MASK: Primitive = (1 << 30) - 1;
20+
const WRITE_LOCKED: Primitive = MASK;
21+
const MAX_READERS: Primitive = MASK - 1;
22+
const READERS_WAITING: Primitive = 1 << 30;
23+
const WRITERS_WAITING: Primitive = 1 << 31;
2524

2625
#[inline]
27-
fn is_unlocked(state: u32) -> bool {
26+
fn is_unlocked(state: Primitive) -> bool {
2827
state & MASK == 0
2928
}
3029

3130
#[inline]
32-
fn is_write_locked(state: u32) -> bool {
31+
fn is_write_locked(state: Primitive) -> bool {
3332
state & MASK == WRITE_LOCKED
3433
}
3534

3635
#[inline]
37-
fn has_readers_waiting(state: u32) -> bool {
36+
fn has_readers_waiting(state: Primitive) -> bool {
3837
state & READERS_WAITING != 0
3938
}
4039

4140
#[inline]
42-
fn has_writers_waiting(state: u32) -> bool {
41+
fn has_writers_waiting(state: Primitive) -> bool {
4342
state & WRITERS_WAITING != 0
4443
}
4544

4645
#[inline]
47-
fn is_read_lockable(state: u32) -> bool {
46+
fn is_read_lockable(state: Primitive) -> bool {
4847
// This also returns false if the counter could overflow if we tried to read lock it.
4948
//
5049
// We don't allow read-locking if there's readers waiting, even if the lock is unlocked
@@ -55,14 +54,14 @@ fn is_read_lockable(state: u32) -> bool {
5554
}
5655

5756
#[inline]
58-
fn has_reached_max_readers(state: u32) -> bool {
57+
fn has_reached_max_readers(state: Primitive) -> bool {
5958
state & MASK == MAX_READERS
6059
}
6160

6261
impl RwLock {
6362
#[inline]
6463
pub const fn new() -> Self {
65-
Self { state: AtomicU32::new(0), writer_notify: AtomicU32::new(0) }
64+
Self { state: Futex::new(0), writer_notify: Futex::new(0) }
6665
}
6766

6867
#[inline]
@@ -225,7 +224,7 @@ impl RwLock {
225224
/// If both are waiting, this will wake up only one writer, but will fall
226225
/// back to waking up readers if there was no writer to wake up.
227226
#[cold]
228-
fn wake_writer_or_readers(&self, mut state: u32) {
227+
fn wake_writer_or_readers(&self, mut state: Primitive) {
229228
assert!(is_unlocked(state));
230229

231230
// The readers waiting bit might be turned on at any point now,
@@ -290,7 +289,7 @@ impl RwLock {
290289

291290
/// Spin for a while, but stop directly at the given condition.
292291
#[inline]
293-
fn spin_until(&self, f: impl Fn(u32) -> bool) -> u32 {
292+
fn spin_until(&self, f: impl Fn(Primitive) -> bool) -> Primitive {
294293
let mut spin = 100; // Chosen by fair dice roll.
295294
loop {
296295
let state = self.state.load(Relaxed);
@@ -303,13 +302,13 @@ impl RwLock {
303302
}
304303

305304
#[inline]
306-
fn spin_write(&self) -> u32 {
305+
fn spin_write(&self) -> Primitive {
307306
// Stop spinning when it's unlocked or when there's waiting writers, to keep things somewhat fair.
308307
self.spin_until(|state| is_unlocked(state) || has_writers_waiting(state))
309308
}
310309

311310
#[inline]
312-
fn spin_read(&self) -> u32 {
311+
fn spin_read(&self) -> Primitive {
313312
// Stop spinning when it's unlocked or read locked, or when there's waiting threads.
314313
self.spin_until(|state| {
315314
!is_write_locked(state) || has_readers_waiting(state) || has_writers_waiting(state)

‎std/src/sys/sync/thread_parking/futex.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ use crate::sync::atomic::Ordering::{Acquire, Release};
44
use crate::sys::futex::{self, futex_wait, futex_wake};
55
use crate::time::Duration;
66

7-
type Atomic = futex::SmallAtomic;
7+
type Futex = futex::SmallFutex;
88
type State = futex::SmallPrimitive;
99

1010
const PARKED: State = State::MAX;
1111
const EMPTY: State = 0;
1212
const NOTIFIED: State = 1;
1313

1414
pub struct Parker {
15-
state: Atomic,
15+
state: Futex,
1616
}
1717

1818
// Notes about memory ordering:
@@ -39,7 +39,7 @@ impl Parker {
3939
/// Constructs the futex parker. The UNIX parker implementation
4040
/// requires this to happen in-place.
4141
pub unsafe fn new_in_place(parker: *mut Parker) {
42-
unsafe { parker.write(Self { state: Atomic::new(EMPTY) }) };
42+
unsafe { parker.write(Self { state: Futex::new(EMPTY) }) };
4343
}
4444

4545
// Assumes this is only called by the thread that owns the Parker,

0 commit comments

Comments
 (0)
Please sign in to comment.