From: Ian Jackson Date: Thu, 14 Nov 2024 17:59:22 +0000 (+0000) Subject: wip X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ian/git?a=commitdiff_plain;h=ef5e6e14d092188b5919e40ef5b97f228b2df163;p=manually-boxed wip --- diff --git a/src/lib.rs b/src/lib.rs index 436871d..d7549d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,33 @@ #![allow(unused)] +use std::fmt::{self, Debug}; use std::marker::PhantomData; use std::ptr::NonNull; -// Not Clone or Copy +/// Singleton, used for compile-time alias checking +#[derive(Debug)] // Not Clone or Copy +#[non_exhaustive] // unsafe to construct pub struct NoAliasSingleton { - _unsafe_to_construct: (), } +/// Something where `&self` implies `&NoAliasSingleton` +/// +/// Implemented for `NoAliasSingleton` and the ZST reference tokens, +/// and `&`-references to them. pub unsafe trait IsRefToken<'a> { - fn ref_token(&'a self) -> RefToken<'a> { RefToken(PhantomData) } + /// Obtain a new ZST token from something that might be a real object. + fn ref_token(&'a self) -> RefToken<'a> { + RefToken(PhantomData) + } } + +/// Something where `&mut self` implies `&mut NoAliasSingleton` +/// +/// Implemented for `NoAliasSingleton` and `MutToken`, +/// and `&mut`-references to them. pub unsafe trait IsMutToken<'a>: IsRefToken<'a> { + /// Obtain a new ZST token from something that might be a real object. fn mut_token<'r, 's>(&'s mut self) -> MutToken<'r> where 's: 'r, 'a: 'r, { @@ -32,28 +47,35 @@ where A: IsMutToken<'a>, 'a: 'r, 'aa: 'r {} unsafe impl<'r, 'a, 'aa, A> IsMutToken<'r> for &'aa mut A where A: IsMutToken<'a>, 'a: 'r, 'aa: 'r {} +/// `&NoAliasSingleton`, but a ZST +/// +/// We don't actually ever need to look at the data for the singleton. +/// So the actual pointer is redundant, and, here, absent. #[derive(Debug, Clone, Copy)] pub struct RefToken<'a>( PhantomData<&'a NoAliasSingleton>, ); + +/// `&mut NoAliasSingleton`, but a ZST +/// +/// See [`RefToken`] #[derive(Debug)] pub struct MutToken<'a>( PhantomData<&'a mut NoAliasSingleton>, ); -/*impl<'a> MutToken<'a>{ - pub fn ref_token(&'a self) -> RefToken<'a> { - RefToken(PhantomData) - } -}*/ - -/*impl<'a> MutToken<'a> { - pub fn reborrow<'s: 'a>(&'s mut self) -> MutToken<'a> { - todo!() - } -}*/ pub struct Ptr { - // invariant over T? + /// # SAFETY + /// + /// Invariants (only while valid, but we can rely on them + /// since our caller is not allowed to call any of our methods + /// on an invalid Ptr): + /// + /// * Always points to a valid T on the heap: was `Box`. + /// * All references given out are compatible with the borrow + /// of the `NoAliasSingleton` (possibly via `IsRefToken` + /// or `IsMutToken`. + // // XXXX check variance ptr: NonNull, } @@ -63,8 +85,23 @@ impl Clone for Ptr { } impl Copy for Ptr {} +impl Debug for Ptr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Ptr").field(&self.ptr).finish() + } +} + impl Ptr { - pub fn new_heap(t: T) -> Self { todo!() } + pub fn new_heap(t: T) -> Self { + let b = Box::new(t); + let ptr = Box::into_raw(b); + let ptr = unsafe { + // SAFETY + // Box.into_raw() guarantees it's not null + NonNull::new_unchecked(ptr) + }; + Ptr { ptr } + } /* /// SAFETY @@ -80,12 +117,26 @@ TODO lifetime is hard #[inline] pub fn borrow<'a>(self, tok: impl IsRefToken<'a>) -> &'a T { - todo!() + unsafe { + // SAFETY + // + // Points to a valid T by our invariant. + // There can be no concurrent &mut T + // because that would imply a concurrent `&mut NoAliasSingleton`. + self.ptr.as_ref() + } } #[inline] - pub fn borrow_mut<'a>(self, tok: impl IsMutToken<'a>) -> &'a mut T { - todo!() + pub fn borrow_mut<'a>(mut self, tok: impl IsMutToken<'a>) -> &'a mut T { + unsafe { + // SAFETY + // + // Points to a valid T by our invariant. + // There can be no other concurrent &T or &mut T + // because that would imply a concurrent `&NoAliasSingleton`. + self.ptr.as_mut() + } } /// Useful if you want to compare pointers, or something @@ -108,7 +159,27 @@ TODO lifetime is hard /// (The compiler will check that no borrows are live.) #[inline] pub unsafe fn free_heap<'a>(self, tok: impl IsMutToken<'a>) -> T { - todo!() + let t: Box = unsafe { + // SAFETY + // + // Points to a valid T that came from Box by our invariant. + // There can be no other concurrent borrows + // (see safety comment for borrow_mut). + // + // We rely on the caller promising that no-one else + // is going to do this. + Box::from_raw(self.ptr.as_ptr()) + }; + *t + } +} + +pub trait OptionPtrExt { + fn as_ptr(self) -> Option<*mut T>; +} +impl OptionPtrExt for Option> { + fn as_ptr(self) -> Option<*mut T> { + self.map(|p| p.ptr.as_ptr()) } } @@ -116,6 +187,8 @@ TODO lifetime is hard }*/ impl NoAliasSingleton { + /// Initialise by creating the singleton for alias checking + /// /// # SAFETY /// /// There must be only one `NoAliasSingleton` used with each `Ptr`. @@ -130,48 +203,53 @@ impl NoAliasSingleton { // // I haven't been able to think of a practical safe API, // but one only needs about 1 call to init_unchecked. - pub unsafe fn init() -> Self { todo!() } + pub unsafe fn init() -> Self { NoAliasSingleton {} } } -impl<'a> MutToken<'a> { -/* - pub fn multi(&mut self) -> MultiAccessor<(), 0> { todo!() } -*/ -} - -/* -impl MultiAccessor<'a, R, const N: usize> { - pub fn finish(self) -> R { todo!() } - - pub fn borrow(self, p: &mut MutToken -*/ - #[cfg(test)] mod tests { use super::*; - use std::fmt::Write as _; + use fmt::Write as _; mod list { use super::*; + #[derive(Debug)] pub struct Node { back: Option>>, next: Option>>, pub data: T, } - pub struct List { + #[derive(Debug)] + pub struct List { head: Option>>, tail: Option>>, noalias: NoAliasSingleton, } - impl List { + impl List { pub fn new() -> Self { let noalias = unsafe { NoAliasSingleton::init() }; List { head: None, tail: None, noalias } } + pub fn check_consistency(&self) { + dbg!(self); + let tok = self.noalias.ref_token(); + let mut last: Option>> = None; + let mut node = self.head; + while let Some(node_) = node { + dbg!(node_); + let node__ = node_.borrow(&tok); + dbg!(node__); + assert_eq!(last.as_ptr(), node__.back.as_ptr()); + last = Some(node_); + node = node__.next; + } + assert_eq!(self.tail.as_ptr(), last.as_ptr()); + } + pub fn append(&mut self, data: T) { let node = Node { back: self.head, @@ -179,8 +257,12 @@ mod tests { data, }; let node = Ptr::new_heap(node); + if let Some(old_tail) = self.tail { + old_tail.borrow_mut(&mut self.noalias).next = Some(node); + } else { + self.head = Some(node); + }; self.tail = Some(node); - if self.head.is_none() { self.head = Some(node) }; } pub fn front(&self) -> Option<&T> { @@ -224,7 +306,7 @@ mod tests { } } - impl Drop for List { + impl Drop for List { fn drop(&mut self) { while let Some(_) = Self::pop_front_inner( self.noalias.mut_token(), @@ -239,15 +321,21 @@ mod tests { fn demo() { use list::List; + let s = |s: &str| format!("{s}"); + let l = List::::new(); + l.check_consistency(); assert_eq!(l.front(), None); drop(l); - let mut l = List::new(); - l.append(format!("hi")); - l.append(format!("ho")); + let mut l = List::new(); l.check_consistency(); + l.append(s("hi")); l.check_consistency(); + l.append(s("ho")); l.check_consistency(); + write!(l.front_mut().unwrap(), "!").unwrap(); - assert_eq!(l.front(), Some(&format!("hi!"))); + assert_eq!(l.pop_front(), Some(s("hi!"))); l.check_consistency(); + assert_eq!(l.front(), Some(&s("ho"))); l.check_consistency(); + drop(l); } }