From b84d8b31db67d76f1245bbd065b590544de4c364 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 23 Jul 2023 16:41:24 -0400 Subject: [PATCH] Add nightly feature to enable MetaTable using ptr_metadata feature, which is potentially more efficient --- Cargo.toml | 1 + src/lib.rs | 1 + src/meta.rs | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 195 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2514aac..ee77eaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ shred-derive = { path = "shred-derive", version = "0.6.3" } [features] default = ["parallel", "shred-derive"] parallel = ["rayon"] +nightly = [] [[example]] name = "async" diff --git a/src/lib.rs b/src/lib.rs index f13ada5..7857e6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(feature = "nightly", feature(ptr_metadata, strict_provenance))] //! **Sh**ared **re**source **d**ispatcher //! //! This library allows to dispatch diff --git a/src/meta.rs b/src/meta.rs index a7059c9..608de48 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -5,6 +5,9 @@ use ahash::AHashMap as HashMap; use crate::cell::{AtomicRef, AtomicRefMut}; use crate::{Resource, ResourceId, World}; +#[cfg(feature = "nightly")] +use core::ptr::{DynMetadata, Pointee}; + /// This implements `Send` and `Sync` unconditionally. /// (the trait itself doesn't need to have these bounds and the /// resources are already guaranteed to fulfill it). @@ -50,7 +53,10 @@ pub unsafe trait CastFrom { /// An iterator for the `MetaTable`. pub struct MetaIter<'a, T: ?Sized + 'a> { + #[cfg(not(feature = "nightly"))] vtable_fns: &'a [fn(*mut ()) -> *mut T], + #[cfg(feature = "nightly")] + vtables: &'a [DynMetadata], index: usize, tys: &'a [TypeId], // `MetaIter` is invariant over `T` @@ -58,6 +64,7 @@ pub struct MetaIter<'a, T: ?Sized + 'a> { world: &'a World, } +#[cfg(not(feature = "nightly"))] impl<'a, T> Iterator for MetaIter<'a, T> where T: ?Sized + 'a, @@ -97,9 +104,53 @@ where } } +#[cfg(feature = "nightly")] +impl<'a, T> Iterator for MetaIter<'a, T> +where + T: ?Sized + 'a, + T: Pointee>, +{ + type Item = AtomicRef<'a, T>; + + #[allow(clippy::borrowed_box)] // variant of https://github.com/rust-lang/rust-clippy/issues/5770 + fn next(&mut self) -> Option<::Item> { + loop { + let resource_id = match self.tys.get(self.index) { + Some(&x) => ResourceId::from_type_id(x), + None => return None, + }; + + let index = self.index; + self.index += 1; + + // SAFETY: We just read the value and don't replace it. + if let Some(res) = unsafe { self.world.try_fetch_internal(resource_id) } { + let vtable = self.vtables[index]; + let trait_object = AtomicRef::map(res.borrow(), |res: &Box| { + let ptr: *const dyn Resource = Box::as_ref(res); + let trait_ptr = core::ptr::from_raw_parts(ptr.cast::<()>(), vtable); + // SAFETY: For a particular index we store a corresponding + // TypeId and vtable in tys and vtables respectively. + // We rely on `try_fetch_interal` returning a trait object + // with a concrete type that has the provided TypeId. The + // signature of the closure parameter of `AtomicRef::map` + // should ensure we aren't accidentally extending the + // lifetime here. Also see safety note in `MetaTable::get`. + unsafe { &*trait_ptr } + }); + + return Some(trait_object); + } + } + } +} + /// A mutable iterator for the `MetaTable`. pub struct MetaIterMut<'a, T: ?Sized + 'a> { + #[cfg(not(feature = "nightly"))] vtable_fns: &'a [fn(*mut ()) -> *mut T], + #[cfg(feature = "nightly")] + vtables: &'a [DynMetadata], index: usize, tys: &'a [TypeId], // `MetaIterMut` is invariant over `T` @@ -107,6 +158,7 @@ pub struct MetaIterMut<'a, T: ?Sized + 'a> { world: &'a World, } +#[cfg(not(feature = "nightly"))] impl<'a, T> Iterator for MetaIterMut<'a, T> where T: ?Sized + 'a, @@ -149,6 +201,49 @@ where } } +impl<'a, T> Iterator for MetaIterMut<'a, T> +where + T: ?Sized + 'a, + T: Pointee>, +{ + type Item = AtomicRefMut<'a, T>; + + fn next(&mut self) -> Option<::Item> { + loop { + let resource_id = match self.tys.get(self.index) { + Some(&x) => ResourceId::from_type_id(x), + None => return None, + }; + + let index = self.index; + self.index += 1; + + // Note: this relies on implementation details of + // try_fetch_internal! + // SAFETY: We don't swap out the Box or expose a mutable reference to it. + if let Some(res) = unsafe { self.world.try_fetch_internal(resource_id) } { + let vtable = self.vtables[index]; + let trait_object = + AtomicRefMut::map(res.borrow_mut(), |res: &mut Box| { + let ptr: *mut dyn Resource = Box::as_mut(res); + let trait_ptr = core::ptr::from_raw_parts_mut(ptr.cast::<()>(), vtable); + // SAFETY: For a particular index we store a corresponding + // TypeId and vtable in tys and vtables respectively. + // We rely on `try_fetch_interal` returning a trait object + // with a concrete type that has the provided TypeId. The + // signature of the closure parameter of `AtomicRefMut::map` + // should ensure we aren't accidentally extending the + // lifetime here. Also see safety note in + // `MetaTable::get_mut`. + unsafe { &mut *trait_ptr } + }); + + return Some(trait_object); + } + } + } +} + /// Given an address and provenance, produces a pointer to a trait object for /// which `CastFrom` is implemented. /// @@ -159,6 +254,7 @@ where /// /// We exclusively operate on pointers here so we only need a single function /// pointer in the meta-table for both `&T` and `&mut T` cases. +#[cfg(not(feature = "nightly"))] fn attach_vtable(value: *mut ()) -> *mut TraitObject where TraitObject: CastFrom + 'static, @@ -245,10 +341,10 @@ where /// } /// ``` pub struct MetaTable { - // TODO: When `ptr_metadata` is stabilized we can use that to implement this - // without a function call (and without trying to make assumptions about the - // layout of trait object pointers). https://github.com/rust-lang/rust/issues/81513 + #[cfg(not(feature = "nightly"))] vtable_fns: Vec *mut T>, + #[cfg(feature = "nightly")] + vtables: Vec>, indices: HashMap, tys: Vec, // `MetaTable` is invariant over `T` @@ -258,12 +354,15 @@ pub struct MetaTable { impl MetaTable { /// Creates a new `MetaTable`. pub fn new() -> Self { + // TODO: when ptr_metadata is stablilized this can just be a trait bound: Pointee> assert_unsized::(); Default::default() } /// Registers a resource `R` that implements the trait `T`. + #[cfg(not(feature = "nightly"))] pub fn register(&mut self) where R: Resource, @@ -289,9 +388,47 @@ impl MetaTable { } } + /// Registers a resource `R` that implements the trait `T`. + #[cfg(feature = "nightly")] + pub fn register(&mut self) + where + R: Resource, + T: CastFrom + 'static, + T: Pointee>, + { + let ty_id = TypeId::of::(); + // use self.addr() for unpredictable address to use for checking consistency below + let invalid_ptr = core::ptr::invalid_mut::((self as *mut Self).addr()); + let trait_ptr = >::cast(invalid_ptr); + // assert that address not changed (to catch some mistakes in CastFrom impl) + assert_eq!( + invalid_ptr.addr(), + trait_ptr.addr(), + "Bug: `CastFrom` did not cast `self`" + ); + let vtable = core::ptr::metadata(trait_ptr); + + // Important: ensure no entry exists twice! + let len = self.indices.len(); + match self.indices.entry(ty_id) { + Entry::Occupied(occ) => { + let ind = *occ.get(); + + self.vtables[ind] = vtable; + } + Entry::Vacant(vac) => { + vac.insert(len); + + self.vtables.push(vtable); + self.tys.push(ty_id); + } + } + } + /// Tries to convert `world` to a trait object of type `&T`. /// If `world` doesn't have an implementation for `T` (or it wasn't /// registered), this will return `None`. + #[cfg(not(feature = "nightly"))] pub fn get<'a>(&self, res: &'a dyn Resource) -> Option<&'a T> { self.indices.get(&res.type_id()).map(|&ind| { let vtable_fn = self.vtable_fns[ind]; @@ -307,9 +444,31 @@ impl MetaTable { }) } + /// Tries to convert `world` to a trait object of type `&T`. + /// If `world` doesn't have an implementation for `T` (or it wasn't + /// registered), this will return `None`. + #[cfg(feature = "nightly")] + pub fn get<'a>(&self, res: &'a dyn Resource) -> Option<&'a T> + where + T: Pointee>, + { + self.indices.get(&res.type_id()).map(|&ind| { + let vtable = self.vtables[ind]; + let ptr = <*const dyn Resource>::cast::<()>(res); + let trait_ptr = core::ptr::from_raw_parts(ptr, vtable); + // SAFETY: We retrieved the `vtable` via TypeId so it will be a + // vtable that corresponds with the erased type that the TypeId + // refers to. `from_raw_parts` will also preserve the provenance and + // address (so we can safely produce a shared reference since we + // started with one). + unsafe { &*trait_ptr } + }) + } + /// Tries to convert `world` to a trait object of type `&mut T`. /// If `world` doesn't have an implementation for `T` (or it wasn't /// registered), this will return `None`. + #[cfg(not(feature = "nightly"))] pub fn get_mut<'a>(&self, res: &'a mut dyn Resource) -> Option<&'a mut T> { self.indices.get(&res.type_id()).map(|&ind| { let vtable_fn = self.vtable_fns[ind]; @@ -324,10 +483,34 @@ impl MetaTable { }) } + /// Tries to convert `world` to a trait object of type `&mut T`. + /// If `world` doesn't have an implementation for `T` (or it wasn't + /// registered), this will return `None`. + #[cfg(feature = "nightly")] + pub fn get_mut<'a>(&self, res: &'a mut dyn Resource) -> Option<&'a mut T> + where + T: Pointee>, + { + self.indices.get(&res.type_id()).map(|&ind| { + let vtable = self.vtables[ind]; + let ptr = <*mut dyn Resource>::cast::<()>(res); + let trait_ptr = core::ptr::from_raw_parts_mut(ptr, vtable); + // SAFETY: We retrieved the `vtable` via TypeId so it will be a + // vtable that corresponds with the erased type that the TypeId + // refers to. `from_raw_parts_mut` will also preserve the provenance + // and address (so we can safely produce a mutable reference since + // we started with one). + unsafe { &mut *trait_ptr } + }) + } + /// Iterates all resources that implement `T` and were registered. pub fn iter<'a>(&'a self, res: &'a World) -> MetaIter<'a, T> { MetaIter { + #[cfg(not(feature = "nightly"))] vtable_fns: &self.vtable_fns, + #[cfg(feature = "nightly")] + vtables: &self.vtables, index: 0, world: res, tys: &self.tys, @@ -338,7 +521,10 @@ impl MetaTable { /// Iterates all resources that implement `T` and were registered mutably. pub fn iter_mut<'a>(&'a self, res: &'a World) -> MetaIterMut<'a, T> { MetaIterMut { + #[cfg(not(feature = "nightly"))] vtable_fns: &self.vtable_fns, + #[cfg(feature = "nightly")] + vtables: &self.vtables, index: 0, world: res, tys: &self.tys, @@ -353,7 +539,10 @@ where { fn default() -> Self { MetaTable { + #[cfg(not(feature = "nightly"))] vtable_fns: Default::default(), + #[cfg(feature = "nightly")] + vtables: Default::default(), indices: Default::default(), tys: Default::default(), marker: Default::default(), @@ -362,7 +551,7 @@ where } fn assert_unsized() { - use std::mem::size_of; + use core::mem::size_of; assert_eq!(size_of::<&T>(), 2 * size_of::()); }