Skip to content

Commit

Permalink
Assume slice len is bounded by allocation size
Browse files Browse the repository at this point in the history
Uses assume to check the length against a constant upper bound. The
inlined result then informs the optimizer of the sound value range.

This was tried with unreachable_unchecked before which introduces a
branch. This has the advantage of not being executed in sound code but
complicates basic blocks. It resulted in ~2% increased compile time in
some worst cases.

Add a codegen test for the assumption, testing the issue from rust-lang#67186
  • Loading branch information
HeroicKatora committed Oct 4, 2020
1 parent d92d28e commit e44784b
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 3 deletions.
3 changes: 2 additions & 1 deletion library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@
#![feature(constctlz)]
#![feature(const_panic)]
#![feature(const_pin)]
#![feature(const_fn_union)]
#![feature(const_fn)]
#![feature(const_fn_union)]
#![feature(const_assume)]
#![cfg_attr(not(bootstrap), feature(const_fn_floating_point_arithmetic))]
#![cfg_attr(not(bootstrap), feature(const_fn_fn_ptr_basics))]
#![feature(const_generics)]
Expand Down
27 changes: 25 additions & 2 deletions library/core/src/slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ pub use index::check_range;
#[lang = "slice"]
#[cfg(not(test))]
impl<T> [T] {
#[cfg(not(bootstrap))] // Unused in bootstrap
/// The maximum, inclusive, length such that the slice is no larger than `isize::MAX` bytes.
/// This constant is used in `len` below.
const MAX_LEN_BOUND: usize = {
if mem::size_of::<T>() == 0 {
usize::MAX
} else {
isize::MAX as usize / mem::size_of::<T>()
}
};

/// Returns the number of elements in the slice.
///
/// # Examples
Expand All @@ -90,11 +101,23 @@ impl<T> [T] {
#[rustc_const_stable(feature = "const_slice_len", since = "1.32.0")]
#[inline]
// SAFETY: const sound because we transmute out the length field as a usize (which it must be)
#[allow_internal_unstable(const_fn_union)]
#[allow_internal_unstable(const_fn_union, const_assume)]
pub const fn len(&self) -> usize {
// SAFETY: this is safe because `&[T]` and `FatPtr<T>` have the same layout.
// Only `std` can make this guarantee.
unsafe { crate::ptr::Repr { rust: self }.raw.len }
let raw_len = unsafe { crate::ptr::Repr { rust: self }.raw.len };

#[cfg(not(bootstrap))] // FIXME: executing assume in const eval not supported in bootstrap
// SAFETY: this assume asserts that `raw_len * size_of::<T>() <= isize::MAX`. All
// references must point to one allocation with size at most isize::MAX. Note that we the
// multiplication could appear to overflow until we have assumed the bound. This overflow
// would make additional values appear 'valid' and then `n > 1` the range of permissible
// length would no longer be the full or even a single range.
unsafe {
crate::intrinsics::assume(raw_len <= Self::MAX_LEN_BOUND)
};

raw_len
}

/// Returns `true` if the slice has a length of 0.
Expand Down
24 changes: 24 additions & 0 deletions src/test/codegen/len-is-bounded.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// min-llvm-version: 11.0
// compile-flags: -O -C panic=abort
#![crate_type = "lib"]

#[no_mangle]
pub fn len_range(a: &[u8], b: &[u8]) -> usize {
// CHECK-NOT: panic
a.len().checked_add(b.len()).unwrap()
}

#[no_mangle]
pub fn len_range_on_non_byte(a: &[u16], b: &[u16]) -> usize {
// CHECK-NOT: panic
a.len().checked_add(b.len()).unwrap()
}

pub struct Zst;

#[no_mangle]
pub fn zst_range(a: &[Zst], b: &[Zst]) -> usize {
// Zsts may be arbitrarily large.
// CHECK: panic
a.len().checked_add(b.len()).unwrap()
}

0 comments on commit e44784b

Please sign in to comment.