diff --git a/crates/fj-kernel/src/stores/handle.rs b/crates/fj-kernel/src/stores/handle.rs new file mode 100644 index 000000000..bf6b992a9 --- /dev/null +++ b/crates/fj-kernel/src/stores/handle.rs @@ -0,0 +1,155 @@ +use std::{any::type_name, fmt, hash::Hash, ops::Deref}; + +use super::store::StoreInner; + +/// A handle for an object +/// +/// You can get an instance of `Handle` by inserting an object into a store. A +/// handle dereferences to the object it points to, via its [`Deref`] +/// implementation. +/// +/// # Equality and Identity +/// +/// Equality of `Handle`s is defined via the objects they reference. If those +/// objects are equal, the `Handle`s are considered equal. +/// +/// This is distinct from the *identity* of the referenced objects. Two objects +/// might be equal, but they might be have been created at different times, for +/// different reasons, and thus live in different slots in the storage. This is +/// a relevant distinction when validating objects, as equal but not identical +/// objects might be a sign of a bug. +/// +/// You can compare the identity of two objects through their `Handle`s, by +/// comparing the values returned by [`Handle::id`]. +pub struct Handle { + pub(super) store: StoreInner, + pub(super) ptr: *const Option, +} + +impl Handle { + /// Access this pointer's unique id + pub fn id(&self) -> ObjectId { + ObjectId(self.ptr as u64) + } + + /// Return a clone of the object this handle refers to + pub fn clone_object(&self) -> T + where + T: Clone, + { + self.deref().clone() + } +} + +impl Deref for Handle { + type Target = T; + + fn deref(&self) -> &Self::Target { + // `Handle` keeps a reference to `StoreInner`. Since that is an `Arc` + // under the hood, we know that as long as an instance of `Handle` + // exists, the `StoreInner` its data lives in is still alive. Even if + // the `Store` was dropped. + // + // The `Store` API ensures two things: + // + // 1. That no `Handle` is ever created, until the object it references + // has at least been reserved. + // 2. That the memory objects live in is never deallocated. + // + // That means that as long as a `Handle` exists, the object is + // references has at least been reserved, and has not been deallocated. + // + // Given all this, we know that the following must be true: + // + // - The pointer is not null. + // - The pointer is properly aligned. + // - The pointer is dereferenceable. + // - The pointer points to an initialized instance of `T`. + // + // Further, there is no way to (safely) get a `&mut` reference to any + // object in a `Store`/`Block`. So we know that the aliasing rules for + // the reference we return here are enforced. + // + // Furthermore, all of the code mentioned here is covered by unit tests, + // which I've run successfully run under Miri. + let cell = unsafe { &*self.ptr }; + + // Can only happen, if the object has been reserved, but the reservation + // was never completed. + cell.as_ref() + .expect("Handle references non-existing object") + } +} + +impl Clone for Handle { + fn clone(&self) -> Self { + Self { + store: self.store.clone(), + ptr: self.ptr, + } + } +} + +impl Eq for Handle where T: Eq {} + +impl PartialEq for Handle +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.deref().eq(other.deref()) + } +} + +impl Hash for Handle +where + T: Hash, +{ + fn hash(&self, state: &mut H) { + self.deref().hash(state) + } +} + +impl Ord for Handle +where + T: Ord, +{ + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.deref().cmp(other.deref()) + } +} + +impl PartialOrd for Handle +where + T: PartialOrd, +{ + fn partial_cmp(&self, other: &Self) -> Option { + self.deref().partial_cmp(other.deref()) + } +} + +impl fmt::Debug for Handle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let name = { + let type_name = type_name::(); + match type_name.rsplit_once("::") { + Some((_, name)) => name, + None => type_name, + } + }; + let id = self.id().0; + + write!(f, "{name} @ {id:#x}")?; + + Ok(()) + } +} + +unsafe impl Send for Handle {} +unsafe impl Sync for Handle {} + +/// Represents the ID of an object +/// +/// See [`Handle::id`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct ObjectId(u64); diff --git a/crates/fj-kernel/src/stores/mod.rs b/crates/fj-kernel/src/stores/mod.rs index 8788bf278..3b871d8a6 100644 --- a/crates/fj-kernel/src/stores/mod.rs +++ b/crates/fj-kernel/src/stores/mod.rs @@ -1,11 +1,15 @@ //! Append-only object storage mod blocks; +mod handle; mod store; use crate::objects::GlobalCurve; -pub use self::store::{Handle, Iter, ObjectId, Reservation, Store}; +pub use self::{ + handle::{Handle, ObjectId}, + store::{Iter, Reservation, Store}, +}; /// The available object stores #[derive(Debug, Default)] diff --git a/crates/fj-kernel/src/stores/store.rs b/crates/fj-kernel/src/stores/store.rs index 1f3f9c451..27d94e53b 100644 --- a/crates/fj-kernel/src/stores/store.rs +++ b/crates/fj-kernel/src/stores/store.rs @@ -21,13 +21,11 @@ //! //! But in any case, this was fun to write, and not that much work. -use std::{ - any::type_name, fmt, hash::Hash, marker::PhantomData, ops::Deref, sync::Arc, -}; +use std::{marker::PhantomData, sync::Arc}; use parking_lot::RwLock; -use super::blocks::Blocks; +use super::{blocks::Blocks, Handle}; /// Append-only object storage #[derive(Debug)] @@ -98,158 +96,6 @@ impl<'a, T> IntoIterator for &'a Store { } } -/// A handle for an object -/// -/// You can get an instance of `Handle` by inserting an object into a store. See -/// [`Store::insert`]. A handle dereferences to the object it points to, via its -/// [`Deref`] implementation. -/// -/// # Equality and Identity -/// -/// Equality of `Handle`s is defined via the objects they reference. If those -/// objects are equal, the `Handle`s are considered equal. -/// -/// This is distinct from the *identity* of the referenced objects. Two objects -/// might be equal, but they might be have been created at different times, for -/// different reasons, and thus live in different slots in the storage. This is -/// a relevant distinction when validating objects, as equal but not identical -/// objects might be a sign of a bug. -/// -/// You can compare the identity of two objects through their `Handle`s, by -/// comparing the values returned by [`Handle::id`]. -pub struct Handle { - store: StoreInner, - ptr: *const Option, -} - -impl Handle { - /// Access this pointer's unique id - pub fn id(&self) -> ObjectId { - ObjectId(self.ptr as u64) - } - - /// Return a clone of the object this handle refers to - pub fn clone_object(&self) -> T - where - T: Clone, - { - self.deref().clone() - } -} - -impl Deref for Handle { - type Target = T; - - fn deref(&self) -> &Self::Target { - // `Handle` keeps a reference to `StoreInner`. Since that is an `Arc` - // under the hood, we know that as long as an instance of `Handle` - // exists, the `StoreInner` its data lives in is still alive. Even if - // the `Store` was dropped. - // - // The `Store` API ensures two things: - // - // 1. That no `Handle` is ever created, until the object it references - // has at least been reserved. - // 2. That the memory objects live in is never deallocated. - // - // That means that as long as a `Handle` exists, the object is - // references has at least been reserved, and has not been deallocated. - // - // Given all this, we know that the following must be true: - // - // - The pointer is not null. - // - The pointer is properly aligned. - // - The pointer is dereferenceable. - // - The pointer points to an initialized instance of `T`. - // - // Further, there is no way to (safely) get a `&mut` reference to any - // object in a `Store`/`Block`. So we know that the aliasing rules for - // the reference we return here are enforced. - // - // Furthermore, all of the code mentioned here is covered by unit tests, - // which I've run successfully run under Miri. - let cell = unsafe { &*self.ptr }; - - // Can only happen, if the object has been reserved, but the reservation - // was never completed. - cell.as_ref() - .expect("Handle references non-existing object") - } -} - -impl Clone for Handle { - fn clone(&self) -> Self { - Self { - store: self.store.clone(), - ptr: self.ptr, - } - } -} - -impl Eq for Handle where T: Eq {} - -impl PartialEq for Handle -where - T: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.deref().eq(other.deref()) - } -} - -impl Hash for Handle -where - T: Hash, -{ - fn hash(&self, state: &mut H) { - self.deref().hash(state) - } -} - -impl Ord for Handle -where - T: Ord, -{ - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.deref().cmp(other.deref()) - } -} - -impl PartialOrd for Handle -where - T: PartialOrd, -{ - fn partial_cmp(&self, other: &Self) -> Option { - self.deref().partial_cmp(other.deref()) - } -} - -impl fmt::Debug for Handle { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let name = { - let type_name = type_name::(); - match type_name.rsplit_once("::") { - Some((_, name)) => name, - None => type_name, - } - }; - let id = self.id().0; - - write!(f, "{name} @ {id:#x}")?; - - Ok(()) - } -} - -unsafe impl Send for Handle {} -unsafe impl Sync for Handle {} - -/// Represents the ID of an object -/// -/// See [`Handle::id`]. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct ObjectId(u64); - /// An iterator over objects in a [`Store`] pub struct Iter<'a, T> { store: StoreInner,