Skip to content

Move select_unpredictable to the hint module #139726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 0 additions & 48 deletions library/core/src/bool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,52 +61,4 @@ impl bool {
pub fn then<T, F: FnOnce() -> T>(self, f: F) -> Option<T> {
if self { Some(f()) } else { None }
}

/// Returns either `true_val` or `false_val` depending on the value of
/// `self`, with a hint to the compiler that `self` is unlikely
/// to be correctly predicted by a CPU’s branch predictor.
///
/// This method is functionally equivalent to
/// ```ignore (this is just for illustrative purposes)
/// fn select_unpredictable<T>(b: bool, true_val: T, false_val: T) -> T {
/// if b { true_val } else { false_val }
/// }
/// ```
/// but might generate different assembly. In particular, on platforms with
/// a conditional move or select instruction (like `cmov` on x86 or `csel`
/// on ARM) the optimizer might use these instructions to avoid branches,
/// which can benefit performance if the branch predictor is struggling
/// with predicting `condition`, such as in an implementation of binary
/// search.
///
/// Note however that this lowering is not guaranteed (on any platform) and
/// should not be relied upon when trying to write constant-time code. Also
/// be aware that this lowering might *decrease* performance if `condition`
/// is well-predictable. It is advisable to perform benchmarks to tell if
/// this function is useful.
///
/// # Examples
///
/// Distribute values evenly between two buckets:
/// ```
/// #![feature(select_unpredictable)]
///
/// use std::hash::BuildHasher;
///
/// fn append<H: BuildHasher>(hasher: &H, v: i32, bucket_one: &mut Vec<i32>, bucket_two: &mut Vec<i32>) {
/// let hash = hasher.hash_one(&v);
/// let bucket = (hash % 2 == 0).select_unpredictable(bucket_one, bucket_two);
/// bucket.push(v);
/// }
/// # let hasher = std::collections::hash_map::RandomState::new();
/// # let mut bucket_one = Vec::new();
/// # let mut bucket_two = Vec::new();
/// # append(&hasher, 42, &mut bucket_one, &mut bucket_two);
/// # assert_eq!(bucket_one.len() + bucket_two.len(), 1);
/// ```
#[inline(always)]
#[unstable(feature = "select_unpredictable", issue = "133962")]
pub fn select_unpredictable<T>(self, true_val: T, false_val: T) -> T {
crate::intrinsics::select_unpredictable(self, true_val, false_val)
}
}
49 changes: 49 additions & 0 deletions library/core/src/hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,3 +734,52 @@ pub const fn unlikely(b: bool) -> bool {
pub const fn cold_path() {
crate::intrinsics::cold_path()
}

/// Returns either `true_val` or `false_val` depending on the value of `b`,
/// with a hint to the compiler that `b` is unlikely to be correctly
/// predicted by a CPU’s branch predictor.
///
/// This method is functionally equivalent to
/// ```ignore (this is just for illustrative purposes)
/// fn select_unpredictable<T>(b: bool, true_val: T, false_val: T) -> T {
/// if b { true_val } else { false_val }
/// }
/// ```
/// but might generate different assembly. In particular, on platforms with
/// a conditional move or select instruction (like `cmov` on x86 or `csel`
/// on ARM) the optimizer might use these instructions to avoid branches,
/// which can benefit performance if the branch predictor is struggling
/// with predicting `condition`, such as in an implementation of binary
/// search.
///
/// Note however that this lowering is not guaranteed (on any platform) and
/// should not be relied upon when trying to write constant-time code. Also
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably say "cryptographic constant-time"; the term "constant-time" also often refers to algorithms that run in O(1). Sadly two CS sub-communities picked the same term for different concepts and both are very widely used.

/// be aware that this lowering might *decrease* performance if `condition`
/// is well-predictable. It is advisable to perform benchmarks to tell if
/// this function is useful.
///
/// # Examples
///
/// Distribute values evenly between two buckets:
/// ```
/// #![feature(select_unpredictable)]
///
/// use std::hash::BuildHasher;
/// use std::hint;
///
/// fn append<H: BuildHasher>(hasher: &H, v: i32, bucket_one: &mut Vec<i32>, bucket_two: &mut Vec<i32>) {
/// let hash = hasher.hash_one(&v);
/// let bucket = hint::select_unpredictable(hash % 2 == 0, bucket_one, bucket_two);
/// bucket.push(v);
/// }
/// # let hasher = std::collections::hash_map::RandomState::new();
/// # let mut bucket_one = Vec::new();
/// # let mut bucket_two = Vec::new();
/// # append(&hasher, 42, &mut bucket_one, &mut bucket_two);
/// # assert_eq!(bucket_one.len() + bucket_two.len(), 1);
/// ```
#[inline(always)]
#[unstable(feature = "select_unpredictable", issue = "133962")]
pub fn select_unpredictable<T>(b: bool, true_val: T, false_val: T) -> T {
crate::intrinsics::select_unpredictable(b, true_val, false_val)
}
2 changes: 1 addition & 1 deletion library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1326,7 +1326,7 @@ pub const fn unlikely(b: bool) -> bool {
/// Therefore, implementations must not require the user to uphold
/// any safety invariants.
///
/// The public form of this instrinsic is [`bool::select_unpredictable`].
/// The public form of this instrinsic is [`core::hint::select_unpredictable`].
#[unstable(feature = "core_intrinsics", issue = "none")]
#[rustc_intrinsic]
#[rustc_nounwind]
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2828,7 +2828,7 @@ impl<T> [T] {
// Binary search interacts poorly with branch prediction, so force
// the compiler to use conditional moves if supported by the target
// architecture.
base = (cmp == Greater).select_unpredictable(base, mid);
base = hint::select_unpredictable(cmp == Greater, base, mid);

// This is imprecise in the case where `size` is odd and the
// comparison returns Greater: the mid element still gets included
Expand Down
18 changes: 9 additions & 9 deletions library/core/src/slice/sort/shared/smallsort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::mem::{self, ManuallyDrop, MaybeUninit};
use crate::slice::sort::shared::FreezeMarker;
use crate::{intrinsics, ptr, slice};
use crate::{hint, intrinsics, ptr, slice};

// It's important to differentiate between SMALL_SORT_THRESHOLD performance for
// small slices and small-sort performance sorting small sub-slices as part of
Expand Down Expand Up @@ -408,8 +408,8 @@ where
// }

// The goal is to generate cmov instructions here.
let v_a_swap = should_swap.select_unpredictable(v_b, v_a);
let v_b_swap = should_swap.select_unpredictable(v_a, v_b);
let v_a_swap = hint::select_unpredictable(should_swap, v_b, v_a);
let v_b_swap = hint::select_unpredictable(should_swap, v_a, v_b);

let v_b_swap_tmp = ManuallyDrop::new(ptr::read(v_b_swap));
ptr::copy(v_a_swap, v_a, 1);
Expand Down Expand Up @@ -640,15 +640,15 @@ pub unsafe fn sort4_stable<T, F: FnMut(&T, &T) -> bool>(
// 1, 1 | c b a d
let c3 = is_less(&*c, &*a);
let c4 = is_less(&*d, &*b);
let min = c3.select_unpredictable(c, a);
let max = c4.select_unpredictable(b, d);
let unknown_left = c3.select_unpredictable(a, c4.select_unpredictable(c, b));
let unknown_right = c4.select_unpredictable(d, c3.select_unpredictable(b, c));
let min = hint::select_unpredictable(c3, c, a);
let max = hint::select_unpredictable(c4, b, d);
let unknown_left = hint::select_unpredictable(c3, a, hint::select_unpredictable(c4, c, b));
let unknown_right = hint::select_unpredictable(c4, d, hint::select_unpredictable(c3, b, c));

// Sort the last two unknown elements.
let c5 = is_less(&*unknown_right, &*unknown_left);
let lo = c5.select_unpredictable(unknown_right, unknown_left);
let hi = c5.select_unpredictable(unknown_left, unknown_right);
let lo = hint::select_unpredictable(c5, unknown_right, unknown_left);
let hi = hint::select_unpredictable(c5, unknown_left, unknown_right);

ptr::copy_nonoverlapping(min, dst, 1);
ptr::copy_nonoverlapping(lo, dst.add(1), 1);
Expand Down
8 changes: 4 additions & 4 deletions tests/codegen/intrinsics/select_unpredictable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,27 @@ pub fn test_zst(p: bool, a: (), b: ()) -> () {
pub fn test_int2(p: bool, a: u64, b: u64) -> u64 {
// CHECK-LABEL: define{{.*}} @test_int2
// CHECK: select i1 %p, i64 %a, i64 %b, !unpredictable
p.select_unpredictable(a, b)
core::hint::select_unpredictable(p, a, b)
}

#[no_mangle]
pub fn test_pair2(p: bool, a: (u64, u64), b: (u64, u64)) -> (u64, u64) {
// CHECK-LABEL: define{{.*}} @test_pair2
// CHECK: select i1 %p, {{.*}}, !unpredictable
p.select_unpredictable(a, b)
core::hint::select_unpredictable(p, a, b)
}

#[no_mangle]
pub fn test_struct2(p: bool, a: Large, b: Large) -> Large {
// CHECK-LABEL: define{{.*}} @test_struct2
// CHECK: select i1 %p, {{.*}}, !unpredictable
p.select_unpredictable(a, b)
core::hint::select_unpredictable(p, a, b)
}

#[no_mangle]
pub fn test_zst2(p: bool, a: (), b: ()) -> () {
// CHECK-LABEL: define{{.*}} @test_zst2
// CHECK-NEXT: start:
// CHECK-NEXT: ret void
p.select_unpredictable(a, b)
core::hint::select_unpredictable(p, a, b)
}
Loading