Skip to content

Commit f3ee3b2

Browse files
committed
Auto merge of rust-lang#120863 - saethlin:slice-get-checked, r=the8472
Use intrinsics::debug_assertions in debug_assert_nounwind This is the first item in rust-lang#120848. Based on the benchmarking in this PR, it looks like, for the programs in our benchmark suite, enabling all these additional checks does not introduce significant compile-time overhead, with the single exception of `Alignment::new_unchecked`. Therefore, I've added `#[cfg(debug_assertions)]` to that one call site, so that it remains compiled out in the distributed standard library. The trailing commas in the previous calls to `debug_assert_nounwind!` were causing the macro to expand to `panic_nouwnind_fmt`, which requires more work to set up its arguments, and that overhead alone is measured between this perf run and the next: rust-lang#120863 (comment)
2 parents ee9c7c9 + a23e6c3 commit f3ee3b2

10 files changed

+65
-365
lines changed

Diff for: library/core/src/panic.rs

+20-9
Original file line numberDiff line numberDiff line change
@@ -139,27 +139,38 @@ pub macro unreachable_2021 {
139139
),
140140
}
141141

142-
/// Asserts that a boolean expression is `true`, and perform a non-unwinding panic otherwise.
142+
/// Like `assert_unsafe_precondition!` the defining features of this macro are that its
143+
/// checks are enabled when they are monomorphized with debug assertions enabled, and upon failure
144+
/// a non-unwinding panic is launched so that failures cannot compromise unwind safety.
143145
///
144-
/// This macro is similar to `debug_assert!`, but is intended to be used in code that should not
145-
/// unwind. For example, checks in `_unchecked` functions that are intended for debugging but should
146-
/// not compromise unwind safety.
146+
/// But there are many differences from `assert_unsafe_precondition!`. This macro does not use
147+
/// `const_eval_select` internally, and therefore it is sound to call this with an expression
148+
/// that evaluates to `false`. Also unlike `assert_unsafe_precondition!` the condition being
149+
/// checked here is not put in an outlined function. If the check compiles to a lot of IR, this
150+
/// can cause code bloat if the check is monomorphized many times. But it also means that the checks
151+
/// from this macro can be deduplicated or otherwise optimized out.
152+
///
153+
/// In general, this macro should be used to check all public-facing preconditions. But some
154+
/// preconditions may be called too often or instantiated too often to make the overhead of the
155+
/// checks tolerable. In such cases, place `#[cfg(debug_assertions)]` on the macro call. That will
156+
/// disable the check in our precompiled standard library, but if a user wishes, they can still
157+
/// enable the check by recompiling the standard library with debug assertions enabled.
147158
#[doc(hidden)]
148159
#[unstable(feature = "panic_internals", issue = "none")]
149-
#[allow_internal_unstable(panic_internals, const_format_args)]
160+
#[allow_internal_unstable(panic_internals, delayed_debug_assertions)]
150161
#[rustc_macro_transparency = "semitransparent"]
151162
pub macro debug_assert_nounwind {
152163
($cond:expr $(,)?) => {
153-
if $crate::cfg!(debug_assertions) {
164+
if $crate::intrinsics::debug_assertions() {
154165
if !$cond {
155166
$crate::panicking::panic_nounwind($crate::concat!("assertion failed: ", $crate::stringify!($cond)));
156167
}
157168
}
158169
},
159-
($cond:expr, $($arg:tt)+) => {
160-
if $crate::cfg!(debug_assertions) {
170+
($cond:expr, $message:expr) => {
171+
if $crate::intrinsics::debug_assertions() {
161172
if !$cond {
162-
$crate::panicking::panic_nounwind_fmt($crate::const_format_args!($($arg)+), false);
173+
$crate::panicking::panic_nounwind($message);
163174
}
164175
}
165176
},

Diff for: library/core/src/ptr/alignment.rs

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ impl Alignment {
7676
#[rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070")]
7777
#[inline]
7878
pub const unsafe fn new_unchecked(align: usize) -> Self {
79+
#[cfg(debug_assertions)]
7980
crate::panic::debug_assert_nounwind!(
8081
align.is_power_of_two(),
8182
"Alignment::new_unchecked requires a power of two"

Diff for: library/core/src/slice/index.rs

+12-8
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ where
1313
{
1414
type Output = I::Output;
1515

16-
#[inline]
16+
#[inline(always)]
1717
fn index(&self, index: I) -> &I::Output {
1818
index.index(self)
1919
}
@@ -24,7 +24,7 @@ impl<T, I> ops::IndexMut<I> for [T]
2424
where
2525
I: SliceIndex<[T]>,
2626
{
27-
#[inline]
27+
#[inline(always)]
2828
fn index_mut(&mut self, index: I) -> &mut I::Output {
2929
index.index_mut(self)
3030
}
@@ -227,14 +227,16 @@ unsafe impl<T> SliceIndex<[T]> for usize {
227227
unsafe fn get_unchecked(self, slice: *const [T]) -> *const T {
228228
debug_assert_nounwind!(
229229
self < slice.len(),
230-
"slice::get_unchecked requires that the index is within the slice",
230+
"slice::get_unchecked requires that the index is within the slice"
231231
);
232232
// SAFETY: the caller guarantees that `slice` is not dangling, so it
233233
// cannot be longer than `isize::MAX`. They also guarantee that
234234
// `self` is in bounds of `slice` so `self` cannot overflow an `isize`,
235235
// so the call to `add` is safe.
236236
unsafe {
237-
crate::hint::assert_unchecked(self < slice.len());
237+
// Use intrinsics::assume instead of hint::assert_unchecked so that we don't check the
238+
// precondition of this function twice.
239+
crate::intrinsics::assume(self < slice.len());
238240
slice.as_ptr().add(self)
239241
}
240242
}
@@ -243,7 +245,7 @@ unsafe impl<T> SliceIndex<[T]> for usize {
243245
unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut T {
244246
debug_assert_nounwind!(
245247
self < slice.len(),
246-
"slice::get_unchecked_mut requires that the index is within the slice",
248+
"slice::get_unchecked_mut requires that the index is within the slice"
247249
);
248250
// SAFETY: see comments for `get_unchecked` above.
249251
unsafe { slice.as_mut_ptr().add(self) }
@@ -305,8 +307,9 @@ unsafe impl<T> SliceIndex<[T]> for ops::IndexRange {
305307
unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut [T] {
306308
debug_assert_nounwind!(
307309
self.end() <= slice.len(),
308-
"slice::get_unchecked_mut requires that the index is within the slice",
310+
"slice::get_unchecked_mut requires that the index is within the slice"
309311
);
312+
310313
// SAFETY: see comments for `get_unchecked` above.
311314
unsafe { ptr::slice_from_raw_parts_mut(slice.as_mut_ptr().add(self.start()), self.len()) }
312315
}
@@ -361,8 +364,9 @@ unsafe impl<T> SliceIndex<[T]> for ops::Range<usize> {
361364
unsafe fn get_unchecked(self, slice: *const [T]) -> *const [T] {
362365
debug_assert_nounwind!(
363366
self.end >= self.start && self.end <= slice.len(),
364-
"slice::get_unchecked requires that the range is within the slice",
367+
"slice::get_unchecked requires that the range is within the slice"
365368
);
369+
366370
// SAFETY: the caller guarantees that `slice` is not dangling, so it
367371
// cannot be longer than `isize::MAX`. They also guarantee that
368372
// `self` is in bounds of `slice` so `self` cannot overflow an `isize`,
@@ -377,7 +381,7 @@ unsafe impl<T> SliceIndex<[T]> for ops::Range<usize> {
377381
unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut [T] {
378382
debug_assert_nounwind!(
379383
self.end >= self.start && self.end <= slice.len(),
380-
"slice::get_unchecked_mut requires that the range is within the slice",
384+
"slice::get_unchecked_mut requires that the range is within the slice"
381385
);
382386
// SAFETY: see comments for `get_unchecked` above.
383387
unsafe {

Diff for: library/core/src/slice/mod.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,7 @@ impl<T> [T] {
938938
pub const unsafe fn swap_unchecked(&mut self, a: usize, b: usize) {
939939
debug_assert_nounwind!(
940940
a < self.len() && b < self.len(),
941-
"slice::swap_unchecked requires that the indices are within the slice",
941+
"slice::swap_unchecked requires that the indices are within the slice"
942942
);
943943

944944
let ptr = self.as_mut_ptr();
@@ -1278,7 +1278,7 @@ impl<T> [T] {
12781278
pub const unsafe fn as_chunks_unchecked<const N: usize>(&self) -> &[[T; N]] {
12791279
debug_assert_nounwind!(
12801280
N != 0 && self.len() % N == 0,
1281-
"slice::as_chunks_unchecked requires `N != 0` and the slice to split exactly into `N`-element chunks",
1281+
"slice::as_chunks_unchecked requires `N != 0` and the slice to split exactly into `N`-element chunks"
12821282
);
12831283
// SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length
12841284
let new_len = unsafe { exact_div(self.len(), N) };
@@ -1432,7 +1432,7 @@ impl<T> [T] {
14321432
pub const unsafe fn as_chunks_unchecked_mut<const N: usize>(&mut self) -> &mut [[T; N]] {
14331433
debug_assert_nounwind!(
14341434
N != 0 && self.len() % N == 0,
1435-
"slice::as_chunks_unchecked requires `N != 0` and the slice to split exactly into `N`-element chunks",
1435+
"slice::as_chunks_unchecked requires `N != 0` and the slice to split exactly into `N`-element chunks"
14361436
);
14371437
// SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length
14381438
let new_len = unsafe { exact_div(self.len(), N) };
@@ -1964,7 +1964,7 @@ impl<T> [T] {
19641964

19651965
debug_assert_nounwind!(
19661966
mid <= len,
1967-
"slice::split_at_unchecked requires the index to be within the slice",
1967+
"slice::split_at_unchecked requires the index to be within the slice"
19681968
);
19691969

19701970
// SAFETY: Caller has to check that `0 <= mid <= self.len()`
@@ -2014,7 +2014,7 @@ impl<T> [T] {
20142014

20152015
debug_assert_nounwind!(
20162016
mid <= len,
2017-
"slice::split_at_mut_unchecked requires the index to be within the slice",
2017+
"slice::split_at_mut_unchecked requires the index to be within the slice"
20182018
);
20192019

20202020
// SAFETY: Caller has to check that `0 <= mid <= self.len()`.

Diff for: library/core/src/str/traits.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ unsafe impl SliceIndex<str> for ops::Range<usize> {
200200
// `str::get_unchecked` without adding a special function
201201
// to `SliceIndex` just for this.
202202
self.end >= self.start && self.end <= slice.len(),
203-
"str::get_unchecked requires that the range is within the string slice",
203+
"str::get_unchecked requires that the range is within the string slice"
204204
);
205205

206206
// SAFETY: the caller guarantees that `self` is in bounds of `slice`
@@ -215,7 +215,7 @@ unsafe impl SliceIndex<str> for ops::Range<usize> {
215215

216216
debug_assert_nounwind!(
217217
self.end >= self.start && self.end <= slice.len(),
218-
"str::get_unchecked_mut requires that the range is within the string slice",
218+
"str::get_unchecked_mut requires that the range is within the string slice"
219219
);
220220

221221
// SAFETY: see comments for `get_unchecked`.

Diff for: tests/mir-opt/pre-codegen/slice_index.slice_get_mut_usize.PreCodegen.after.panic-abort.mir

+1-77
Original file line numberDiff line numberDiff line change
@@ -7,89 +7,13 @@ fn slice_get_mut_usize(_1: &mut [u32], _2: usize) -> Option<&mut u32> {
77
scope 1 (inlined core::slice::<impl [u32]>::get_mut::<usize>) {
88
debug self => _1;
99
debug index => _2;
10-
scope 2 (inlined <usize as SliceIndex<[u32]>>::get_mut) {
11-
debug self => _2;
12-
debug slice => _1;
13-
let mut _3: usize;
14-
let mut _4: bool;
15-
let mut _5: *mut [u32];
16-
let mut _7: *mut u32;
17-
let mut _8: &mut u32;
18-
scope 3 {
19-
scope 4 (inlined <usize as SliceIndex<[u32]>>::get_unchecked_mut) {
20-
debug self => _2;
21-
debug slice => _5;
22-
let mut _6: *mut u32;
23-
let mut _9: *mut [u32];
24-
let mut _10: &[&str];
25-
scope 5 {
26-
scope 10 (inlined std::ptr::mut_ptr::<impl *mut [u32]>::as_mut_ptr) {
27-
debug self => _5;
28-
}
29-
scope 11 (inlined std::ptr::mut_ptr::<impl *mut u32>::add) {
30-
debug self => _6;
31-
debug count => _2;
32-
scope 12 {
33-
}
34-
}
35-
}
36-
scope 6 (inlined std::ptr::mut_ptr::<impl *mut [u32]>::len) {
37-
debug self => _9;
38-
let mut _11: *const [u32];
39-
scope 7 (inlined std::ptr::metadata::<[u32]>) {
40-
debug ptr => _11;
41-
scope 8 {
42-
}
43-
}
44-
}
45-
scope 9 (inlined Arguments::<'_>::new_const) {
46-
debug pieces => _10;
47-
}
48-
}
49-
}
50-
}
5110
}
5211

5312
bb0: {
54-
StorageLive(_7);
55-
StorageLive(_4);
56-
StorageLive(_3);
57-
_3 = Len((*_1));
58-
_4 = Lt(_2, move _3);
59-
switchInt(move _4) -> [0: bb1, otherwise: bb2];
13+
_0 = <usize as SliceIndex<[u32]>>::get_mut(move _2, move _1) -> [return: bb1, unwind unreachable];
6014
}
6115

6216
bb1: {
63-
StorageDead(_3);
64-
_0 = const Option::<&mut u32>::None;
65-
goto -> bb3;
66-
}
67-
68-
bb2: {
69-
StorageDead(_3);
70-
StorageLive(_8);
71-
StorageLive(_5);
72-
_5 = &raw mut (*_1);
73-
StorageLive(_9);
74-
StorageLive(_10);
75-
StorageLive(_11);
76-
StorageLive(_6);
77-
_6 = _5 as *mut u32 (PtrToPtr);
78-
_7 = Offset(_6, _2);
79-
StorageDead(_6);
80-
StorageDead(_11);
81-
StorageDead(_10);
82-
StorageDead(_9);
83-
StorageDead(_5);
84-
_8 = &mut (*_7);
85-
_0 = Option::<&mut u32>::Some(move _8);
86-
StorageDead(_8);
87-
goto -> bb3;
88-
}
89-
90-
bb3: {
91-
StorageDead(_4);
92-
StorageDead(_7);
9317
return;
9418
}
9519
}

Diff for: tests/mir-opt/pre-codegen/slice_index.slice_get_mut_usize.PreCodegen.after.panic-unwind.mir

+1-77
Original file line numberDiff line numberDiff line change
@@ -7,89 +7,13 @@ fn slice_get_mut_usize(_1: &mut [u32], _2: usize) -> Option<&mut u32> {
77
scope 1 (inlined core::slice::<impl [u32]>::get_mut::<usize>) {
88
debug self => _1;
99
debug index => _2;
10-
scope 2 (inlined <usize as SliceIndex<[u32]>>::get_mut) {
11-
debug self => _2;
12-
debug slice => _1;
13-
let mut _3: usize;
14-
let mut _4: bool;
15-
let mut _5: *mut [u32];
16-
let mut _7: *mut u32;
17-
let mut _8: &mut u32;
18-
scope 3 {
19-
scope 4 (inlined <usize as SliceIndex<[u32]>>::get_unchecked_mut) {
20-
debug self => _2;
21-
debug slice => _5;
22-
let mut _6: *mut u32;
23-
let mut _9: *mut [u32];
24-
let mut _10: &[&str];
25-
scope 5 {
26-
scope 10 (inlined std::ptr::mut_ptr::<impl *mut [u32]>::as_mut_ptr) {
27-
debug self => _5;
28-
}
29-
scope 11 (inlined std::ptr::mut_ptr::<impl *mut u32>::add) {
30-
debug self => _6;
31-
debug count => _2;
32-
scope 12 {
33-
}
34-
}
35-
}
36-
scope 6 (inlined std::ptr::mut_ptr::<impl *mut [u32]>::len) {
37-
debug self => _9;
38-
let mut _11: *const [u32];
39-
scope 7 (inlined std::ptr::metadata::<[u32]>) {
40-
debug ptr => _11;
41-
scope 8 {
42-
}
43-
}
44-
}
45-
scope 9 (inlined Arguments::<'_>::new_const) {
46-
debug pieces => _10;
47-
}
48-
}
49-
}
50-
}
5110
}
5211

5312
bb0: {
54-
StorageLive(_7);
55-
StorageLive(_4);
56-
StorageLive(_3);
57-
_3 = Len((*_1));
58-
_4 = Lt(_2, move _3);
59-
switchInt(move _4) -> [0: bb1, otherwise: bb2];
13+
_0 = <usize as SliceIndex<[u32]>>::get_mut(move _2, move _1) -> [return: bb1, unwind continue];
6014
}
6115

6216
bb1: {
63-
StorageDead(_3);
64-
_0 = const Option::<&mut u32>::None;
65-
goto -> bb3;
66-
}
67-
68-
bb2: {
69-
StorageDead(_3);
70-
StorageLive(_8);
71-
StorageLive(_5);
72-
_5 = &raw mut (*_1);
73-
StorageLive(_9);
74-
StorageLive(_10);
75-
StorageLive(_11);
76-
StorageLive(_6);
77-
_6 = _5 as *mut u32 (PtrToPtr);
78-
_7 = Offset(_6, _2);
79-
StorageDead(_6);
80-
StorageDead(_11);
81-
StorageDead(_10);
82-
StorageDead(_9);
83-
StorageDead(_5);
84-
_8 = &mut (*_7);
85-
_0 = Option::<&mut u32>::Some(move _8);
86-
StorageDead(_8);
87-
goto -> bb3;
88-
}
89-
90-
bb3: {
91-
StorageDead(_4);
92-
StorageDead(_7);
9317
return;
9418
}
9519
}

0 commit comments

Comments
 (0)