blob: cb5282a1d5a7adacd296f3ab2fd1e2ce490755f5 [file] [log] [blame]
// 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], &copy[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;
// }
}