diff --git a/stabby-abi/src/alloc/mod.rs b/stabby-abi/src/alloc/mod.rs index e3f99b4..d4d414d 100644 --- a/stabby-abi/src/alloc/mod.rs +++ b/stabby-abi/src/alloc/mod.rs @@ -264,6 +264,13 @@ impl AllocPtr { marker: PhantomData, } } + /// Casts an allocated pointer. + pub const fn cast(self) -> AllocPtr { + AllocPtr { + ptr: self.ptr.cast(), + marker: PhantomData, + } + } /// The offset between `self.ptr` and the prefix. pub const fn prefix_skip() -> usize { AllocPrefix::::skip_to::() diff --git a/stabby-abi/src/fatptr.rs b/stabby-abi/src/fatptr.rs index 7c1c1cf..ffbc680 100644 --- a/stabby-abi/src/fatptr.rs +++ b/stabby-abi/src/fatptr.rs @@ -140,7 +140,7 @@ impl<'a, Vt: Copy + 'a> DynRef<'a, Vt> { where Vt: PartialEq + IConstConstructor<'a, T>, { - (self.vtable == Vt::VTABLE).then(|| unsafe { self.ptr.as_ref() }) + (self.vtable == Vt::vtable()).then(|| unsafe { self.ptr.as_ref() }) } /// Downcasts the reference based on its reflection report. pub fn stable_downcast(&self) -> Option<&T> @@ -286,8 +286,7 @@ impl<'a, P: IPtrOwned + IPtr, Vt: HasDropVt + 'a> Dyn<'a, P, Vt> { where Vt: PartialEq + Copy + IConstConstructor<'a, T>, { - eprintln!("{:p} vs {:p}", self.vtable(), Vt::VTABLE); - (self.vtable == Vt::VTABLE).then(|| unsafe { self.ptr.as_ref() }) + (self.vtable == Vt::vtable()).then(|| unsafe { self.ptr.as_ref() }) } /// Downcasts the reference based on its reflection report. pub fn stable_downcast_ref(&self) -> Option<&T> @@ -321,7 +320,7 @@ impl<'a, P: IPtrOwned + IPtr, Vt: HasDropVt + 'a> Dyn<'a, P, Vt> { Vt: PartialEq + Copy + IConstConstructor<'a, T>, P: IPtrMut, { - (self.vtable == Vt::VTABLE).then(|| unsafe { self.ptr.as_mut() }) + (self.vtable == Vt::vtable()).then(|| unsafe { self.ptr.as_mut() }) } /// Downcasts the mutable reference based on its reflection report. pub fn stable_downcast_mut(&mut self) -> Option<&mut T> @@ -344,7 +343,7 @@ where fn from(value: P) -> Self { Self { ptr: core::mem::ManuallyDrop::new(value.anonimize()), - vtable: Vt::VTABLE, + vtable: Vt::vtable(), unsend: core::marker::PhantomData, } } @@ -363,7 +362,7 @@ impl<'a, T, Vt: Copy + IConstConstructor<'a, T>> From<&'a T> for DynRef<'a, Vt> unsafe { DynRef { ptr: core::mem::transmute(value), - vtable: Vt::VTABLE, + vtable: Vt::vtable(), unsend: core::marker::PhantomData, } } diff --git a/stabby-abi/src/vtable.rs b/stabby-abi/src/vtable.rs index c4e7e68..5b00427 100644 --- a/stabby-abi/src/vtable.rs +++ b/stabby-abi/src/vtable.rs @@ -12,21 +12,140 @@ // Pierre Avital, // -use crate as stabby; +use crate::{self as stabby}; +use core::{ + hash::{Hash, Hasher}, + marker::PhantomData, + ptr::NonNull, +}; #[rustversion::nightly] /// Implementation detail for stabby's version of dyn traits. /// Any type that implements a trait `ITrait` must implement `IConstConstructor` for `stabby::dyn!(Ptr)::from(value)` to work. pub trait IConstConstructor<'a, Source>: 'a + Copy + core::marker::Freeze { /// The vtable. - const VTABLE: &'a Self; + const VTABLE: Self; + /// A reference to the vtable + const VTABLE_REF: &'a Self = &Self::VTABLE; + /// Returns the reference to the vtable + fn vtable() -> &'a Self { + Self::VTABLE_REF + } } -#[rustversion::not(nightly)] + +#[rustversion::before(1.78.0)] /// Implementation detail for stabby's version of dyn traits. /// Any type that implements a trait `ITrait` must implement `IConstConstructor` for `stabby::dyn!(Ptr)::from(value)` to work. pub trait IConstConstructor<'a, Source>: 'a + Copy { /// The vtable. - const VTABLE: &'a Self; + const VTABLE: Self; + /// A reference to the vtable + const VTABLE_REF: &'a Self = &Self::VTABLE; + /// Returns the reference to the vtable + fn vtable() -> &'a Self { + Self::VTABLE_REF + } +} + +#[cfg(feature = "libc")] +#[rustversion::all(since(1.78.0), not(nightly))] +static VTABLES: core::sync::atomic::AtomicPtr< + crate::alloc::vec::Vec<( + u64, + crate::alloc::AllocPtr<*const (), crate::alloc::DefaultAllocator>, + )>, +> = core::sync::atomic::AtomicPtr::new(core::ptr::null_mut()); +#[cfg(feature = "libc")] +#[rustversion::all(since(1.78.0), not(nightly))] +/// Implementation detail for stabby's version of dyn traits. +/// Any type that implements a trait `ITrait` must implement `IConstConstructor` for `stabby::dyn!(Ptr)::from(value)` to work. +pub trait IConstConstructor<'a, Source>: 'a + Copy + core::hash::Hash + core::fmt::Debug { + /// The vtable. + const VTABLE: Self; + /// Returns the reference to the vtable + fn vtable() -> &'a Self { + use crate::alloc::{boxed::Box, vec::Vec, AllocPtr, DefaultAllocator}; + let vtable = Self::VTABLE; + #[allow(deprecated)] + let hash = { + let mut hasher = core::hash::SipHasher::new(); + vtable.hash(&mut hasher); + hasher.finish() + }; + fn insert_vtable(hash: u64, vtable: &[*const ()]) -> AllocPtr<*const (), DefaultAllocator> { + let mut search_start = 0; + let mut allocated_vt = None; + let mut vtables = VTABLES.load(core::sync::atomic::Ordering::SeqCst); + loop { + let vts = match unsafe { vtables.as_ref() } { + None => [].as_slice(), + Some(vts) => match vts[search_start..] + .iter() + .find_map(|e| (e.0 == hash).then_some(e.1)) + { + Some(vt) => return vt, + None => vts, + }, + }; + let vt = allocated_vt.unwrap_or_else(|| { + let mut ptr = + AllocPtr::alloc_array(&mut DefaultAllocator::new(), vtable.len()).unwrap(); + unsafe { + core::ptr::copy_nonoverlapping(vtable.as_ptr(), ptr.as_mut(), vtable.len()) + }; + ptr + }); + allocated_vt = Some(vt); + let updated = VTABLES.load(core::sync::atomic::Ordering::SeqCst); + if !core::ptr::eq(vtables, updated) { + vtables = updated; + continue; + } + let mut vec = Vec::with_capacity(vts.len() + 1); + vec.copy_extend(vts); + vec.push((hash, vt)); + let mut vec = Box::into_raw(Box::new(vec)); + match VTABLES.compare_exchange( + updated, + unsafe { vec.as_mut() }, + core::sync::atomic::Ordering::SeqCst, + core::sync::atomic::Ordering::SeqCst, + ) { + Ok(updated) => { + if let Some(updated) = NonNull::new(updated) { + unsafe { + Box::from_raw(AllocPtr { + ptr: updated, + marker: PhantomData::, + }) + }; + } + return vt; + } + Err(new_vtables) => { + search_start = vts.len(); + vtables = new_vtables; + unsafe { Box::from_raw(vec) }; + } + } + } + } + unsafe { + let vtable = core::slice::from_raw_parts( + (&vtable as *const Self).cast(), + core::mem::size_of::() / core::mem::size_of::<*const ()>(), + ); + let vt = insert_vtable(hash, vtable).cast().as_ref(); + debug_assert_eq!( + core::slice::from_raw_parts( + (vt as *const Self).cast::<*const ()>(), + core::mem::size_of::() / core::mem::size_of::<*const ()>(), + ), + vtable + ); + vt + } + } } /// Implementation detail for stabby's version of dyn traits. @@ -42,7 +161,7 @@ pub struct T(T); /// You should _always_ use `stabby::vtable!(Trait1 + Trait2 + ...)` to generate this type, /// as this macro will ensure that traits are ordered consistently in the vtable. #[stabby::stabby] -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct VTable { /// The rest of the vtable. /// @@ -63,13 +182,13 @@ where Head: IConstConstructor<'a, T>, Tail: IConstConstructor<'a, T>, { - const VTABLE: &'a VTable = &VTable { - head: *Head::VTABLE, - tail: *Tail::VTABLE, + const VTABLE: VTable = VTable { + head: Head::VTABLE, + tail: Tail::VTABLE, }; } impl<'a, T> IConstConstructor<'a, T> for () { - const VTABLE: &'a () = &(); + const VTABLE: () = (); } impl TransitiveDeref for VTable { fn tderef(&self) -> &Head { @@ -122,11 +241,21 @@ impl HasSyncVt for VTable {} // DROP /// The vtable to drop a value in place #[stabby::stabby] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Eq)] pub struct VtDrop { /// The [`Drop::drop`] function, shimmed with the C calling convention. pub drop: crate::StableLike, } +impl core::fmt::Debug for VtDrop { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "VtDrop({:p})", unsafe { self.drop.as_ref_unchecked() }) + } +} +impl Hash for VtDrop { + fn hash(&self, state: &mut H) { + self.drop.hash(state) + } +} impl PartialEq for VtDrop { fn eq(&self, other: &Self) -> bool { core::ptr::eq( @@ -136,7 +265,7 @@ impl PartialEq for VtDrop { } } impl<'a, T> IConstConstructor<'a, T> for VtDrop { - const VTABLE: &'a VtDrop = &VtDrop { + const VTABLE: VtDrop = VtDrop { drop: unsafe { core::mem::transmute({ unsafe extern "C" fn drop(this: &mut T) { @@ -150,7 +279,7 @@ impl<'a, T> IConstConstructor<'a, T> for VtDrop { /// A marker for vtables for types that are `Send` #[stabby::stabby] -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct VtSend(pub T); impl CompoundVt for dyn Send { type Vt = VtSend; @@ -166,18 +295,18 @@ impl From>> for VTable> IConstConstructor<'a, T> for VtSend { - const VTABLE: &'a VtSend = &VtSend(*Vt::VTABLE); + const VTABLE: VtSend = VtSend(Vt::VTABLE); } /// A marker for vtables for types that are `Sync` #[stabby::stabby] -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct VtSync(pub T); impl CompoundVt for dyn Sync { type Vt = VtSync; } impl<'a, T: Sync, Vt: IConstConstructor<'a, T>> IConstConstructor<'a, T> for VtSync { - const VTABLE: &'a VtSync = &VtSync(*Vt::VTABLE); + const VTABLE: VtSync = VtSync(Vt::VTABLE); } impl, Vt, N> TransitiveDeref for VtSync { fn tderef(&self) -> &Vt { diff --git a/stabby-macros/src/traits.rs b/stabby-macros/src/traits.rs index 55bb2c6..fd937fe 100644 --- a/stabby-macros/src/traits.rs +++ b/stabby-macros/src/traits.rs @@ -654,6 +654,8 @@ impl<'a> DynTraitDescription<'a> { )* } }; + let vtid_str = vtid.to_string(); + let all_fn_ids_str = all_fn_ids.iter().map(|id| id.to_string()); vtable_decl = crate::stabby(proc_macro::TokenStream::new(), vtable_decl.into()).into(); quote! { #[doc = #vt_doc] @@ -669,6 +671,18 @@ impl<'a> DynTraitDescription<'a> { #(core::ptr::eq((*unsafe{self.#all_fn_ids.as_ref_unchecked()}) as *const (), (*unsafe{other.#all_fn_ids.as_ref_unchecked()}) as *const _) &&)* true } } + impl< #vt_generics > core::hash::Hash for #vtid < #nbvt_generics > where #(#vt_bounds)*{ + fn hash(&self, state: &mut H) { + #(self.#all_fn_ids.hash(state);)* + } + } + impl< #vt_generics > core::fmt::Debug for #vtid < #nbvt_generics > where #(#vt_bounds)*{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut s = f.debug_struct(#vtid_str); + #(s.field(#all_fn_ids_str, &core::format_args!("{:p}", unsafe{self.#all_fn_ids.as_ref_unchecked()}));)* + s.finish() + } + } impl< 'stabby_vt_lt, #(#trait_lts,)* @@ -683,7 +697,7 @@ impl<'a> DynTraitDescription<'a> { #(#unbound_trait_types: 'stabby_vt_lt,)* #(#dyntrait_types: 'stabby_vt_lt,)* { #[doc = #vt_doc] - const VTABLE: &'stabby_vt_lt #vt_signature = & #vtid { + const VTABLE: #vt_signature = #vtid { #(#all_fn_ids: unsafe {core::mem::transmute(#self_as_trait::#all_fn_ids as #fn_ptrs)},)* }; }