66//! The specialization in this module applies to iterators in the shape of
77//! `source.adapter().adapter().adapter().collect::<Vec<U>>()`
88//! where `source` is an owning iterator obtained from [`Vec<T>`], [`Box<[T]>`][box] (by conversion to `Vec`)
9- //! or [`BinaryHeap<T>`], the adapters each consume one or more items per step
10- //! (represented by [`InPlaceIterable`]), provide transitive access to `source` (via [`SourceIter`])
11- //! and thus the underlying allocation. And finally the layouts of `T` and `U` must
12- //! have the same size and alignment , this is currently ensured via const eval instead of trait bounds
13- //! in the specialized [`SpecFromIter`] implementation.
9+ //! or [`BinaryHeap<T>`], the adapters guarantee to consume enough items per step to make room
10+ //! for the results (represented by [`InPlaceIterable`]), provide transitive access to `source`
11+ //! (via [`SourceIter`]) and thus the underlying allocation.
12+ //! And finally there are alignment and size constriants to consider , this is currently ensured via
13+ //! const eval instead of trait bounds in the specialized [`SpecFromIter`] implementation.
1414//!
1515//! [`BinaryHeap<T>`]: crate::collections::BinaryHeap
1616//! [box]: crate::boxed::Box
3535//! the step of reading a value and getting a reference to write to. Instead raw pointers must be
3636//! used on the reader and writer side.
3737//!
38- //! That writes never clobber a yet-to-be-read item is ensured by the [`InPlaceIterable`] requirements.
38+ //! That writes never clobber a yet-to-be-read items is ensured by the [`InPlaceIterable`] requirements.
3939//!
4040//! # Layout constraints
4141//!
42- //! [`Allocator`] requires that `allocate()` and `deallocate()` have matching alignment and size.
42+ //! When recycling an allocation between different types we must uphold the [`Allocator`] contract
43+ //! which means that the input and output Layouts have to "fit".
44+ //!
45+ //! To complicate things further `InPlaceIterable` supports splitting or merging items into smaller/
46+ //! larger ones to enable (de)aggregation of arrays.
47+ //!
48+ //! Ultimately each step of the iterator must free up enough *bytes* in the source to make room
49+ //! for the next output item.
50+ //! If `T` and `U` have the same size no fixup is needed.
51+ //! If `T`'s size is a multiple of `U`'s we can compensate by multiplying the capacity accordingly.
52+ //! Otherwise the input capacity (and thus layout) in bytes may not be representable by the output
53+ //! `Vec<U>`. In that case `alloc.shrink()` is used to update the allocation's layout.
54+ //!
55+ //! Alignments of `T` must be the same or larger than `U`. Since alignments are always a power
56+ //! of two _larger_ implies _is a multiple of_.
57+ //!
58+ //! See `in_place_collectible()` for the current conditions.
59+ //!
4360//! Additionally this specialization doesn't make sense for ZSTs as there is no reallocation to
4461//! avoid and it would make pointer arithmetic more difficult.
4562//!
137154//! }
138155//! vec.truncate(write_idx);
139156//! ```
157+ use crate :: alloc:: { handle_alloc_error, Global } ;
158+ use core:: alloc:: Allocator ;
159+ use core:: alloc:: Layout ;
140160use core:: iter:: { InPlaceIterable , SourceIter , TrustedRandomAccessNoCoerce } ;
141161use core:: mem:: { self , ManuallyDrop , SizedTypeProperties } ;
142- use core:: ptr:: { self } ;
162+ use core:: num:: NonZeroUsize ;
163+ use core:: ptr:: { self , NonNull } ;
143164
144165use super :: { InPlaceDrop , InPlaceDstBufDrop , SpecFromIter , SpecFromIterNested , Vec } ;
145166
146- /// Specialization marker for collecting an iterator pipeline into a Vec while reusing the
147- /// source allocation, i.e. executing the pipeline in place.
148- #[ rustc_unsafe_specialization_marker]
149- pub ( super ) trait InPlaceIterableMarker { }
167+ const fn in_place_collectible < DEST , SRC > (
168+ step_merge : Option < NonZeroUsize > ,
169+ step_expand : Option < NonZeroUsize > ,
170+ ) -> bool {
171+ if DEST :: IS_ZST || mem:: align_of :: < SRC > ( ) < mem:: align_of :: < DEST > ( ) {
172+ return false ;
173+ }
174+
175+ match ( step_merge, step_expand) {
176+ ( Some ( step_merge) , Some ( step_expand) ) => {
177+ // At least N merged source items -> at most M expanded destination items
178+ // e.g.
179+ // - 1 x [u8; 4] -> 4x u8, via flatten
180+ // - 4 x u8 -> 1x [u8; 4], via array_chunks
181+ mem:: size_of :: < SRC > ( ) * step_merge. get ( ) >= mem:: size_of :: < DEST > ( ) * step_expand. get ( )
182+ }
183+ // Fall back to other from_iter impls if an overflow occurred in the step merge/expansion
184+ // tracking.
185+ _ => false ,
186+ }
187+ }
150188
151- impl < T > InPlaceIterableMarker for T where T : InPlaceIterable { }
189+ /// This provides a shorthand for the source type since local type aliases aren't a thing.
190+ #[ rustc_specialization_trait]
191+ trait InPlaceCollect : SourceIter < Source : AsVecIntoIter > + InPlaceIterable {
192+ type Src ;
193+ }
194+
195+ impl < T > InPlaceCollect for T
196+ where
197+ T : SourceIter < Source : AsVecIntoIter > + InPlaceIterable ,
198+ {
199+ type Src = <<T as SourceIter >:: Source as AsVecIntoIter >:: Item ;
200+ }
152201
153202impl < T , I > SpecFromIter < T , I > for Vec < T >
154203where
155- I : Iterator < Item = T > + SourceIter < Source : AsVecIntoIter > + InPlaceIterableMarker ,
204+ I : Iterator < Item = T > + InPlaceCollect ,
205+ <I as SourceIter >:: Source : AsVecIntoIter ,
156206{
157207 default fn from_iter ( mut iterator : I ) -> Self {
158208 // See "Layout constraints" section in the module documentation. We rely on const
159209 // optimization here since these conditions currently cannot be expressed as trait bounds
160- if T :: IS_ZST
161- || mem:: size_of :: < T > ( )
162- != mem:: size_of :: < <<I as SourceIter >:: Source as AsVecIntoIter >:: Item > ( )
163- || mem:: align_of :: < T > ( )
164- != mem:: align_of :: < <<I as SourceIter >:: Source as AsVecIntoIter >:: Item > ( )
165- {
210+ if const { !in_place_collectible :: < T , I :: Src > ( I :: MERGE_BY , I :: EXPAND_BY ) } {
166211 // fallback to more generic implementations
167212 return SpecFromIterNested :: from_iter ( iterator) ;
168213 }
169214
170- let ( src_buf, src_ptr, dst_buf, dst_end, cap ) = unsafe {
215+ let ( src_buf, src_ptr, src_cap , mut dst_buf, dst_end, dst_cap ) = unsafe {
171216 let inner = iterator. as_inner ( ) . as_into_iter ( ) ;
172217 (
173218 inner. buf . as_ptr ( ) ,
174219 inner. ptr ,
220+ inner. cap ,
175221 inner. buf . as_ptr ( ) as * mut T ,
176222 inner. end as * const T ,
177- inner. cap ,
223+ inner. cap * mem :: size_of :: < I :: Src > ( ) / mem :: size_of :: < T > ( ) ,
178224 )
179225 } ;
180226
@@ -196,18 +242,55 @@ where
196242 }
197243
198244 // The ownership of the allocation and the new `T` values is temporarily moved into `dst_guard`.
199- // This is safe because `forget_allocation_drop_remaining` immediately forgets the allocation
245+ // This is safe because
246+ // * `forget_allocation_drop_remaining` immediately forgets the allocation
200247 // before any panic can occur in order to avoid any double free, and then proceeds to drop
201248 // any remaining values at the tail of the source.
249+ // * the shrink either panics without invalidating the allocation, aborts or
250+ // succeeds. In the last case we disarm the guard.
202251 //
203252 // Note: This access to the source wouldn't be allowed by the TrustedRandomIteratorNoCoerce
204253 // contract (used by SpecInPlaceCollect below). But see the "O(1) collect" section in the
205254 // module documentation why this is ok anyway.
206- let dst_guard = InPlaceDstBufDrop { ptr : dst_buf, len, cap } ;
255+ let dst_guard = InPlaceDstBufDrop { ptr : dst_buf, len, cap : dst_cap } ;
207256 src. forget_allocation_drop_remaining ( ) ;
257+
258+ // Adjust the allocation if the alignment didn't match or the source had a capacity in bytes
259+ // that wasn't a multiple of the destination type size.
260+ // Since the discrepancy should generally be small this should only result in some
261+ // bookkeeping updates and no memmove.
262+ if ( const {
263+ let src_sz = mem:: size_of :: < I :: Src > ( ) ;
264+ src_sz > 0 && mem:: size_of :: < T > ( ) % src_sz != 0
265+ } && src_cap * mem:: size_of :: < I :: Src > ( ) != dst_cap * mem:: size_of :: < T > ( ) )
266+ || const { mem:: align_of :: < T > ( ) != mem:: align_of :: < I :: Src > ( ) }
267+ {
268+ let alloc = Global ;
269+ unsafe {
270+ // The old allocation exists, therefore it must have a valid layout.
271+ let src_align = mem:: align_of :: < I :: Src > ( ) ;
272+ let src_size = mem:: size_of :: < I :: Src > ( ) . unchecked_mul ( src_cap) ;
273+ let old_layout = Layout :: from_size_align_unchecked ( src_size, src_align) ;
274+
275+ // The must be equal or smaller for in-place iteration to be possible
276+ // therefore the new layout must be ≤ the old one and therefore valid.
277+ let dst_align = mem:: align_of :: < T > ( ) ;
278+ let dst_size = mem:: size_of :: < T > ( ) . unchecked_mul ( dst_cap) ;
279+ let new_layout = Layout :: from_size_align_unchecked ( dst_size, dst_align) ;
280+
281+ let result = alloc. shrink (
282+ NonNull :: new_unchecked ( dst_buf as * mut u8 ) ,
283+ old_layout,
284+ new_layout,
285+ ) ;
286+ let Ok ( reallocated) = result else { handle_alloc_error ( new_layout) } ;
287+ dst_buf = reallocated. as_ptr ( ) as * mut T ;
288+ }
289+ }
290+
208291 mem:: forget ( dst_guard) ;
209292
210- let vec = unsafe { Vec :: from_raw_parts ( dst_buf, len, cap ) } ;
293+ let vec = unsafe { Vec :: from_raw_parts ( dst_buf, len, dst_cap ) } ;
211294
212295 vec
213296 }
0 commit comments