From 01455bc8101dc304b688db89b7ab07549bad64b1 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Wed, 1 May 2024 16:23:09 +0200 Subject: [PATCH 01/10] Better modulo computation --- stabby-abi/src/typenum2/unsigned.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/stabby-abi/src/typenum2/unsigned.rs b/stabby-abi/src/typenum2/unsigned.rs index f5051c8..00a0438 100644 --- a/stabby-abi/src/typenum2/unsigned.rs +++ b/stabby-abi/src/typenum2/unsigned.rs @@ -295,6 +295,8 @@ pub trait IPowerOf2: IUnsigned { type Min: IPowerOf2; /// max(Self, T) type Max: IPowerOf2; + /// T % Self + type Modulate: IUnsigned; } impl IUnsigned for U { const U128: u128 = Self::_U128; @@ -319,7 +321,7 @@ impl IUnsigned for U { type Min = as IBit>::UTernary; type Max = as IBit>::UTernary; type Truncate = as IUnsignedBase>::_Simplified; - type Mod = Self::Truncate; + type Mod = T::Modulate; type Padding = Self::_Padding; type NonZero = Self::_NonZero; type NextMultipleOf = @@ -441,11 +443,14 @@ impl> IPowerOf2 for UInt { type Log2 = U0; type Min = as IBit>::PTernary; type Max = as IBit>::PTernary; + type Modulate = UTerm; } impl IPowerOf2 for UInt { type Log2 = ::Increment; type Min = as IBit>::PTernary; type Max = as IBit>::PTernary; + type Modulate = + , T::Bit> as IUnsignedBase>::_Simplified; } #[test] @@ -574,6 +579,7 @@ fn ops() { let _: U2 = <::BitAnd>::default(); let _: B1 = <::Equal<::BitAnd>>::default(); let _: B0 = <::Equal<::BitAnd>>::default(); + let _: U11 = <::Modulate>::default(); let _: U11 = <::Mod>::default(); let _: U10 = <::Mod>::default(); let _: U3 = <::Mod>::default(); From caca752aaa4b80a318202296c60b8d85dc15ef6b Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Wed, 1 May 2024 16:48:04 +0200 Subject: [PATCH 02/10] More internal testing --- stabby-abi/src/typenum2/unsigned.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/stabby-abi/src/typenum2/unsigned.rs b/stabby-abi/src/typenum2/unsigned.rs index 00a0438..671333f 100644 --- a/stabby-abi/src/typenum2/unsigned.rs +++ b/stabby-abi/src/typenum2/unsigned.rs @@ -591,6 +591,11 @@ fn ops() { let _: U0 = <::Mod>::default(); let _: U255 = UxFF::default(); let _: Ub111100 = <::BitAnd>::default(); + let _: U0 = <::NextMultipleOf>::default(); + let _: U16 = <::NextMultipleOf>::default(); + let _: U16 = <::NextMultipleOf>::default(); + let _: U32 = <::NextMultipleOf>::default(); + let _: U32 = <::NextMultipleOf>::default(); assert_eq!(U0::_U128, 0); assert_eq!(U1::_U128, 1); assert_eq!(U2::_U128, 2); From d42f14983acdcadb599b67417dc8aea81eb01991 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Wed, 1 May 2024 17:25:18 +0200 Subject: [PATCH 03/10] Better computations for struct layout --- stabby-abi/src/istable.rs | 112 ++++------------------------ stabby-abi/src/typenum2/unsigned.rs | 9 +++ 2 files changed, 24 insertions(+), 97 deletions(-) diff --git a/stabby-abi/src/istable.rs b/stabby-abi/src/istable.rs index 2f8b203..3f5a031 100644 --- a/stabby-abi/src/istable.rs +++ b/stabby-abi/src/istable.rs @@ -14,6 +14,8 @@ use crate::report::TypeReport; +use self::unsigned::IUnsignedBase; + use super::typenum2::*; use super::unsigned::{IBitBase, NonZero}; use super::{FieldPair, Struct, Union}; @@ -37,9 +39,9 @@ macro_rules! same_as { /// # Safety /// Mis-implementing this trait can lead to memory corruption in sum tyoes pub unsafe trait IStable: Sized { - /// The size of the annotated type. + /// The size of the annotated type in bytes. type Size: Unsigned; - /// The alignment of the annotated type. + /// The alignment of the annotated type in bytes. type Align: PowerOf2; /// The values that the annotated type cannot occupy. type ForbiddenValues: IForbiddenValues; @@ -336,10 +338,7 @@ impl IsEnd for Array { type Output = B0; } -unsafe impl IStable for FieldPair -where - AlignedAfter: IStable, -{ +unsafe impl IStable for FieldPair { type ForbiddenValues = Or as IStable>::ForbiddenValues>; type UnusedBits = @@ -499,106 +498,25 @@ where pub struct AlignedAfter(core::marker::PhantomData<(T, Start)>); // AlignedAfter a ZST -unsafe impl IStable for AlignedAfter { - same_as!(T); -} -// Aligned after a non-ZST -unsafe impl IStable for AlignedAfter> -where - (Self, T::Align): IStable, -{ - same_as!((Self, T::Align)); -} - -unsafe impl IStable for (AlignedAfter, U1) { - type Align = U1; - type Size = tyeval!(Start + T::Size); - type UnusedBits = ::Shift; - type ForbiddenValues = ::Shift; - type HasExactlyOneNiche = T::HasExactlyOneNiche; - type ContainsIndirections = T::ContainsIndirections; - primitive_report!("FP"); -} -// non-ZST aligned after a non-ZST -unsafe impl IStable - for ( - AlignedAfter, - UInt, TAlignB2>, - ) -where - UInt, TAlignB2>: PowerOf2, - ( - Self, - tyeval!(Start % UInt, TAlignB2>), - ): IStable, -{ - same_as!(( - Self, - tyeval!(Start % UInt, TAlignB2>) - )); -} -// non-ZST already aligned -unsafe impl IStable - for ((AlignedAfter, UInt), U0) -{ +unsafe impl IStable for AlignedAfter { type Align = T::Align; - type Size = tyeval!(Start + T::Size); - type UnusedBits = ::Shift; - type ForbiddenValues = ::Shift; - type HasExactlyOneNiche = T::HasExactlyOneNiche; - type ContainsIndirections = T::ContainsIndirections; - primitive_report!("FP"); -} -// non-ZST needs alignment -unsafe impl - IStable - for ( - (AlignedAfter, UInt), - UInt, - ) -where -// ) as Unsigned>::Padding: IStable, -{ - type Align = T::Align; - type Size = tyeval!((Start + (T::Align - UInt)) + T::Size); - type UnusedBits = <<<) as Unsigned>::Padding as IStable>::UnusedBits as IBitMask>::Shift as IBitMask>::BitOr< - ::Shift))>>; + type Size = ::Add>; type ForbiddenValues = - ::Shift))>; - type HasExactlyOneNiche = Saturator; + ::Shift>; + type UnusedBits = << - Start) as IUnsignedBase>::PaddingBitMask as IBitMask>::Shift as IBitMask>::BitOr< + ::Shift>, + >; + type HasExactlyOneNiche = T::HasExactlyOneNiche; type ContainsIndirections = T::ContainsIndirections; primitive_report!("FP"); } -unsafe impl IStable for Struct -where - (Self, T::Align): IStable, -{ - same_as!((Self, T::Align)); -} -unsafe impl IStable for (Struct, U0) { - same_as!(T); -} -unsafe impl IStable for (Struct, UInt) -where - UInt: PowerOf2, - (Self, tyeval!(T::Size % UInt)): IStable, -{ - same_as!((Self, tyeval!(T::Size % UInt))); -} -unsafe impl IStable for ((Struct, Align), U0) { - same_as!(T); -} -unsafe impl IStable - for ((Struct, Align), UInt) -where -// ) as Unsigned>::Padding: IStable, -{ - type Size = tyeval!(T::Size + (T::Align - UInt)); +unsafe impl IStable for Struct { + type Size = ::NextMultipleOf; type Align = T::Align; type ForbiddenValues = T::ForbiddenValues; type UnusedBits = ::BitOr< - <<) as Unsigned>::Padding as IStable>::UnusedBits as IBitMask>::Shift>; + <::NextMultipleOf - T::Size) as IUnsignedBase>::PaddingBitMask as IBitMask>::Shift>; type HasExactlyOneNiche = Saturator; type ContainsIndirections = T::ContainsIndirections; primitive_report!("FP"); diff --git a/stabby-abi/src/typenum2/unsigned.rs b/stabby-abi/src/typenum2/unsigned.rs index 671333f..6e6a789 100644 --- a/stabby-abi/src/typenum2/unsigned.rs +++ b/stabby-abi/src/typenum2/unsigned.rs @@ -225,6 +225,8 @@ pub trait IUnsignedBase { type _NonZero: NonZero; /// Support for [`IUnsigned`] type _Mul: IUnsigned; + /// Generates the bitmask for a Self bytes long padding. + type PaddingBitMask: IBitMask; } /// A is smaller than B if `A::Cmp` = Lesser. pub struct Lesser; @@ -352,6 +354,7 @@ impl IUnsignedBase for UTerm { type _TruncateAtRightmostOne = Saturator; type _NonZero = Saturator; type _Mul = UTerm; + type PaddingBitMask = End; } impl IUnsignedBase for Saturator { #[cfg(not(doc))] @@ -376,6 +379,7 @@ impl IUnsignedBase for Saturator { type _TruncateAtRightmostOne = Saturator; type _NonZero = Saturator; type _Mul = Saturator; + type PaddingBitMask = End; } /// A non-zero unsigned number. @@ -438,6 +442,11 @@ impl IUnsignedBase for UInt { type _Mul = as IUnsigned>::Add< , B0> as IUnsignedBase>::_Simplified, >; + type PaddingBitMask = Array< + U0, + U255, + <::PaddingBitMask as IBitMask>::Shift, + >; } impl> IPowerOf2 for UInt { type Log2 = U0; From c09045754f75bf54554ead1c3c066aea6d1806fd Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Wed, 1 May 2024 17:32:31 +0200 Subject: [PATCH 04/10] Allow generating fixed sized arrays from an Unsigned --- stabby-abi/src/typenum2/unsigned.rs | 35 ++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/stabby-abi/src/typenum2/unsigned.rs b/stabby-abi/src/typenum2/unsigned.rs index 6e6a789..b66b582 100644 --- a/stabby-abi/src/typenum2/unsigned.rs +++ b/stabby-abi/src/typenum2/unsigned.rs @@ -22,7 +22,7 @@ use typenames::*; use crate::{ istable::{IBitMask, IForbiddenValues, ISaturatingAdd, ISingleForbiddenValue, Saturator}, - Array, End, IStable, + Array, End, IStable, Tuple, }; /// (unsigned)0 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] @@ -76,6 +76,8 @@ pub trait IBitBase { /// Support for [`IBit`] type _SaddTernary: ISaturatingAdd; /// Support for [`IBit`] + type _StabTernary: IStable; + /// Support for [`IBit`] type AsForbiddenValue: ISingleForbiddenValue; } /// false @@ -95,6 +97,7 @@ impl IBitBase for B0 { type _FvTernary = B; type _UbTernary = B; type _SaddTernary = B; + type _StabTernary = B; type AsForbiddenValue = Saturator; } /// true @@ -114,6 +117,7 @@ impl IBitBase for B1 { type _FvTernary = A; type _UbTernary = A; type _SaddTernary = A; + type _StabTernary = A; type AsForbiddenValue = End; } /// A boolean. [`B0`] and [`B1`] are the canonical members of this type-class @@ -144,6 +148,8 @@ pub trait IBit: IBitBase { type UbTernary: IBitMask; /// Self ? A : B, preserving bounds. type SaddTernary: ISaturatingAdd; + /// Self ? A : B, preserving bounds. + type StabTernary: IStable; /// !(Self & Other) type Nand: IBit + Sized; /// Self ^ Other @@ -173,6 +179,7 @@ impl IBit for Bit { type FvTernary = Self::_FvTernary; type UbTernary = Self::_UbTernary; type SaddTernary = Self::_SaddTernary; + type StabTernary = Self::_StabTernary; type Nand = as IBitBase>::_Not; type Xor = as IBitBase>::_Or>; type Equals = as IBitBase>::_Not; @@ -227,6 +234,10 @@ pub trait IUnsignedBase { type _Mul: IUnsigned; /// Generates the bitmask for a Self bytes long padding. type PaddingBitMask: IBitMask; + /// Helper for [`IUnsignedBase::Array`] + type _AddToArray: IStable; + /// Generates a type that has the same layout as `[T; Self]` + type Array: IStable; } /// A is smaller than B if `A::Cmp` = Lesser. pub struct Lesser; @@ -299,6 +310,8 @@ pub trait IPowerOf2: IUnsigned { type Max: IPowerOf2; /// T % Self type Modulate: IUnsigned; + /// T / Self + type Divide: IUnsigned; } impl IUnsigned for U { const U128: u128 = Self::_U128; @@ -355,6 +368,8 @@ impl IUnsignedBase for UTerm { type _NonZero = Saturator; type _Mul = UTerm; type PaddingBitMask = End; + type _AddToArray = LsbArray; + type Array = (); } impl IUnsignedBase for Saturator { #[cfg(not(doc))] @@ -380,6 +395,8 @@ impl IUnsignedBase for Saturator { type _NonZero = Saturator; type _Mul = Saturator; type PaddingBitMask = End; + type _AddToArray = (); + type Array = (); } /// A non-zero unsigned number. @@ -447,12 +464,24 @@ impl IUnsignedBase for UInt { U255, <::PaddingBitMask as IBitMask>::Shift, >; + type _AddToArray = Msb::_AddToArray< + Bit::StabTernary< + <::_IsUTerm as IBit>::StabTernary< + ArrayStack, + Tuple, + >, + LsbArray, + >, + Tuple, + >; + type Array = Self::_AddToArray<(), T>; } impl> IPowerOf2 for UInt { type Log2 = U0; type Min = as IBit>::PTernary; type Max = as IBit>::PTernary; type Modulate = UTerm; + type Divide = T; } impl IPowerOf2 for UInt { type Log2 = ::Increment; @@ -460,6 +489,7 @@ impl IPowerOf2 for UInt { type Max = as IBit>::PTernary; type Modulate = , T::Bit> as IUnsignedBase>::_Simplified; + type Divide = Msb::Divide; } #[test] @@ -612,4 +642,7 @@ fn ops() { assert_eq!(U4::_U128, 4); assert_eq!(U5::_U128, 5); assert_eq!(U10::_U128, 10); + unsafe { core::mem::transmute::<_, ::Array>([0u8; 22]) }; + unsafe { core::mem::transmute::<_, ::Array>([0u16; 122]) }; + unsafe { core::mem::transmute::<[u8; 0], ::Array>([]) }; } From 7b78e3c32df61c94337f35844fac9d53cdb6fb6e Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Wed, 1 May 2024 20:18:32 +0200 Subject: [PATCH 05/10] change rustc's vision of enums to avoid potential UB --- examples/dynlinkage/src/main.rs | 2 +- examples/library/src/lib.rs | 5 +- stabby-abi/src/enums/err_non_empty.rs | 18 +-- stabby-abi/src/fatptr.rs | 19 ++- stabby-abi/src/istable.rs | 8 +- stabby-abi/src/lib.rs | 5 +- stabby-abi/src/result.rs | 222 +++++++++++++++++--------- stabby-abi/src/typenum2/unsigned.rs | 32 ++++ stabby-abi/src/vtable.rs | 5 + stabby/tests/traits.rs | 24 +-- 10 files changed, 231 insertions(+), 109 deletions(-) diff --git a/examples/dynlinkage/src/main.rs b/examples/dynlinkage/src/main.rs index 4bf9cd1..f78bfcd 100644 --- a/examples/dynlinkage/src/main.rs +++ b/examples/dynlinkage/src/main.rs @@ -14,7 +14,7 @@ #[stabby::import(name = "library")] extern "C" { - pub fn stable_fn(v: u8); + pub fn stable_fn(v: u8) -> stabby::option::Option<()>; } #[stabby::import(canaries = "", name = "library")] diff --git a/examples/library/src/lib.rs b/examples/library/src/lib.rs index f8cf72f..da413c1 100644 --- a/examples/library/src/lib.rs +++ b/examples/library/src/lib.rs @@ -13,8 +13,9 @@ // #[stabby::export] -pub extern "C" fn stable_fn(v: u8) { - println!("{v}") +pub extern "C" fn stable_fn(v: u8) -> stabby::option::Option<()> { + println!("{v}"); + Default::default() } #[stabby::export(canaries)] diff --git a/stabby-abi/src/enums/err_non_empty.rs b/stabby-abi/src/enums/err_non_empty.rs index 77738fb..037e7d9 100644 --- a/stabby-abi/src/enums/err_non_empty.rs +++ b/stabby-abi/src/enums/err_non_empty.rs @@ -26,7 +26,7 @@ pub struct Layout< ErrFv: IForbiddenValues, ErrUb: IBitMask, ErrSize: Unsigned, - ErrAlign: PowerOf2, + ErrAlign: Alignment, ErrOffset: Unsigned, >( core::marker::PhantomData<( @@ -89,7 +89,7 @@ impl< ErrFv: IForbiddenValues, ErrUb: IBitMask, ErrSize: Unsigned, - ErrAlign: PowerOf2, + ErrAlign: Alignment, ErrOffset: Unsigned, > IDeterminantProviderInner for Layout @@ -108,7 +108,7 @@ impl< ErrFv: IForbiddenValues, ErrUb: IBitMask, ErrSize: Unsigned, - ErrAlign: PowerOf2, + ErrAlign: Alignment, ErrOffset: Unsigned, > IDeterminantProviderInner for Layout, OkFv, OkUb, ErrFv, ErrUb, ErrSize, ErrAlign, ErrOffset> @@ -145,7 +145,7 @@ impl< ErrFv: IForbiddenValues, ErrUb: IBitMask, ErrSize: Unsigned, - ErrAlign: PowerOf2, + ErrAlign: Alignment, ErrOffset: Unsigned, Offset: Unsigned, V: Unsigned, @@ -179,7 +179,7 @@ impl< ErrFv: IForbiddenValues, ErrUb: IBitMask, ErrSize: Unsigned, - ErrAlign: PowerOf2, + ErrAlign: Alignment, ErrOffset: Unsigned, Offset: Unsigned, V: Unsigned, @@ -211,7 +211,7 @@ impl< ErrFv: IForbiddenValues, ErrUb: IBitMask, ErrSize: Unsigned, - ErrAlign: PowerOf2, + ErrAlign: Alignment, ErrOffset: Unsigned, Offset: Unsigned, V: NonZero, @@ -242,7 +242,7 @@ impl< ErrFv: IForbiddenValues, ErrUb: IBitMask, ErrSize: Unsigned, - ErrAlign: PowerOf2, + ErrAlign: Alignment, ErrOffset: Unsigned, > IDeterminantProviderInner for DeterminantProvider< @@ -271,7 +271,7 @@ impl< ErrFv: IForbiddenValues, ErrUb: IBitMask, ErrSize: Unsigned, - ErrAlign: PowerOf2, + ErrAlign: Alignment, ErrOffset: Unsigned, > IDeterminantProviderInner for ( @@ -291,7 +291,7 @@ impl< ErrFv: IForbiddenValues, ErrUb: IBitMask, ErrSize: Unsigned, - ErrAlign: PowerOf2, + ErrAlign: Alignment, ErrOffset: Unsigned, > IDeterminantProviderInner for ( diff --git a/stabby-abi/src/fatptr.rs b/stabby-abi/src/fatptr.rs index 3582355..7c1c1cf 100644 --- a/stabby-abi/src/fatptr.rs +++ b/stabby-abi/src/fatptr.rs @@ -267,6 +267,12 @@ impl<'a, P: IPtrOwned + IPtr, Vt: HasDropVt + 'a> Dyn<'a, P, Vt> { /// This implies that this downcast will always yield `None` when attempting to downcast /// values constructed accross an FFI. /// + /// Note that the compiler may chose to have multiple copies of the vtable, notably in optimized builds. + /// This means that even within a same compile unit, this function may fail to downcast a value even if + /// the type should have matched. + /// + /// In general, you should prefer [`Self::stable_downcast_ref`] + /// /// # Safety /// This may have false positives if all of the following applies: /// - `self` was built from `&U`, within the same FFI-boundary, @@ -280,6 +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() }) } /// Downcasts the reference based on its reflection report. @@ -287,13 +294,19 @@ impl<'a, P: IPtrOwned + IPtr, Vt: HasDropVt + 'a> Dyn<'a, P, Vt> { where Vt: TransitiveDeref + IConstConstructor<'a, T>, { - (self.report() == T::REPORT).then(|| unsafe { self.ptr.as_ref() }) + (self.id() == T::ID && self.report() == T::REPORT).then(|| unsafe { self.ptr.as_ref() }) } /// Downcasts the mutable reference based on vtable equality. /// /// This implies that this downcast will always yield `None` when attempting to downcast /// values constructed accross an FFI. /// + /// Note that the compiler may chose to have multiple copies of the vtable, notably in optimized builds. + /// This means that even within a same compile unit, this function may fail to downcast a value even if + /// the type should have matched. + /// + /// In general, you should prefer [`Self::stable_downcast_mut`] + /// /// # Safety /// This may have false positives if all of the following applies: /// - `self` was built from `&U`, within the same FFI-boundary, @@ -310,13 +323,13 @@ impl<'a, P: IPtrOwned + IPtr, Vt: HasDropVt + 'a> Dyn<'a, P, Vt> { { (self.vtable == Vt::VTABLE).then(|| unsafe { self.ptr.as_mut() }) } - /// Downcasts the reference based on its reflection report. + /// Downcasts the mutable reference based on its reflection report. pub fn stable_downcast_mut(&mut self) -> Option<&mut T> where Vt: TransitiveDeref + IConstConstructor<'a, T>, P: IPtrMut, { - (self.report() == T::REPORT).then(|| unsafe { self.ptr.as_mut() }) + (self.id() == T::ID && self.report() == T::REPORT).then(|| unsafe { self.ptr.as_mut() }) } } diff --git a/stabby-abi/src/istable.rs b/stabby-abi/src/istable.rs index 3f5a031..b65ac9b 100644 --- a/stabby-abi/src/istable.rs +++ b/stabby-abi/src/istable.rs @@ -14,7 +14,7 @@ use crate::report::TypeReport; -use self::unsigned::IUnsignedBase; +use self::unsigned::{Alignment, IUnsignedBase}; use super::typenum2::*; use super::unsigned::{IBitBase, NonZero}; @@ -42,7 +42,7 @@ pub unsafe trait IStable: Sized { /// The size of the annotated type in bytes. type Size: Unsigned; /// The alignment of the annotated type in bytes. - type Align: PowerOf2; + type Align: Alignment; /// The values that the annotated type cannot occupy. type ForbiddenValues: IForbiddenValues; /// The padding bits in the annotated types @@ -344,7 +344,7 @@ unsafe impl IStable for FieldPair { type UnusedBits = ::BitOr< as IStable>::UnusedBits>; type Size = as IStable>::Size; - type Align = ::Max; + type Align = ::Max; type HasExactlyOneNiche = ::SaturatingAdd< as IStable>::HasExactlyOneNiche, >; @@ -482,7 +482,7 @@ unsafe impl IStable for (Union, B1) { type ForbiddenValues = End; type UnusedBits = End; type Size = ::Max; - type Align = ::Max; + type Align = ::Max; type HasExactlyOneNiche = B0; type ContainsIndirections = ::Or; primitive_report!("Union"); diff --git a/stabby-abi/src/lib.rs b/stabby-abi/src/lib.rs index adae2da..054a6f7 100644 --- a/stabby-abi/src/lib.rs +++ b/stabby-abi/src/lib.rs @@ -32,6 +32,7 @@ pub mod alloc; pub mod num; pub use stabby_macros::{canary_suffixes, dynptr, export, import, stabby, vtable as vtmacro}; +use typenum2::unsigned::Alignment; use core::fmt::{Debug, Display}; @@ -256,7 +257,7 @@ unsafe impl IStable for StableLike { /// transitively containing the emulated type are indeed ABI-stable. pub struct NoNiches< Size: Unsigned, - Align: PowerOf2, + Align: Alignment, HasExactlyOneNiche: ISaturatingAdd = Saturator, ContainsIndirections: Bit = B0, >( @@ -265,7 +266,7 @@ pub struct NoNiches< ); unsafe impl< Size: Unsigned, - Align: PowerOf2, + Align: Alignment, HasExactlyOneNiche: ISaturatingAdd, ContainsIndirections: Bit, > IStable for NoNiches diff --git a/stabby-abi/src/result.rs b/stabby-abi/src/result.rs index bcaf186..932eac3 100644 --- a/stabby-abi/src/result.rs +++ b/stabby-abi/src/result.rs @@ -14,28 +14,75 @@ //! Stable results! +use stabby_macros::tyeval; + pub use crate::enums::IDeterminant; use crate::enums::IDeterminantProvider; -use crate::padding::Padded; -use crate::Union; -use crate::{self as stabby, unreachable_unchecked, Bit, IStable}; +use crate::istable::IBitMask; +use crate::report::FieldReport; +use crate::str::Str; +use crate::unsigned::IUnsignedBase; +use crate::{self as stabby, unreachable_unchecked, Bit, IStable, B0}; +use crate::{Alignment, Tuple, Unsigned}; +#[repr(transparent)] /// An ABI-stable, niche optimizing equivalent of [`core::result::Result`] -#[stabby::stabby] pub struct Result where Ok: IDeterminantProvider, Err: IStable, { - niche_exporter: >::NicheExporter, - determinant: >::Determinant, - #[allow(clippy::type_complexity)] - union: core::mem::MaybeUninit< - Union< - Padded<>::OkShift, Ok>, - Padded<>::ErrShift, Err>, - >, - >, + storage: Storage<::Size, ::Align>, +} +impl Unpin for Result +where + Ok: IDeterminantProvider, + Err: IStable, +{ +} +type Determinant = >::Determinant; +unsafe impl IStable for Result +where + Ok: IDeterminantProvider, + Err: IStable, +{ + type Size = tyeval!(< as IStable>::Size as Unsigned>::NextMultipleOf + ::Max); + type Align = ::Max; + type ContainsIndirections = ::Or; + type ForbiddenValues = + <>::NicheExporter as IStable>::ForbiddenValues; + type UnusedBits = <, ::AsUint> as IStable>::UnusedBits as IBitMask>::BitOr<<<>::NicheExporter as IStable>::UnusedBits as IBitMask>::Shift<< as IStable>::Size as Unsigned>::NextMultipleOf>>; + type HasExactlyOneNiche = B0; + const REPORT: &'static crate::report::TypeReport = &crate::report::TypeReport { + name: Str::new("Result"), + module: Str::new("stabby_abi::result"), + tyty: crate::report::TyTy::Enum(Str::new("stabby")), + version: 1, + fields: crate::StableLike::new(Some(&FieldReport { + name: Str::new("Ok"), + ty: Ok::REPORT, + next_field: crate::StableLike::new(Some(&FieldReport { + name: Str::new("Err"), + ty: Err::REPORT, + next_field: crate::StableLike::new(None), + })), + })), + }; + const ID: u64 = crate::report::gen_id(Self::REPORT); +} + +#[stabby::stabby] +struct Storage { + inner: as IUnsignedBase>::Array, +} + +impl Storage { + const fn as_ptr(&self) -> *const u8 { + self as *const Self as *const _ + } + fn as_mut_ptr(&mut self) -> *mut u8 { + self as *mut Self as *mut _ + } } impl Clone for Result @@ -125,10 +172,11 @@ where Err: IStable, { fn drop(&mut self) { - if self.is_ok() { - unsafe { core::ptr::drop_in_place(&mut self.union.assume_init_mut().ok.value) } - } else { - unsafe { core::ptr::drop_in_place(&mut self.union.assume_init_mut().err.value) } + unsafe { + self.match_mut( + |mut ok| core::ptr::drop_in_place::(&mut *ok), + |mut err| core::ptr::drop_in_place::(&mut *err), + ) } } } @@ -137,40 +185,67 @@ where Ok: IDeterminantProvider, Err: IStable, { + const DET_SIZE: usize = << as IStable>::Size as Unsigned>::NextMultipleOf< + ::Align, + > as Unsigned>::USIZE; + const OK_OFFSET: usize = + <>::OkShift as Unsigned>::USIZE + Self::DET_SIZE; + const ERR_OFFSET: usize = + <>::ErrShift as Unsigned>::USIZE + Self::DET_SIZE; + const fn ok_ptr( + storage: *const Storage<::Size, ::Align>, + ) -> *const Ok { + unsafe { storage.cast::().add(Self::OK_OFFSET).cast() } + } + const fn ok_ptr_mut( + storage: *mut Storage<::Size, ::Align>, + ) -> *mut Ok { + unsafe { storage.cast::().add(Self::OK_OFFSET).cast() } + } + const fn err_ptr( + storage: *const Storage<::Size, ::Align>, + ) -> *const Err { + unsafe { storage.cast::().add(Self::ERR_OFFSET).cast() } + } + const fn err_ptr_mut( + storage: *mut Storage<::Size, ::Align>, + ) -> *mut Err { + unsafe { storage.cast::().add(Self::ERR_OFFSET).cast() } + } + const fn det_ptr( + storage: *const Storage<::Size, ::Align>, + ) -> *const Determinant { + storage.cast() + } + const fn det_ptr_mut( + storage: *mut Storage<::Size, ::Align>, + ) -> *mut Determinant { + storage.cast() + } /// Construct the `Ok` variant. #[allow(non_snake_case)] pub fn Ok(value: Ok) -> Self { - let mut union = core::mem::MaybeUninit::new(Union { - ok: core::mem::ManuallyDrop::new(Padded { - lpad: Default::default(), - value, - }), - }); - let determinant = unsafe { - >::Determinant::ok(union.as_mut_ptr().cast()) - }; - Self { - niche_exporter: Default::default(), - determinant, - union, + let mut storage = core::mem::MaybeUninit::zeroed(); + unsafe { + let storage_ptr = storage.as_mut_ptr(); + Self::ok_ptr_mut(storage_ptr).write(value); + Self::det_ptr_mut(storage_ptr).write(Determinant::::ok(storage_ptr.cast())); + Self { + storage: storage.assume_init(), + } } } /// Construct the `Err` variant. #[allow(non_snake_case)] pub fn Err(value: Err) -> Self { - let mut union = core::mem::MaybeUninit::new(Union { - err: core::mem::ManuallyDrop::new(Padded { - lpad: Default::default(), - value, - }), - }); - let determinant = unsafe { - >::Determinant::err(union.as_mut_ptr().cast()) - }; - Self { - niche_exporter: Default::default(), - determinant, - union, + let mut storage = core::mem::MaybeUninit::zeroed(); + unsafe { + let storage_ptr = storage.as_mut_ptr(); + Self::err_ptr_mut(storage_ptr).write(value); + Self::det_ptr_mut(storage_ptr).write(Determinant::::err(storage_ptr.cast())); + Self { + storage: storage.assume_init(), + } } } /// Converts to a standard [`Result`](core::result::Result) of immutable references to the variants. @@ -262,12 +337,15 @@ where err: FnErr, ) -> U { let is_ok = self.is_ok(); - let union = unsafe { self.union.assume_init_read() }; - core::mem::forget(self); + let storage = &self.storage; if is_ok { - ok(core::mem::ManuallyDrop::into_inner(unsafe { union.ok }).value) + let t = unsafe { core::ptr::read(Self::ok_ptr(storage)) }; + core::mem::forget(self); + ok(t) } else { - err(core::mem::ManuallyDrop::into_inner(unsafe { union.err }).value) + let t = unsafe { core::ptr::read(Self::err_ptr(storage)) }; + core::mem::forget(self); + err(t) } } /// Equivalent to `match self`. @@ -278,24 +356,20 @@ where err: FnErr, ) -> U { let is_ok = self.is_ok(); - let union = unsafe { self.union.assume_init_read() }; - core::mem::forget(self); + let storage = &self.storage; if is_ok { - ok( - ctx, - core::mem::ManuallyDrop::into_inner(unsafe { union.ok }).value, - ) + let t = unsafe { core::ptr::read(Self::ok_ptr(storage)) }; + core::mem::forget(self); + ok(ctx, t) } else { - err( - ctx, - core::mem::ManuallyDrop::into_inner(unsafe { union.err }).value, - ) + let t = unsafe { core::ptr::read(Self::err_ptr(storage)) }; + core::mem::forget(self); + err(ctx, t) } } /// Returns `true` if in the `Ok` variant. pub fn is_ok(&self) -> bool { - self.determinant - .is_det_ok(&self.union as *const _ as *const _) + unsafe { &*Self::det_ptr(&self.storage) }.is_det_ok(self.storage.as_ptr()) } /// Returns `true` if in the `Err` variant. pub fn is_err(&self) -> bool { @@ -373,11 +447,11 @@ where { self.unwrap_err_or_else(|e| panic!("Result::unwrap_err called on Ok variant: {e:?}")) } - unsafe fn ok_unchecked(&self) -> &Ok { - &self.union.assume_init_ref().ok.value + const unsafe fn ok_unchecked(&self) -> &Ok { + &*Self::ok_ptr(&self.storage) } - unsafe fn err_unchecked(&self) -> &Err { - &self.union.assume_init_ref().err.value + const unsafe fn err_unchecked(&self) -> &Err { + &*Self::err_ptr(&self.storage) } unsafe fn ok_mut_unchecked(&mut self) -> OkGuard<'_, Ok, Err> { OkGuard { inner: self } @@ -405,7 +479,7 @@ where { type Target = Ok; fn deref(&self) -> &Self::Target { - unsafe { &self.inner.union.assume_init_ref().ok.value } + unsafe { self.inner.ok_unchecked() } } } impl<'a, Ok, Err> core::ops::DerefMut for OkGuard<'a, Ok, Err> @@ -414,7 +488,7 @@ where Err: IStable, { fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut self.inner.union.assume_init_mut().ok.value } + unsafe { &mut *Result::::ok_ptr_mut(&mut self.inner.storage) } } } impl<'a, Ok, Err> Drop for OkGuard<'a, Ok, Err> @@ -423,10 +497,8 @@ where Err: IStable, { fn drop(&mut self) { - if <<>::Determinant as IDeterminant>::IsNicheTrick as Bit>::BOOL { - unsafe { - >::Determinant::ok(self.inner.union.as_mut_ptr().cast()) - }; + if < as IDeterminant>::IsNicheTrick as Bit>::BOOL { + unsafe { Determinant::::ok(self.inner.storage.as_mut_ptr()) }; } } } @@ -450,7 +522,7 @@ where { type Target = Err; fn deref(&self) -> &Self::Target { - unsafe { &self.inner.union.assume_init_ref().err.value } + unsafe { self.inner.err_unchecked() } } } impl<'a, Ok, Err> core::ops::DerefMut for ErrGuard<'a, Ok, Err> @@ -459,7 +531,7 @@ where Err: IStable, { fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut self.inner.union.assume_init_mut().err.value } + unsafe { &mut *Result::::err_ptr_mut(&mut self.inner.storage) } } } impl<'a, Ok, Err> Drop for ErrGuard<'a, Ok, Err> @@ -468,12 +540,8 @@ where Err: IStable, { fn drop(&mut self) { - if <<>::Determinant as IDeterminant>::IsNicheTrick as Bit>::BOOL { - unsafe { - >::Determinant::err( - self.inner.union.as_mut_ptr().cast(), - ) - }; + if < as IDeterminant>::IsNicheTrick as Bit>::BOOL { + unsafe { Determinant::::err(self.inner.storage.as_mut_ptr()) }; } } } diff --git a/stabby-abi/src/typenum2/unsigned.rs b/stabby-abi/src/typenum2/unsigned.rs index b66b582..1d44580 100644 --- a/stabby-abi/src/typenum2/unsigned.rs +++ b/stabby-abi/src/typenum2/unsigned.rs @@ -77,6 +77,8 @@ pub trait IBitBase { type _SaddTernary: ISaturatingAdd; /// Support for [`IBit`] type _StabTernary: IStable; + /// Ternary for Aligments + type _ATernary: Alignment; /// Support for [`IBit`] type AsForbiddenValue: ISingleForbiddenValue; } @@ -98,6 +100,7 @@ impl IBitBase for B0 { type _UbTernary = B; type _SaddTernary = B; type _StabTernary = B; + type _ATernary = B; type AsForbiddenValue = Saturator; } /// true @@ -118,6 +121,7 @@ impl IBitBase for B1 { type _UbTernary = A; type _SaddTernary = A; type _StabTernary = A; + type _ATernary = A; type AsForbiddenValue = End; } /// A boolean. [`B0`] and [`B1`] are the canonical members of this type-class @@ -492,6 +496,34 @@ impl IPowerOf2 for UInt { type Divide = Msb::Divide; } +/// An alignment that `stabby` can build an array arround. +pub trait Alignment: IPowerOf2 { + /// max(Self, T) + type Max: Alignment; + /// A type with size and aligment equal to Self + type AsUint: Copy + Default + IStable; +} +impl Alignment for U1 { + type Max = as IBitBase>::_ATernary; + type AsUint = u8; +} +impl Alignment for U2 { + type Max = as IBitBase>::_ATernary; + type AsUint = u16; +} +impl Alignment for U4 { + type Max = as IBitBase>::_ATernary; + type AsUint = u32; +} +impl Alignment for U8 { + type Max = as IBitBase>::_ATernary; + type AsUint = u64; +} +impl Alignment for U16 { + type Max = as IBitBase>::_ATernary; + type AsUint = u128; +} + #[test] fn ops() { fn test_pair() { diff --git a/stabby-abi/src/vtable.rs b/stabby-abi/src/vtable.rs index 64010b5..c4e7e68 100644 --- a/stabby-abi/src/vtable.rs +++ b/stabby-abi/src/vtable.rs @@ -207,9 +207,14 @@ impl From>> for VTable &'static crate::report::TypeReport; + /// The id of the type. + extern "C" fn id(&self) -> u64; } impl Any for T { extern "C" fn report(&self) -> &'static crate::report::TypeReport { Self::REPORT } + extern "C" fn id(&self) -> u64 { + Self::ID + } } diff --git a/stabby/tests/traits.rs b/stabby/tests/traits.rs index cf3b1b1..326d727 100644 --- a/stabby/tests/traits.rs +++ b/stabby/tests/traits.rs @@ -129,24 +129,26 @@ impl<'b> AsyncRead for stabby::slice::Slice<'b, u8> { #[test] fn dyn_traits() { + let boxed = Box::new(6u8); + let dyned = , A = u8, B = u8> + Send> + )>::from(boxed); + let dyned: stabby::dynptr!(Box) = dyned.into_super(); + assert_eq!(dyned.stable_downcast_ref::(), Some(&6)); + assert!(dyned.stable_downcast_ref::().is_none()); + let boxed = Box::new(6u8); let mut dyned = , A = u8, B = u8> + Sync + MyTrait> )>::from(boxed); - assert_eq!(unsafe { dyned.downcast_ref::() }, Some(&6)); assert_eq!(dyned.do_stuff(&0), &6); assert_eq!(dyned.gen_stuff(), 6); assert_eq!(dyned.gen_stuff3(Box::new(())), 6); - assert!(unsafe { dyned.downcast_ref::() }.is_none()); + // assert_eq!(unsafe { dyned.downcast_ref::() }, Some(&6)); + // assert!(unsafe { dyned.downcast_ref::() }.is_none()); + fn trait_assertions(_t: T) {} trait_assertions(dyned); - let boxed = Box::new(6u8); - let dyned = , A = u8, B = u8> + Send> - )>::from(boxed); - let dyned: stabby::dynptr!(Box) = dyned.into_super(); - assert_eq!(dyned.stable_downcast_ref::(), Some(&6)); - assert!(dyned.stable_downcast_ref::().is_none()); } #[test] @@ -155,9 +157,9 @@ fn arc_traits() { let boxed = Arc::new(6u8); let dyned = >)>::from(boxed); - assert_eq!(unsafe { dyned.downcast_ref::() }, Some(&6)); assert_eq!(dyned.do_stuff(&0), &6); - assert!(unsafe { dyned.downcast_ref::() }.is_none()); + // assert_eq!(unsafe { dyned.downcast_ref::() }, Some(&6)); + // assert!(unsafe { dyned.downcast_ref::() }.is_none()); fn trait_assertions(_t: T) {} trait_assertions(dyned); let boxed = Arc::new(6u8); From d7bb5df05b84fd8e3fff766a6d1f7f411bc4da11 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Wed, 1 May 2024 20:33:28 +0200 Subject: [PATCH 06/10] fix a test that wasn't adapted to a changed signature --- examples/libloading/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/libloading/src/main.rs b/examples/libloading/src/main.rs index cf13bbf..d130453 100644 --- a/examples/libloading/src/main.rs +++ b/examples/libloading/src/main.rs @@ -31,7 +31,9 @@ fn main() { .map(|d| d.map(|f| f.unwrap().file_name()).collect::>()) ) }); - let stable_fn = lib.get_stabbied::(b"stable_fn").unwrap(); + let stable_fn = lib + .get_stabbied:: stabby::option::Option<()>>(b"stable_fn") + .unwrap(); let unstable_fn = lib .get_canaried::(b"unstable_fn") .unwrap(); From b8746111adc4f06e78932fe995f9aa14a6a20c73 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Fri, 3 May 2024 01:05:01 +0200 Subject: [PATCH 07/10] compensate for regression in 1.78 (https://github.com/rust-lang/rust/issues/123281) --- stabby-abi/src/alloc/mod.rs | 7 ++ stabby-abi/src/fatptr.rs | 11 ++- stabby-abi/src/vtable.rs | 159 ++++++++++++++++++++++++++++++++---- stabby-macros/src/traits.rs | 16 +++- 4 files changed, 171 insertions(+), 22 deletions(-) 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)},)* }; } From 06090f0b0895ed0d7054f36ea086c4a4dd34b912 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Fri, 3 May 2024 07:28:43 +0200 Subject: [PATCH 08/10] clean up static to avoid accidents --- stabby-abi/src/vtable.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/stabby-abi/src/vtable.rs b/stabby-abi/src/vtable.rs index 5b00427..cebf5ba 100644 --- a/stabby-abi/src/vtable.rs +++ b/stabby-abi/src/vtable.rs @@ -47,14 +47,6 @@ pub trait IConstConstructor<'a, Source>: 'a + Copy { } } -#[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. @@ -64,6 +56,12 @@ pub trait IConstConstructor<'a, Source>: 'a + Copy + core::hash::Hash + core::fm const VTABLE: Self; /// Returns the reference to the vtable fn vtable() -> &'a Self { + 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()); use crate::alloc::{boxed::Box, vec::Vec, AllocPtr, DefaultAllocator}; let vtable = Self::VTABLE; #[allow(deprecated)] From eeff95b584f796746966c2cec30ea5aa1b7d5fbc Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Fri, 3 May 2024 09:08:12 +0200 Subject: [PATCH 09/10] v5.0.0 --- CHANGELOG.md | 11 +++++++++++ stabby-abi/Cargo.toml | 4 ++-- stabby-macros/Cargo.toml | 2 +- stabby/Cargo.toml | 4 ++-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da74fa7..c912e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# 5.0.0 +- BREAKING CHANGE: + - Due to a soundness hole in the previous implementation of `stabby::result::Result`, its representation was overhauled. While it's still technically compatible binary-wise (meaning it still follows the original spec), the soundness hole could possibly lead to UB, so `stabby` will treat them as incompatible. +- Add support for Rust 1.78: + - With 1.78, [Rust merged a breaking change](https://github.com/rust-lang/rust/issues/123281) which impacts `stabby`. This change prevents consts from refering to generics entirely, which was key to `stabby`'s implementation of vtables. + - More accurately, it prevents consts from refering to generics that aren't bound by `core::marker::Freeze`, but that trait hasn't been stabilized at the same time as the new error has. + - While the team was aware that this would be a breaking change, `crater` failed to report that `stabby` was impacted by the regression, as it tried compiling an obsolete version of `stabby` that could only build with pre-1.77 versions of Rust due to the `u128` ABI-break on x86. This led them to judge that the breaking change was acceptable. + - To compensate for this, `stabby` will (for non-nighly `>=1.78` versions of Rust) draw its vtable references from a heap-allocated, lazily populated, global set of vtables. This is in opposition to the `<1.78` and `nightly` behaviour where it'll keep on drawing these vtable references straight from the binary. + - From this release onwards, a new priority for `stabby` will be to improve the performance of this behaviour; or better yet find a new way to obtain the previous behaviour that compiles. + - While I can't hide that I am very annoyed at this development, I must also state that I understand the Rust Team's choice to ship this breaking change: they considered this potential window for a soundness hole a bug, and even though `crater` didn't report any use of this bug that was unsound, it also failed to report `stabby` as a legitimate user of it. I do wish they'd have waited for `Freeze`'s stabilization to make the breaking change however, as the sound pattern it would prevent, as well as the fact that it couldn't be replicated without `Freeze`, _was_ known. + # 4.0.5 - Fix for 1.72: `AllocPtr::prefix` is `const fn` from 1.73 onwards rather than 1.72 onwards (@yellowhatter). diff --git a/stabby-abi/Cargo.toml b/stabby-abi/Cargo.toml index b5e880a..f008b59 100644 --- a/stabby-abi/Cargo.toml +++ b/stabby-abi/Cargo.toml @@ -14,7 +14,7 @@ [package] name = "stabby-abi" -version = "4.0.5" +version = "5.0.0" edition = "2021" authors = { workspace = true } license = { workspace = true } @@ -38,7 +38,7 @@ abi_stable-channels = ["abi_stable", "abi_stable/channels"] # unsafe_wakers = [] # unsafe_wakers is no longer a feature, but a compile option: you can enable them using `RUST_FLAGS='--cfg unsafe_wakers="true"'` [dependencies] -stabby-macros = { path = "../stabby-macros/", version = "4.0.5" } +stabby-macros = { path = "../stabby-macros/", version = "5.0.0" } abi_stable = { workspace = true, optional = true } libc = { workspace = true, optional = true } rustversion = { workspace = true } diff --git a/stabby-macros/Cargo.toml b/stabby-macros/Cargo.toml index 2da4657..fe0f2e6 100644 --- a/stabby-macros/Cargo.toml +++ b/stabby-macros/Cargo.toml @@ -14,7 +14,7 @@ [package] name = "stabby-macros" -version = "4.0.5" +version = "5.0.0" edition = "2021" authors = { workspace = true } license = { workspace = true } diff --git a/stabby/Cargo.toml b/stabby/Cargo.toml index ed9a402..ddb3fac 100644 --- a/stabby/Cargo.toml +++ b/stabby/Cargo.toml @@ -14,7 +14,7 @@ [package] name = "stabby" -version = "4.0.5" +version = "5.0.0" edition = "2021" authors = { workspace = true } license = { workspace = true } @@ -30,7 +30,7 @@ libloading = ["dep:libloading", "std"] libc = ["stabby-abi/libc"] [dependencies] -stabby-abi = { path = "../stabby-abi/", version = "4.0.5" } +stabby-abi = { path = "../stabby-abi/", version = "5.0.0" } lazy_static = { workspace = true } libloading = { workspace = true, optional = true } From 0be0b585d6c1d7c8de418172f69e6241a3a80ffc Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Fri, 3 May 2024 09:31:41 +0200 Subject: [PATCH 10/10] update readmes --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 82c99cb..07585f5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,19 @@ [![Crates.io (latest)](https://img.shields.io/crates/v/stabby)](https://lib.rs/crates/stabby) [![docs.rs](https://img.shields.io/docsrs/stabby)](https://docs.rs/stabby/latest/stabby/) +> [!WARNING] +> Due to a breaking change in Rust 1.78, `stabby`'s implementation of trait objects may raise performance issues: +> - __Only non-nightly, >= 1.78 versions of Rust are affected__ +> - The v-tables backing trait objects are now inserted in a global lock-free set. +> - This set is leaked: `valgrind` _will_ be angry at you. +> - This set grows with the number of distinct `(type, trait-set)` pairs. Its current implementation is a vector: +> - Lookup is done through linear search (O(n)), which stays the fastest for <100 number of elements. +> - Insertion is done by cloning the vector (O(n)) and replacing it atomically, repeating the operation in case of collision. +> - Efforts to replace this implementation with immutable b-tree maps are ongoing (they will be scrapped if found to be much slower than the current implementation). +> +> This note will be updated as the situation evolves. In the meantime, if your project uses many `stabby`-defined trait objects, +> I suggest using either `nightly` or a `< 1.78` version of the compiler. + # A Stable ABI for Rust with compact sum-types `stabby` is your one-stop-shop to create stable binary interfaces for your shared libraries easily, without having your sum-types (enums) explode in size.