| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // IMPORTANT: Upon any code-related modification to this file, please ensure that all commented-out |
| // tests that start with `fails_` actually fail to compile *independently* from one |
| // another. |
| |
| use std::{ |
| fmt, hint, |
| marker::PhantomData, |
| mem, |
| ops::{Bound, Deref, DerefMut, RangeBounds}, |
| ptr::NonNull, |
| slice, |
| }; |
| |
| use crossbeam_utils::atomic::AtomicCell; |
| |
| // `SliceCache` is virtually identical to a `Vec<Range<usize>>` whose range are statically |
| // guaranteed not to overlap or overflow the initial `slice` that the object has been made with. |
| // |
| // This is achieved by forcing the user to produce `Span`s in a closure provided to the constructor |
| // from a root `Span` that cannot escape the closure. |
| // |
| // In `SliceCache::access`, we make sure that the slice doesn't overflow the `len` passed in |
| // `SliceCache::new` and then save the pointer to the global `ROOT` that is then guarded until the |
| // `Ref` is dropped. |
| |
| #[repr(transparent)] |
| #[derive(Clone, Copy, Eq, PartialEq)] |
| struct SendNonNull<T> { |
| ptr: NonNull<T>, |
| } |
| |
| unsafe impl<T> Send for SendNonNull<T> {} |
| |
| impl<T> From<NonNull<T>> for SendNonNull<T> { |
| fn from(ptr: NonNull<T>) -> Self { |
| Self { ptr } |
| } |
| } |
| |
| static ROOT: AtomicCell<Option<SendNonNull<()>>> = AtomicCell::new(None); |
| |
| /// A [`prim@slice`] wrapper produced by [`SliceCache::access`]. |
| #[repr(C)] |
| pub struct Slice<'a, T> { |
| offset: isize, |
| len: usize, |
| _phantom: PhantomData<&'a mut [T]>, |
| } |
| |
| // Since this type is equivalent to `&mut [T]`, it also implements `Send`. |
| unsafe impl<'a, T: Send> Send for Slice<'a, T> {} |
| |
| // Since this type is equivalent to `&mut [T]`, it also implements `Sync`. |
| unsafe impl<'a, T: Sync> Sync for Slice<'a, T> {} |
| |
| impl<'a, T> Deref for Slice<'a, T> { |
| type Target = [T]; |
| |
| #[inline] |
| fn deref(&self) -> &'a Self::Target { |
| let root: NonNull<T> = ROOT.load().unwrap().ptr.cast(); |
| |
| // `Slice`s should only be dereferences when tainted with the `'s` lifetime from the |
| // `SliceCache::access` method. This ensures that the slice that results from derefrencing |
| // here will also be constrained by the same lifetime. |
| // |
| // This also expects the `ROOT` pointer to be correctly set up in `SliceCache::access`. |
| unsafe { slice::from_raw_parts(root.as_ptr().offset(self.offset), self.len) } |
| } |
| } |
| |
| impl<'a, T> DerefMut for Slice<'a, T> { |
| #[inline] |
| fn deref_mut(&mut self) -> &'a mut Self::Target { |
| let root: NonNull<T> = ROOT.load().unwrap().ptr.cast(); |
| |
| // `Slice`s should only be dereferences when tainted with the `'s` lifetime from the |
| // `SliceCache::access` method. This ensures that the slice that results from derefrencing |
| // here will also be constrained by the same lifetime. |
| // |
| // This also expects the `ROOT` pointer to be correctly set up in `SliceCache::access`. |
| unsafe { slice::from_raw_parts_mut(root.as_ptr().offset(self.offset), self.len) } |
| } |
| } |
| |
| impl<T: fmt::Debug> fmt::Debug for Slice<'_, T> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| (**self).fmt(f) |
| } |
| } |
| |
| /// A marker produced by [`SliceCache`] that ensures that all resulting `Span`s will be mutually |
| /// non-overlapping. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::SliceCache; |
| /// let mut cache = SliceCache::new(4, |span| { |
| /// Box::new([span]) |
| /// }); |
| /// ``` |
| #[repr(transparent)] |
| pub struct Span<'a>(Slice<'a, ()>); |
| |
| impl<'a> Span<'a> { |
| fn from_slice(slice: &Slice<'a, ()>) -> Self { |
| Self(Slice { offset: slice.offset, len: slice.len, _phantom: PhantomData }) |
| } |
| |
| /// cache span at `mid`. Analogous to [`slice::split_at`]. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::SliceCache; |
| /// let mut cache = SliceCache::new(4, |span| { |
| /// let (left, right) = span.split_at(2); |
| /// Box::new([left, right]) |
| /// }); |
| /// ``` |
| #[inline] |
| pub fn slice<R: RangeBounds<usize>>(&self, range: R) -> Option<Self> { |
| let start = match range.start_bound() { |
| Bound::Included(&i) => i, |
| Bound::Excluded(&i) => i + 1, |
| Bound::Unbounded => 0, |
| }; |
| let end = match range.end_bound() { |
| Bound::Included(&i) => i + 1, |
| Bound::Excluded(&i) => i, |
| Bound::Unbounded => self.0.len, |
| }; |
| |
| (start <= end && end <= self.0.len) |
| .then(|| Span(Slice { offset: self.0.offset + start as isize, len: end, ..self.0 })) |
| } |
| |
| /// cache span at `mid`. Analogous to [`slice::split_at`]. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::SliceCache; |
| /// let mut cache = SliceCache::new(4, |span| { |
| /// let (left, right) = span.split_at(2); |
| /// Box::new([left, right]) |
| /// }); |
| /// ``` |
| #[inline] |
| pub fn split_at(&self, mid: usize) -> (Self, Self) { |
| assert!(mid <= self.0.len); |
| |
| ( |
| Span(Slice { len: mid, ..self.0 }), |
| Span(Slice { offset: self.0.offset + mid as isize, len: self.0.len - mid, ..self.0 }), |
| ) |
| } |
| |
| /// Returns an [Iterator](Chunks) over `chunk_size` elements of thr slice. Analogous to [`slice::chunks`]. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::SliceCache; |
| /// let mut cache = SliceCache::new(4, |span| { |
| /// span.chunks(2).collect() |
| /// }); |
| /// ``` |
| #[inline] |
| pub fn chunks(self, chunk_size: usize) -> Chunks<'a> { |
| Chunks { slice: self.0, size: chunk_size } |
| } |
| } |
| |
| impl fmt::Debug for Span<'_> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.0.offset.fmt(f)?; |
| write!(f, "..")?; |
| self.0.len.fmt(f)?; |
| |
| Ok(()) |
| } |
| } |
| |
| /// An [iterator](std::iter::Iterator) returned by [`Span::chunks`]. |
| pub struct Chunks<'a> { |
| slice: Slice<'a, ()>, |
| size: usize, |
| } |
| |
| impl<'a> Iterator for Chunks<'a> { |
| type Item = Span<'a>; |
| |
| #[inline] |
| fn next(&mut self) -> Option<Self::Item> { |
| (self.slice.len > 0).then(|| { |
| let span = Span(Slice { len: self.size.min(self.slice.len), ..self.slice }); |
| |
| self.slice.offset += self.size as isize; |
| self.slice.len = self.slice.len.saturating_sub(self.size); |
| |
| span |
| }) |
| } |
| } |
| |
| impl fmt::Debug for Chunks<'_> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.debug_struct("Chunks") |
| .field("span", &Span::from_slice(&self.slice)) |
| .field("size", &self.size) |
| .finish() |
| } |
| } |
| |
| /// A [reference] wrapper returned by [`SliceCache::access`]. |
| #[repr(transparent)] |
| #[derive(Debug)] |
| pub struct Ref<'a, T: ?Sized>(&'a mut T); |
| |
| impl<'a, T: ?Sized> Ref<'a, T> { |
| pub fn get(&'a mut self) -> &'a mut T { |
| self.0 |
| } |
| } |
| |
| impl<T: ?Sized> Deref for Ref<'_, T> { |
| type Target = T; |
| |
| #[inline] |
| fn deref(&self) -> &Self::Target { |
| self.0 |
| } |
| } |
| |
| impl<T: ?Sized> DerefMut for Ref<'_, T> { |
| #[inline] |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| self.0 |
| } |
| } |
| |
| impl<T: ?Sized> Drop for Ref<'_, T> { |
| #[inline] |
| fn drop(&mut self) { |
| ROOT.store(None); |
| } |
| } |
| |
| /// A cache of non-overlapping mutable sub-slices of that enforces lifetimes dynamically. |
| /// |
| /// This type is useful when you have to give up on the mutable reference to a slice but need |
| /// a way to cache mutable sub-slices deriving from it. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::SliceCache; |
| /// let mut array = [1, 2, 3]; |
| /// |
| /// let mut cache = SliceCache::new(3, |span| { |
| /// let (left, right) = span.split_at(1); |
| /// Box::new([left, right]) |
| /// }); |
| /// |
| /// for slice in cache.access(&mut array).unwrap().iter_mut() { |
| /// for val in slice.iter_mut() { |
| /// *val += 1; |
| /// } |
| /// } |
| /// |
| /// assert_eq!(array, [2, 3, 4]); |
| /// ``` |
| pub struct SliceCache { |
| len: usize, |
| slices: Box<[Slice<'static, ()>]>, |
| } |
| |
| impl SliceCache { |
| /// Creates a new slice cache by storing sub-spans created from a root passed to the closure |
| /// `f`. `len` is the minimum slice length that can then be passed to [`access`](Self::access). |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::SliceCache; |
| /// let mut cache = SliceCache::new(3, |span| { |
| /// let (left, right) = span.split_at(1); |
| /// // All returned sub-spans stem from the span passed above. |
| /// Box::new([left, right]) |
| /// }); |
| /// ``` |
| #[inline] |
| pub fn new<F>(len: usize, f: F) -> Self |
| where |
| F: Fn(Span<'_>) -> Box<[Span<'_>]> + 'static, |
| { |
| let span = Span(Slice { offset: 0, len, _phantom: PhantomData }); |
| |
| // `Span<'_>` is transparent over `Slice<'_, ()>`. Since the `'_` above is used just to |
| // trap the span inside the closure, transmuting to `Slice<'static, ()>` does not make any |
| // difference. |
| Self { len, slices: unsafe { mem::transmute(f(span)) } } |
| } |
| |
| /// Accesses the `slice` by returning all the sub-slices equivalent to the previously created |
| /// [spans](Span) in the closure passed to [`new`](Self::new). |
| /// |
| /// If the `slice` does not have a length at least as large as the one passed to |
| /// [`new`](Self::new), this function returns `None`. |
| /// |
| /// Note: this method should not be called concurrently with any other `access` calls since it |
| /// will wait for the previously returned [`Ref`] to be dropped. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use surpass::layout::SliceCache; |
| /// let mut array = [1, 2, 3]; |
| /// |
| /// let mut cache = SliceCache::new(3, |span| { |
| /// let (left, right) = span.split_at(1); |
| /// Box::new([left, right]) |
| /// }); |
| /// |
| /// let mut copy = array; |
| /// let skipped_one = cache.access(&mut array).unwrap(); |
| /// |
| /// assert_eq!(&*skipped_one[1], ©[1..]); |
| /// ``` |
| #[inline] |
| pub fn access<'c, 's, T>(&'c mut self, slice: &'s mut [T]) -> Option<Ref<'c, [Slice<'s, T>]>> { |
| if slice.len() >= self.len { |
| while ROOT |
| .compare_exchange( |
| None, |
| Some(NonNull::new(slice.as_mut_ptr()).unwrap().cast().into()), |
| ) |
| .is_err() |
| { |
| // This spin lock here is mostly for being able to run tests in parallel. Being |
| // able to render to `forma::Composition`s in parallel is currently not supported |
| // and might poor performance due to priority inversion. |
| hint::spin_loop(); |
| } |
| |
| // Generic `Slice<'static, ()>` are transmuted to `Slice<'s, T>`, enforcing the |
| // original `slice`'s lifetime. Since slices are simply pairs of `(offset, len)`, |
| // transmuting `()` to `T` relies on the `ROOT` being set up above with the correct pointer. |
| return Some(unsafe { mem::transmute(&mut *self.slices) }); |
| } |
| |
| None |
| } |
| |
| #[cfg(test)] |
| fn try_access<'c, 's, T>(&'c mut self, slice: &'s mut [T]) -> Option<Ref<'c, [Slice<'s, T>]>> { |
| if slice.len() >= self.len |
| && ROOT |
| .compare_exchange( |
| None, |
| Some(NonNull::new(slice.as_mut_ptr()).unwrap().cast().into()), |
| ) |
| .is_ok() |
| { |
| // Generic `Slice<'static, ()>` are transmuted to `Slice<'s, T>`, enforcing the |
| // original `slice`'s lifetime. Since slices are simply pairs of `(offset, len)`, |
| // transmuting `()` to `T` relies on the `ROOT` being set up above with the correct pointer. |
| return Some(unsafe { mem::transmute(&mut *self.slices) }); |
| } |
| |
| None |
| } |
| } |
| |
| impl fmt::Debug for SliceCache { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.debug_list().entries(self.slices.iter().map(Span::from_slice)).finish() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn split_at() { |
| let mut cache = SliceCache::new(5, |span| { |
| let (left, right) = span.split_at(2); |
| Box::new([left, right]) |
| }); |
| let mut array = [1, 2, 3, 4, 5]; |
| |
| for slice in cache.access(&mut array).unwrap().iter_mut() { |
| for val in slice.iter_mut() { |
| *val += 1; |
| } |
| } |
| |
| assert_eq!(array, [2, 3, 4, 5, 6]); |
| } |
| |
| #[test] |
| fn chunks() { |
| let mut cache = SliceCache::new(5, |span| span.chunks(2).collect()); |
| let mut array = [1, 2, 3, 4, 5]; |
| |
| for slice in cache.access(&mut array).unwrap().iter_mut() { |
| for val in slice.iter_mut() { |
| *val += 1; |
| } |
| } |
| |
| assert_eq!(array, [2, 3, 4, 5, 6]); |
| } |
| |
| #[test] |
| fn ref_twice() { |
| let mut cache = SliceCache::new(5, |span| { |
| let (left, right) = span.split_at(2); |
| Box::new([left, right]) |
| }); |
| let mut array = [1, 2, 3, 4, 5]; |
| |
| for slice in cache.access(&mut array).unwrap().iter_mut() { |
| for val in slice.iter_mut() { |
| *val += 1; |
| } |
| } |
| |
| for slice in cache.access(&mut array).unwrap().iter_mut() { |
| for val in slice.iter_mut() { |
| *val += 1; |
| } |
| } |
| |
| assert_eq!(array, [3, 4, 5, 6, 7]); |
| } |
| |
| #[test] |
| fn access_twice() { |
| let mut cache0 = SliceCache::new(5, |span| Box::new([span])); |
| let mut cache1 = SliceCache::new(5, |span| Box::new([span])); |
| |
| let mut array0 = [1, 2, 3, 4, 5]; |
| let mut array1 = [1, 2, 3, 4, 5]; |
| |
| let _slices = cache0.access(&mut array0).unwrap(); |
| |
| assert!(matches!(cache1.try_access(&mut array1), None)); |
| } |
| |
| // #[test] |
| // fn fails_due_to_too_short_lifetime() { |
| // let mut cache = SliceCache::new(16, |span| Box::new([span])); |
| |
| // let slices = { |
| // let mut buffer = [0u8; 16]; |
| |
| // let slices = cache.access(&mut buffer).unwrap(); |
| // let slice = &mut *slices[0]; |
| |
| // slice |
| // }; |
| |
| // &slices[0]; |
| // } |
| |
| // #[test] |
| // fn fails_due_to_mixed_spans() { |
| // SliceCache::new(16, |span0| { |
| // let (left, right) = span0.split_at(2); |
| |
| // SliceCache::new(4, |span1| { |
| // Box::new([left]) |
| // }); |
| |
| // Box::new([right]) |
| // }); |
| // } |
| |
| // #[test] |
| // fn fails_due_to_t_not_being_send() { |
| // use std::rc::Rc; |
| |
| // use rayon::prelude::*; |
| |
| // let mut array = [Rc::new(1), Rc::new(2), Rc::new(3)]; |
| |
| // let mut cache = SliceCache::new(3, |span| { |
| // let (left, right) = span.split_at(1); |
| // Box::new([left, right]) |
| // }); |
| |
| // cache.access(&mut array).unwrap().par_iter_mut().for_each(|slice| { |
| // for val in slice.iter_mut() { |
| // *val += 1; |
| // } |
| // }); |
| // } |
| |
| // #[test] |
| // fn fails_to_export_span() { |
| // let mut leaked = None; |
| |
| // let mut cache0 = SliceCache::new(1, |span| { |
| // leaked = Some(span); |
| // Box::new([]) |
| // }); |
| |
| // let mut cache1 = SliceCache::new(1, |span| { |
| // Box::new([leaked.take().unwrap()]) |
| // }); |
| // } |
| |
| // #[test] |
| // fn fails_due_to_dropped_slice() { |
| // let mut array = [1, 2, 3]; |
| |
| // let mut cache = SliceCache::new(3, |span| { |
| // let (left, right) = span.split_at(1); |
| // Box::new([left, right]) |
| // }); |
| |
| // let slices = cache.access(&mut array).unwrap(); |
| |
| // std::mem::drop(array); |
| |
| // slices[0][0] = 0; |
| // } |
| } |