Skip to content

Commit f8141a8

Browse files
committed
Add in-place optimization for array map
1 parent c16d52d commit f8141a8

File tree

3 files changed

+107
-18
lines changed

3 files changed

+107
-18
lines changed

library/core/src/array/mod.rs

+64-3
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,68 @@ impl<T, const N: usize> [T; N] {
429429
where
430430
F: FnMut(T) -> U,
431431
{
432-
use crate::mem::MaybeUninit;
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
481+
unsafe {
482+
let v = f(src_dst[i].src.read());
483+
src_dst[i].dst = ManuallyDrop::new(v);
484+
}
485+
guard.initialized += 1;
486+
}
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) };
492+
}
493+
433494
struct Guard<T, const N: usize> {
434495
dst: *mut T,
435496
initialized: usize,
@@ -457,10 +518,10 @@ impl<T, const N: usize> [T; N] {
457518
}
458519
// FIXME: Convert to crate::mem::transmute once it works with generics.
459520
// unsafe { crate::mem::transmute::<[MaybeUninit<U>; N], [U; N]>(dst) }
460-
crate::mem::forget(guard);
521+
forget(guard);
461522
// SAFETY: At this point we've properly initialized the whole array
462523
// and we just need to cast it to the correct type.
463-
unsafe { crate::mem::transmute_copy::<_, [U; N]>(&dst) }
524+
unsafe { transmute_copy::<_, [U; N]>(&dst) }
464525
}
465526

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

library/core/tests/array.rs

+42-15
Original file line numberDiff line numberDiff line change
@@ -323,26 +323,53 @@ fn array_map() {
323323
fn array_map_drop_safety() {
324324
use core::sync::atomic::AtomicUsize;
325325
use core::sync::atomic::Ordering;
326-
static DROPPED: AtomicUsize = AtomicUsize::new(0);
327-
struct DropCounter;
328-
impl Drop for DropCounter {
326+
static DROPPED: [AtomicUsize; 3] =
327+
[AtomicUsize::new(0), AtomicUsize::new(0), AtomicUsize::new(0)];
328+
struct DropCounter<const N: usize>;
329+
impl<const N: usize> Drop for DropCounter<N> {
329330
fn drop(&mut self) {
330-
DROPPED.fetch_add(1, Ordering::SeqCst);
331+
DROPPED[N].fetch_add(1, Ordering::SeqCst);
331332
}
332333
}
333334

334-
let num_to_create = 5;
335-
let success = std::panic::catch_unwind(|| {
336-
let items = [0; 10];
337-
let mut nth = 0;
338-
items.map(|_| {
339-
assert!(nth < num_to_create);
340-
nth += 1;
341-
DropCounter
335+
{
336+
let num_to_create = 5;
337+
let success = std::panic::catch_unwind(|| {
338+
let items = [0; 10];
339+
let mut nth = 0;
340+
items.map(|_| {
341+
assert!(nth < num_to_create);
342+
nth += 1;
343+
DropCounter::<0>
344+
});
345+
});
346+
assert!(success.is_err());
347+
assert_eq!(DROPPED[0].load(Ordering::SeqCst), num_to_create);
348+
}
349+
350+
{
351+
assert_eq!(DROPPED[1].load(Ordering::SeqCst), 0);
352+
let num_to_create = 3;
353+
const TOTAL: usize = 5;
354+
let success = std::panic::catch_unwind(|| {
355+
let items: [DropCounter<1>; TOTAL] = [
356+
DropCounter::<1>,
357+
DropCounter::<1>,
358+
DropCounter::<1>,
359+
DropCounter::<1>,
360+
DropCounter::<1>,
361+
];
362+
let mut nth = 0;
363+
items.map(|_| {
364+
assert!(nth < num_to_create);
365+
nth += 1;
366+
DropCounter::<2>
367+
});
342368
});
343-
});
344-
assert!(success.is_err());
345-
assert_eq!(DROPPED.load(Ordering::SeqCst), num_to_create);
369+
assert!(success.is_err());
370+
assert_eq!(DROPPED[2].load(Ordering::SeqCst), num_to_create);
371+
assert_eq!(DROPPED[1].load(Ordering::SeqCst), TOTAL);
372+
}
346373
panic!("test succeeded")
347374
}
348375

library/core/tests/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#![feature(raw)]
3333
#![feature(sort_internals)]
3434
#![feature(slice_partition_at_index)]
35+
#![feature(min_const_generics)]
3536
#![feature(min_specialization)]
3637
#![feature(step_trait)]
3738
#![feature(step_trait_ext)]

0 commit comments

Comments
 (0)