Skip to content

Commit 52df055

Browse files
committedFeb 5, 2023
Stop forcing array::map through an unnecessary Result
1 parent 5a7342c commit 52df055

File tree

2 files changed

+71
-60
lines changed

2 files changed

+71
-60
lines changed
 

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

+69-57
Original file line numberDiff line numberDiff line change
@@ -825,14 +825,13 @@ impl<T, const N: usize> [T; N] {
825825
/// Pulls `N` items from `iter` and returns them as an array. If the iterator
826826
/// yields fewer than `N` items, this function exhibits undefined behavior.
827827
///
828-
/// See [`try_collect_into_array`] for more information.
829-
///
830-
///
831828
/// # Safety
832829
///
833830
/// It is up to the caller to guarantee that `iter` yields at least `N` items.
834831
/// Violating this condition causes undefined behavior.
835-
unsafe fn try_collect_into_array_unchecked<I, T, R, const N: usize>(iter: &mut I) -> R::TryType
832+
unsafe fn try_collect_into_array_unchecked<I, T, R, const N: usize>(
833+
iter: &mut I,
834+
) -> ChangeOutputType<I::Item, [T; N]>
836835
where
837836
// Note: `TrustedLen` here is somewhat of an experiment. This is just an
838837
// internal function, so feel free to remove if this bound turns out to be a
@@ -845,11 +844,21 @@ where
845844
debug_assert!(N <= iter.size_hint().1.unwrap_or(usize::MAX));
846845
debug_assert!(N <= iter.size_hint().0);
847846

848-
// SAFETY: covered by the function contract.
849-
unsafe { try_collect_into_array(iter).unwrap_unchecked() }
847+
let mut array = MaybeUninit::uninit_array::<N>();
848+
let cf = try_collect_into_array_erased(iter, &mut array);
849+
match cf {
850+
ControlFlow::Break(r) => FromResidual::from_residual(r),
851+
ControlFlow::Continue(initialized) => {
852+
debug_assert_eq!(initialized, N);
853+
// SAFETY: because of our function contract, all the elements
854+
// must have been initialized.
855+
let output = unsafe { MaybeUninit::array_assume_init(array) };
856+
Try::from_output(output)
857+
}
858+
}
850859
}
851860

852-
// Infallible version of `try_collect_into_array_unchecked`.
861+
/// Infallible version of [`try_collect_into_array_unchecked`].
853862
unsafe fn collect_into_array_unchecked<I, const N: usize>(iter: &mut I) -> [I::Item; N]
854863
where
855864
I: Iterator + TrustedLen,
@@ -864,63 +873,48 @@ where
864873
}
865874
}
866875

867-
/// Pulls `N` items from `iter` and returns them as an array. If the iterator
868-
/// yields fewer than `N` items, `Err` is returned containing an iterator over
869-
/// the already yielded items.
876+
/// Rather than *returning* the array, this fills in a passed-in buffer.
877+
/// If any of the iterator elements short-circuit, it drops everything in the
878+
/// buffer and return the error. Otherwise it returns the number of items
879+
/// which were initialized in the buffer.
870880
///
871-
/// Since the iterator is passed as a mutable reference and this function calls
872-
/// `next` at most `N` times, the iterator can still be used afterwards to
873-
/// retrieve the remaining items.
881+
/// (The caller is responsible for dropping those items on success, but not
882+
/// doing that is just a leak, not UB, so this function is itself safe.)
874883
///
875-
/// If `iter.next()` panicks, all items already yielded by the iterator are
876-
/// dropped.
884+
/// This means less monomorphization, but more importantly it means that the
885+
/// returned array doesn't need to be copied into the `Result`, since returning
886+
/// the result seemed (2023-01) to cause in an extra `N + 1`-length `alloca`
887+
/// even if it's always `unwrap_unchecked` later.
877888
#[inline]
878-
fn try_collect_into_array<I, T, R, const N: usize>(
889+
fn try_collect_into_array_erased<I, T, R>(
879890
iter: &mut I,
880-
) -> Result<R::TryType, IntoIter<T, N>>
891+
buffer: &mut [MaybeUninit<T>],
892+
) -> ControlFlow<R, usize>
881893
where
882894
I: Iterator,
883895
I::Item: Try<Output = T, Residual = R>,
884-
R: Residual<[T; N]>,
885896
{
886-
if N == 0 {
887-
// SAFETY: An empty array is always inhabited and has no validity invariants.
888-
return Ok(Try::from_output(unsafe { mem::zeroed() }));
889-
}
890-
891-
let mut array = MaybeUninit::uninit_array::<N>();
892-
let mut guard = Guard { array_mut: &mut array, initialized: 0 };
897+
let n = buffer.len();
898+
let mut guard = Guard { array_mut: buffer, initialized: 0 };
893899

894-
for _ in 0..N {
900+
for _ in 0..n {
895901
match iter.next() {
896902
Some(item_rslt) => {
897-
let item = match item_rslt.branch() {
898-
ControlFlow::Break(r) => {
899-
return Ok(FromResidual::from_residual(r));
900-
}
901-
ControlFlow::Continue(elem) => elem,
902-
};
903+
let item = item_rslt.branch()?;
903904

904905
// SAFETY: `guard.initialized` starts at 0, which means push can be called
905-
// at most N times, which this loop does.
906+
// at most `n` times, which this loop does.
906907
unsafe {
907908
guard.push_unchecked(item);
908909
}
909910
}
910-
None => {
911-
let alive = 0..guard.initialized;
912-
mem::forget(guard);
913-
// SAFETY: `array` was initialized with exactly `initialized`
914-
// number of elements.
915-
return Err(unsafe { IntoIter::new_unchecked(array, alive) });
916-
}
911+
None => break,
917912
}
918913
}
919914

915+
let initialized = guard.initialized;
920916
mem::forget(guard);
921-
// SAFETY: All elements of the array were populated in the loop above.
922-
let output = unsafe { array.transpose().assume_init() };
923-
Ok(Try::from_output(output))
917+
ControlFlow::Continue(initialized)
924918
}
925919

926920
/// Panic guard for incremental initialization of arrays.
@@ -934,14 +928,14 @@ where
934928
///
935929
/// To minimize indirection fields are still pub but callers should at least use
936930
/// `push_unchecked` to signal that something unsafe is going on.
937-
pub(crate) struct Guard<'a, T, const N: usize> {
931+
pub(crate) struct Guard<'a, T> {
938932
/// The array to be initialized.
939-
pub array_mut: &'a mut [MaybeUninit<T>; N],
933+
pub array_mut: &'a mut [MaybeUninit<T>],
940934
/// The number of items that have been initialized so far.
941935
pub initialized: usize,
942936
}
943937

944-
impl<T, const N: usize> Guard<'_, T, N> {
938+
impl<T> Guard<'_, T> {
945939
/// Adds an item to the array and updates the initialized item counter.
946940
///
947941
/// # Safety
@@ -959,9 +953,9 @@ impl<T, const N: usize> Guard<'_, T, N> {
959953
}
960954
}
961955

962-
impl<T, const N: usize> Drop for Guard<'_, T, N> {
956+
impl<T> Drop for Guard<'_, T> {
963957
fn drop(&mut self) {
964-
debug_assert!(self.initialized <= N);
958+
debug_assert!(self.initialized <= self.array_mut.len());
965959

966960
// SAFETY: this slice will contain only initialized objects.
967961
unsafe {
@@ -972,15 +966,33 @@ impl<T, const N: usize> Drop for Guard<'_, T, N> {
972966
}
973967
}
974968

975-
/// Returns the next chunk of `N` items from the iterator or errors with an
976-
/// iterator over the remainder. Used for `Iterator::next_chunk`.
969+
/// Pulls `N` items from `iter` and returns them as an array. If the iterator
970+
/// yields fewer than `N` items, `Err` is returned containing an iterator over
971+
/// the already yielded items.
972+
///
973+
/// Since the iterator is passed as a mutable reference and this function calls
974+
/// `next` at most `N` times, the iterator can still be used afterwards to
975+
/// retrieve the remaining items.
976+
///
977+
/// If `iter.next()` panicks, all items already yielded by the iterator are
978+
/// dropped.
979+
///
980+
/// Used for [`Iterator::next_chunk`].
977981
#[inline]
978-
pub(crate) fn iter_next_chunk<I, const N: usize>(
979-
iter: &mut I,
980-
) -> Result<[I::Item; N], IntoIter<I::Item, N>>
981-
where
982-
I: Iterator,
983-
{
982+
pub(crate) fn iter_next_chunk<T, const N: usize>(
983+
iter: &mut impl Iterator<Item = T>,
984+
) -> Result<[T; N], IntoIter<T, N>> {
984985
let mut map = iter.map(NeverShortCircuit);
985-
try_collect_into_array(&mut map).map(|NeverShortCircuit(arr)| arr)
986+
let mut array = MaybeUninit::uninit_array::<N>();
987+
let ControlFlow::Continue(initialized) = try_collect_into_array_erased(&mut map, &mut array);
988+
if initialized == N {
989+
// SAFETY: All elements of the array were populated.
990+
let output = unsafe { MaybeUninit::array_assume_init(array) };
991+
Ok(output)
992+
} else {
993+
let alive = 0..initialized;
994+
// SAFETY: `array` was initialized with exactly `initialized`
995+
// number of elements.
996+
return Err(unsafe { IntoIter::new_unchecked(array, alive) });
997+
}
986998
}

‎tests/codegen/array-map.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// compile-flags: -C opt-level=3 -C target-cpu=x86-64-v3 -C llvm-args=-x86-asm-syntax=intel --emit=llvm-ir,asm
1+
// compile-flags: -C opt-level=3 -C target-cpu=x86-64-v3
22
// no-system-llvm
33
// only-x86_64
44
// ignore-debug (the extra assertions get in the way)
@@ -40,8 +40,7 @@ pub fn short_integer_zip_map(x: [u32; 8], y: [u32; 8]) -> [u32; 8] {
4040
#[no_mangle]
4141
pub fn long_integer_map(x: [u32; 64]) -> [u32; 64] {
4242
// CHECK: start:
43-
// CHECK-NEXT: alloca [{{64|65}} x i32]
44-
// CHECK-NEXT: alloca [{{64|65}} x i32]
43+
// CHECK-NEXT: alloca [64 x i32]
4544
// CHECK-NEXT: alloca %"core::mem::manually_drop::ManuallyDrop<[u32; 64]>"
4645
// CHECK-NOT: alloca
4746
x.map(|x| 2 * x + 1)

0 commit comments

Comments
 (0)
Please sign in to comment.