Skip to main content

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}