Skip to content

Commit 35a2d1c

Browse files
committed
Fix in-place collect not reallocating when necessary
1 parent df0295f commit 35a2d1c

File tree

2 files changed

+40
-7
lines changed

2 files changed

+40
-7
lines changed

library/alloc/src/vec/in_place_collect.rs

+32-7
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ const fn in_place_collectible<DEST, SRC>(
168168
step_merge: Option<NonZeroUsize>,
169169
step_expand: Option<NonZeroUsize>,
170170
) -> bool {
171-
if DEST::IS_ZST || mem::align_of::<SRC>() < mem::align_of::<DEST>() {
171+
if const { SRC::IS_ZST || DEST::IS_ZST || mem::align_of::<SRC>() < mem::align_of::<DEST>() } {
172172
return false;
173173
}
174174

@@ -186,6 +186,34 @@ const fn in_place_collectible<DEST, SRC>(
186186
}
187187
}
188188

189+
const fn needs_realloc<SRC, DEST>(src_cap: usize, dst_cap: usize) -> bool {
190+
if const { mem::align_of::<SRC>() != mem::align_of::<DEST>() } {
191+
return true;
192+
}
193+
194+
if const {
195+
let src_sz = mem::size_of::<SRC>();
196+
let dest_sz = mem::size_of::<DEST>();
197+
198+
// examples that may require reallocs unless src/dest capacities turn out to be multiples at runtime
199+
// Vec<u8> -> Vec<[u8; 4]>
200+
let dst_larger = src_sz < dest_sz;
201+
202+
// Vec<[u8; 3]> -> Vec<[u8; 2]>
203+
// dest_sz can't actually be 0 since in_place_collectible() makes this unreachable in that case.
204+
// but const eval still runs on dead branches.
205+
let src_not_multiple_of_dest = dest_sz == 0 || src_sz % dest_sz != 0;
206+
207+
dst_larger || src_not_multiple_of_dest
208+
} {
209+
return src_cap * mem::size_of::<SRC>() != dst_cap * mem::size_of::<DEST>();
210+
}
211+
212+
// Equal size + alignment won't need a realloc.
213+
// src size being an integer multiple of the dest size works too
214+
return false;
215+
}
216+
189217
/// This provides a shorthand for the source type since local type aliases aren't a thing.
190218
#[rustc_specialization_trait]
191219
trait InPlaceCollect: SourceIter<Source: AsVecIntoIter> + InPlaceIterable {
@@ -259,12 +287,7 @@ where
259287
// that wasn't a multiple of the destination type size.
260288
// Since the discrepancy should generally be small this should only result in some
261289
// 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-
{
290+
if needs_realloc::<I::Src, T>(src_cap, dst_cap) {
268291
let alloc = Global;
269292
unsafe {
270293
// The old allocation exists, therefore it must have a valid layout.
@@ -286,6 +309,8 @@ where
286309
let Ok(reallocated) = result else { handle_alloc_error(new_layout) };
287310
dst_buf = reallocated.as_ptr() as *mut T;
288311
}
312+
} else {
313+
debug_assert_eq!(src_cap * mem::size_of::<I::Src>(), dst_cap * mem::size_of::<T>());
289314
}
290315

291316
mem::forget(dst_guard);

library/alloc/tests/vec.rs

+8
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,14 @@ fn test_in_place_specialization_step_up_down() {
12131213
assert_ne!(src_bytes, sink_bytes);
12141214
assert_eq!(sink.len(), 2);
12151215

1216+
let mut src: Vec<[u8; 3]> = Vec::with_capacity(17);
1217+
src.resize( 8, [0; 3]);
1218+
let iter = src.into_iter().map(|[a, b, _]| [a, b]);
1219+
assert_in_place_trait(&iter);
1220+
let sink: Vec<[u8; 2]> = iter.collect();
1221+
assert_eq!(sink.len(), 8);
1222+
assert!(sink.capacity() <= 25);
1223+
12161224
let src = vec![[0u8; 4]; 256];
12171225
let srcptr = src.as_ptr();
12181226
let iter = src

0 commit comments

Comments
 (0)