Skip to content

Commit e44784b

Browse files
committedOct 4, 2020
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 rust-lang#67186
1 parent d92d28e commit e44784b

File tree

3 files changed

+51
-3
lines changed

3 files changed

+51
-3
lines changed
 

‎library/core/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@
8080
#![feature(constctlz)]
8181
#![feature(const_panic)]
8282
#![feature(const_pin)]
83-
#![feature(const_fn_union)]
8483
#![feature(const_fn)]
84+
#![feature(const_fn_union)]
85+
#![feature(const_assume)]
8586
#![cfg_attr(not(bootstrap), feature(const_fn_floating_point_arithmetic))]
8687
#![cfg_attr(not(bootstrap), feature(const_fn_fn_ptr_basics))]
8788
#![feature(const_generics)]

‎library/core/src/slice/mod.rs

+25-2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ pub use index::check_range;
7878
#[lang = "slice"]
7979
#[cfg(not(test))]
8080
impl<T> [T] {
81+
#[cfg(not(bootstrap))] // Unused in bootstrap
82+
/// The maximum, inclusive, length such that the slice is no larger than `isize::MAX` bytes.
83+
/// This constant is used in `len` below.
84+
const MAX_LEN_BOUND: usize = {
85+
if mem::size_of::<T>() == 0 {
86+
usize::MAX
87+
} else {
88+
isize::MAX as usize / mem::size_of::<T>()
89+
}
90+
};
91+
8192
/// Returns the number of elements in the slice.
8293
///
8394
/// # Examples
@@ -90,11 +101,23 @@ impl<T> [T] {
90101
#[rustc_const_stable(feature = "const_slice_len", since = "1.32.0")]
91102
#[inline]
92103
// SAFETY: const sound because we transmute out the length field as a usize (which it must be)
93-
#[allow_internal_unstable(const_fn_union)]
104+
#[allow_internal_unstable(const_fn_union, const_assume)]
94105
pub const fn len(&self) -> usize {
95106
// SAFETY: this is safe because `&[T]` and `FatPtr<T>` have the same layout.
96107
// Only `std` can make this guarantee.
97-
unsafe { crate::ptr::Repr { rust: self }.raw.len }
108+
let raw_len = unsafe { crate::ptr::Repr { rust: self }.raw.len };
109+
110+
#[cfg(not(bootstrap))] // FIXME: executing assume in const eval not supported in bootstrap
111+
// SAFETY: this assume asserts that `raw_len * size_of::<T>() <= isize::MAX`. All
112+
// references must point to one allocation with size at most isize::MAX. Note that we the
113+
// multiplication could appear to overflow until we have assumed the bound. This overflow
114+
// would make additional values appear 'valid' and then `n > 1` the range of permissible
115+
// length would no longer be the full or even a single range.
116+
unsafe {
117+
crate::intrinsics::assume(raw_len <= Self::MAX_LEN_BOUND)
118+
};
119+
120+
raw_len
98121
}
99122

100123
/// Returns `true` if the slice has a length of 0.

‎src/test/codegen/len-is-bounded.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// min-llvm-version: 11.0
2+
// compile-flags: -O -C panic=abort
3+
#![crate_type = "lib"]
4+
5+
#[no_mangle]
6+
pub fn len_range(a: &[u8], b: &[u8]) -> usize {
7+
// CHECK-NOT: panic
8+
a.len().checked_add(b.len()).unwrap()
9+
}
10+
11+
#[no_mangle]
12+
pub fn len_range_on_non_byte(a: &[u16], b: &[u16]) -> usize {
13+
// CHECK-NOT: panic
14+
a.len().checked_add(b.len()).unwrap()
15+
}
16+
17+
pub struct Zst;
18+
19+
#[no_mangle]
20+
pub fn zst_range(a: &[Zst], b: &[Zst]) -> usize {
21+
// Zsts may be arbitrarily large.
22+
// CHECK: panic
23+
a.len().checked_add(b.len()).unwrap()
24+
}

0 commit comments

Comments
 (0)