Skip to content

Commit 9570ac4

Browse files
authored
Rollup merge of #123564 - scottmcm:step-by-div-zero, r=joboet
Don't emit divide-by-zero panic paths in `StepBy::len` I happened to notice today that there's actually two such calls emitted in the assembly: <https://rust.godbolt.org/z/1Wbbd3Ts6> Since they're impossible, hopefully telling LLVM that will also help optimizations elsewhere.
2 parents 5627497 + 00bd247 commit 9570ac4

File tree

2 files changed

+75
-31
lines changed

2 files changed

+75
-31
lines changed

library/core/src/iter/adapters/step_by.rs

+49-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::{
22
intrinsics,
33
iter::{from_fn, TrustedLen, TrustedRandomAccess},
4+
num::NonZeroUsize,
45
ops::{Range, Try},
56
};
67

@@ -22,7 +23,11 @@ pub struct StepBy<I> {
2223
/// Additionally this type-dependent preprocessing means specialized implementations
2324
/// cannot be used interchangeably.
2425
iter: I,
25-
step: usize,
26+
/// This field is `step - 1`, aka the correct amount to pass to `nth` when iterating.
27+
/// It MUST NOT be `usize::MAX`, as `unsafe` code depends on being able to add one
28+
/// without the risk of overflow. (This is important so that length calculations
29+
/// don't need to check for division-by-zero, for example.)
30+
step_minus_one: usize,
2631
first_take: bool,
2732
}
2833

@@ -31,7 +36,16 @@ impl<I> StepBy<I> {
3136
pub(in crate::iter) fn new(iter: I, step: usize) -> StepBy<I> {
3237
assert!(step != 0);
3338
let iter = <I as SpecRangeSetup<I>>::setup(iter, step);
34-
StepBy { iter, step: step - 1, first_take: true }
39+
StepBy { iter, step_minus_one: step - 1, first_take: true }
40+
}
41+
42+
/// The `step` that was originally passed to `Iterator::step_by(step)`,
43+
/// aka `self.step_minus_one + 1`.
44+
#[inline]
45+
fn original_step(&self) -> NonZeroUsize {
46+
// SAFETY: By type invariant, `step_minus_one` cannot be `MAX`, which
47+
// means the addition cannot overflow and the result cannot be zero.
48+
unsafe { NonZeroUsize::new_unchecked(intrinsics::unchecked_add(self.step_minus_one, 1)) }
3549
}
3650
}
3751

@@ -81,8 +95,8 @@ where
8195
// The zero-based index starting from the end of the iterator of the
8296
// last element. Used in the `DoubleEndedIterator` implementation.
8397
fn next_back_index(&self) -> usize {
84-
let rem = self.iter.len() % (self.step + 1);
85-
if self.first_take { if rem == 0 { self.step } else { rem - 1 } } else { rem }
98+
let rem = self.iter.len() % self.original_step();
99+
if self.first_take { if rem == 0 { self.step_minus_one } else { rem - 1 } } else { rem }
86100
}
87101
}
88102

@@ -209,30 +223,30 @@ unsafe impl<I: Iterator> StepByImpl<I> for StepBy<I> {
209223

210224
#[inline]
211225
default fn spec_next(&mut self) -> Option<I::Item> {
212-
let step_size = if self.first_take { 0 } else { self.step };
226+
let step_size = if self.first_take { 0 } else { self.step_minus_one };
213227
self.first_take = false;
214228
self.iter.nth(step_size)
215229
}
216230

217231
#[inline]
218232
default fn spec_size_hint(&self) -> (usize, Option<usize>) {
219233
#[inline]
220-
fn first_size(step: usize) -> impl Fn(usize) -> usize {
221-
move |n| if n == 0 { 0 } else { 1 + (n - 1) / (step + 1) }
234+
fn first_size(step: NonZeroUsize) -> impl Fn(usize) -> usize {
235+
move |n| if n == 0 { 0 } else { 1 + (n - 1) / step }
222236
}
223237

224238
#[inline]
225-
fn other_size(step: usize) -> impl Fn(usize) -> usize {
226-
move |n| n / (step + 1)
239+
fn other_size(step: NonZeroUsize) -> impl Fn(usize) -> usize {
240+
move |n| n / step
227241
}
228242

229243
let (low, high) = self.iter.size_hint();
230244

231245
if self.first_take {
232-
let f = first_size(self.step);
246+
let f = first_size(self.original_step());
233247
(f(low), high.map(f))
234248
} else {
235-
let f = other_size(self.step);
249+
let f = other_size(self.original_step());
236250
(f(low), high.map(f))
237251
}
238252
}
@@ -247,10 +261,9 @@ unsafe impl<I: Iterator> StepByImpl<I> for StepBy<I> {
247261
}
248262
n -= 1;
249263
}
250-
// n and self.step are indices, we need to add 1 to get the amount of elements
264+
// n and self.step_minus_one are indices, we need to add 1 to get the amount of elements
251265
// When calling `.nth`, we need to subtract 1 again to convert back to an index
252-
// step + 1 can't overflow because `.step_by` sets `self.step` to `step - 1`
253-
let mut step = self.step + 1;
266+
let mut step = self.original_step().get();
254267
// n + 1 could overflow
255268
// thus, if n is usize::MAX, instead of adding one, we call .nth(step)
256269
if n == usize::MAX {
@@ -288,8 +301,11 @@ unsafe impl<I: Iterator> StepByImpl<I> for StepBy<I> {
288301
R: Try<Output = Acc>,
289302
{
290303
#[inline]
291-
fn nth<I: Iterator>(iter: &mut I, step: usize) -> impl FnMut() -> Option<I::Item> + '_ {
292-
move || iter.nth(step)
304+
fn nth<I: Iterator>(
305+
iter: &mut I,
306+
step_minus_one: usize,
307+
) -> impl FnMut() -> Option<I::Item> + '_ {
308+
move || iter.nth(step_minus_one)
293309
}
294310

295311
if self.first_take {
@@ -299,16 +315,19 @@ unsafe impl<I: Iterator> StepByImpl<I> for StepBy<I> {
299315
Some(x) => acc = f(acc, x)?,
300316
}
301317
}
302-
from_fn(nth(&mut self.iter, self.step)).try_fold(acc, f)
318+
from_fn(nth(&mut self.iter, self.step_minus_one)).try_fold(acc, f)
303319
}
304320

305321
default fn spec_fold<Acc, F>(mut self, mut acc: Acc, mut f: F) -> Acc
306322
where
307323
F: FnMut(Acc, Self::Item) -> Acc,
308324
{
309325
#[inline]
310-
fn nth<I: Iterator>(iter: &mut I, step: usize) -> impl FnMut() -> Option<I::Item> + '_ {
311-
move || iter.nth(step)
326+
fn nth<I: Iterator>(
327+
iter: &mut I,
328+
step_minus_one: usize,
329+
) -> impl FnMut() -> Option<I::Item> + '_ {
330+
move || iter.nth(step_minus_one)
312331
}
313332

314333
if self.first_take {
@@ -318,7 +337,7 @@ unsafe impl<I: Iterator> StepByImpl<I> for StepBy<I> {
318337
Some(x) => acc = f(acc, x),
319338
}
320339
}
321-
from_fn(nth(&mut self.iter, self.step)).fold(acc, f)
340+
from_fn(nth(&mut self.iter, self.step_minus_one)).fold(acc, f)
322341
}
323342
}
324343

@@ -336,7 +355,7 @@ unsafe impl<I: DoubleEndedIterator + ExactSizeIterator> StepByBackImpl<I> for St
336355
// is out of bounds because the length of `self.iter` does not exceed
337356
// `usize::MAX` (because `I: ExactSizeIterator`) and `nth_back` is
338357
// zero-indexed
339-
let n = n.saturating_mul(self.step + 1).saturating_add(self.next_back_index());
358+
let n = n.saturating_mul(self.original_step().get()).saturating_add(self.next_back_index());
340359
self.iter.nth_back(n)
341360
}
342361

@@ -348,16 +367,16 @@ unsafe impl<I: DoubleEndedIterator + ExactSizeIterator> StepByBackImpl<I> for St
348367
#[inline]
349368
fn nth_back<I: DoubleEndedIterator>(
350369
iter: &mut I,
351-
step: usize,
370+
step_minus_one: usize,
352371
) -> impl FnMut() -> Option<I::Item> + '_ {
353-
move || iter.nth_back(step)
372+
move || iter.nth_back(step_minus_one)
354373
}
355374

356375
match self.next_back() {
357376
None => try { init },
358377
Some(x) => {
359378
let acc = f(init, x)?;
360-
from_fn(nth_back(&mut self.iter, self.step)).try_fold(acc, f)
379+
from_fn(nth_back(&mut self.iter, self.step_minus_one)).try_fold(acc, f)
361380
}
362381
}
363382
}
@@ -371,16 +390,16 @@ unsafe impl<I: DoubleEndedIterator + ExactSizeIterator> StepByBackImpl<I> for St
371390
#[inline]
372391
fn nth_back<I: DoubleEndedIterator>(
373392
iter: &mut I,
374-
step: usize,
393+
step_minus_one: usize,
375394
) -> impl FnMut() -> Option<I::Item> + '_ {
376-
move || iter.nth_back(step)
395+
move || iter.nth_back(step_minus_one)
377396
}
378397

379398
match self.next_back() {
380399
None => init,
381400
Some(x) => {
382401
let acc = f(init, x);
383-
from_fn(nth_back(&mut self.iter, self.step)).fold(acc, f)
402+
from_fn(nth_back(&mut self.iter, self.step_minus_one)).fold(acc, f)
384403
}
385404
}
386405
}
@@ -424,8 +443,7 @@ macro_rules! spec_int_ranges {
424443
fn spec_next(&mut self) -> Option<$t> {
425444
// if a step size larger than the type has been specified fall back to
426445
// t::MAX, in which case remaining will be at most 1.
427-
// The `+ 1` can't overflow since the constructor substracted 1 from the original value.
428-
let step = <$t>::try_from(self.step + 1).unwrap_or(<$t>::MAX);
446+
let step = <$t>::try_from(self.original_step().get()).unwrap_or(<$t>::MAX);
429447
let remaining = self.iter.end;
430448
if remaining > 0 {
431449
let val = self.iter.start;
@@ -474,7 +492,7 @@ macro_rules! spec_int_ranges {
474492
{
475493
// if a step size larger than the type has been specified fall back to
476494
// t::MAX, in which case remaining will be at most 1.
477-
let step = <$t>::try_from(self.step + 1).unwrap_or(<$t>::MAX);
495+
let step = <$t>::try_from(self.original_step().get()).unwrap_or(<$t>::MAX);
478496
let remaining = self.iter.end;
479497
let mut acc = init;
480498
let mut val = self.iter.start;
@@ -500,7 +518,7 @@ macro_rules! spec_int_ranges_r {
500518
fn spec_next_back(&mut self) -> Option<Self::Item>
501519
where Range<$t>: DoubleEndedIterator + ExactSizeIterator,
502520
{
503-
let step = (self.step + 1) as $t;
521+
let step = self.original_step().get() as $t;
504522
let remaining = self.iter.end;
505523
if remaining > 0 {
506524
let start = self.iter.start;
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//@ compile-flags: -O
2+
3+
#![crate_type = "lib"]
4+
5+
use std::iter::StepBy;
6+
use std::slice::Iter;
7+
8+
// The constructor for `StepBy` ensures we can never end up needing to do zero
9+
// checks on denominators, so check that the code isn't emitting panic paths.
10+
11+
// CHECK-LABEL: @step_by_len_std
12+
#[no_mangle]
13+
pub fn step_by_len_std(x: &StepBy<Iter<i32>>) -> usize {
14+
// CHECK-NOT: div_by_zero
15+
// CHECK: udiv
16+
// CHECK-NOT: div_by_zero
17+
x.len()
18+
}
19+
20+
// CHECK-LABEL: @step_by_len_naive
21+
#[no_mangle]
22+
pub fn step_by_len_naive(x: Iter<i32>, step_minus_one: usize) -> usize {
23+
// CHECK: udiv
24+
// CHECK: call{{.+}}div_by_zero
25+
x.len() / (step_minus_one + 1)
26+
}

0 commit comments

Comments
 (0)