mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-26 13:01:27 +03:00
Explicitly forget the zero remaining elements in vec::IntoIter::fold() and spec_extend().
Adds internal `vec::IntoIter::forget_remaining_elements_and_dealloc()`, which is used by `fold()` and `spec_extend()`, when those operations complete, to forget the zero remaining elements and only deallocate the allocation, ensuring that there will never be a useless loop to drop zero remaining elements when the iterator is dropped.
This commit is contained in:
@@ -78,7 +78,7 @@ impl<T, I, A: Allocator> SpecExtend<T, I> for VecDeque<T, A>
|
||||
|
||||
#[cfg(not(test))]
|
||||
impl<T, A1: Allocator, A2: Allocator> SpecExtend<T, vec::IntoIter<T, A2>> for VecDeque<T, A1> {
|
||||
fn spec_extend(&mut self, mut iterator: vec::IntoIter<T, A2>) {
|
||||
fn spec_extend(&mut self, iterator: vec::IntoIter<T, A2>) {
|
||||
let slice = iterator.as_slice();
|
||||
self.reserve(slice.len());
|
||||
|
||||
@@ -86,7 +86,7 @@ fn spec_extend(&mut self, mut iterator: vec::IntoIter<T, A2>) {
|
||||
self.copy_slice(self.to_physical_idx(self.len), slice);
|
||||
self.len += slice.len();
|
||||
}
|
||||
iterator.forget_remaining_elements();
|
||||
iterator.forget_remaining_elements_and_dealloc();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,12 +155,12 @@ impl<T, I, A: Allocator> SpecExtendFront<T, I> for VecDeque<T, A>
|
||||
#[cfg(not(test))]
|
||||
impl<T, A1: Allocator, A2: Allocator> SpecExtendFront<T, vec::IntoIter<T, A2>> for VecDeque<T, A1> {
|
||||
#[track_caller]
|
||||
fn spec_extend_front(&mut self, mut iterator: vec::IntoIter<T, A2>) {
|
||||
fn spec_extend_front(&mut self, iterator: vec::IntoIter<T, A2>) {
|
||||
let slice = iterator.as_slice();
|
||||
self.reserve(slice.len());
|
||||
// SAFETY: `slice.len()` space was just reserved and elements in the slice are forgotten after this call
|
||||
unsafe { prepend_reversed(self, slice) };
|
||||
iterator.forget_remaining_elements();
|
||||
iterator.forget_remaining_elements_and_dealloc();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,12 +170,12 @@ impl<T, A1: Allocator, A2: Allocator> SpecExtendFront<T, Rev<vec::IntoIter<T, A2
|
||||
{
|
||||
#[track_caller]
|
||||
fn spec_extend_front(&mut self, iterator: Rev<vec::IntoIter<T, A2>>) {
|
||||
let mut iterator = iterator.into_inner();
|
||||
let iterator = iterator.into_inner();
|
||||
let slice = iterator.as_slice();
|
||||
self.reserve(slice.len());
|
||||
// SAFETY: `slice.len()` space was just reserved and elements in the slice are forgotten after this call
|
||||
unsafe { prepend(self, slice) };
|
||||
iterator.forget_remaining_elements();
|
||||
iterator.forget_remaining_elements_and_dealloc();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,12 +159,51 @@ pub(super) fn forget_allocation_drop_remaining(&mut self) {
|
||||
}
|
||||
|
||||
/// Forgets to Drop the remaining elements while still allowing the backing allocation to be freed.
|
||||
///
|
||||
/// This method does not consume `self`, and leaves deallocation to `impl Drop for IntoIter`.
|
||||
/// If consuming `self` is possible, consider calling
|
||||
/// [`Self::forget_remaining_elements_and_dealloc()`] instead.
|
||||
pub(crate) fn forget_remaining_elements(&mut self) {
|
||||
// For the ZST case, it is crucial that we mutate `end` here, not `ptr`.
|
||||
// `ptr` must stay aligned, while `end` may be unaligned.
|
||||
self.end = self.ptr.as_ptr();
|
||||
}
|
||||
|
||||
/// Forgets to Drop the remaining elements and frees the backing allocation.
|
||||
/// Consuming version of [`Self::forget_remaining_elements()`].
|
||||
///
|
||||
/// This can be used in place of `drop(self)` when `self` is known to be exhausted,
|
||||
/// to avoid producing a needless `drop_in_place::<[T]>()`.
|
||||
#[inline]
|
||||
pub(crate) fn forget_remaining_elements_and_dealloc(self) {
|
||||
let mut this = ManuallyDrop::new(self);
|
||||
// SAFETY: `this` is in ManuallyDrop, so it will not be double-freed.
|
||||
unsafe {
|
||||
this.dealloc_only();
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees the allocation, without checking or dropping anything else.
|
||||
///
|
||||
/// The safe version of this method is [`Self::forget_remaining_elements_and_dealloc()`].
|
||||
/// This function exists only to share code between that method and the `impl Drop`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function must only be called with an [`IntoIter`] that is not going to be dropped
|
||||
/// or otherwise used in any way, either because it is being forgotten or because its `Drop`
|
||||
/// is already executing; otherwise a double-free will occur, and possibly a read from freed
|
||||
/// memory if there are any remaining elements.
|
||||
#[inline]
|
||||
unsafe fn dealloc_only(&mut self) {
|
||||
unsafe {
|
||||
// SAFETY: our caller promises not to touch `*self` again
|
||||
let alloc = ManuallyDrop::take(&mut self.alloc);
|
||||
// RawVec handles deallocation
|
||||
let _ = RawVec::from_nonnull_in(self.buf, self.cap, alloc);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(no_global_oom_handling))]
|
||||
#[inline]
|
||||
pub(crate) fn into_vecdeque(self) -> VecDeque<T, A> {
|
||||
@@ -328,6 +367,12 @@ fn fold<B, F>(mut self, mut accum: B, mut f: F) -> B
|
||||
accum = f(accum, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
// There are in fact no remaining elements to forget, but by doing this we can avoid
|
||||
// potentially generating a needless loop to drop the elements that cannot exist at
|
||||
// this point.
|
||||
self.forget_remaining_elements_and_dealloc();
|
||||
|
||||
accum
|
||||
}
|
||||
|
||||
@@ -500,10 +545,7 @@ fn drop(&mut self) {
|
||||
impl<T, A: Allocator> Drop for DropGuard<'_, T, A> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// `IntoIter::alloc` is not used anymore after this and will be dropped by RawVec
|
||||
let alloc = ManuallyDrop::take(&mut self.0.alloc);
|
||||
// RawVec handles deallocation
|
||||
let _ = RawVec::from_nonnull_in(self.0.buf, self.0.cap, alloc);
|
||||
self.0.dealloc_only();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ impl<T, I, A: Allocator> SpecExtend<T, I> for Vec<T, A>
|
||||
}
|
||||
|
||||
impl<T, A1: Allocator, A2: Allocator> SpecExtend<T, IntoIter<T, A2>> for Vec<T, A1> {
|
||||
fn spec_extend(&mut self, mut iterator: IntoIter<T, A2>) {
|
||||
fn spec_extend(&mut self, iterator: IntoIter<T, A2>) {
|
||||
unsafe {
|
||||
self.append_elements(iterator.as_slice() as _);
|
||||
}
|
||||
iterator.forget_remaining_elements();
|
||||
iterator.forget_remaining_elements_and_dealloc();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
//@ compile-flags: -Copt-level=3
|
||||
// Test that we can avoid generating an element dropping loop when `vec::IntoIter` is consumed.
|
||||
#![crate_type = "lib"]
|
||||
|
||||
use std::vec;
|
||||
|
||||
struct Bomb;
|
||||
impl Drop for Bomb {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
panic!("dropped")
|
||||
}
|
||||
}
|
||||
|
||||
/// Test case originally from https://users.rust-lang.org/t/unnecessary-drop-in-place-emitted-for-a-fully-consumed-intoiter/135119
|
||||
///
|
||||
/// What we are looking for is that there should be no calls to `impl Drop for Bomb`
|
||||
/// because every element is unconditionally forgotten.
|
||||
//
|
||||
// CHECK-LABEL: @vec_for_each_doesnt_drop
|
||||
#[no_mangle]
|
||||
pub fn vec_for_each_doesnt_drop(v: vec::Vec<(usize, Option<Bomb>)>) -> usize {
|
||||
// CHECK-NOT: panic
|
||||
// CHECK-NOT: drop_in_place
|
||||
// CHECK-NOT: Bomb$u20$as$u20$core..ops..drop..Drop
|
||||
let mut last = 0;
|
||||
v.into_iter().for_each(|(x, bomb)| {
|
||||
last = x;
|
||||
std::mem::forget(bomb);
|
||||
});
|
||||
last
|
||||
}
|
||||
|
||||
/// Test that does *not* get the above optimization we are expecting:
|
||||
/// it uses a normal `for` loop which calls `Iterator::next()` and then drops the iterator,
|
||||
/// and dropping the iterator drops remaining items.
|
||||
///
|
||||
/// This test exists to prove that the above CHECK-NOT is looking for the right things.
|
||||
/// However, it might start failing if LLVM figures out that there are no remaining items.
|
||||
//
|
||||
// CHECK-LABEL: @vec_for_loop
|
||||
#[no_mangle]
|
||||
pub fn vec_for_loop(v: vec::Vec<(usize, Option<Bomb>)>) -> usize {
|
||||
// CHECK: drop_in_place
|
||||
let mut last = 0;
|
||||
for (x, bomb) in v {
|
||||
last = x;
|
||||
std::mem::forget(bomb);
|
||||
}
|
||||
last
|
||||
}
|
||||
|
||||
/// Test where there still should be drops because there are no forgets.
|
||||
///
|
||||
/// This test exists to prove that the above CHECK-NOT is looking for the right things
|
||||
/// and does not say anything interesting about codegen itself.
|
||||
//
|
||||
// CHECK-LABEL: @vec_for_each_does_drop
|
||||
#[no_mangle]
|
||||
pub fn vec_for_each_does_drop(v: vec::Vec<(usize, Option<Bomb>)>) -> usize {
|
||||
// CHECK: begin_panic
|
||||
let mut last = 0;
|
||||
v.into_iter().for_each(|(x, bomb)| {
|
||||
last = x;
|
||||
});
|
||||
last
|
||||
}
|
||||
Reference in New Issue
Block a user