Skip to content

Commit b84d8b3

Browse files
committed
Add nightly feature to enable MetaTable using ptr_metadata feature, which is potentially more efficient
1 parent 146b35d commit b84d8b3

3 files changed

Lines changed: 195 additions & 4 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ shred-derive = { path = "shred-derive", version = "0.6.3" }
3535
[features]
3636
default = ["parallel", "shred-derive"]
3737
parallel = ["rayon"]
38+
nightly = []
3839

3940
[[example]]
4041
name = "async"

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#![cfg_attr(feature = "nightly", feature(ptr_metadata, strict_provenance))]
12
//! **Sh**ared **re**source **d**ispatcher
23
//!
34
//! This library allows to dispatch

src/meta.rs

Lines changed: 193 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ use ahash::AHashMap as HashMap;
55
use crate::cell::{AtomicRef, AtomicRefMut};
66
use crate::{Resource, ResourceId, World};
77

8+
#[cfg(feature = "nightly")]
9+
use core::ptr::{DynMetadata, Pointee};
10+
811
/// This implements `Send` and `Sync` unconditionally.
912
/// (the trait itself doesn't need to have these bounds and the
1013
/// resources are already guaranteed to fulfill it).
@@ -50,14 +53,18 @@ pub unsafe trait CastFrom<T> {
5053

5154
/// An iterator for the `MetaTable`.
5255
pub struct MetaIter<'a, T: ?Sized + 'a> {
56+
#[cfg(not(feature = "nightly"))]
5357
vtable_fns: &'a [fn(*mut ()) -> *mut T],
58+
#[cfg(feature = "nightly")]
59+
vtables: &'a [DynMetadata<T>],
5460
index: usize,
5561
tys: &'a [TypeId],
5662
// `MetaIter` is invariant over `T`
5763
marker: PhantomData<Invariant<T>>,
5864
world: &'a World,
5965
}
6066

67+
#[cfg(not(feature = "nightly"))]
6168
impl<'a, T> Iterator for MetaIter<'a, T>
6269
where
6370
T: ?Sized + 'a,
@@ -97,16 +104,61 @@ where
97104
}
98105
}
99106

107+
#[cfg(feature = "nightly")]
108+
impl<'a, T> Iterator for MetaIter<'a, T>
109+
where
110+
T: ?Sized + 'a,
111+
T: Pointee<Metadata = DynMetadata<T>>,
112+
{
113+
type Item = AtomicRef<'a, T>;
114+
115+
#[allow(clippy::borrowed_box)] // variant of https://github.com/rust-lang/rust-clippy/issues/5770
116+
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
117+
loop {
118+
let resource_id = match self.tys.get(self.index) {
119+
Some(&x) => ResourceId::from_type_id(x),
120+
None => return None,
121+
};
122+
123+
let index = self.index;
124+
self.index += 1;
125+
126+
// SAFETY: We just read the value and don't replace it.
127+
if let Some(res) = unsafe { self.world.try_fetch_internal(resource_id) } {
128+
let vtable = self.vtables[index];
129+
let trait_object = AtomicRef::map(res.borrow(), |res: &Box<dyn Resource>| {
130+
let ptr: *const dyn Resource = Box::as_ref(res);
131+
let trait_ptr = core::ptr::from_raw_parts(ptr.cast::<()>(), vtable);
132+
// SAFETY: For a particular index we store a corresponding
133+
// TypeId and vtable in tys and vtables respectively.
134+
// We rely on `try_fetch_interal` returning a trait object
135+
// with a concrete type that has the provided TypeId. The
136+
// signature of the closure parameter of `AtomicRef::map`
137+
// should ensure we aren't accidentally extending the
138+
// lifetime here. Also see safety note in `MetaTable::get`.
139+
unsafe { &*trait_ptr }
140+
});
141+
142+
return Some(trait_object);
143+
}
144+
}
145+
}
146+
}
147+
100148
/// A mutable iterator for the `MetaTable`.
101149
pub struct MetaIterMut<'a, T: ?Sized + 'a> {
150+
#[cfg(not(feature = "nightly"))]
102151
vtable_fns: &'a [fn(*mut ()) -> *mut T],
152+
#[cfg(feature = "nightly")]
153+
vtables: &'a [DynMetadata<T>],
103154
index: usize,
104155
tys: &'a [TypeId],
105156
// `MetaIterMut` is invariant over `T`
106157
marker: PhantomData<Invariant<T>>,
107158
world: &'a World,
108159
}
109160

161+
#[cfg(not(feature = "nightly"))]
110162
impl<'a, T> Iterator for MetaIterMut<'a, T>
111163
where
112164
T: ?Sized + 'a,
@@ -149,6 +201,49 @@ where
149201
}
150202
}
151203

204+
impl<'a, T> Iterator for MetaIterMut<'a, T>
205+
where
206+
T: ?Sized + 'a,
207+
T: Pointee<Metadata = DynMetadata<T>>,
208+
{
209+
type Item = AtomicRefMut<'a, T>;
210+
211+
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
212+
loop {
213+
let resource_id = match self.tys.get(self.index) {
214+
Some(&x) => ResourceId::from_type_id(x),
215+
None => return None,
216+
};
217+
218+
let index = self.index;
219+
self.index += 1;
220+
221+
// Note: this relies on implementation details of
222+
// try_fetch_internal!
223+
// SAFETY: We don't swap out the Box or expose a mutable reference to it.
224+
if let Some(res) = unsafe { self.world.try_fetch_internal(resource_id) } {
225+
let vtable = self.vtables[index];
226+
let trait_object =
227+
AtomicRefMut::map(res.borrow_mut(), |res: &mut Box<dyn Resource>| {
228+
let ptr: *mut dyn Resource = Box::as_mut(res);
229+
let trait_ptr = core::ptr::from_raw_parts_mut(ptr.cast::<()>(), vtable);
230+
// SAFETY: For a particular index we store a corresponding
231+
// TypeId and vtable in tys and vtables respectively.
232+
// We rely on `try_fetch_interal` returning a trait object
233+
// with a concrete type that has the provided TypeId. The
234+
// signature of the closure parameter of `AtomicRefMut::map`
235+
// should ensure we aren't accidentally extending the
236+
// lifetime here. Also see safety note in
237+
// `MetaTable::get_mut`.
238+
unsafe { &mut *trait_ptr }
239+
});
240+
241+
return Some(trait_object);
242+
}
243+
}
244+
}
245+
}
246+
152247
/// Given an address and provenance, produces a pointer to a trait object for
153248
/// which `CastFrom<T>` is implemented.
154249
///
@@ -159,6 +254,7 @@ where
159254
///
160255
/// We exclusively operate on pointers here so we only need a single function
161256
/// pointer in the meta-table for both `&T` and `&mut T` cases.
257+
#[cfg(not(feature = "nightly"))]
162258
fn attach_vtable<TraitObject: ?Sized, T>(value: *mut ()) -> *mut TraitObject
163259
where
164260
TraitObject: CastFrom<T> + 'static,
@@ -245,10 +341,10 @@ where
245341
/// }
246342
/// ```
247343
pub struct MetaTable<T: ?Sized> {
248-
// TODO: When `ptr_metadata` is stabilized we can use that to implement this
249-
// without a function call (and without trying to make assumptions about the
250-
// layout of trait object pointers). https://github.com/rust-lang/rust/issues/81513
344+
#[cfg(not(feature = "nightly"))]
251345
vtable_fns: Vec<fn(*mut ()) -> *mut T>,
346+
#[cfg(feature = "nightly")]
347+
vtables: Vec<DynMetadata<T>>,
252348
indices: HashMap<TypeId, usize>,
253349
tys: Vec<TypeId>,
254350
// `MetaTable` is invariant over `T`
@@ -258,12 +354,15 @@ pub struct MetaTable<T: ?Sized> {
258354
impl<T: ?Sized> MetaTable<T> {
259355
/// Creates a new `MetaTable`.
260356
pub fn new() -> Self {
357+
// TODO: when ptr_metadata is stablilized this can just be a trait bound: Pointee<Metadata
358+
// = DynMetadata<T>>
261359
assert_unsized::<T>();
262360

263361
Default::default()
264362
}
265363

266364
/// Registers a resource `R` that implements the trait `T`.
365+
#[cfg(not(feature = "nightly"))]
267366
pub fn register<R>(&mut self)
268367
where
269368
R: Resource,
@@ -289,9 +388,47 @@ impl<T: ?Sized> MetaTable<T> {
289388
}
290389
}
291390

391+
/// Registers a resource `R` that implements the trait `T`.
392+
#[cfg(feature = "nightly")]
393+
pub fn register<R>(&mut self)
394+
where
395+
R: Resource,
396+
T: CastFrom<R> + 'static,
397+
T: Pointee<Metadata = DynMetadata<T>>,
398+
{
399+
let ty_id = TypeId::of::<R>();
400+
// use self.addr() for unpredictable address to use for checking consistency below
401+
let invalid_ptr = core::ptr::invalid_mut::<R>((self as *mut Self).addr());
402+
let trait_ptr = <T as CastFrom<R>>::cast(invalid_ptr);
403+
// assert that address not changed (to catch some mistakes in CastFrom impl)
404+
assert_eq!(
405+
invalid_ptr.addr(),
406+
trait_ptr.addr(),
407+
"Bug: `CastFrom` did not cast `self`"
408+
);
409+
let vtable = core::ptr::metadata(trait_ptr);
410+
411+
// Important: ensure no entry exists twice!
412+
let len = self.indices.len();
413+
match self.indices.entry(ty_id) {
414+
Entry::Occupied(occ) => {
415+
let ind = *occ.get();
416+
417+
self.vtables[ind] = vtable;
418+
}
419+
Entry::Vacant(vac) => {
420+
vac.insert(len);
421+
422+
self.vtables.push(vtable);
423+
self.tys.push(ty_id);
424+
}
425+
}
426+
}
427+
292428
/// Tries to convert `world` to a trait object of type `&T`.
293429
/// If `world` doesn't have an implementation for `T` (or it wasn't
294430
/// registered), this will return `None`.
431+
#[cfg(not(feature = "nightly"))]
295432
pub fn get<'a>(&self, res: &'a dyn Resource) -> Option<&'a T> {
296433
self.indices.get(&res.type_id()).map(|&ind| {
297434
let vtable_fn = self.vtable_fns[ind];
@@ -307,9 +444,31 @@ impl<T: ?Sized> MetaTable<T> {
307444
})
308445
}
309446

447+
/// Tries to convert `world` to a trait object of type `&T`.
448+
/// If `world` doesn't have an implementation for `T` (or it wasn't
449+
/// registered), this will return `None`.
450+
#[cfg(feature = "nightly")]
451+
pub fn get<'a>(&self, res: &'a dyn Resource) -> Option<&'a T>
452+
where
453+
T: Pointee<Metadata = DynMetadata<T>>,
454+
{
455+
self.indices.get(&res.type_id()).map(|&ind| {
456+
let vtable = self.vtables[ind];
457+
let ptr = <*const dyn Resource>::cast::<()>(res);
458+
let trait_ptr = core::ptr::from_raw_parts(ptr, vtable);
459+
// SAFETY: We retrieved the `vtable` via TypeId so it will be a
460+
// vtable that corresponds with the erased type that the TypeId
461+
// refers to. `from_raw_parts` will also preserve the provenance and
462+
// address (so we can safely produce a shared reference since we
463+
// started with one).
464+
unsafe { &*trait_ptr }
465+
})
466+
}
467+
310468
/// Tries to convert `world` to a trait object of type `&mut T`.
311469
/// If `world` doesn't have an implementation for `T` (or it wasn't
312470
/// registered), this will return `None`.
471+
#[cfg(not(feature = "nightly"))]
313472
pub fn get_mut<'a>(&self, res: &'a mut dyn Resource) -> Option<&'a mut T> {
314473
self.indices.get(&res.type_id()).map(|&ind| {
315474
let vtable_fn = self.vtable_fns[ind];
@@ -324,10 +483,34 @@ impl<T: ?Sized> MetaTable<T> {
324483
})
325484
}
326485

486+
/// Tries to convert `world` to a trait object of type `&mut T`.
487+
/// If `world` doesn't have an implementation for `T` (or it wasn't
488+
/// registered), this will return `None`.
489+
#[cfg(feature = "nightly")]
490+
pub fn get_mut<'a>(&self, res: &'a mut dyn Resource) -> Option<&'a mut T>
491+
where
492+
T: Pointee<Metadata = DynMetadata<T>>,
493+
{
494+
self.indices.get(&res.type_id()).map(|&ind| {
495+
let vtable = self.vtables[ind];
496+
let ptr = <*mut dyn Resource>::cast::<()>(res);
497+
let trait_ptr = core::ptr::from_raw_parts_mut(ptr, vtable);
498+
// SAFETY: We retrieved the `vtable` via TypeId so it will be a
499+
// vtable that corresponds with the erased type that the TypeId
500+
// refers to. `from_raw_parts_mut` will also preserve the provenance
501+
// and address (so we can safely produce a mutable reference since
502+
// we started with one).
503+
unsafe { &mut *trait_ptr }
504+
})
505+
}
506+
327507
/// Iterates all resources that implement `T` and were registered.
328508
pub fn iter<'a>(&'a self, res: &'a World) -> MetaIter<'a, T> {
329509
MetaIter {
510+
#[cfg(not(feature = "nightly"))]
330511
vtable_fns: &self.vtable_fns,
512+
#[cfg(feature = "nightly")]
513+
vtables: &self.vtables,
331514
index: 0,
332515
world: res,
333516
tys: &self.tys,
@@ -338,7 +521,10 @@ impl<T: ?Sized> MetaTable<T> {
338521
/// Iterates all resources that implement `T` and were registered mutably.
339522
pub fn iter_mut<'a>(&'a self, res: &'a World) -> MetaIterMut<'a, T> {
340523
MetaIterMut {
524+
#[cfg(not(feature = "nightly"))]
341525
vtable_fns: &self.vtable_fns,
526+
#[cfg(feature = "nightly")]
527+
vtables: &self.vtables,
342528
index: 0,
343529
world: res,
344530
tys: &self.tys,
@@ -353,7 +539,10 @@ where
353539
{
354540
fn default() -> Self {
355541
MetaTable {
542+
#[cfg(not(feature = "nightly"))]
356543
vtable_fns: Default::default(),
544+
#[cfg(feature = "nightly")]
545+
vtables: Default::default(),
357546
indices: Default::default(),
358547
tys: Default::default(),
359548
marker: Default::default(),
@@ -362,7 +551,7 @@ where
362551
}
363552

364553
fn assert_unsized<T: ?Sized>() {
365-
use std::mem::size_of;
554+
use core::mem::size_of;
366555

367556
assert_eq!(size_of::<&T>(), 2 * size_of::<usize>());
368557
}

0 commit comments

Comments
 (0)