diff --git a/README.md b/README.md index fe91269..b110dcb 100644 --- a/README.md +++ b/README.md @@ -7,24 +7,32 @@ A crate for stuffing things into a pointer. This crate is tested using miri (with `-Zmiri-tag-raw-pointers`). -This crate consists of three parts: -* The type `StuffedPtr` -* The trait `StuffingStrategy` -* The trait `Backend` -`StuffedPtr` is the main type of this crate. You it's a type whose size depends on the +`stuff` helps you to + +- Stuff arbitrary data into pointers +- Stuff pointers or arbitrary data into fixed size storage (u64, u128) + +in a **portable and provenance friendly** way. + +It does by providing an abstraction around it, completely abstracting away the provenance and pointers from +the user, allowing the user to do their bit stuffing only on integers (pointer addresses) themselves. + +`StuffedPtr` is the main type of this crate. It's a type whose size depends on the choice of `Backend` (defaults to `usize`, `u64` and `u128` are also possible). It can store a pointer or some extra data. -except that the extra data is bitstuffed into the pointer. You can chose any arbitrary bitstuffing -depending on the `StuffingStrategy`, an unsafe trait that governs how the extra data -(or the pointer itself) will be packed into the backend. +You can choose any arbitrary bitstuffing depending on the `StuffingStrategy`, an unsafe trait that governs +how the extra data (or the pointer itself) will be packed into the backend. While this trait is still unsafe, +it's a lot safer than doing everything by hand. # Example: NaN-Boxing Pointers are hidden in the NaN values of floats. NaN boxing often involves also hiding booleans or null in there, but we stay with floats and pointers (pointers to a `HashMap` that servers as our "object" type). + See [crafting interpreters](https://craftinginterpreters.com/optimization.html#nan-boxing) for more details. + ```rust use std::collections::HashMap; use stuff::{StuffedPtr, StuffingStrategy}; diff --git a/src/backend.rs b/src/backend.rs index 8856fd7..1909aae 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -4,8 +4,8 @@ use sptr::Strict; /// A backend where the stuffed pointer is stored. Must be bigger or equal to the pointer size. /// -/// The [`Backend`] is a trait to define types that store the stuffed pointer. It's supposed to -/// be implemented on `Copy` types like `usize``u64`, `u128`. Note that these integers are basically +/// The `Backend` is a trait to define types that store the stuffed pointer. It's supposed to +/// be implemented on `Copy` types like `usize`, `u64`, or `u128`. Note that these integers are basically /// just the strategy and exchange types for addresses, but *not* the actual underlying storage, which /// always contains a pointer to keep provenance (for example `(*mut T, u32)` on 32 bit for `u64`). /// @@ -21,10 +21,10 @@ pub unsafe trait Backend { /// and some integers to fill up the bytes. type Stored: Copy; - /// Get the pointer from the backed. Since the [`crate::StuffingStrategy`] is able to use the full - /// bytes to pack in the pointer address, the full address is returned in the second tuple field, - /// as the integer. The provenance of the pointer is returned as the first tuple field, but its - /// address should be ignored and may be invalid. + /// Get the pointer from the backed. Since the [`StuffingStrategy`](`crate::StuffingStrategy`) + /// is able to use the full bytes to pack in the pointer address, the full address is returned + /// in the second tuple field, as the integer. The provenance of the pointer is returned as + /// the first tuple field, but its address should be ignored and may be invalid. fn get_ptr(s: Self::Stored) -> (*mut T, Self); /// Set a new pointer address. The provenance of the new pointer is transferred in the first argument, diff --git a/src/lib.rs b/src/lib.rs index 88e620f..55da9c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,18 +5,20 @@ //! A crate for stuffing things into a pointer. //! -//! This crate consists of three parts: -//! * The type [`StuffedPtr`] -//! * The trait [`StuffingStrategy`] -//! * The trait [`Backend`] +//! `stuff` helps you to +//! +//! - Stuff arbitrary data into pointers +//! - Stuff pointers or arbitrary data into fixed size storage (u64, u128) +//! +//! in a **portable and provenance friendly** way. //! //! [`StuffedPtr`] is the main type of this crate. You it's a type whose size depends on the //! choice of [`Backend`] (defaults to `usize`, `u64` and `u128` are also possible). It can store a //! pointer or some extra data. //! -//! except that the extra data is bitstuffed into the pointer. You can chose any arbitrary bitstuffing -//! depending on the [`StuffingStrategy`], an unsafe trait that governs how the extra data -//! (or the pointer itself) will be packed into the backend. +//! You can choose any arbitrary bitstuffing depending on the [`StuffingStrategy`], an unsafe trait that governs +//! how the extra data (or the pointer itself) will be packed into the backend. While this trait is still unsafe, +//! it's a lot safer than doing everything by hand. //! //! # Example: NaN-Boxing //! Pointers are hidden in the NaN values of floats. NaN boxing often involves also hiding booleans @@ -99,29 +101,37 @@ use sptr::Strict; pub use crate::{backend::Backend, strategy::StuffingStrategy}; -/// A union of a pointer and some extra data, bitpacked into a pointer (or custom, using the third -/// generic param `I`) size. +/// A union of a pointer or some extra data, bitpacked into a value with the size depending on +/// `B`. It defaults to `usize`, meaning pointer sized, but `u64` and `u128` are also provided +/// by this crate. You can also provide your own [`Backend`] implementation +/// +/// The stuffing strategy is supplied as the second generic parameter `S`. +/// +/// The first generic parameter `T` is the type that the pointer is pointing to. /// /// For a usage example, view the crate level documentation. /// /// This pointer does *not* drop extra data, [`StuffedPtr::into_extra`] can be used if that is required. /// /// `StuffedPtr` implements most traits like `Clone`, `PartialEq` or `Copy` if the extra type does. -pub struct StuffedPtr(I::Stored, PhantomData) +/// +/// This type is guaranteed to be `#[repr(transparent)]` to a `B::Stored`. +#[repr(transparent)] +pub struct StuffedPtr(B::Stored, PhantomData) where - S: StuffingStrategy, - I: Backend; + S: StuffingStrategy, + B: Backend; -impl StuffedPtr +impl StuffedPtr where - S: StuffingStrategy, - I: Backend, + S: StuffingStrategy, + B: Backend, { /// Create a new `StuffedPtr` from a pointer pub fn new_ptr(ptr: *mut T) -> Self { let addr = Strict::addr(ptr); let stuffed = S::stuff_ptr(addr); - Self(I::set_ptr(ptr, stuffed), PhantomData) + Self(B::set_ptr(ptr, stuffed), PhantomData) } /// Create a new `StuffPtr` from extra data @@ -130,7 +140,7 @@ where // if the user calls `set_ptr` it will use the new provenance from that ptr let ptr = core::ptr::null_mut(); let extra = S::stuff_extra(extra); - Self(I::set_ptr(ptr, extra), PhantomData) + Self(B::set_ptr(ptr, extra), PhantomData) } /// Get the pointer data, or `None` if it contains extra data @@ -147,7 +157,7 @@ where /// # Safety /// `StuffedPtr` must contain pointer data and not extra data pub unsafe fn get_ptr_unchecked(&self) -> *mut T { - let (provenance, addr) = I::get_ptr(self.0); + let (provenance, addr) = B::get_ptr(self.0); let addr = S::extract_ptr(addr); Strict::with_addr(provenance, addr) } @@ -189,8 +199,8 @@ where unsafe { S::extract_extra(data) } } - fn addr(&self) -> I { - I::get_int(self.0) + fn addr(&self) -> B { + B::get_int(self.0) } fn is_extra(&self) -> bool { @@ -198,11 +208,12 @@ where } } -impl StuffedPtr +/// Extra implementations if the extra type is `Copy` +impl StuffedPtr where - S: StuffingStrategy, + S: StuffingStrategy, S::Extra: Copy, - I: Backend, + B: Backend, { /// Get extra data from this, or `None` if it's pointer data pub fn copy_extra(&self) -> Option { @@ -219,11 +230,11 @@ where } } -impl Debug for StuffedPtr +impl Debug for StuffedPtr where - S: StuffingStrategy, + S: StuffingStrategy, S::Extra: Debug, - I: Backend, + B: Backend, { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { // SAFETY: @@ -246,11 +257,11 @@ where } } -impl Clone for StuffedPtr +impl Clone for StuffedPtr where - S: StuffingStrategy, + S: StuffingStrategy, S::Extra: Clone, - I: Backend, + B: Backend, { fn clone(&self) -> Self { // SAFETY: We forget that `extra` ever existed after taking the reference and cloning it @@ -265,19 +276,19 @@ where } } -impl Copy for StuffedPtr +impl Copy for StuffedPtr where - S: StuffingStrategy, + S: StuffingStrategy, S::Extra: Copy, - I: Backend, + B: Backend, { } -impl PartialEq for StuffedPtr +impl PartialEq for StuffedPtr where - S: StuffingStrategy, + S: StuffingStrategy, S::Extra: PartialEq, - I: Backend, + B: Backend, { fn eq(&self, other: &Self) -> bool { // SAFETY: We forget them after @@ -302,19 +313,19 @@ where } } -impl Eq for StuffedPtr +impl Eq for StuffedPtr where - S: StuffingStrategy, + S: StuffingStrategy, S::Extra: PartialEq + Eq, - I: Backend, + B: Backend, { } -impl Hash for StuffedPtr +impl Hash for StuffedPtr where - S: StuffingStrategy, + S: StuffingStrategy, S::Extra: Hash, - I: Backend, + B: Backend, { fn hash(&self, state: &mut H) { // SAFETY: We forget that `extra` ever existed after taking the reference and cloning it @@ -346,10 +357,10 @@ mod tests { // note: the tests mostly use the `PanicsInDrop` type and strategy, to make sure that no // extra is ever dropped accidentally. - fn from_box(boxed: Box) -> StuffedPtr + fn from_box(boxed: Box) -> StuffedPtr where - S: StuffingStrategy, - I: Backend, + S: StuffingStrategy, + B: Backend, { StuffedPtr::new_ptr(Box::into_raw(boxed)) } diff --git a/src/strategy.rs b/src/strategy.rs index 421a778..c7f3d99 100644 --- a/src/strategy.rs +++ b/src/strategy.rs @@ -5,6 +5,8 @@ /// it's also completely possible to implement it on the type in [`StuffingStrategy::Extra`] directly /// if possible. /// +/// The generic parameter `B` stands for the [`Backend`](`crate::Backend`) used by the strategy. +/// /// # Safety /// /// If [`StuffingStrategy::is_extra`] returns true for a value, then @@ -15,22 +17,22 @@ /// /// For [`StuffingStrategy::stuff_ptr`] and [`StuffingStrategy::extract_ptr`], /// `ptr == extract_ptr(stuff_ptr(ptr))` *must* hold true. -pub unsafe trait StuffingStrategy { +pub unsafe trait StuffingStrategy { /// The type of the extra. type Extra; /// Checks whether the `StufferPtr` data value contains an extra value. The result of this /// function can be trusted. - fn is_extra(data: I) -> bool; + fn is_extra(data: B) -> bool; /// Stuff extra data into a usize that is then put into the pointer. This operation /// must be infallible. - fn stuff_extra(inner: Self::Extra) -> I; + fn stuff_extra(inner: Self::Extra) -> B; /// Extract extra data from the data. /// # Safety /// `data` must contain data created by [`StuffingStrategy::stuff_extra`]. - unsafe fn extract_extra(data: I) -> Self::Extra; + unsafe fn extract_extra(data: B) -> Self::Extra; /// Stuff a pointer address into the pointer sized integer. /// @@ -38,12 +40,12 @@ pub unsafe trait StuffingStrategy { /// cursed things with it. /// /// The default implementation just returns the address directly. - fn stuff_ptr(addr: usize) -> I; + fn stuff_ptr(addr: usize) -> B; /// Extract the pointer address from the data. /// /// This function expects `inner` to come directly from [`StuffingStrategy::stuff_ptr`]. - fn extract_ptr(inner: I) -> usize; + fn extract_ptr(inner: B) -> usize; } unsafe impl StuffingStrategy for () {