From e44784b8750016a695361c990024750e037d8f9f Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Sun, 20 Sep 2020 17:22:17 +0200 Subject: [PATCH] Assume slice len is bounded by allocation size 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 #67186 --- library/core/src/lib.rs | 3 ++- library/core/src/slice/mod.rs | 27 +++++++++++++++++++++++++-- src/test/codegen/len-is-bounded.rs | 24 ++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 src/test/codegen/len-is-bounded.rs diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 22bf2b15d6695..49cec16276260 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -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)] diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 12dcd6c6ba8d0..d2b64f3ac6ad0 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -78,6 +78,17 @@ pub use index::check_range; #[lang = "slice"] #[cfg(not(test))] impl [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::() == 0 { + usize::MAX + } else { + isize::MAX as usize / mem::size_of::() + } + }; + /// Returns the number of elements in the slice. /// /// # Examples @@ -90,11 +101,23 @@ impl [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` 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::() <= 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. diff --git a/src/test/codegen/len-is-bounded.rs b/src/test/codegen/len-is-bounded.rs new file mode 100644 index 0000000000000..bb74fc3b2752c --- /dev/null +++ b/src/test/codegen/len-is-bounded.rs @@ -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() +}