Skip to main content

ptab/
array.rs

1use core::marker::PhantomData;
2use core::mem::ManuallyDrop;
3use core::mem::MaybeUninit;
4use core::ptr::NonNull;
5use core::slice;
6
7use crate::alloc::alloc;
8use crate::alloc::dealloc;
9use crate::alloc::handle_alloc_error;
10use crate::index::Concrete;
11use crate::params::Params;
12use crate::params::ParamsExt;
13
14/// A fixed-size array with cache-line-aligned allocation.
15#[repr(transparent)]
16pub(crate) struct Array<T, P>
17where
18  P: Params + ?Sized,
19{
20  nonnull: NonNull<T>,
21  phantom: PhantomData<P>,
22}
23
24impl<T, P> Array<T, P>
25where
26  P: Params + ?Sized,
27{
28  /// Creates an array where each element is produced by calling `init` with
29  /// that element’s index while walking forward through the array.
30  #[inline]
31  pub(crate) fn new<F>(init: F) -> Self
32  where
33    F: Fn(usize, &mut MaybeUninit<T>),
34  {
35    let this: Array<MaybeUninit<T>, P> = Self::new_uninit();
36
37    for index in 0..P::LENGTH.as_usize() {
38      // SAFETY:
39      // - `index` is strictly less than `P::LENGTH`.
40      // - The allocation performed by `new_uninit` reserves space for exactly
41      //   `P::LENGTH` contiguous elements.
42      // - The pointer returned by `as_non_null` is properly aligned for `T`.
43      // - We have exclusive access to the allocation, so creating a unique
44      //   mutable reference is sound.
45      init(index, unsafe { this.as_non_null().add(index).as_mut() });
46    }
47
48    // SAFETY: The loop above initializes every element in the allocation
49    //         exactly once, so all `P::LENGTH` elements are initialized.
50    unsafe { this.assume_init() }
51  }
52
53  /// Constructs a new array with uninitialized contents.
54  #[inline]
55  pub(crate) fn new_uninit() -> Array<MaybeUninit<T>, P> {
56    // SAFETY:
57    // - `P::LAYOUT` describes a non-zero-sized allocation.
58    // - Its size and alignment have been validated when constructing the
59    //   associated `Params` implementation.
60    let raw: *mut u8 = unsafe { alloc(P::LAYOUT) };
61
62    Array {
63      nonnull: match NonNull::new(raw.cast()) {
64        Some(ptr) => ptr,
65        None => handle_alloc_error(P::LAYOUT),
66      },
67      phantom: PhantomData,
68    }
69  }
70
71  /// Returns a `NonNull` pointer to the array's buffer.
72  #[inline]
73  pub(crate) const fn as_non_null(&self) -> NonNull<T> {
74    self.nonnull
75  }
76
77  /// Returns a raw pointer to the array's buffer.
78  #[cfg(test)]
79  #[inline]
80  pub(crate) const fn as_ptr(&self) -> *const T {
81    self.as_non_null().as_ptr()
82  }
83
84  /// Returns a raw mutable pointer to the array's buffer.
85  #[inline]
86  pub(crate) const fn as_mut_ptr(&self) -> *mut T {
87    self.as_non_null().as_ptr()
88  }
89
90  /// Extracts a slice containing the entire array.
91  #[cfg(test)]
92  #[inline]
93  pub(crate) const fn as_slice(&self) -> &[T] {
94    // SAFETY:
95    // - The allocation contains `P::LENGTH` contiguous elements of `T`.
96    // - For `Array<T, P>`, all elements are guaranteed to be initialized.
97    // - The pointer is valid for reads for the entire range.
98    unsafe { slice::from_raw_parts(self.as_ptr(), P::LENGTH.as_usize()) }
99  }
100
101  /// Extracts a mutable slice of the entire array.
102  #[inline]
103  pub(crate) const fn as_mut_slice(&mut self) -> &mut [T] {
104    // SAFETY:
105    // - The allocation contains `P::LENGTH` contiguous elements of `T`.
106    // - For `Array<T, P>`, all elements are guaranteed to be initialized.
107    // - `&mut self` guarantees unique access to the allocation.
108    unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), P::LENGTH.as_usize()) }
109  }
110
111  /// Returns a reference to the element at `index`.
112  #[inline]
113  pub(crate) const fn get(&self, index: Concrete<P>) -> &T {
114    // SAFETY: `Concrete<P>` ensures that the underlying index is strictly less
115    //         than `P::LENGTH`, so it is within bounds.
116    unsafe { self.get_unchecked(index.get()) }
117  }
118
119  /// Returns a reference to the element at `index`, without doing bounds
120  /// checking.
121  ///
122  /// # Safety
123  ///
124  /// `index` must be strictly less than `P::LENGTH`. Passing an out-of-bounds
125  /// index results in undefined behavior, even if the returned reference is not
126  /// used.
127  #[inline]
128  pub(crate) const unsafe fn get_unchecked(&self, index: usize) -> &T {
129    if true {
    if !(index < P::LENGTH.as_usize()) {
        {
            ::core::panicking::panic_fmt(format_args!("Array::get_unchecked requires that the index is in bounds"));
        }
    };
};debug_assert!(
130      index < P::LENGTH.as_usize(),
131      "Array::get_unchecked requires that the index is in bounds",
132    );
133
134    // SAFETY:
135    // - The caller guarantees `index < P::LENGTH`.
136    // - The allocation holds `P::LENGTH` contiguous elements.
137    // - The pointer is properly aligned and valid for reads.
138    unsafe { self.as_non_null().add(index).as_ref() }
139  }
140}
141
142impl<T, P> Array<MaybeUninit<T>, P>
143where
144  P: Params + ?Sized,
145{
146  /// Converts to `Array<T, P>`.
147  ///
148  /// # Safety
149  ///
150  /// The caller must guarantee that all elements in the allocation are fully
151  /// initialized. If any element is uninitialized, converting to `Array<T, P>`
152  /// results in immediate undefined behavior.
153  #[inline]
154  pub(crate) unsafe fn assume_init(self) -> Array<T, P> {
155    // SAFETY:
156    // - The caller guarantees that all elements are initialized.
157    // - `Array<MaybeUninit<T>, P>` and `Array<T, P>` have identical layout.
158    // - `ManuallyDrop` prevents `self` from being dropped, so the allocation is
159    //   not freed during the conversion.
160    Array {
161      nonnull: ManuallyDrop::new(self).as_non_null().cast(),
162      phantom: PhantomData,
163    }
164  }
165}
166
167impl<T, P> Drop for Array<T, P>
168where
169  P: Params + ?Sized,
170{
171  fn drop(&mut self) {
172    // SAFETY:
173    // - The allocation was created with `alloc(P::LAYOUT)` in `new_uninit`.
174    // - `P::LAYOUT` is the exact layout used for allocation.
175    // - `self.nonnull` still points to the original allocation.
176    unsafe {
177      dealloc(self.as_non_null().cast().as_ptr(), P::LAYOUT);
178    }
179  }
180}
181
182// -----------------------------------------------------------------------------
183// Tests
184// -----------------------------------------------------------------------------
185
186#[cfg_attr(coverage_nightly, coverage(off))]
187#[cfg(test)]
188mod tests {
189  use crate::array::Array;
190  use crate::index::Concrete;
191  use crate::params::CACHE_LINE;
192  use crate::params::Params;
193  use crate::utils::each_capacity;
194
195  #[cfg_attr(loom, ignore = "loom does not run this test")]
196  #[test]
197  fn alignment() {
198    each_capacity!({
199      let array: Array<usize, P> = Array::new(|_, slot| {
200        slot.write(0);
201      });
202
203      // TODO: ptr::is_aligned_to once stable
204      assert_eq!(array.as_ptr().addr() & (CACHE_LINE - 1), 0);
205    });
206  }
207
208  #[cfg_attr(loom, ignore = "loom does not run this test")]
209  #[test]
210  fn get() {
211    each_capacity!({
212      let array: Array<usize, P> = Array::new(|index, slot| {
213        slot.write(index);
214      });
215
216      for index in 0..P::LENGTH.as_usize() {
217        assert_eq!(array.get(Concrete::<P>::new(index)), &index);
218      }
219    });
220  }
221
222  #[cfg_attr(loom, ignore = "loom does not run this test")]
223  #[test]
224  fn as_slice() {
225    each_capacity!({
226      let array: Array<usize, P> = Array::new(|index, slot| {
227        slot.write(index);
228      });
229
230      assert_eq!(array.as_slice().len(), P::LENGTH.as_usize());
231
232      for (index, value) in array.as_slice().iter().enumerate() {
233        assert_eq!(*value, index);
234      }
235    });
236  }
237
238  #[cfg_attr(loom, ignore = "loom does not run this test")]
239  #[test]
240  fn as_mut_slice() {
241    each_capacity!({
242      let mut array: Array<usize, P> = Array::new(|index, slot| {
243        slot.write(index);
244      });
245
246      assert_eq!(array.as_mut_slice().len(), P::LENGTH.as_usize());
247
248      for (index, value) in array.as_mut_slice().iter_mut().enumerate() {
249        assert_eq!(*value, index);
250        *value += 1;
251      }
252
253      for (index, value) in array.as_slice().iter().enumerate() {
254        assert_eq!(*value, index + 1);
255      }
256    });
257  }
258}