Skip to content

Commit bf87dee

Browse files
committed
Add BlackBox
Adds a `VolatileCell`-like struct which introduces an optimization barrier on all accesses. The `*Cell`-like `get()` method is the only accessor for the inner value and uses a more generalized (i.e. for all `Copy` types) `black_box` to preclude optimizations. This is useful for things like bitwise mask values to ensure LLVM doesn't try to optimize around special cases like `0`, especially in the context of loops. For an example use case, see: https://rustsec.org/advisories/RUSTSEC-2024-0344.html
1 parent 22606eb commit bf87dee

File tree

2 files changed

+36
-10
lines changed

2 files changed

+36
-10
lines changed

src/lib.rs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ use core::cmp;
9696
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not};
9797
use core::option::Option;
9898

99+
#[cfg(feature = "core_hint_black_box")]
100+
use core::hint::black_box;
101+
99102
/// The `Choice` struct represents a choice for use in conditional assignment.
100103
///
101104
/// It is a wrapper around a `u8`, which should have the value either `1` (true)
@@ -217,25 +220,23 @@ impl Not for Choice {
217220
/// code may break in a non-destructive way in the future, “constant-time” code
218221
/// is a continually moving target, and this is better than doing nothing.
219222
#[inline(never)]
220-
fn black_box(input: u8) -> u8 {
221-
debug_assert!((input == 0u8) | (input == 1u8));
222-
223+
fn black_box<T: Copy>(input: T) -> T {
223224
unsafe {
224225
// Optimization barrier
225226
//
226-
// Unsafe is ok, because:
227-
// - &input is not NULL;
228-
// - size of input is not zero;
229-
// - u8 is neither Sync, nor Send;
230-
// - u8 is Copy, so input is always live;
231-
// - u8 type is always properly aligned.
232-
core::ptr::read_volatile(&input as *const u8)
227+
// SAFETY:
228+
// - &input is not NULL because we own input;
229+
// - input is Copy and always live;
230+
// - input is always properly aligned.
231+
core::ptr::read_volatile(&input)
233232
}
234233
}
235234

236235
impl From<u8> for Choice {
237236
#[inline]
238237
fn from(input: u8) -> Choice {
238+
debug_assert!((input == 0u8) | (input == 1u8));
239+
239240
// Our goal is to prevent the compiler from inferring that the value held inside the
240241
// resulting `Choice` struct is really an `i1` instead of an `i8`.
241242
Choice(black_box(input))
@@ -986,3 +987,21 @@ impl ConstantTimeLess for cmp::Ordering {
986987
(a as u8).ct_lt(&(b as u8))
987988
}
988989
}
990+
991+
/// Wrapper type which implements an optimization barrier for all accesses.
992+
#[derive(Clone, Copy, Debug)]
993+
pub struct BlackBox<T: Copy>(T);
994+
995+
impl<T: Copy> BlackBox<T> {
996+
/// Constructs a new instance of `BlackBox` which will wrap the specified value.
997+
///
998+
/// All access to the inner value will be mediated by a `black_box` optimization barrier.
999+
pub fn new(value: T) -> Self {
1000+
Self(value)
1001+
}
1002+
1003+
/// Read the inner value, applying an optimization barrier on access.
1004+
pub fn get(self) -> T {
1005+
black_box(self.0)
1006+
}
1007+
}

tests/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,3 +423,10 @@ fn less_than_ordering() {
423423
assert_eq!(cmp::Ordering::Greater.ct_lt(&cmp::Ordering::Less).unwrap_u8(), 0);
424424
assert_eq!(cmp::Ordering::Less.ct_lt(&cmp::Ordering::Greater).unwrap_u8(), 1);
425425
}
426+
427+
#[test]
428+
fn black_box_round_trip() {
429+
let n = 42u64;
430+
let black_box = BlackBox::new(n);
431+
assert_eq!(n, black_box.get());
432+
}

0 commit comments

Comments
 (0)