wasm/core/
fixed_capacity_vec.rs

1use core::mem::MaybeUninit;
2
3use alloc::boxed::Box;
4
5/// The operation would remove more elements than currently present or tries to access an element
6/// when none are present
7#[derive(Debug, PartialEq, Eq, Clone)]
8pub struct EmptyContainerError;
9
10/// The operation would add elements past the maximum capacity
11#[derive(Debug, PartialEq, Eq, Clone)]
12pub struct FullContainerError;
13
14impl From<FullContainerError> for crate::RuntimeError {
15    fn from(_: FullContainerError) -> Self {
16        Self::StackExhaustion
17    }
18}
19
20// TODO consider adding a generic ContainerError enum?
21
22pub struct FixedCapacityVec<T> {
23    /// A contiguous, non-reallocating, heap allocated vector. Behaves like a subset of
24    /// [`alloc::vec::Vec`], cleansed of any operation that reallocates. Backed by via boxed
25    /// slice containing elements of type `MaybeUninit<T>`. The maximum size (pendant to
26    /// [`alloc::vec::Vec::capacity`]) is determined at creation and remains unchanged over the
27    /// lifetime of [`Self`]. Nonetheless a variable amount of `T` can be kept within [`Self`], just
28    /// never more than the maximum size passed to the constructor.
29    ///
30    /// This can be used as a stack, in which case the first element (forming the bottom of the
31    /// stack) is at index 0, and new elements grow towards [`Self`]'s end. The last element
32    /// (`self.stack[self.stack.len() - 1]` is the topmost possible entry.
33    elements: Box<[MaybeUninit<T>]>,
34
35    /// Number of elements in [`Self`]. Also an index, pointing always to the first unused slot in
36    /// [`Self::elements`].
37    // Developer notes:
38    // - It is UB to access `self.elements[self.len]` or any other index larger than `self.len`.
39    // - If `self.stack_height == 0`, then there are no elements.
40    // - The guaranteed to be initialized range of [`Self::stack`] always is `self.stack[..self.stack_height]`.
41    len: usize,
42}
43
44// TODO Optimize Clone impl by using unsafe code
45impl<T: Clone> Clone for FixedCapacityVec<T> {
46    fn clone(&self) -> Self {
47        let mut cloned = Self::with_capacity(self.elements.len());
48
49        for index in 0..self.len {
50            let element = self
51                .get(index)
52                .expect("that this index is valid because it is less that self.len");
53            cloned
54                .push(element.clone())
55                .expect("that the new cloned vec has the same length as self and cannot overflow");
56        }
57
58        cloned
59    }
60}
61
62impl<T> FixedCapacityVec<T> {
63    /// Construct new [`Self`], holding up to `capacity` elements of type `T`
64    pub fn with_capacity(capacity: usize) -> Self {
65        Self {
66            elements: Box::new_uninit_slice(capacity),
67            len: 0,
68        }
69    }
70
71    /// Check if the [`Self`] is empty
72    #[inline(always)]
73    pub const fn is_empty(&self) -> bool {
74        self.len == 0
75    }
76
77    /// Check if the [`Self`] is full
78    #[inline(always)]
79    pub const fn is_full(&self) -> bool {
80        self.len == self.elements.len()
81    }
82
83    /// Get the [`Self`] is height.
84    #[inline(always)]
85    pub const fn len(&self) -> usize {
86        self.len
87    }
88
89    /// Get the maximum number of elements that fit into [`Self`].
90    #[inline(always)]
91    pub const fn capacity(&self) -> usize {
92        self.elements.len()
93    }
94
95    /// Push a value to the end of [`Self`]
96    ///
97    /// # Safety
98    ///
99    /// - Causes UB if [`Self`] is already full.
100    /// - Causes UB if [`Self`] has a capacity equal to `usize::MAX`
101    #[inline(always)]
102    pub unsafe fn push_unchecked(&mut self, value: T) {
103        debug_assert!(!self.is_full());
104        debug_assert!(self.capacity() < usize::MAX);
105
106        // SAFETY: This must never be called when `Self` is full
107        unsafe { *self.elements.get_unchecked_mut(self.len) = MaybeUninit::new(value) };
108
109        // SAFETY: This must never be called when `self.capacity()` is `usize::MAX`
110        self.len = unsafe { self.len.unchecked_add(1) };
111    }
112
113    /// Push a value to the end of [`Self`]
114    pub fn push(&mut self, value: T) -> Result<(), FullContainerError> {
115        // check if insertion will overflow `self.len`
116        let new_len = self.len.checked_add(1).ok_or(FullContainerError)?;
117
118        *self.elements.get_mut(self.len).ok_or(FullContainerError)? = MaybeUninit::new(value);
119
120        // only "commit" the `new_len` if the insertion actually succeeded
121        self.len = new_len;
122
123        Ok(())
124    }
125
126    /// Pop a value from [`Self`]'s top/tail
127    ///
128    /// # Safety
129    ///
130    /// Causes UB if the stack is empty.
131    #[inline(always)]
132    pub unsafe fn pop_unchecked(&mut self) -> T {
133        debug_assert!(!self.is_empty());
134
135        // SAFETY: This must never be called when `self` is empty. If the stack is indeed not
136        // empty, then both the unchecked subtraction is guaranteed not to underflow.
137        self.len = unsafe { self.len.unchecked_sub(1) };
138
139        // SAFETY: This is guaranteed to be initialized, as `self.len` accurately tracks the number
140        // of initialized values starting from `self.element`'s beginning. As we require `self.len`
141        // not to be zero before calling this function, it is now at zero and thus a valid index to
142        // an initialized element.
143        unsafe { self.elements.get_unchecked(self.len).assume_init_read() }
144    }
145
146    /// Pop a value from [`Self`]'s top/tail
147    pub fn pop(&mut self) -> Result<T, EmptyContainerError> {
148        self.len = self.len.checked_sub(1).ok_or(EmptyContainerError)?;
149
150        // SAFETY: This is guaranteed to be initialized, as `self.len` accurately tracks the number
151        // of initialized values starting from `self.element`'s beginning. As we require `self.len`
152        // not to be zero before calling this function, it is now at zero and thus a valid index to
153        // an initialized element.
154        let value = unsafe { self.elements.get_unchecked(self.len).assume_init_read() };
155
156        Ok(value)
157    }
158
159    /// Peek at the topmost/last value from [`Self`]
160    ///
161    /// # Safety
162    ///
163    /// Causes UB if the stack is empty.
164    #[inline(always)]
165    pub unsafe fn peek_unchecked(&self) -> &T {
166        debug_assert!(!self.is_empty());
167
168        // SAFETY: This must never be called when the `self` is empty. If the stack is indeed not
169        // empty, then both the unchecked subtraction can not underflow, and the element at that
170        // index will be already initialized.
171        unsafe {
172            self.elements
173                .get_unchecked(self.len.unchecked_sub(1))
174                .assume_init_ref()
175        }
176    }
177
178    /// Peek at the topmost/last value from [`Self`]
179    pub fn peek(&self) -> Result<&T, EmptyContainerError> {
180        let idx = self.len.checked_sub(1).ok_or(EmptyContainerError)?;
181
182        // SAFETY: This is guaranteed to be initialized, as `self.len` accurately tracks the number
183        // of initialized values starting from `self.element`'s beginning. As we require `self.len`
184        // not to be zero before calling this function, it is now at zero and thus a valid index to
185        // an initialized element.
186        let value = unsafe { self.elements.get_unchecked(idx).assume_init_ref() };
187
188        Ok(value)
189    }
190
191    /// Get a shared ref to the nth element in [`Self`]
192    #[inline(always)]
193    pub fn get(&self, idx: usize) -> Option<&T> {
194        if idx < self.len {
195            self.elements.get(idx).map(|e| {
196                // SAFETY: This is guaranteed to be initialized, as `self.len` accurately tracks the
197                // number of initialized values starting from `self.element`'s beginning. The pervious
198                // if condition in term checks that idx is Smaller than
199                unsafe { e.assume_init_ref() }
200            })
201        } else {
202            None
203        }
204    }
205
206    /// Get a mut ref to the nth element in [`Self`]
207    #[inline(always)]
208    pub fn get_mut(&mut self, idx: usize) -> Option<&mut T> {
209        if idx < self.len {
210            self.elements.get_mut(idx).map(|e| {
211                // SAFETY: per the previous if statement, the index points into the range of
212                // initialized stack members.
213                unsafe { e.assume_init_mut() }
214            })
215        } else {
216            None
217        }
218    }
219}
220
221impl<T: Clone> FixedCapacityVec<T> {
222    /// Push from a slice into [`Self`], appending after the last/topmost element
223    pub fn push_from_slice(&mut self, values: &[T]) -> Result<(), FullContainerError> {
224        // verify `values` fits into the new self
225        if values.len() > (self.elements.len() - self.len) {
226            return Err(FullContainerError);
227        }
228
229        for e in values {
230            // SAFETY: this is safe, as the previous if statement verifies that there is enough
231            // space for all elements from `values` in `stack`
232            unsafe { self.push_unchecked(e.clone()) };
233        }
234
235        Ok(())
236    }
237
238    /// Pop `n` elements from [`Self`] into a slice. The topmost/last element of [`Self`] will
239    /// become the slice's last element.
240    pub fn pop_into_slice(
241        &mut self,
242        n: usize,
243    ) -> Result<impl core::ops::Deref<Target = [T]> + '_, EmptyContainerError> {
244        // verify at least `n` elements are still one the stack
245        if n > self.elements.len() {
246            return Err(EmptyContainerError);
247        }
248
249        let old_len = self.len();
250
251        // SAFETY: the previous if statement ensure that `n` is smaller than or equal to
252        // `self.elements.len()`, hence this subtraction is guaranteed to not underflow
253        self.len = unsafe { self.len.unchecked_sub(n) };
254
255        // SAFETY: the prior if statement checks that `stack_height - n` wont underflow. And the
256        // rest of the module ensures that stack_height never gets bigger than `self.stack.len()`.
257        // Thus the index range is always in-bounds.
258        Ok(SliceDropGuard(unsafe {
259            self.elements.get_unchecked_mut(self.len..old_len)
260        }))
261    }
262}
263
264pub struct SliceDropGuard<'a, T>(&'a mut [MaybeUninit<T>]);
265
266impl<'a, T> core::ops::Deref for SliceDropGuard<'a, T> {
267    type Target = [T];
268
269    fn deref(&self) -> &Self::Target {
270        // SAFETY: The rest of the module ensures that stack_height never gets bigger than
271        // `self.stack.len()`. Thus the index range is always in-bounds.
272        unsafe { slice_assume_init(self.0) }
273    }
274}
275
276impl<'a, T> Drop for SliceDropGuard<'a, T> {
277    fn drop(&mut self) {
278        for e in &mut *self.0 {
279            // SAFETY: The rest of the module ensures that stack_height never gets bigger than
280            // `self.stack.len()`. Thus the index range is always in-bounds.
281            unsafe { e.assume_init_drop() };
282        }
283    }
284}
285
286impl<T: Copy> FixedCapacityVec<T> {
287    /// Remove `remove_count` values from [`Self`], keeping the topmost `keep_count` values
288    ///
289    /// From the [`Self`], remove `remove_count` elements, by sliding down the `keep_count` last/topmost
290    /// values `remove_count` positions forward/towards the bottom.
291    ///
292    /// **Effects**
293    ///
294    /// - after the operation, [`Self`] will contain `remove_count` fewer elements
295    /// - `keep_count` topmost elements will be identical before and after the operation
296    /// - all elements below the `remove_count + keep_count` topmost stack entry remain
297    pub fn remove_in_between(&mut self, remove_count: usize, keep_count: usize) {
298        // TODO make unchecked version, remove overflowing arithmetic in safe version
299        let len = self.len();
300        self.elements
301            .copy_within(len - keep_count..len, len - keep_count - remove_count);
302        self.len -= remove_count;
303    }
304}
305
306impl<T: core::fmt::Debug> core::fmt::Debug for FixedCapacityVec<T> {
307    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
308        // SAFETY: per the guarantees of push/pop/etc., `0..self.stack_height` is always a range to
309        // initalized values.
310        let stack_elements_slice = unsafe { slice_assume_init(&self.elements[..self.len]) };
311
312        f.debug_struct("Stack")
313            .field("stack", &stack_elements_slice)
314            .field("stack_height", &self.len)
315            .finish()
316    }
317}
318
319impl<T> Drop for FixedCapacityVec<T> {
320    fn drop(&mut self) {
321        // SAFETY: self is guaranteed to have at least `self.len` elements, because `sel.len` can
322        // not grow past `self.elements.len` (aka `self.capacity()`).
323        for e in unsafe { self.elements.get_unchecked_mut(0..self.len) } {
324            // SAFETY: This is guaranteed to be initialized, as `self.len` accurately tracks the
325            // number of initialized values starting from `self.element`'s beginning.
326            unsafe { e.assume_init_drop() }
327        }
328    }
329}
330
331// TODO use assume_init_ref, once we get the MSRV to 1.93.0
332/// # Safety
333///
334/// Use this to assume a range of a slice to be initialized. This is provided starting with Rust
335/// 1.93.0.
336#[inline(always)]
337unsafe fn slice_assume_init<T>(slice: &[MaybeUninit<T>]) -> &[T] {
338    // SAFETY: casting `slice` to a `*const [T]` is safe since the caller guarantees that
339    // `slice` is initialized, and `MaybeUninit` is guaranteed to have the same layout as `T`.
340    // The pointer obtained is valid since it refers to memory owned by `slice` which is a
341    // reference and thus guaranteed to be valid for reads.
342    unsafe { &*(slice as *const _ as *const [T]) }
343}