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}