Skip to content

Commit d40d614

Browse files
committed
Auto merge of #119207 - scottmcm:manual-index-range-fold, r=<try>
Update `slice::Iter::rfold` to match `slice::Iter::fold` Adds a new codegen test for `rfold`, like the one from #106343, and makes a similar fix, updating `rfold` to work via indices too.
2 parents aaef5fe + d75741f commit d40d614

File tree

4 files changed

+77
-35
lines changed

4 files changed

+77
-35
lines changed

library/core/src/ops/index_range.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,32 @@ impl Iterator for IndexRange {
134134
let taken = self.take_prefix(n);
135135
NonZeroUsize::new(n - taken.len()).map_or(Ok(()), Err)
136136
}
137+
138+
#[inline]
139+
fn fold<B, F>(self, mut init: B, mut f: F) -> B
140+
where
141+
Self: Sized,
142+
F: FnMut(B, Self::Item) -> B,
143+
{
144+
// Manually canonicalize, as in #106343.
145+
// It's not clear why it's needed -- maybe avoiding the `Option`
146+
// checks simplifies something? -- but it does seem to help, as without
147+
// this override the `slice_iter_fold` codegen test no longer passes.
148+
let Self { mut start, end } = self;
149+
if start < end {
150+
loop {
151+
init = f(init, start);
152+
// SAFETY: The if above ensures this is in-range for the first
153+
// iteration, then we break below on getting to end which will
154+
// ensure it never tries to go above usize::MAX.
155+
start = unsafe { unchecked_add(start, 1) };
156+
if start == end {
157+
break;
158+
}
159+
}
160+
}
161+
init
162+
}
137163
}
138164

139165
impl DoubleEndedIterator for IndexRange {

library/core/src/slice/iter.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::iter::{
1313
use crate::marker::PhantomData;
1414
use crate::mem::{self, SizedTypeProperties};
1515
use crate::num::NonZeroUsize;
16+
use crate::ops::IndexRange;
1617
use crate::ptr::{self, invalid, invalid_mut, NonNull};
1718

1819
use super::{from_raw_parts, from_raw_parts_mut};

library/core/src/slice/iter/macros.rs

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,18 @@ macro_rules! iterator {
133133
},
134134
)
135135
}
136+
137+
#[inline]
138+
fn via_indices(self) -> impl DoubleEndedIterator<Item = $elem> {
139+
let len = len!(self);
140+
let ptr = self.ptr;
141+
IndexRange::zero_to(len)
142+
.map(move |i| {
143+
// SAFETY: the loop iterates `i in 0..len`, which always
144+
// is in bounds of the slice allocation
145+
unsafe { ptr.add(i).$into_ref() }
146+
})
147+
}
136148
}
137149

138150
#[stable(feature = "rust1", since = "1.0.0")]
@@ -209,50 +221,24 @@ macro_rules! iterator {
209221
}
210222

211223
#[inline]
212-
fn fold<B, F>(self, init: B, mut f: F) -> B
224+
fn fold<B, F>(self, init: B, f: F) -> B
213225
where
214226
F: FnMut(B, Self::Item) -> B,
215227
{
216-
// this implementation consists of the following optimizations compared to the
217-
// default implementation:
218-
// - do-while loop, as is llvm's preferred loop shape,
219-
// see https://releases.llvm.org/16.0.0/docs/LoopTerminology.html#more-canonical-loops
220-
// - bumps an index instead of a pointer since the latter case inhibits
221-
// some optimizations, see #111603
222-
// - avoids Option wrapping/matching
223-
if is_empty!(self) {
224-
return init;
225-
}
226-
let mut acc = init;
227-
let mut i = 0;
228-
let len = len!(self);
229-
loop {
230-
// SAFETY: the loop iterates `i in 0..len`, which always is in bounds of
231-
// the slice allocation
232-
acc = f(acc, unsafe { & $( $mut_ )? *self.ptr.add(i).as_ptr() });
233-
// SAFETY: `i` can't overflow since it'll only reach usize::MAX if the
234-
// slice had that length, in which case we'll break out of the loop
235-
// after the increment
236-
i = unsafe { i.unchecked_add(1) };
237-
if i == len {
238-
break;
239-
}
240-
}
241-
acc
228+
// Apparently it's better to loop over indexes instead of
229+
// pointers, see #111603. Maybe it's easier on SCEV?
230+
// This also makes sure we're never doing `Option` checks.
231+
self.via_indices().fold(init, f)
242232
}
243233

244-
// We override the default implementation, which uses `try_fold`,
245-
// because this simple implementation generates less LLVM IR and is
246-
// faster to compile.
247234
#[inline]
248-
fn for_each<F>(mut self, mut f: F)
235+
fn for_each<F>(self, f: F)
249236
where
250237
Self: Sized,
251238
F: FnMut(Self::Item),
252239
{
253-
while let Some(x) = self.next() {
254-
f(x);
255-
}
240+
// See `fold`.
241+
self.via_indices().for_each(f)
256242
}
257243

258244
// We override the default implementation, which uses `try_fold`,
@@ -427,6 +413,15 @@ macro_rules! iterator {
427413
unsafe { self.pre_dec_end(advance) };
428414
NonZeroUsize::new(n - advance).map_or(Ok(()), Err)
429415
}
416+
417+
#[inline]
418+
fn rfold<B, F>(self, init: B, f: F) -> B
419+
where
420+
F: FnMut(B, Self::Item) -> B,
421+
{
422+
// See `fold`.
423+
self.via_indices().rfold(init, f)
424+
}
430425
}
431426

432427
#[stable(feature = "fused", since = "1.26.0")]

tests/codegen/slice-iter-fold.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
// compile-flags: -O
33
#![crate_type = "lib"]
44

5-
// CHECK-LABEL: @slice_fold_to_last
5+
// CHECK-LABEL: @slice_for_each_to_last =
6+
// CHECK-SAME: alias{{.*}}@slice_fold_to_last
7+
8+
// CHECK-LABEL: @slice_fold_to_last(
69
#[no_mangle]
710
pub fn slice_fold_to_last(slice: &[i32]) -> Option<&i32> {
811
// CHECK-NOT: loop
@@ -11,3 +14,20 @@ pub fn slice_fold_to_last(slice: &[i32]) -> Option<&i32> {
1114
// CHECK: ret
1215
slice.iter().fold(None, |_, i| Some(i))
1316
}
17+
18+
#[no_mangle]
19+
pub fn slice_for_each_to_last(slice: &[i32]) -> Option<&i32> {
20+
let mut last = None;
21+
slice.iter().for_each(|i| last = Some(i));
22+
last
23+
}
24+
25+
// CHECK-LABEL: @slice_rfold_to_first(
26+
#[no_mangle]
27+
pub fn slice_rfold_to_first(slice: &[i32]) -> Option<&i32> {
28+
// CHECK-NOT: loop
29+
// CHECK-NOT: br
30+
// CHECK-NOT: call
31+
// CHECK: ret
32+
slice.iter().rfold(None, |_, i| Some(i))
33+
}

0 commit comments

Comments
 (0)