diff --git a/Cargo.toml b/Cargo.toml index 304a10f..4f86e55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "im-lists" -version = "0.10.0" +version = "0.11.0" authors = ["mattwparas "] edition = "2021" readme = "README.md" @@ -14,6 +14,7 @@ categories = ["data-structures"] [dependencies] smallvec = { version = "1.10.0" } +generic_singleton = "0.5.1" [dev-dependencies] criterion = "0.3.5" diff --git a/src/list.rs b/src/list.rs index e43bb4a..d541632 100644 --- a/src/list.rs +++ b/src/list.rs @@ -15,7 +15,7 @@ use std::{cmp::Ordering, iter::FromIterator, marker::PhantomData}; use crate::{ handler::{DefaultDropHandler, DropHandler}, shared::{ArcPointer, PointerFamily, RcPointer}, - unrolled::{ConsumingWrapper, IterWrapper, UnrolledList}, + unrolled::{ConsumingWrapper, IterWrapper, UnrolledCell, UnrolledList}, }; /// A persistent list. @@ -55,7 +55,7 @@ use crate::{ /// This means for operations that for a normal linked list may take linear time *Θ(n)*, we get a constant factor /// decrease of either a factor of *m* or *m / 2*. Similarly, we will see O(log(n)) performance characteristics if the growth rate is set to be larger than 1. pub struct GenericList< - T: Clone, + T: Clone + 'static, P: PointerFamily = RcPointer, const N: usize = 256, const G: usize = 1, @@ -68,6 +68,16 @@ pub type List = GenericList; pub type SharedVList = GenericList; pub type VList = GenericList; +#[doc(hidden)] +#[derive(Copy, Clone)] +pub struct RawCell< + T: Clone + 'static, + P: PointerFamily, + const N: usize, + const G: usize, + D: DropHandler>, +>(*const UnrolledCell, PhantomData); + impl> Clone for GenericList { @@ -140,8 +150,21 @@ impl impl Iterator { + pub fn draining_iterator( + mut self, + // default: UnrolledList, + ) -> impl Iterator { std::mem::take(&mut self.0).draining_iterator() + // std::mem::replace(&mut self.0, default).draining_iterator() + // todo!() + // let x = MaybeUninit::new(self); + // let x = x.as_ptr(); + + // unsafe { std::ptr::read(&(*x).0).draining_iterator() } + + // self.0.draining_iterator() + // ManuallyDrop::take(slot) + // todo!() } #[doc(hidden)] @@ -156,6 +179,32 @@ impl RawCell { + RawCell(self.0.as_ptr(), PhantomData) + } + + /// Call a function on a raw pointer + /// # Safety + /// This must be called with a valid pointer as returned from as_ptr + #[doc(hidden)] + pub unsafe fn call_from_raw O>( + cell: RawCell, + func: F, + ) -> O { + let value = unsafe { Self::from_raw(cell) }; + let res = func(&value); + std::mem::forget(value); + res + } + + /// # Safety + /// This must be called with a valid pointer as returned from as_ptr + #[doc(hidden)] + unsafe fn from_raw(cell: RawCell) -> Self { + Self(UnrolledList(P::from_raw(cell.0)), PhantomData) + } + /// Get the length of the list /// /// # Examples @@ -593,7 +642,7 @@ impl< /// An iterator over lists with values of type `T`. pub struct Iter< 'a, - T: Clone, + T: Clone + 'static, P: PointerFamily, const N: usize, const G: usize, @@ -602,7 +651,7 @@ pub struct Iter< impl< 'a, - T: Clone, + T: Clone + 'static, P: PointerFamily, const N: usize, const G: usize, @@ -651,7 +700,7 @@ impl< /// A consuming iterator over lists with values of type `T`. pub struct ConsumingIter< - T: Clone, + T: Clone + 'static, P: PointerFamily, const N: usize, const G: usize, @@ -851,8 +900,15 @@ mod tests { use crate::{list, vlist}; #[test] - fn strong_count() { + fn strong_count_empty() { let list: List = List::new(); + assert!(list.strong_count() >= 1); + } + + #[test] + fn strong_count() { + let mut list: List = List::new(); + list.cons_mut(1); assert_eq!(list.strong_count(), 1); } @@ -1219,8 +1275,15 @@ mod arc_tests { use crate::{shared_list, shared_vlist, vlist}; #[test] - fn strong_count() { + fn strong_count_empty() { let list: SharedList = SharedList::new(); + assert!(list.strong_count() >= 1); + } + + #[test] + fn strong_count() { + let mut list: SharedList = SharedList::new(); + list.cons_mut(1); assert_eq!(list.strong_count(), 1); } @@ -1587,4 +1650,17 @@ mod arc_tests { // assert!(list.is_empty()) } + + #[test] + fn raw_test() { + let list = (0..1000usize).into_iter().collect::>(); + + // Get the inner pointer, and then otherwise + // call the drop implementation as neatly as possible. + let pointer = list.as_ptr(); + + // Create value from pointer + let value = unsafe { SharedVList::from_raw(pointer) }; + std::mem::forget(value); + } } diff --git a/src/shared.rs b/src/shared.rs index 2879ce4..3a00cfe 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -1,7 +1,7 @@ use std::{ops::Deref, rc::Rc, sync::Arc}; -pub trait PointerFamily { - type Pointer: Deref; +pub trait PointerFamily: 'static { + type Pointer: Deref; fn new(value: T) -> Self::Pointer; fn strong_count(this: &Self::Pointer) -> usize; @@ -11,80 +11,100 @@ pub trait PointerFamily { fn make_mut(ptr: &mut Self::Pointer) -> &mut T; fn clone(ptr: &Self::Pointer) -> Self::Pointer; fn as_ptr(this: &Self::Pointer) -> *const T; + fn into_raw(this: Self::Pointer) -> *const T; + /// # Safety + /// This must be called from a pointer as returned from into_raw + unsafe fn from_raw(this: *const T) -> Self::Pointer; } pub struct RcPointer; impl PointerFamily for RcPointer { - type Pointer = Rc; + type Pointer = Rc; - fn new(value: T) -> Self::Pointer { + fn new(value: T) -> Self::Pointer { Rc::new(value) } - fn strong_count(this: &Self::Pointer) -> usize { + fn strong_count(this: &Self::Pointer) -> usize { Rc::strong_count(this) } - fn try_unwrap(this: Self::Pointer) -> Option { + fn try_unwrap(this: Self::Pointer) -> Option { Rc::try_unwrap(this).ok() } - fn get_mut(this: &mut Self::Pointer) -> Option<&mut T> { + fn get_mut(this: &mut Self::Pointer) -> Option<&mut T> { Rc::get_mut(this) } - fn ptr_eq(this: &Self::Pointer, other: &Self::Pointer) -> bool { + fn ptr_eq(this: &Self::Pointer, other: &Self::Pointer) -> bool { Rc::ptr_eq(this, other) } - fn make_mut(ptr: &mut Self::Pointer) -> &mut T { + fn make_mut(ptr: &mut Self::Pointer) -> &mut T { Rc::make_mut(ptr) } - fn clone(ptr: &Self::Pointer) -> Self::Pointer { + fn clone(ptr: &Self::Pointer) -> Self::Pointer { Rc::clone(ptr) } - fn as_ptr(this: &Self::Pointer) -> *const T { + fn as_ptr(this: &Self::Pointer) -> *const T { Rc::as_ptr(this) } + + fn into_raw(this: Self::Pointer) -> *const T { + Rc::into_raw(this) + } + + unsafe fn from_raw(this: *const T) -> Self::Pointer { + Rc::from_raw(this) + } } pub struct ArcPointer; impl PointerFamily for ArcPointer { - type Pointer = Arc; + type Pointer = Arc; - fn new(value: T) -> Self::Pointer { + fn new(value: T) -> Self::Pointer { Arc::new(value) } - fn strong_count(this: &Self::Pointer) -> usize { + fn strong_count(this: &Self::Pointer) -> usize { Arc::strong_count(this) } - fn try_unwrap(this: Self::Pointer) -> Option { + fn try_unwrap(this: Self::Pointer) -> Option { Arc::try_unwrap(this).ok() } - fn get_mut(this: &mut Self::Pointer) -> Option<&mut T> { + fn get_mut(this: &mut Self::Pointer) -> Option<&mut T> { Arc::get_mut(this) } - fn ptr_eq(this: &Self::Pointer, other: &Self::Pointer) -> bool { + fn ptr_eq(this: &Self::Pointer, other: &Self::Pointer) -> bool { Arc::ptr_eq(this, other) } - fn make_mut(ptr: &mut Self::Pointer) -> &mut T { + fn make_mut(ptr: &mut Self::Pointer) -> &mut T { Arc::make_mut(ptr) } - fn clone(ptr: &Self::Pointer) -> Self::Pointer { + fn clone(ptr: &Self::Pointer) -> Self::Pointer { Arc::clone(ptr) } - fn as_ptr(this: &Self::Pointer) -> *const T { + fn as_ptr(this: &Self::Pointer) -> *const T { Arc::as_ptr(this) } + + fn into_raw(this: Self::Pointer) -> *const T { + Arc::into_raw(this) + } + + unsafe fn from_raw(this: *const T) -> Self::Pointer { + Arc::from_raw(this) + } } diff --git a/src/unrolled.rs b/src/unrolled.rs index 27cbf8b..67194d8 100644 --- a/src/unrolled.rs +++ b/src/unrolled.rs @@ -16,7 +16,7 @@ type ConsumingIter = FlatMap< fn(UnrolledList) -> MaybeCloned, // Rev>>, >; -enum MaybeCloned { +enum MaybeCloned { Owned(Rev>>), Cloned(OwnedNodeIterator), } @@ -86,8 +86,24 @@ type DrainingConsumingIter = FlatMap< fn(UnrolledList) -> Rev>>, >; +fn empty_list( +) -> P::Pointer> +where + P::Pointer>: 'static, +{ + let mut output = None; + generic_singleton::get_or_init_thread_local!( + || P::new(UnrolledCell::new()), + |cell: &P::Pointer>| { + output = Some(P::clone(cell)); + } + ); + + output.unwrap() +} + #[derive(Eq)] -pub(crate) struct UnrolledList( +pub struct UnrolledList( pub(crate) P::Pointer>, ); @@ -126,7 +142,13 @@ impl Default impl UnrolledList { pub fn new() -> Self { - UnrolledList(P::new(UnrolledCell::new())) + // UnrolledList(P::new(UnrolledCell::new())) + + UnrolledList(empty_list::()) + } + + pub fn as_ptr(&self) -> *const UnrolledCell { + P::as_ptr(&self.0) } pub fn new_with_capacity() -> Self { @@ -423,20 +445,36 @@ impl UnrolledList Option<&mut Self> { if self.0.index > 1 { + // This will allocate a new cell P::make_mut(&mut self.0).index -= 1; Some(self) } else { - let inner = P::make_mut(&mut self.0); - let output = inner.next.take(); - match output { - Some(x) => { - *self = x; - Some(self) - } - None => { - *self = Self::new(); - None + let inner = P::get_mut(&mut self.0); + + match inner { + Some(inner) => { + let output = inner.next.take(); + match output { + Some(x) => { + *self = x; + Some(self) + } + None => { + *self = Self::new(); + None + } + } } + None => match &self.0.next { + Some(x) => { + *self = x.clone(); + Some(self) + } + None => { + *self = Self::new(); + None + } + }, } } } @@ -573,7 +611,9 @@ impl UnrolledList Drop for UnrolledCell { +impl Drop + for UnrolledCell +{ fn drop(&mut self) { let mut cur = self.next.take().map(|x| x.0); loop { @@ -588,7 +628,7 @@ impl Drop for Unroll } } -pub(crate) struct UnrolledCell { +pub struct UnrolledCell { index: usize, elements: P::Pointer>, pub(crate) next: Option>, @@ -702,7 +742,12 @@ impl Extend } } -pub(crate) struct DrainingNodeIter { +pub(crate) struct DrainingNodeIter< + T: Clone + 'static, + P: PointerFamily, + const N: usize, + const G: usize, +> { cur: Option>, _inner: PhantomData, } @@ -712,6 +757,8 @@ impl Iterator { type Item = UnrolledList; fn next(&mut self) -> Option { + // This is doing allocation. Don't want that. + if let Some(mut _self) = std::mem::take(&mut self.cur) { if let Some(next) = _self.0.next.as_ref() { // If we can, drop these values! @@ -729,10 +776,43 @@ impl Iterator } else { None } + + // let mut _self = &mut self.cur; + // let mut ret = None; + + // if let Some(next) = _self.as_mut().and_then(|x| x.0.next.as_ref()) { + // // If the next is empty, then we don't want to point to it? + // if next.is_empty() { + // let mut value = None; + // std::mem::swap(&mut self.cur, &mut value); + // return value; + // } + + // // If we can, drop these values! + // if next.strong_count() == 1 && P::strong_count(&next.0.elements) == 1 { + // // self.cur = _self.0.next.clone(); + // // self.cur = P::get_mut(&mut _self.0).and_then(|x| x.next.take()); + // // todo!() + + // let mut value = _self + // .as_mut() + // .and_then(|x| P::get_mut(&mut x.0).and_then(|x| x.next.take())); + + // std::mem::swap(&mut self.cur, &mut value); + + // ret = value + // } else { + // self.cur = None + // } + // } else { + // self.cur = None + // } + + // ret } } -pub(crate) struct NodeIter { +pub(crate) struct NodeIter { cur: Option>, _inner: PhantomData, } @@ -749,12 +829,18 @@ impl Iterator for No } } -pub(crate) struct NodeIterRef<'a, T: Clone, P: PointerFamily, const N: usize, const G: usize> { +pub(crate) struct NodeIterRef< + 'a, + T: Clone + 'static, + P: PointerFamily, + const N: usize, + const G: usize, +> { cur: Option<&'a UnrolledList>, _inner: PhantomData, } -impl<'a, T: Clone, P: PointerFamily, const N: usize, const G: usize> Iterator +impl<'a, T: Clone + 'static, P: PointerFamily, const N: usize, const G: usize> Iterator for NodeIterRef<'a, T, P, N, G> { type Item = &'a UnrolledList; @@ -769,8 +855,8 @@ impl<'a, T: Clone, P: PointerFamily, const N: usize, const G: usize> Iterator } } -pub(crate) struct DrainingConsumingWrapper< - T: Clone, +pub struct DrainingConsumingWrapper< + T: Clone + 'static, P: PointerFamily, const N: usize, const G: usize, @@ -801,8 +887,7 @@ impl Iterator } } -// TODO have this expose tryfold -pub(crate) struct ConsumingWrapper( +pub struct ConsumingWrapper( ConsumingIter, ); @@ -831,7 +916,7 @@ impl Iterator } } -struct OwnedNodeIterator { +struct OwnedNodeIterator { list: UnrolledCell, } @@ -890,7 +975,7 @@ impl IntoIterator } // TODO have this also expose TryFold -pub(crate) struct IterWrapper<'a, T: Clone, P: PointerFamily, const N: usize, const G: usize>( +pub struct IterWrapper<'a, T: Clone + 'static, P: PointerFamily, const N: usize, const G: usize>( RefIter<'a, T, P, N, G>, ); @@ -1134,7 +1219,7 @@ mod tests { use crate::shared::RcPointer; - type RcList = UnrolledList; + type RcList = UnrolledList; use super::*; @@ -1182,7 +1267,7 @@ mod iterator_tests { const CAPACITY: usize = 256; - type RcList = UnrolledList; + type RcList = UnrolledList; #[test] fn check_size() { @@ -1686,7 +1771,7 @@ mod reference_counting_correctness { use super::*; use crate::shared::RcPointer; - type RcList = UnrolledList; + type RcList = UnrolledList; #[derive(Clone)] enum Value { diff --git a/src/unrolled/proptests.rs b/src/unrolled/proptests.rs index 82f12e6..7d3a9ee 100644 --- a/src/unrolled/proptests.rs +++ b/src/unrolled/proptests.rs @@ -83,7 +83,8 @@ fn action_strategy() -> impl Strategy { any::().prop_map(Action::PushBack), Just(Action::PopFront), // Getting values should work... - (0..100usize).prop_map(Action::Get) + (0..100usize).prop_map(Action::Get), + Just(Action::Clone), ] } @@ -580,7 +581,7 @@ fn append_zero_then_popfront() { } } -run_property_tests!(unrolled_linked_list, crate::unrolled::UnrolledList); +run_property_tests!(unrolled_linked_list, crate::unrolled::UnrolledList); run_property_tests!(vlist, crate::unrolled::UnrolledList);