Skip to content

Commit 5a7342c

Browse files
committed
Stop using into_iter in array::map
1 parent 50d3ba5 commit 5a7342c

File tree

4 files changed

+147
-11
lines changed

4 files changed

+147
-11
lines changed

library/core/src/array/drain.rs

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use crate::iter::TrustedLen;
2+
use crate::mem::ManuallyDrop;
3+
use crate::ptr::drop_in_place;
4+
use crate::slice;
5+
6+
// INVARIANT: It's ok to drop the remainder of the inner iterator.
7+
pub(crate) struct Drain<'a, T>(slice::IterMut<'a, T>);
8+
9+
pub(crate) fn drain_array_with<T, R, const N: usize>(
10+
array: [T; N],
11+
func: impl for<'a> FnOnce(Drain<'a, T>) -> R,
12+
) -> R {
13+
let mut array = ManuallyDrop::new(array);
14+
// SAFETY: Now that the local won't drop it, it's ok to construct the `Drain` which will.
15+
let drain = Drain(array.iter_mut());
16+
func(drain)
17+
}
18+
19+
impl<T> Drop for Drain<'_, T> {
20+
fn drop(&mut self) {
21+
// SAFETY: By the type invariant, we're allowed to drop all these.
22+
unsafe { drop_in_place(self.0.as_mut_slice()) }
23+
}
24+
}
25+
26+
impl<T> Iterator for Drain<'_, T> {
27+
type Item = T;
28+
29+
#[inline]
30+
fn next(&mut self) -> Option<T> {
31+
let p: *const T = self.0.next()?;
32+
// SAFETY: The iterator was already advanced, so we won't drop this later.
33+
Some(unsafe { p.read() })
34+
}
35+
36+
#[inline]
37+
fn size_hint(&self) -> (usize, Option<usize>) {
38+
let n = self.len();
39+
(n, Some(n))
40+
}
41+
}
42+
43+
impl<T> ExactSizeIterator for Drain<'_, T> {
44+
#[inline]
45+
fn len(&self) -> usize {
46+
self.0.len()
47+
}
48+
}
49+
50+
// SAFETY: This is a 1:1 wrapper for a slice iterator, which is also `TrustedLen`.
51+
unsafe impl<T> TrustedLen for Drain<'_, T> {}

library/core/src/array/mod.rs

+23-11
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ use crate::ops::{
1717
};
1818
use crate::slice::{Iter, IterMut};
1919

20+
mod drain;
2021
mod equality;
2122
mod iter;
2223

24+
pub(crate) use drain::drain_array_with;
25+
2326
#[stable(feature = "array_value_iter", since = "1.51.0")]
2427
pub use iter::IntoIter;
2528

@@ -513,9 +516,12 @@ impl<T, const N: usize> [T; N] {
513516
where
514517
F: FnMut(T) -> U,
515518
{
516-
// SAFETY: we know for certain that this iterator will yield exactly `N`
517-
// items.
518-
unsafe { collect_into_array_unchecked(&mut IntoIterator::into_iter(self).map(f)) }
519+
drain_array_with(self, |iter| {
520+
let mut iter = iter.map(f);
521+
// SAFETY: we know for certain that this iterator will yield exactly `N`
522+
// items.
523+
unsafe { collect_into_array_unchecked(&mut iter) }
524+
})
519525
}
520526

521527
/// A fallible function `f` applied to each element on array `self` in order to
@@ -552,9 +558,12 @@ impl<T, const N: usize> [T; N] {
552558
R: Try,
553559
R::Residual: Residual<[R::Output; N]>,
554560
{
555-
// SAFETY: we know for certain that this iterator will yield exactly `N`
556-
// items.
557-
unsafe { try_collect_into_array_unchecked(&mut IntoIterator::into_iter(self).map(f)) }
561+
drain_array_with(self, |iter| {
562+
let mut iter = iter.map(f);
563+
// SAFETY: we know for certain that this iterator will yield exactly `N`
564+
// items.
565+
unsafe { try_collect_into_array_unchecked(&mut iter) }
566+
})
558567
}
559568

560569
/// 'Zips up' two arrays into a single array of pairs.
@@ -575,11 +584,14 @@ impl<T, const N: usize> [T; N] {
575584
/// ```
576585
#[unstable(feature = "array_zip", issue = "80094")]
577586
pub fn zip<U>(self, rhs: [U; N]) -> [(T, U); N] {
578-
let mut iter = IntoIterator::into_iter(self).zip(rhs);
579-
580-
// SAFETY: we know for certain that this iterator will yield exactly `N`
581-
// items.
582-
unsafe { collect_into_array_unchecked(&mut iter) }
587+
drain_array_with(self, |lhs| {
588+
drain_array_with(rhs, |rhs| {
589+
let mut iter = crate::iter::zip(lhs, rhs);
590+
// SAFETY: we know for certain that this iterator will yield exactly `N`
591+
// items.
592+
unsafe { collect_into_array_unchecked(&mut iter) }
593+
})
594+
})
583595
}
584596

585597
/// Returns a slice containing the entire array. Equivalent to `&s[..]`.

library/core/tests/array.rs

+25
Original file line numberDiff line numberDiff line change
@@ -700,3 +700,28 @@ fn array_into_iter_rfold() {
700700
let s = it.rfold(10, |a, b| 10 * a + b);
701701
assert_eq!(s, 10432);
702702
}
703+
704+
#[cfg(not(panic = "abort"))]
705+
#[test]
706+
fn array_map_drops_unmapped_elements_on_panic() {
707+
struct DropCounter<'a>(usize, &'a AtomicUsize);
708+
impl Drop for DropCounter<'_> {
709+
fn drop(&mut self) {
710+
self.1.fetch_add(1, Ordering::SeqCst);
711+
}
712+
}
713+
714+
const MAX: usize = 11;
715+
for panic_after in 0..MAX {
716+
let counter = AtomicUsize::new(0);
717+
let a = array::from_fn::<_, 11, _>(|i| DropCounter(i, &counter));
718+
let success = std::panic::catch_unwind(|| {
719+
let _ = a.map(|x| {
720+
assert!(x.0 < panic_after);
721+
assert_eq!(counter.load(Ordering::SeqCst), x.0);
722+
});
723+
});
724+
assert!(success.is_err());
725+
assert_eq!(counter.load(Ordering::SeqCst), MAX);
726+
}
727+
}

tests/codegen/array-map.rs

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// compile-flags: -C opt-level=3 -C target-cpu=x86-64-v3 -C llvm-args=-x86-asm-syntax=intel --emit=llvm-ir,asm
2+
// no-system-llvm
3+
// only-x86_64
4+
// ignore-debug (the extra assertions get in the way)
5+
6+
#![crate_type = "lib"]
7+
#![feature(array_zip)]
8+
9+
// CHECK-LABEL: @short_integer_map
10+
#[no_mangle]
11+
pub fn short_integer_map(x: [u32; 8]) -> [u32; 8] {
12+
// CHECK: load <8 x i32>
13+
// CHECK: shl <8 x i32>
14+
// CHECK: or <8 x i32>
15+
// CHECK: store <8 x i32>
16+
x.map(|x| 2 * x + 1)
17+
}
18+
19+
// CHECK-LABEL: @short_integer_zip_map
20+
#[no_mangle]
21+
pub fn short_integer_zip_map(x: [u32; 8], y: [u32; 8]) -> [u32; 8] {
22+
// CHECK: %[[A:.+]] = load <8 x i32>
23+
// CHECK: %[[B:.+]] = load <8 x i32>
24+
// CHECK: sub <8 x i32> %[[A]], %[[B]]
25+
// CHECK: store <8 x i32>
26+
x.zip(y).map(|(x, y)| x - y)
27+
}
28+
29+
// This test is checking that LLVM can SRoA away a bunch of the overhead,
30+
// like fully moving the iterators to registers. Notably, previous implementations
31+
// of `map` ended up `alloca`ing the whole `array::IntoIterator`, meaning both a
32+
// hard-to-eliminate `memcpy` and that the iteration counts needed to be written
33+
// out to stack every iteration, even for infallible operations on `Copy` types.
34+
//
35+
// This is still imperfect, as there's more copies than would be ideal,
36+
// but hopefully work like #103830 will improve that in future,
37+
// and update this test to be stricter.
38+
//
39+
// CHECK-LABEL: @long_integer_map
40+
#[no_mangle]
41+
pub fn long_integer_map(x: [u32; 64]) -> [u32; 64] {
42+
// CHECK: start:
43+
// CHECK-NEXT: alloca [{{64|65}} x i32]
44+
// CHECK-NEXT: alloca [{{64|65}} x i32]
45+
// CHECK-NEXT: alloca %"core::mem::manually_drop::ManuallyDrop<[u32; 64]>"
46+
// CHECK-NOT: alloca
47+
x.map(|x| 2 * x + 1)
48+
}

0 commit comments

Comments
 (0)