Rollup merge of #123588 - tgross35:stabilize-assert_unchecked, r=dtolnay

Stabilize `hint::assert_unchecked`

Make the following API stable, including const:

```rust
// core::hint, std::hint

pub const unsafe fn assert_unchecked(p: bool);
```

This PR also reworks some of the documentation and adds an example.

Tracking issue: https://github.com/rust-lang/rust/issues/119131
FCP: https://github.com/rust-lang/rust/issues/119131#issuecomment-1906394087. The docs update should resolve the remaining concern.
This commit is contained in:
Jacob Pratt 2024-07-03 03:03:13 -04:00 committed by GitHub
commit db592253a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 77 additions and 33 deletions

View File

@ -127,7 +127,6 @@
#![feature(fmt_internals)]
#![feature(fn_traits)]
#![feature(hasher_prefixfree_extras)]
#![feature(hint_assert_unchecked)]
#![feature(inplace_iteration)]
#![feature(iter_advance_by)]
#![feature(iter_next_chunk)]

View File

@ -111,41 +111,92 @@ pub const unsafe fn unreachable_unchecked() -> ! {
/// Makes a *soundness* promise to the compiler that `cond` holds.
///
/// This may allow the optimizer to simplify things,
/// but it might also make the generated code slower.
/// Either way, calling it will most likely make compilation take longer.
/// This may allow the optimizer to simplify things, but it might also make the generated code
/// slower. Either way, calling it will most likely make compilation take longer.
///
/// This is a situational tool for micro-optimization, and is allowed to do nothing.
/// Any use should come with a repeatable benchmark to show the value
/// and allow removing it later should the optimizer get smarter and no longer need it.
/// You may know this from other places as
/// [`llvm.assume`](https://llvm.org/docs/LangRef.html#llvm-assume-intrinsic) or, in C,
/// [`__builtin_assume`](https://clang.llvm.org/docs/LanguageExtensions.html#builtin-assume).
///
/// The more complicated the condition the less likely this is to be fruitful.
/// For example, `assert_unchecked(foo.is_sorted())` is a complex enough value
/// that the compiler is unlikely to be able to take advantage of it.
/// This promotes a correctness requirement to a soundness requirement. Don't do that without
/// very good reason.
///
/// There's also no need to `assert_unchecked` basic properties of things. For
/// example, the compiler already knows the range of `count_ones`, so there's no
/// benefit to `let n = u32::count_ones(x); assert_unchecked(n <= u32::BITS);`.
/// # Usage
///
/// If ever you're tempted to write `assert_unchecked(false)`, then you're
/// actually looking for [`unreachable_unchecked()`].
/// This is a situational tool for micro-optimization, and is allowed to do nothing. Any use
/// should come with a repeatable benchmark to show the value, with the expectation to drop it
/// later should the optimizer get smarter and no longer need it.
///
/// You may know this from other places
/// as [`llvm.assume`](https://llvm.org/docs/LangRef.html#llvm-assume-intrinsic)
/// or [`__builtin_assume`](https://clang.llvm.org/docs/LanguageExtensions.html#builtin-assume).
/// The more complicated the condition, the less likely this is to be useful. For example,
/// `assert_unchecked(foo.is_sorted())` is a complex enough value that the compiler is unlikely
/// to be able to take advantage of it.
///
/// This promotes a correctness requirement to a soundness requirement.
/// Don't do that without very good reason.
/// There's also no need to `assert_unchecked` basic properties of things. For example, the
/// compiler already knows the range of `count_ones`, so there is no benefit to
/// `let n = u32::count_ones(x); assert_unchecked(n <= u32::BITS);`.
///
/// `assert_unchecked` is logically equivalent to `if !cond { unreachable_unchecked(); }`. If
/// ever you are tempted to write `assert_unchecked(false)`, you should instead use
/// [`unreachable_unchecked()`] directly.
///
/// # Safety
///
/// `cond` must be `true`. It's immediate UB to call this with `false`.
/// `cond` must be `true`. It is immediate UB to call this with `false`.
///
/// # Example
///
/// ```
/// use core::hint;
///
/// /// # Safety
/// ///
/// /// `p` must be nonnull and valid
/// pub unsafe fn next_value(p: *const i32) -> i32 {
/// // SAFETY: caller invariants guarantee that `p` is not null
/// unsafe { hint::assert_unchecked(!p.is_null()) }
///
/// if p.is_null() {
/// return -1;
/// } else {
/// // SAFETY: caller invariants guarantee that `p` is valid
/// unsafe { *p + 1 }
/// }
/// }
/// ```
///
/// Without the `assert_unchecked`, the above function produces the following with optimizations
/// enabled:
///
/// ```asm
/// next_value:
/// test rdi, rdi
/// je .LBB0_1
/// mov eax, dword ptr [rdi]
/// inc eax
/// ret
/// .LBB0_1:
/// mov eax, -1
/// ret
/// ```
///
/// Adding the assertion allows the optimizer to remove the extra check:
///
/// ```asm
/// next_value:
/// mov eax, dword ptr [rdi]
/// inc eax
/// ret
/// ```
///
/// This example is quite unlike anything that would be used in the real world: it is redundant
/// to put an an assertion right next to code that checks the same thing, and dereferencing a
/// pointer already has the builtin assumption that it is nonnull. However, it illustrates the
/// kind of changes the optimizer can make even when the behavior is less obviously related.
#[track_caller]
#[inline(always)]
#[doc(alias = "assume")]
#[track_caller]
#[unstable(feature = "hint_assert_unchecked", issue = "119131")]
#[rustc_const_unstable(feature = "const_hint_assert_unchecked", issue = "119131")]
#[stable(feature = "hint_assert_unchecked", since = "CURRENT_RUSTC_VERSION")]
#[rustc_const_stable(feature = "hint_assert_unchecked", since = "CURRENT_RUSTC_VERSION")]
pub const unsafe fn assert_unchecked(cond: bool) {
// SAFETY: The caller promised `cond` is true.
unsafe {

View File

@ -959,7 +959,7 @@ extern "rust-intrinsic" {
/// not be used if the invariant can be discovered by the optimizer on its
/// own, or if it does not enable any significant optimizations.
///
/// This intrinsic does not have a stable counterpart.
/// The stabilized version of this intrinsic is [`core::hint::assert_unchecked`].
#[rustc_const_stable(feature = "const_assume", since = "1.77.0")]
#[rustc_nounwind]
#[unstable(feature = "core_intrinsics", issue = "none")]

View File

@ -127,7 +127,6 @@
#![feature(const_fmt_arguments_new)]
#![feature(const_hash)]
#![feature(const_heap)]
#![feature(const_hint_assert_unchecked)]
#![feature(const_index_range_slice_index)]
#![feature(const_int_from_str)]
#![feature(const_intrinsic_copy)]

View File

@ -334,7 +334,6 @@
#![feature(fmt_internals)]
#![feature(hasher_prefixfree_extras)]
#![feature(hashmap_internals)]
#![feature(hint_assert_unchecked)]
#![feature(ip)]
#![feature(maybe_uninit_slice)]
#![feature(maybe_uninit_write_slice)]

View File

@ -1,10 +1,6 @@
#![feature(hint_assert_unchecked)]
#![feature(const_hint_assert_unchecked)]
const _: () = unsafe {
let n = u32::MAX.count_ones();
std::hint::assert_unchecked(n < 32); //~ ERROR evaluation of constant value failed
};
fn main() {
}
fn main() {}

View File

@ -1,5 +1,5 @@
error[E0080]: evaluation of constant value failed
--> $DIR/const-assert-unchecked-ub.rs:6:5
--> $DIR/const-assert-unchecked-ub.rs:3:5
|
LL | std::hint::assert_unchecked(n < 32);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `assume` called with `false`