1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Freeze a transient builder into arena-owned `Rc`, `Arc`, or `Box`
//! slices.
use core::mem::ManuallyDrop;
use core::ptr::NonNull;
use allocator_api2::alloc::{AllocError, Allocator};
use super::Vec;
use crate::arc::Arc;
use crate::r#box::Box;
use crate::internal::drop_list::{drop_shim_slice, noop_drop_shim};
use crate::rc::Rc;
impl<T, A: Allocator + Clone> Vec<'_, T, A> {
/// Freeze into an [`Rc<[T], A>`](crate::Rc).
///
/// **O(1)** when possible: the existing buffer is handed to the
/// `Rc` directly (no copy, no fresh allocation). Falls back to a
/// copy when the in-place transfer is impossible (zero-sized
/// elements, an unallocated builder, or a `T: Drop` payload that
/// is too large or whose chunk has no remaining slot for the
/// trailing slice drop entry).
///
/// If the buffer's tail still sits at the chunk's bump cursor (the
/// common case when no other allocation interleaved), the unused
/// tail (`cap - len`) is returned to the cursor so subsequent
/// allocations pack adjacently.
///
/// # Panics
///
/// Panics if the underlying allocator fails on the copy fallback,
/// or if `T: Drop` and `align_of::<T>() >= 32 KiB` (alignments at
/// or above half the chunk size cannot be safely dropped through a
/// smart pointer; see the crate-level docs).
#[must_use]
// EQUIVALENCE: the `u16::MAX` guard cannot fire on an in-place-eligible
// buffer; `len > 0` to `>=` only installs a zero-length drop entry; and
// `cap > len` to `>=` only changes a zero-byte reclaim.
#[cfg_attr(test, mutants::skip)]
pub fn into_arena_rc(self) -> Rc<[T], A> {
// Copy when there is no transferable buffer or the slice-drop entry
// cannot encode this length.
let elem_size = core::mem::size_of::<T>();
let needs_drop = core::mem::needs_drop::<T>();
if elem_size == 0 || self.cap == 0 || (needs_drop && self.len > u16::MAX as usize) {
return Self::into_arena_rc_copy(self);
}
// The Vec's chunk ref becomes the Rc's chunk ref; suppress Vec drop.
let me = ManuallyDrop::new(self);
let len = me.len;
let cap = me.cap;
let data = me.data;
let arena = me.arena;
if needs_drop && len > 0 {
// Install a slice drop entry in-place; otherwise fall back to copy.
// SAFETY: `data` was returned by an arena allocation, and
// the buffer holds exactly `len` initialized `T`s.
let installed = unsafe {
arena.try_install_slice_drop_entry(
data.cast::<u8>(),
drop_shim_slice::<T>,
u16::try_from(len).expect("guarded by len > u16::MAX check above"),
)
};
if !installed {
// Restore the Vec unchanged for the copy fallback.
let restored = ManuallyDrop::into_inner(me);
return Self::into_arena_rc_copy(restored);
}
}
// Best-effort reclaim of any unused tail space.
if cap > len {
let reclaim_bytes = (cap - len) * elem_size;
// SAFETY: `data + cap` is one past the buffer end, within
// (or one past) the original allocation.
let buffer_end = unsafe { data.as_ptr().add(cap).cast::<u8>() };
// SAFETY: we computed `buffer_end = data + cap*elem_size`
// from a live arena allocation; reclaim_bytes is the
// matching unused-tail size.
let _ = unsafe { arena.try_shrink_at_cursor(buffer_end, reclaim_bytes) };
}
// Rewrap the existing buffer as `Rc<[T]>`.
let fat = core::ptr::slice_from_raw_parts_mut(data.as_ptr(), len);
// SAFETY: `fat` is non-null, the chunk ref now belongs to the Rc, and
// any installed slice drop entry will run exactly once.
unsafe { Rc::from_value_ptr(NonNull::new_unchecked(fat)) }
}
/// Copy fallback for `into_arena_rc`.
#[cold]
#[inline(never)]
fn into_arena_rc_copy(self) -> Rc<[T], A> {
let mut me = ManuallyDrop::new(self);
let len = me.len;
let data = me.data;
let cap = me.cap;
let arena = me.arena;
let consumed_cell: core::cell::Cell<usize> = core::cell::Cell::new(0);
let result = arena.try_alloc_slice_fill_with_rc(len, |_| {
let idx = consumed_cell.get();
// SAFETY: `idx < len` for each call, and each element is read exactly once.
let value = unsafe { data.as_ptr().add(idx).read() };
consumed_cell.set(idx + 1);
value
});
match result {
Ok(rc) => {
me.len = 0;
Self::deallocate_buffer(arena, data, cap);
rc
}
Err(_) => {
// Move any surviving tail down so `ManuallyDrop::drop` only
// sees live elements.
Self::cleanup_after_partial_move(&mut me, consumed_cell.get());
// SAFETY: `me.len` was updated to the remaining live count.
unsafe { ManuallyDrop::drop(&mut me) };
panic!("multitude: allocator returned AllocError");
}
}
}
/// Freeze into an [`Arc<[T], A>`](crate::Arc).
///
/// The contents are moved into a fresh shared-flavor arena
/// allocation. Unlike [`Self::into_arena_rc`], no in-place fast
/// path is available because the builder's buffer lives in a
/// local (single-thread) chunk, while `Arc<[T]>` must reference a
/// shared-flavor chunk.
///
/// # Panics
///
/// Panics if the underlying allocator fails.
#[must_use]
pub fn into_arena_arc(self) -> Arc<[T], A>
where
T: Send + Sync,
A: Send + Sync,
{
match self.try_into_arena_arc() {
Ok(a) => a,
Err(_) => panic!("multitude: allocator returned AllocError"),
}
}
/// Fallible variant of [`Self::into_arena_arc`].
///
/// # Errors
///
/// Returns [`AllocError`] if the backing shared-flavor allocation
/// fails. On error, `self` is consumed and any elements remaining
/// after a partial move are dropped before this function returns.
pub fn try_into_arena_arc(self) -> Result<Arc<[T], A>, AllocError>
where
T: Send + Sync,
A: Send + Sync,
{
let mut me = ManuallyDrop::new(self);
let len = me.len;
let data = me.data;
let cap = me.cap;
let arena = me.arena;
// Track how many elements the fill closure has already moved.
let consumed_cell: core::cell::Cell<usize> = core::cell::Cell::new(0);
let result = arena.try_alloc_slice_fill_with_arc(len, |_| {
let idx = consumed_cell.get();
// SAFETY: `idx < len` for each call (the helper invokes
// the closure exactly `len` times on success); each element
// is read exactly once.
let value = unsafe { data.as_ptr().add(idx).read() };
consumed_cell.set(idx + 1);
value
});
match result {
Ok(arc) => {
// Ownership fully moved into the new allocation.
me.len = 0;
Self::deallocate_buffer(arena, data, cap);
Ok(arc)
}
Err(e) => {
// Move any surviving tail down so `ManuallyDrop::drop` only
// sees live elements.
Self::cleanup_after_partial_move(&mut me, consumed_cell.get());
// SAFETY: `me.len` was updated to the remaining live count.
unsafe { ManuallyDrop::drop(&mut me) };
Err(e)
}
}
}
/// Shift a partially moved tail down so Vec drop only sees live elements.
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg_attr(test, mutants::skip)] // Current callers fail before consuming elements; keep the future panic-safety path covered.
fn cleanup_after_partial_move(me: &mut ManuallyDrop<Self>, consumed: usize) {
let original_len = me.len;
let tail_len = original_len.saturating_sub(consumed);
if tail_len > 0 && consumed > 0 {
// SAFETY: both ranges lie within the same allocation;
// `ptr::copy` handles overlap.
unsafe {
let base = me.data.as_ptr();
core::ptr::copy(base.add(consumed), base, tail_len);
}
}
me.len = tail_len;
}
/// Freeze into a [`Box<[T], A>`](crate::Box).
///
/// **O(1)**: the existing buffer is handed to the `Box` directly.
/// `Box<[T]>`'s `Drop` runs `drop_in_place::<[T]>` over the slice
/// (using its fat-pointer length) when the box is dropped, so no
/// trailing chunk drop entry is needed regardless of `T: Drop`.
///
/// If the buffer's tail still sits at the chunk's bump cursor, the
/// unused tail (`cap - len`) is returned to the cursor.
///
/// # Panics
///
/// Panics if the underlying allocator fails on the copy fallback
/// for the ZST / empty-builder cases.
#[must_use]
// EQUIVALENCE: the extra `||` cases only install noop or empty drop
// entries; the `u16::MAX` guard cannot fire on an in-place-eligible
// buffer; and `cap > len` to `>=` only changes a zero-byte reclaim.
#[cfg_attr(test, mutants::skip)]
pub fn into_arena_box(self) -> Box<[T], A> {
let elem_size = core::mem::size_of::<T>();
let needs_drop = core::mem::needs_drop::<T>();
// Copy when there is no transferable buffer or the slice-drop entry
// cannot encode this length. Keep the `u16::MAX` guard as defense in
// depth even though the in-place path cannot currently reach it.
if elem_size == 0 || self.cap == 0 || (needs_drop && self.len > u16::MAX as usize) {
return Self::into_arena_box_copy(self);
}
// `Vec -> Box -> Rc` for `T: Drop` needs a preinstalled noop entry so
// later retargeting can switch it to `drop_shim_slice`. If that cannot
// be installed here, fall back to the normal copy path.
if needs_drop && self.len > 0 {
let me = ManuallyDrop::new(self);
let arena = me.arena;
let data = me.data;
// SAFETY: `data` was returned by an arena allocation and
// the Vec holds the chunk's +1.
let installed = unsafe {
arena.try_install_slice_drop_entry(
data.cast::<u8>(),
noop_drop_shim,
u16::try_from(me.len).expect("guarded by len > u16::MAX check above"),
)
};
if !installed {
let restored = ManuallyDrop::into_inner(me);
return Self::into_arena_box_copy(restored);
}
let len = me.len;
let cap = me.cap;
if cap > len {
let reclaim_bytes = (cap - len) * elem_size;
// SAFETY: `data + cap` is one past the buffer end.
let buffer_end = unsafe { data.as_ptr().add(cap).cast::<u8>() };
// SAFETY: see `into_arena_rc`.
let _ = unsafe { arena.try_shrink_at_cursor(buffer_end, reclaim_bytes) };
}
let fat = core::ptr::slice_from_raw_parts_mut(data.as_ptr(), len);
// SAFETY: `fat` is non-null; the chunk +1 the Vec held now
// belongs to the Box.
return unsafe { Box::from_raw_unsized(NonNull::new_unchecked(fat)) };
}
let me = ManuallyDrop::new(self);
let len = me.len;
let cap = me.cap;
let data = me.data;
let arena = me.arena;
if cap > len {
let reclaim_bytes = (cap - len) * elem_size;
// SAFETY: `data + cap` is one past the buffer end.
let buffer_end = unsafe { data.as_ptr().add(cap).cast::<u8>() };
// SAFETY: see `into_arena_rc`.
let _ = unsafe { arena.try_shrink_at_cursor(buffer_end, reclaim_bytes) };
}
// Keep `me` alive only to suppress Vec drop; the chunk ref transfers.
let _ = me;
let fat = core::ptr::slice_from_raw_parts_mut(data.as_ptr(), len);
// SAFETY: `fat` is non-null; the chunk +1 the Vec held now
// belongs to the Box. `Box<[T]>`'s `Drop` will call
// `drop_in_place::<[T]>` over the slice, dropping each `T`.
unsafe { Box::from_raw_unsized(NonNull::new_unchecked(fat)) }
}
/// Copy fallback for `into_arena_box` (ZST / empty-builder cases).
#[cold]
#[inline(never)]
// EQUIVALENCE: this fallback only runs for ZSTs or empty builders, so
// changing `idx + 1` cannot affect observable behavior.
#[cfg_attr(test, mutants::skip)]
fn into_arena_box_copy(self) -> Box<[T], A> {
let mut me = ManuallyDrop::new(self);
let len = me.len;
let data = me.data;
let cap = me.cap;
let arena = me.arena;
let consumed_cell: core::cell::Cell<usize> = core::cell::Cell::new(0);
let result = arena.try_alloc_slice_fill_with_box(len, |_| {
let idx = consumed_cell.get();
// SAFETY: `idx < len` for each call, and each element is read exactly once.
let value = unsafe { data.as_ptr().add(idx).read() };
consumed_cell.set(idx + 1);
value
});
match result {
Ok(b) => {
me.len = 0;
Self::deallocate_buffer(arena, data, cap);
b
}
Err(_) => {
Self::cleanup_after_partial_move(&mut me, consumed_cell.get());
// SAFETY: `me.len` was updated to the remaining live count.
unsafe { ManuallyDrop::drop(&mut me) };
panic!("multitude: allocator returned AllocError");
}
}
}
}