Skip to content

Commit fd4ab51

Browse files
committed
Update w/ suggestion
This instead changes the code to use one union type for the constructed array, which basically rms the numbers of moves that LLVM has to do as well better mimicking C-like ptrs allowing LLVM to optimize it better. I also updated the test to basically check for NRVO optimizations
1 parent c0559d4 commit fd4ab51

File tree

2 files changed

+60
-94
lines changed

2 files changed

+60
-94
lines changed

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

+43-84
Original file line numberDiff line numberDiff line change
@@ -425,103 +425,62 @@ impl<T, const N: usize> [T; N] {
425425
/// assert_eq!(y, [6, 9, 3, 3]);
426426
/// ```
427427
#[unstable(feature = "array_map", issue = "75243")]
428-
pub fn map<F, U>(self, mut f: F) -> [U; N]
428+
pub fn map<F, U>(self, f: F) -> [U; N]
429429
where
430430
F: FnMut(T) -> U,
431431
{
432-
use crate::mem::{align_of, forget, size_of, transmute_copy, ManuallyDrop, MaybeUninit};
433-
434-
if align_of::<T>() == align_of::<U>() && size_of::<T>() == size_of::<U>() {
435-
// this branch allows reuse of the original array as a backing store
436-
// kind of. As written with no compiler optimizations, transmute copy will
437-
// still require 2 copies of the original array, but when it can be converted to
438-
// transmute, this will require 0 copies.
439-
union Translated<T, U> {
440-
src: MaybeUninit<T>,
441-
dst: ManuallyDrop<U>,
442-
};
443-
struct Guard<T, U, const N: usize> {
444-
data: *mut [Translated<T, U>; N],
445-
initialized: usize,
446-
}
447-
impl<T, U, const N: usize> Drop for Guard<T, U, N> {
448-
fn drop(&mut self) {
449-
debug_assert!(self.initialized < N);
450-
let initialized_part =
451-
crate::ptr::slice_from_raw_parts_mut(self.data as *mut U, self.initialized);
452-
// SAFETY:
453-
// since we read from the element at initialized then panicked,
454-
// we have to skip over it to not double drop.
455-
let todo_ptr = unsafe { self.data.add(self.initialized + 1) as *mut T };
456-
let todo_part =
457-
crate::ptr::slice_from_raw_parts_mut(todo_ptr, N - self.initialized - 1);
458-
// SAFETY:
459-
// Have to remove both the initialized and not yet reached items.
460-
unsafe {
461-
crate::ptr::drop_in_place(initialized_part);
462-
crate::ptr::drop_in_place(todo_part);
463-
}
464-
}
465-
}
466-
// SAFETY:
467-
// Since we know that T & U have the same size and alignment we can safely transmute
468-
// between them here
469-
let mut src_dst = unsafe { transmute_copy::<_, [Translated<T, U>; N]>(&self) };
470-
471-
let mut guard: Guard<T, U, N> = Guard { data: &mut src_dst, initialized: 0 };
472-
// Need to forget self now because the guard is responsible for dropping the items
473-
forget(self);
474-
for i in 0..N {
475-
// SAFETY:
476-
// All items prior to `i` are the `dst` variant.
477-
// In order to convert `i` from src to dst, we take it from `MaybeUninit`,
478-
// leaving uninitialized in its place, and set the destination as
479-
// ManuallyDrop::new(..), and implicitly know that it will be a `dst` variant
480-
// from where
432+
use crate::mem::{forget, ManuallyDrop, MaybeUninit};
433+
use crate::ptr;
434+
435+
union MaybeUninitArray<T, const N: usize> {
436+
none: (),
437+
partial: ManuallyDrop<[MaybeUninit<T>; N]>,
438+
complete: ManuallyDrop<[T; N]>,
439+
}
440+
struct MapGuard<'a, T, const N: usize> {
441+
arr: &'a mut MaybeUninitArray<T, N>,
442+
len: usize,
443+
}
444+
impl<'a, T, const N: usize> MapGuard<'a, T, N> {
445+
fn push(&mut self, value: T) {
446+
// SAFETY: Since we know the input size is N, and the output is N,
447+
// this will never exceed the capacity, and MaybeUninit is always in the
448+
// structure of an array.
481449
unsafe {
482-
let v = f(src_dst[i].src.assume_init_read());
483-
src_dst[i].dst = ManuallyDrop::new(v);
450+
self.arr.partial[self.len].write(value);
451+
self.len += 1;
484452
}
485-
guard.initialized += 1;
486453
}
487-
forget(guard);
488-
// SAFETY:
489-
// At this point all the items have been initialized and are in `dst` discriminant.
490-
// We can switch them over to being of type `U`.
491-
return unsafe { transmute_copy::<_, [U; N]>(&src_dst) };
492454
}
493-
494-
struct Guard<T, const N: usize> {
495-
dst: *mut T,
496-
initialized: usize,
497-
}
498-
499-
impl<T, const N: usize> Drop for Guard<T, N> {
455+
impl<'a, T, const N: usize> Drop for MapGuard<'a, T, N> {
500456
fn drop(&mut self) {
501-
debug_assert!(self.initialized <= N);
502-
503-
let initialized_part =
504-
crate::ptr::slice_from_raw_parts_mut(self.dst, self.initialized);
505-
// SAFETY: this raw slice will contain only initialized objects
506-
// that's why, it is allowed to drop it.
457+
//debug_assert!(self.len <= N);
458+
// SAFETY: already pushed `len` elements, but need to drop them now that
459+
// `f` panicked.
507460
unsafe {
508-
crate::ptr::drop_in_place(initialized_part);
461+
let p: *mut MaybeUninit<T> = self.arr.partial.as_mut_ptr();
462+
let slice: *mut [T] = ptr::slice_from_raw_parts_mut(p.cast(), self.len);
463+
ptr::drop_in_place(slice);
509464
}
510465
}
511466
}
512-
let mut dst = MaybeUninit::uninit_array::<N>();
513-
let mut guard: Guard<U, N> =
514-
Guard { dst: MaybeUninit::slice_as_mut_ptr(&mut dst), initialized: 0 };
515-
for (src, dst) in IntoIter::new(self).zip(&mut dst) {
516-
dst.write(f(src));
517-
guard.initialized += 1;
467+
468+
fn map_guard_fn<T, const N: usize>(
469+
buffer: &mut MaybeUninitArray<T, N>,
470+
iter: impl Iterator<Item = T>,
471+
) {
472+
let mut guard = MapGuard { arr: buffer, len: 0 };
473+
for v in iter {
474+
guard.push(v);
475+
}
476+
forget(guard);
518477
}
519-
// FIXME: Convert to crate::mem::transmute once it works with generics.
520-
// unsafe { crate::mem::transmute::<[MaybeUninit<U>; N], [U; N]>(dst) }
521-
forget(guard);
522-
// SAFETY: At this point we've properly initialized the whole array
523-
// and we just need to cast it to the correct type.
524-
unsafe { transmute_copy::<_, [U; N]>(&dst) }
478+
479+
let mut buffer = MaybeUninitArray::<U, N> { none: () };
480+
map_guard_fn(&mut buffer, IntoIter::new(self).map(f));
481+
// SAFETY: all elements have successfully initialized, don't run guard's drop code
482+
// and take completed buffer out of MaybeUninitArray.
483+
unsafe { ManuallyDrop::into_inner(buffer.complete) }
525484
}
526485

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

Diff for: src/test/codegen/array-map.rs

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1-
// compile-flags: -O
2-
1+
// compile-flags: -C opt-level=3 -Zmir-opt-level=3
32
#![crate_type = "lib"]
43
#![feature(array_map)]
54

6-
// CHECK-LABEL: @array_inc
7-
// CHECK-NOT: alloca
5+
const SIZE: usize = 4;
6+
7+
// CHECK-LABEL: @array_cast_to_float
88
#[no_mangle]
9-
pub fn array_inc(x: [u8; 1000]) -> [u8; 1000] {
10-
x.map(|v| v + 1)
9+
pub fn array_cast_to_float(x: [u32; SIZE]) -> [f32; SIZE] {
10+
// CHECK: cast
11+
// CHECK: @llvm.memcpy
12+
// CHECK: ret
13+
// CHECK-EMPTY
14+
x.map(|v| v as f32)
1115
}
1216

13-
// CHECK-LABEL: @array_inc_cast
14-
// CHECK: alloca
17+
// CHECK-LABEL: @array_cast_to_u64
1518
#[no_mangle]
16-
pub fn array_inc_cast(x: [u8; 1000]) -> [u16; 1000] {
17-
x.map(|v| v as u16 + 1)
19+
pub fn array_cast_to_u64(x: [u32; SIZE]) -> [u64; SIZE] {
20+
// CHECK: cast
21+
// CHECK: @llvm.memcpy
22+
// CHECK: ret
23+
// CHECK-EMPTY
24+
x.map(|v| v as u64)
1825
}

0 commit comments

Comments
 (0)