mnemosyne_arena/scratch/aligned_vec/storage.rs
1//! The buffer itself: its layout, how it is created, and how it grows.
2//!
3//! The operations that change its length, the byte views over it, and the
4//! traits it implements each live beside this. The growth helpers are
5//! `pub(super)` so those siblings reach them through the parent.
6
7use super::ScratchElement;
8use alloc::vec::Vec;
9use core::marker::PhantomData;
10
11/// A growable buffer with guaranteed byte alignment for SIMD operations.
12pub struct AlignedVec<T: ScratchElement> {
13 pub(super) ptr: *mut T,
14 pub(super) len: usize,
15 pub(super) capacity: usize,
16 pub(super) _phantom: PhantomData<T>,
17}
18
19impl<T: ScratchElement> AlignedVec<T> {
20 /// Creates a dangling sentinel with zero capacity.
21 #[inline]
22 pub const fn dangling() -> Self {
23 Self {
24 ptr: core::ptr::NonNull::dangling().as_ptr(),
25 len: 0,
26 capacity: 0,
27 _phantom: PhantomData,
28 }
29 }
30
31 /// Creates a new `AlignedVec` with the given initial capacity.
32 #[inline]
33 pub fn with_capacity(capacity: usize) -> Self {
34 if capacity == 0 {
35 return Self::dangling();
36 }
37 let layout = Self::layout_for(capacity);
38 // SAFETY: `capacity != 0` here and `layout_for` clamps the byte size to
39 // at least 1, so `layout` is a valid non-zero-size layout for the global
40 // allocator. The returned pointer is null-checked immediately below
41 // before it is stored or dereferenced.
42 let ptr = unsafe { alloc::alloc::alloc(layout) } as *mut T;
43 if ptr.is_null() {
44 alloc::alloc::handle_alloc_error(layout);
45 }
46 Self {
47 ptr,
48 len: 0,
49 capacity,
50 _phantom: PhantomData,
51 }
52 }
53
54 /// Returns a mutable slice of the initialized elements.
55 #[inline]
56 pub fn as_mut_slice(&mut self) -> &mut [T] {
57 // SAFETY: `self.ptr` addresses an allocation of `self.capacity >= self.len`
58 // elements, and `[0, self.len)` is fully initialized (`with_capacity`
59 // starts `len` at 0; `ensure_len` zero-initializes every newly exposed
60 // element before advancing `len`). `T: ScratchElement` is `Copy`/POD, so
61 // the initialized bytes form valid `T` values. `&mut self` proves
62 // exclusive access for the slice's lifetime.
63 unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
64 }
65
66 /// Returns a shared slice of the initialized elements.
67 #[inline]
68 pub fn as_slice(&self) -> &[T] {
69 // SAFETY: same validity argument as `as_mut_slice` — `[0, self.len)` is
70 // initialized POD `T`. `&self` precludes concurrent mutation for the
71 // slice's lifetime.
72 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
73 }
74
75 /// Number of initialized elements: the prefix `as_slice` exposes.
76 #[inline]
77 pub fn len(&self) -> usize {
78 self.len
79 }
80
81 /// Whether no element is initialized (`len() == 0`).
82 #[inline]
83 pub fn is_empty(&self) -> bool {
84 self.len == 0
85 }
86
87 /// Elements the current allocation can hold before `ensure_len` must
88 /// reallocate; never less than `len()`.
89 #[inline]
90 pub fn capacity(&self) -> usize {
91 self.capacity
92 }
93
94 /// Grows the allocation so `additional` more elements fit without another
95 /// reallocation. Leaves `len` and the buffer contents alone.
96 #[inline]
97 pub(super) fn reserve(&mut self, additional: usize) {
98 let needed = self.len.saturating_add(additional);
99 if needed > self.capacity {
100 self.grow_geometric(needed);
101 }
102 }
103
104 /// Sets the initialized length without writing the newly exposed range.
105 ///
106 /// # Safety
107 ///
108 /// `new_len` must not exceed [`Self::capacity`], and every element in the
109 /// newly exposed range must be initialized before a safe read observes it.
110 #[inline]
111 pub unsafe fn set_len_unchecked(&mut self, new_len: usize) {
112 debug_assert!(
113 new_len <= self.capacity,
114 "set_len_unchecked: {new_len} > capacity {}",
115 self.capacity
116 );
117 self.len = new_len;
118 }
119
120 /// Raw pointer to the start of the aligned allocation. Valid for
121 /// `capacity()` elements, of which the first `len()` are initialized;
122 /// writes past `len()` do not extend the initialized prefix.
123 #[inline]
124 pub fn as_mut_ptr(&mut self) -> *mut T {
125 self.ptr
126 }
127
128 /// Returns a raw pointer to the start of the initialized slice.
129 ///
130 /// Equivalent to `self.as_slice().as_ptr()`. Safe to call on shared
131 /// references; the pointer is valid for `len()` elements.
132 #[inline]
133 pub fn as_ptr(&self) -> *const T {
134 self.ptr
135 }
136
137 /// Returns the pointer range `as_ptr()..as_ptr().add(len())` as a
138 /// `core::ops::Range<*const T>`.
139 ///
140 /// Useful for bounds checks and FFI handoffs that expect a pointer pair
141 /// rather than a fat pointer.
142 #[inline]
143 pub fn as_ptr_range(&self) -> core::ops::Range<*const T> {
144 // SAFETY: `self.ptr.add(self.len)` stays within (or one-past-end of)
145 // the allocation — `capacity >= len`.
146 let start = self.ptr as *const T;
147 // SAFETY: same argument as `as_slice`.
148 let end = unsafe { start.add(self.len) };
149 start..end
150 }
151
152 /// Mutable counterpart of [`as_ptr_range`][Self::as_ptr_range].
153 ///
154 /// Returns `as_mut_ptr()..as_mut_ptr().add(len())`.
155 #[inline]
156 pub fn as_mut_ptr_range(&mut self) -> core::ops::Range<*mut T> {
157 let start = self.ptr;
158 // SAFETY: `self.ptr.add(self.len)` stays within the allocation.
159 let end = unsafe { start.add(self.len) };
160 start..end
161 }
162
163 /// Consumes self and returns a `Vec<T>` with the initialized data.
164 #[inline]
165 pub fn into_vec(self) -> Vec<T> {
166 let mut v = Vec::with_capacity(self.len);
167 // SAFETY: `v` was reserved with capacity `self.len`, so its buffer holds
168 // `self.len` elements and is a distinct allocation that cannot overlap
169 // `self.ptr`. `[0, self.len)` of `self.ptr` is initialized POD `T`, and
170 // `T: Copy` makes the bytewise copy valid. `set_len(self.len)` matches
171 // exactly the number of elements copied. The source retains ownership
172 // of its distinct allocation and is released by its normal `Drop`
173 // after this method returns.
174 unsafe {
175 core::ptr::copy_nonoverlapping(self.ptr, v.as_mut_ptr(), self.len);
176 v.set_len(self.len);
177 }
178 v
179 }
180
181 #[cold]
182 #[inline(never)]
183 pub(super) fn grow_to(&mut self, new_capacity: usize) {
184 let new_layout = Self::layout_for(new_capacity);
185 let new_ptr = if self.capacity == 0 {
186 // SAFETY: `new_layout` has non-zero size (`layout_for` clamps to
187 // `>= 1`); the result is null-checked below before use.
188 unsafe { alloc::alloc::alloc(new_layout) as *mut T }
189 } else {
190 let old_layout = Self::layout_for(self.capacity);
191 // SAFETY: `self.ptr` was allocated by this same allocator with
192 // `old_layout` (the `capacity != 0` branch), and `old_layout` and
193 // `new_layout` share the same alignment because `layout_for`'s
194 // alignment depends only on `T`. `new_layout.size()` is non-zero.
195 // The result is null-checked below before it replaces `self.ptr`.
196 unsafe {
197 alloc::alloc::realloc(self.ptr as *mut u8, old_layout, new_layout.size()) as *mut T
198 }
199 };
200 if new_ptr.is_null() {
201 alloc::alloc::handle_alloc_error(new_layout);
202 }
203 self.ptr = new_ptr;
204 self.capacity = new_capacity;
205 }
206
207 /// Applies the shared geometric growth policy to a required capacity.
208 ///
209 /// A virgin allocation is exact because `capacity` is zero; an existing
210 /// allocation doubles unless the request is larger. The policy keeps
211 /// reallocation and copy traffic amortized, while [`shrink_to_capacity`]
212 /// and the scratch-pool release path bound idle retention separately.
213 #[cold]
214 #[inline(never)]
215 pub(super) fn grow_geometric(&mut self, needed: usize) {
216 self.grow_to(needed.max(self.capacity.saturating_mul(2)));
217 }
218
219 /// Reallocates the buffer down to exactly `new_capacity` elements,
220 /// returning the surplus to the allocator.
221 ///
222 /// This is the counterpart of [`grow_to`](Self::grow_to) and the only
223 /// operation on this type that gives memory back. It never grows: when
224 /// `new_capacity >= self.capacity()` it is a no-op. `len` is clamped to
225 /// the new capacity, so the type's `len <= capacity` invariant survives;
226 /// the retained elements keep their values.
227 ///
228 /// Callers that need the released region to read as fresh later must
229 /// re-zero it themselves; shrinking only preserves the initialized prefix.
230 ///
231 /// # Panics
232 ///
233 /// Panics if the allocator reports failure for the smaller layout.
234 fn shrink_to_capacity(&mut self, new_capacity: usize) {
235 if new_capacity >= self.capacity {
236 return;
237 }
238 if new_capacity == 0 {
239 if self.capacity > 0 {
240 // Let `Drop` return the old allocation, then install the
241 // dangling sentinel: `*self =` drops the old value first, so a
242 // manual `dealloc` here would free the same block twice.
243 *self = Self::dangling();
244 }
245 return;
246 }
247 let old_layout = Self::layout_for(self.capacity);
248 let new_layout = Self::layout_for(new_capacity);
249 // SAFETY: `self.ptr` was allocated by this same allocator with
250 // `old_layout` (`capacity != 0` is guaranteed by the early return
251 // above), and both layouts share the alignment of `T`.
252 // `new_layout.size()` is non-zero (`layout_for` clamps to >= 1), so
253 // this shrinks rather than frees; the result is null-checked before
254 // it replaces `self.ptr`.
255 let new_ptr =
256 unsafe { alloc::alloc::realloc(self.ptr as *mut u8, old_layout, new_layout.size()) }
257 as *mut T;
258 if new_ptr.is_null() {
259 alloc::alloc::handle_alloc_error(new_layout);
260 }
261 self.ptr = new_ptr;
262 self.capacity = new_capacity;
263 if self.len > self.capacity {
264 self.len = self.capacity;
265 }
266 }
267
268 /// Reallocates the buffer down to exactly `new_capacity` elements,
269 /// returning the surplus to the allocator.
270 ///
271 /// This is the public length-oriented entry point for the capacity
272 /// reduction operation.
273 #[inline]
274 pub fn shrink_to(&mut self, new_capacity: usize) {
275 self.shrink_to_capacity(new_capacity);
276 }
277
278 #[inline]
279 pub(super) fn layout_for(capacity: usize) -> core::alloc::Layout {
280 let elem_size = core::mem::size_of::<T>();
281 let size = capacity.saturating_mul(elem_size).max(1);
282 let align = T::ALIGN_BYTES.max(elem_size);
283 core::alloc::Layout::from_size_align(size, align).expect("AlignedVec: invalid layout")
284 }
285}