Skip to content

Commit a212037

Browse files
committed
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.
1 parent 8cba9ab commit a212037

File tree

1 file changed

+46
-63
lines changed

1 file changed

+46
-63
lines changed

crates/std_detect/src/detect/cache.rs

Lines changed: 46 additions & 63 deletions
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,51 @@ impl Initializer {
8177
}
8278

8379
/// This global variable is a cache of the features supported by the CPU.
84-
static CACHE: Cache = Cache::uninitialized();
85-
86-
/// Feature cache with capacity for `CACHE_CAPACITY` features.
87-
///
88-
/// Note: the last feature bit is used to represent an
89-
/// uninitialized cache.
9080
#[cfg(target_pointer_width = "64")]
91-
struct Cache(AtomicU64);
81+
static CACHE: [Cache; 1] = [Cache::uninitialized()];
9282

93-
#[cfg(target_pointer_width = "64")]
94-
#[allow(clippy::use_self)]
95-
impl Cache {
96-
/// Creates an uninitialized cache.
97-
#[allow(clippy::declare_interior_mutable_const)]
98-
const fn uninitialized() -> Self {
99-
Cache(AtomicU64::new(u64::max_value()))
100-
}
101-
/// Is the cache uninitialized?
102-
#[inline]
103-
pub(crate) fn is_uninitialized(&self) -> bool {
104-
self.0.load(Ordering::Relaxed) == u64::max_value()
105-
}
106-
107-
/// Is the `bit` in the cache set?
108-
#[inline]
109-
pub(crate) fn test(&self, bit: u32) -> bool {
110-
test_bit(CACHE.0.load(Ordering::Relaxed), bit)
111-
}
112-
113-
/// Initializes the cache.
114-
#[inline]
115-
pub(crate) fn initialize(&self, value: Initializer) {
116-
self.0.store(value.0, Ordering::Relaxed);
117-
}
118-
}
83+
/// This global variable is a cache of the features supported by the CPU.
84+
#[cfg(target_pointer_width = "32")]
85+
static CACHE: [Cache; 2] = [Cache::uninitialized(), Cache::uninitialized()];
11986

120-
/// Feature cache with capacity for `CACHE_CAPACITY` features.
87+
/// Feature cache with capacity for `usize::max_value() - 1` features.
12188
///
12289
/// Note: the last feature bit is used to represent an
12390
/// uninitialized cache.
124-
#[cfg(target_pointer_width = "32")]
125-
struct Cache(AtomicU32, AtomicU32);
91+
///
92+
/// Note: we use `Relaxed` atomic operations, because we are only interested
93+
/// in the effects of operations on a single memory location. That is, we only
94+
/// need "modification order", and not the full-blown "happens before".
95+
struct Cache(AtomicUsize);
12696

127-
#[cfg(target_pointer_width = "32")]
12897
impl Cache {
98+
const CAPACITY: u32 = (std::mem::size_of::<usize>() * 8 - 1) as u32;
99+
const MASK: usize = (1 << Cache::CAPACITY) - 1;
100+
129101
/// Creates an uninitialized cache.
102+
#[allow(clippy::declare_interior_mutable_const)]
130103
const fn uninitialized() -> Self {
131-
Cache(
132-
AtomicU32::new(u32::max_value()),
133-
AtomicU32::new(u32::max_value()),
134-
)
104+
Cache(AtomicUsize::new(usize::max_value()))
135105
}
136106
/// Is the cache uninitialized?
137107
#[inline]
138108
pub(crate) fn is_uninitialized(&self) -> bool {
139-
self.1.load(Ordering::Relaxed) == u32::max_value()
109+
self.0.load(Ordering::Relaxed) == usize::max_value()
140110
}
141111

142112
/// Is the `bit` in the cache set?
143113
#[inline]
144114
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-
}
115+
test_bit(self.0.load(Ordering::Relaxed) as u64, bit)
150116
}
151117

152118
/// Initializes the cache.
153119
#[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);
120+
fn initialize(&self, value: usize) {
121+
self.0.store(value, Ordering::Relaxed);
159122
}
160123
}
124+
161125
cfg_if::cfg_if! {
162126
if #[cfg(feature = "std_detect_env_override")] {
163127
#[inline(never)]
@@ -167,16 +131,25 @@ cfg_if::cfg_if! {
167131
let _ = super::Feature::from_str(v).map(|v| value.unset(v as u32));
168132
}
169133
}
170-
CACHE.initialize(value);
134+
do_initialize(value);
171135
}
172136
} else {
173137
#[inline]
174138
fn initialize(value: Initializer) {
175-
CACHE.initialize(value);
139+
do_initialize(value);
176140
}
177141
}
178142
}
179143

144+
#[inline]
145+
fn do_initialize(value: Initializer) {
146+
CACHE[0].initialize((value.0) as usize & Cache::MASK);
147+
#[cfg(target_pointer_width = "32")]
148+
{
149+
CACHE[1].initialize((value.0 >> Cache::CAPACITY) as usize & Cache::MASK);
150+
}
151+
}
152+
180153
/// Tests the `bit` of the storage. If the storage has not been initialized,
181154
/// initializes it with the result of `f()`.
182155
///
@@ -194,8 +167,18 @@ pub(crate) fn test<F>(bit: u32, f: F) -> bool
194167
where
195168
F: FnOnce() -> Initializer,
196169
{
197-
if CACHE.is_uninitialized() {
198-
initialize(f());
170+
#[cfg(target_pointer_width = "32")]
171+
{
172+
if bit >= Cache::CAPACITY {
173+
if CACHE[1].is_uninitialized() {
174+
initialize(f())
175+
}
176+
return CACHE[1].test(bit - Cache::CAPACITY);
177+
}
178+
}
179+
180+
if CACHE[0].is_uninitialized() {
181+
initialize(f())
199182
}
200-
CACHE.test(bit)
183+
CACHE[0].test(bit)
201184
}

0 commit comments

Comments
 (0)