Skip to content

Commit 9fa96fa

Browse files
matkladalexcrichton
authored andcommitted
Fix race condition in feature cache on 32 platforms (#837)
* Fix race condition in feature cache on 32 platforms If we observe that the second word is initialized, we can't really assume that the first is initialized as well. So check each word separately. * Use stronger atomic ordering Better SeqCst than sorry! * Use two caches on x64 for simplicity
1 parent 654ce02 commit 9fa96fa

File tree

1 file changed

+36
-63
lines changed

1 file changed

+36
-63
lines changed

crates/std_detect/src/detect/cache.rs

+36-63
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55

66
use crate::sync::atomic::Ordering;
77

8-
#[cfg(target_pointer_width = "64")]
9-
use crate::sync::atomic::AtomicU64;
10-
11-
#[cfg(target_pointer_width = "32")]
12-
use crate::sync::atomic::AtomicU32;
8+
use crate::sync::atomic::AtomicUsize;
139

1410
/// Sets the `bit` of `x`.
1511
#[inline]
@@ -30,7 +26,7 @@ const fn unset_bit(x: u64, bit: u32) -> u64 {
3026
}
3127

3228
/// Maximum number of features that can be cached.
33-
const CACHE_CAPACITY: u32 = 63;
29+
const CACHE_CAPACITY: u32 = 62;
3430

3531
/// This type is used to initialize the cache
3632
#[derive(Copy, Clone)]
@@ -81,83 +77,48 @@ impl Initializer {
8177
}
8278

8379
/// This global variable is a cache of the features supported by the CPU.
84-
static CACHE: Cache = Cache::uninitialized();
80+
// Note: on x64, we only use the first slot
81+
static CACHE: [Cache; 2] = [Cache::uninitialized(), Cache::uninitialized()];
8582

86-
/// Feature cache with capacity for `CACHE_CAPACITY` features.
83+
/// Feature cache with capacity for `usize::max_value() - 1` features.
8784
///
8885
/// Note: the last feature bit is used to represent an
8986
/// uninitialized cache.
90-
#[cfg(target_pointer_width = "64")]
91-
struct Cache(AtomicU64);
87+
///
88+
/// Note: we can use `Relaxed` atomic operations, because we are only interested
89+
/// in the effects of operations on a single memory location. That is, we only
90+
/// need "modification order", and not the full-blown "happens before". However,
91+
/// we use `SeqCst` just to be on the safe side.
92+
struct Cache(AtomicUsize);
9293

93-
#[cfg(target_pointer_width = "64")]
94-
#[allow(clippy::use_self)]
9594
impl Cache {
95+
const CAPACITY: u32 = (core::mem::size_of::<usize>() * 8 - 1) as u32;
96+
const MASK: usize = (1 << Cache::CAPACITY) - 1;
97+
9698
/// Creates an uninitialized cache.
9799
#[allow(clippy::declare_interior_mutable_const)]
98100
const fn uninitialized() -> Self {
99-
Cache(AtomicU64::new(u64::max_value()))
101+
Cache(AtomicUsize::new(usize::max_value()))
100102
}
101103
/// Is the cache uninitialized?
102104
#[inline]
103105
pub(crate) fn is_uninitialized(&self) -> bool {
104-
self.0.load(Ordering::Relaxed) == u64::max_value()
106+
self.0.load(Ordering::SeqCst) == usize::max_value()
105107
}
106108

107109
/// Is the `bit` in the cache set?
108110
#[inline]
109111
pub(crate) fn test(&self, bit: u32) -> bool {
110-
test_bit(CACHE.0.load(Ordering::Relaxed), bit)
112+
test_bit(self.0.load(Ordering::SeqCst) as u64, bit)
111113
}
112114

113115
/// Initializes the cache.
114116
#[inline]
115-
pub(crate) fn initialize(&self, value: Initializer) {
116-
self.0.store(value.0, Ordering::Relaxed);
117+
fn initialize(&self, value: usize) {
118+
self.0.store(value, Ordering::SeqCst);
117119
}
118120
}
119121

120-
/// Feature cache with capacity for `CACHE_CAPACITY` features.
121-
///
122-
/// Note: the last feature bit is used to represent an
123-
/// uninitialized cache.
124-
#[cfg(target_pointer_width = "32")]
125-
struct Cache(AtomicU32, AtomicU32);
126-
127-
#[cfg(target_pointer_width = "32")]
128-
impl Cache {
129-
/// Creates an uninitialized cache.
130-
const fn uninitialized() -> Self {
131-
Cache(
132-
AtomicU32::new(u32::max_value()),
133-
AtomicU32::new(u32::max_value()),
134-
)
135-
}
136-
/// Is the cache uninitialized?
137-
#[inline]
138-
pub(crate) fn is_uninitialized(&self) -> bool {
139-
self.1.load(Ordering::Relaxed) == u32::max_value()
140-
}
141-
142-
/// Is the `bit` in the cache set?
143-
#[inline]
144-
pub(crate) fn test(&self, bit: u32) -> bool {
145-
if bit < 32 {
146-
test_bit(CACHE.0.load(Ordering::Relaxed) as u64, bit)
147-
} else {
148-
test_bit(CACHE.1.load(Ordering::Relaxed) as u64, bit - 32)
149-
}
150-
}
151-
152-
/// Initializes the cache.
153-
#[inline]
154-
pub(crate) fn initialize(&self, value: Initializer) {
155-
let lo: u32 = value.0 as u32;
156-
let hi: u32 = (value.0 >> 32) as u32;
157-
self.0.store(lo, Ordering::Relaxed);
158-
self.1.store(hi, Ordering::Relaxed);
159-
}
160-
}
161122
cfg_if::cfg_if! {
162123
if #[cfg(feature = "std_detect_env_override")] {
163124
#[inline(never)]
@@ -167,16 +128,22 @@ cfg_if::cfg_if! {
167128
let _ = super::Feature::from_str(v).map(|v| value.unset(v as u32));
168129
}
169130
}
170-
CACHE.initialize(value);
131+
do_initialize(value);
171132
}
172133
} else {
173134
#[inline]
174135
fn initialize(value: Initializer) {
175-
CACHE.initialize(value);
136+
do_initialize(value);
176137
}
177138
}
178139
}
179140

141+
#[inline]
142+
fn do_initialize(value: Initializer) {
143+
CACHE[0].initialize((value.0) as usize & Cache::MASK);
144+
CACHE[1].initialize((value.0 >> Cache::CAPACITY) as usize & Cache::MASK);
145+
}
146+
180147
/// Tests the `bit` of the storage. If the storage has not been initialized,
181148
/// initializes it with the result of `f()`.
182149
///
@@ -194,8 +161,14 @@ pub(crate) fn test<F>(bit: u32, f: F) -> bool
194161
where
195162
F: FnOnce() -> Initializer,
196163
{
197-
if CACHE.is_uninitialized() {
198-
initialize(f());
164+
let (bit, idx) = if bit < Cache::CAPACITY {
165+
(bit, 0)
166+
} else {
167+
(bit - Cache::CAPACITY, 1)
168+
};
169+
170+
if CACHE[idx].is_uninitialized() {
171+
initialize(f())
199172
}
200-
CACHE.test(bit)
173+
CACHE[idx].test(bit)
201174
}

0 commit comments

Comments
 (0)