Skip to content

Commit de124d9

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

File tree

3 files changed

+196
-4
lines changed

3 files changed

+196
-4
lines changed

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: 194 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,50 @@ where
149201
}
150202
}
151203

204+
#[cfg(feature = "nightly")]
205+
impl<'a, T> Iterator for MetaIterMut<'a, T>
206+
where
207+
T: ?Sized + 'a,
208+
T: Pointee<Metadata = DynMetadata<T>>,
209+
{
210+
type Item = AtomicRefMut<'a, T>;
211+
212+
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
213+
loop {
214+
let resource_id = match self.tys.get(self.index) {
215+
Some(&x) => ResourceId::from_type_id(x),
216+
None => return None,
217+
};
218+
219+
let index = self.index;
220+
self.index += 1;
221+
222+
// Note: this relies on implementation details of
223+
// try_fetch_internal!
224+
// SAFETY: We don't swap out the Box or expose a mutable reference to it.
225+
if let Some(res) = unsafe { self.world.try_fetch_internal(resource_id) } {
226+
let vtable = self.vtables[index];
227+
let trait_object =
228+
AtomicRefMut::map(res.borrow_mut(), |res: &mut Box<dyn Resource>| {
229+
let ptr: *mut dyn Resource = Box::as_mut(res);
230+
let trait_ptr = core::ptr::from_raw_parts_mut(ptr.cast::<()>(), vtable);
231+
// SAFETY: For a particular index we store a corresponding
232+
// TypeId and vtable in tys and vtables respectively.
233+
// We rely on `try_fetch_interal` returning a trait object
234+
// with a concrete type that has the provided TypeId. The
235+
// signature of the closure parameter of `AtomicRefMut::map`
236+
// should ensure we aren't accidentally extending the
237+
// lifetime here. Also see safety note in
238+
// `MetaTable::get_mut`.
239+
unsafe { &mut *trait_ptr }
240+
});
241+
242+
return Some(trait_object);
243+
}
244+
}
245+
}
246+
}
247+
152248
/// Given an address and provenance, produces a pointer to a trait object for
153249
/// which `CastFrom<T>` is implemented.
154250
///
@@ -159,6 +255,7 @@ where
159255
///
160256
/// We exclusively operate on pointers here so we only need a single function
161257
/// pointer in the meta-table for both `&T` and `&mut T` cases.
258+
#[cfg(not(feature = "nightly"))]
162259
fn attach_vtable<TraitObject: ?Sized, T>(value: *mut ()) -> *mut TraitObject
163260
where
164261
TraitObject: CastFrom<T> + 'static,
@@ -245,10 +342,10 @@ where
245342
/// }
246343
/// ```
247344
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
345+
#[cfg(not(feature = "nightly"))]
251346
vtable_fns: Vec<fn(*mut ()) -> *mut T>,
347+
#[cfg(feature = "nightly")]
348+
vtables: Vec<DynMetadata<T>>,
252349
indices: HashMap<TypeId, usize>,
253350
tys: Vec<TypeId>,
254351
// `MetaTable` is invariant over `T`
@@ -258,12 +355,15 @@ pub struct MetaTable<T: ?Sized> {
258355
impl<T: ?Sized> MetaTable<T> {
259356
/// Creates a new `MetaTable`.
260357
pub fn new() -> Self {
358+
// TODO: when ptr_metadata is stablilized this can just be a trait bound: Pointee<Metadata
359+
// = DynMetadata<T>>
261360
assert_unsized::<T>();
262361

263362
Default::default()
264363
}
265364

266365
/// Registers a resource `R` that implements the trait `T`.
366+
#[cfg(not(feature = "nightly"))]
267367
pub fn register<R>(&mut self)
268368
where
269369
R: Resource,
@@ -289,9 +389,47 @@ impl<T: ?Sized> MetaTable<T> {
289389
}
290390
}
291391

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

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

487+
/// Tries to convert `world` to a trait object of type `&mut T`.
488+
/// If `world` doesn't have an implementation for `T` (or it wasn't
489+
/// registered), this will return `None`.
490+
#[cfg(feature = "nightly")]
491+
pub fn get_mut<'a>(&self, res: &'a mut dyn Resource) -> Option<&'a mut T>
492+
where
493+
T: Pointee<Metadata = DynMetadata<T>>,
494+
{
495+
self.indices.get(&res.type_id()).map(|&ind| {
496+
let vtable = self.vtables[ind];
497+
let ptr = <*mut dyn Resource>::cast::<()>(res);
498+
let trait_ptr = core::ptr::from_raw_parts_mut(ptr, vtable);
499+
// SAFETY: We retrieved the `vtable` via TypeId so it will be a
500+
// vtable that corresponds with the erased type that the TypeId
501+
// refers to. `from_raw_parts_mut` will also preserve the provenance
502+
// and address (so we can safely produce a mutable reference since
503+
// we started with one).
504+
unsafe { &mut *trait_ptr }
505+
})
506+
}
507+
327508
/// Iterates all resources that implement `T` and were registered.
328509
pub fn iter<'a>(&'a self, res: &'a World) -> MetaIter<'a, T> {
329510
MetaIter {
511+
#[cfg(not(feature = "nightly"))]
330512
vtable_fns: &self.vtable_fns,
513+
#[cfg(feature = "nightly")]
514+
vtables: &self.vtables,
331515
index: 0,
332516
world: res,
333517
tys: &self.tys,
@@ -338,7 +522,10 @@ impl<T: ?Sized> MetaTable<T> {
338522
/// Iterates all resources that implement `T` and were registered mutably.
339523
pub fn iter_mut<'a>(&'a self, res: &'a World) -> MetaIterMut<'a, T> {
340524
MetaIterMut {
525+
#[cfg(not(feature = "nightly"))]
341526
vtable_fns: &self.vtable_fns,
527+
#[cfg(feature = "nightly")]
528+
vtables: &self.vtables,
342529
index: 0,
343530
world: res,
344531
tys: &self.tys,
@@ -353,7 +540,10 @@ where
353540
{
354541
fn default() -> Self {
355542
MetaTable {
543+
#[cfg(not(feature = "nightly"))]
356544
vtable_fns: Default::default(),
545+
#[cfg(feature = "nightly")]
546+
vtables: Default::default(),
357547
indices: Default::default(),
358548
tys: Default::default(),
359549
marker: Default::default(),
@@ -362,7 +552,7 @@ where
362552
}
363553

364554
fn assert_unsized<T: ?Sized>() {
365-
use std::mem::size_of;
555+
use core::mem::size_of;
366556

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

0 commit comments

Comments
 (0)