tidy up docs

This commit is contained in:
nora 2022-04-05 17:12:37 +02:00
parent 82cc768531
commit fe9196eae0
4 changed files with 85 additions and 64 deletions

View file

@ -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 is tested using miri (with `-Zmiri-tag-raw-pointers`).
This crate consists of three parts: `stuff` helps you to
* The type `StuffedPtr`
* The trait `StuffingStrategy` - Stuff arbitrary data into pointers
* The trait `Backend` - Stuff pointers or arbitrary data into fixed size storage (u64, u128)
`StuffedPtr` is the main type of this crate. You it's a type whose size depends on the
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 choice of `Backend` (defaults to `usize`, `u64` and `u128` are also possible). It can store a
pointer or some extra data. pointer or some extra data.
except that the extra data is bitstuffed into the pointer. You can chose any arbitrary bitstuffing You can choose any arbitrary bitstuffing depending on the `StuffingStrategy`, an unsafe trait that governs
depending on the `StuffingStrategy`, an unsafe trait that governs how the extra data how the extra data (or the pointer itself) will be packed into the backend. While this trait is still unsafe,
(or the pointer itself) will be packed into the backend. it's a lot safer than doing everything by hand.
# Example: NaN-Boxing # Example: NaN-Boxing
Pointers are hidden in the NaN values of floats. NaN boxing often involves also hiding booleans 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 or null in there, but we stay with floats and pointers (pointers to a `HashMap` that servers
as our "object" type). as our "object" type).
See [crafting interpreters](https://craftinginterpreters.com/optimization.html#nan-boxing) See [crafting interpreters](https://craftinginterpreters.com/optimization.html#nan-boxing)
for more details. for more details.
```rust ```rust
use std::collections::HashMap; use std::collections::HashMap;
use stuff::{StuffedPtr, StuffingStrategy}; use stuff::{StuffedPtr, StuffingStrategy};

View file

@ -4,8 +4,8 @@ use sptr::Strict;
/// A backend where the stuffed pointer is stored. Must be bigger or equal to the pointer size. /// 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 /// 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 /// 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 /// 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`). /// 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<T> {
/// and some integers to fill up the bytes. /// and some integers to fill up the bytes.
type Stored: Copy; type Stored: Copy;
/// Get the pointer from the backed. Since the [`crate::StuffingStrategy`] is able to use the full /// Get the pointer from the backed. Since the [`StuffingStrategy`](`crate::StuffingStrategy`)
/// bytes to pack in the pointer address, the full address is returned in the second tuple field, /// is able to use the full bytes to pack in the pointer address, the full address is returned
/// as the integer. The provenance of the pointer is returned as the first tuple field, but its /// in the second tuple field, as the integer. The provenance of the pointer is returned as
/// address should be ignored and may be invalid. /// the first tuple field, but its address should be ignored and may be invalid.
fn get_ptr(s: Self::Stored) -> (*mut T, Self); 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, /// Set a new pointer address. The provenance of the new pointer is transferred in the first argument,

View file

@ -5,18 +5,20 @@
//! A crate for stuffing things into a pointer. //! A crate for stuffing things into a pointer.
//! //!
//! This crate consists of three parts: //! `stuff` helps you to
//! * The type [`StuffedPtr`] //!
//! * The trait [`StuffingStrategy`] //! - Stuff arbitrary data into pointers
//! * The trait [`Backend`] //! - 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 //! [`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 //! choice of [`Backend`] (defaults to `usize`, `u64` and `u128` are also possible). It can store a
//! pointer or some extra data. //! pointer or some extra data.
//! //!
//! except that the extra data is bitstuffed into the pointer. You can chose any arbitrary bitstuffing //! You can choose any arbitrary bitstuffing depending on the [`StuffingStrategy`], an unsafe trait that governs
//! depending on the [`StuffingStrategy`], an unsafe trait that governs how the extra data //! how the extra data (or the pointer itself) will be packed into the backend. While this trait is still unsafe,
//! (or the pointer itself) will be packed into the backend. //! it's a lot safer than doing everything by hand.
//! //!
//! # Example: NaN-Boxing //! # Example: NaN-Boxing
//! Pointers are hidden in the NaN values of floats. NaN boxing often involves also hiding booleans //! 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}; 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 /// A union of a pointer or some extra data, bitpacked into a value with the size depending on
/// generic param `I`) size. /// `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. /// 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. /// 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. /// `StuffedPtr` implements most traits like `Clone`, `PartialEq` or `Copy` if the extra type does.
pub struct StuffedPtr<T, S, I = usize>(I::Stored, PhantomData<S>) ///
/// This type is guaranteed to be `#[repr(transparent)]` to a `B::Stored`.
#[repr(transparent)]
pub struct StuffedPtr<T, S, B = usize>(B::Stored, PhantomData<S>)
where where
S: StuffingStrategy<I>, S: StuffingStrategy<B>,
I: Backend<T>; B: Backend<T>;
impl<T, S, I> StuffedPtr<T, S, I> impl<T, S, B> StuffedPtr<T, S, B>
where where
S: StuffingStrategy<I>, S: StuffingStrategy<B>,
I: Backend<T>, B: Backend<T>,
{ {
/// Create a new `StuffedPtr` from a pointer /// Create a new `StuffedPtr` from a pointer
pub fn new_ptr(ptr: *mut T) -> Self { pub fn new_ptr(ptr: *mut T) -> Self {
let addr = Strict::addr(ptr); let addr = Strict::addr(ptr);
let stuffed = S::stuff_ptr(addr); 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 /// 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 // if the user calls `set_ptr` it will use the new provenance from that ptr
let ptr = core::ptr::null_mut(); let ptr = core::ptr::null_mut();
let extra = S::stuff_extra(extra); 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 /// Get the pointer data, or `None` if it contains extra data
@ -147,7 +157,7 @@ where
/// # Safety /// # Safety
/// `StuffedPtr` must contain pointer data and not extra data /// `StuffedPtr` must contain pointer data and not extra data
pub unsafe fn get_ptr_unchecked(&self) -> *mut T { 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); let addr = S::extract_ptr(addr);
Strict::with_addr(provenance, addr) Strict::with_addr(provenance, addr)
} }
@ -189,8 +199,8 @@ where
unsafe { S::extract_extra(data) } unsafe { S::extract_extra(data) }
} }
fn addr(&self) -> I { fn addr(&self) -> B {
I::get_int(self.0) B::get_int(self.0)
} }
fn is_extra(&self) -> bool { fn is_extra(&self) -> bool {
@ -198,11 +208,12 @@ where
} }
} }
impl<T, S, I> StuffedPtr<T, S, I> /// Extra implementations if the extra type is `Copy`
impl<T, S, B> StuffedPtr<T, S, B>
where where
S: StuffingStrategy<I>, S: StuffingStrategy<B>,
S::Extra: Copy, S::Extra: Copy,
I: Backend<T>, B: Backend<T>,
{ {
/// Get extra data from this, or `None` if it's pointer data /// Get extra data from this, or `None` if it's pointer data
pub fn copy_extra(&self) -> Option<S::Extra> { pub fn copy_extra(&self) -> Option<S::Extra> {
@ -219,11 +230,11 @@ where
} }
} }
impl<T, S, I> Debug for StuffedPtr<T, S, I> impl<T, S, B> Debug for StuffedPtr<T, S, B>
where where
S: StuffingStrategy<I>, S: StuffingStrategy<B>,
S::Extra: Debug, S::Extra: Debug,
I: Backend<T>, B: Backend<T>,
{ {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
// SAFETY: // SAFETY:
@ -246,11 +257,11 @@ where
} }
} }
impl<T, S, I> Clone for StuffedPtr<T, S, I> impl<T, S, B> Clone for StuffedPtr<T, S, B>
where where
S: StuffingStrategy<I>, S: StuffingStrategy<B>,
S::Extra: Clone, S::Extra: Clone,
I: Backend<T>, B: Backend<T>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
// SAFETY: We forget that `extra` ever existed after taking the reference and cloning it // SAFETY: We forget that `extra` ever existed after taking the reference and cloning it
@ -265,19 +276,19 @@ where
} }
} }
impl<T, S, I> Copy for StuffedPtr<T, S, I> impl<T, S, B> Copy for StuffedPtr<T, S, B>
where where
S: StuffingStrategy<I>, S: StuffingStrategy<B>,
S::Extra: Copy, S::Extra: Copy,
I: Backend<T>, B: Backend<T>,
{ {
} }
impl<T, S, I> PartialEq for StuffedPtr<T, S, I> impl<T, S, B> PartialEq for StuffedPtr<T, S, B>
where where
S: StuffingStrategy<I>, S: StuffingStrategy<B>,
S::Extra: PartialEq, S::Extra: PartialEq,
I: Backend<T>, B: Backend<T>,
{ {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// SAFETY: We forget them after // SAFETY: We forget them after
@ -302,19 +313,19 @@ where
} }
} }
impl<T, S, I> Eq for StuffedPtr<T, S, I> impl<T, S, B> Eq for StuffedPtr<T, S, B>
where where
S: StuffingStrategy<I>, S: StuffingStrategy<B>,
S::Extra: PartialEq + Eq, S::Extra: PartialEq + Eq,
I: Backend<T>, B: Backend<T>,
{ {
} }
impl<T, S, I> Hash for StuffedPtr<T, S, I> impl<T, S, B> Hash for StuffedPtr<T, S, B>
where where
S: StuffingStrategy<I>, S: StuffingStrategy<B>,
S::Extra: Hash, S::Extra: Hash,
I: Backend<T>, B: Backend<T>,
{ {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
// SAFETY: We forget that `extra` ever existed after taking the reference and cloning it // 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 // note: the tests mostly use the `PanicsInDrop` type and strategy, to make sure that no
// extra is ever dropped accidentally. // extra is ever dropped accidentally.
fn from_box<T, S, I>(boxed: Box<T>) -> StuffedPtr<T, S, I> fn from_box<T, S, B>(boxed: Box<T>) -> StuffedPtr<T, S, B>
where where
S: StuffingStrategy<I>, S: StuffingStrategy<B>,
I: Backend<T>, B: Backend<T>,
{ {
StuffedPtr::new_ptr(Box::into_raw(boxed)) StuffedPtr::new_ptr(Box::into_raw(boxed))
} }

View file

@ -5,6 +5,8 @@
/// it's also completely possible to implement it on the type in [`StuffingStrategy::Extra`] directly /// it's also completely possible to implement it on the type in [`StuffingStrategy::Extra`] directly
/// if possible. /// if possible.
/// ///
/// The generic parameter `B` stands for the [`Backend`](`crate::Backend`) used by the strategy.
///
/// # Safety /// # Safety
/// ///
/// If [`StuffingStrategy::is_extra`] returns true for a value, then /// If [`StuffingStrategy::is_extra`] returns true for a value, then
@ -15,22 +17,22 @@
/// ///
/// For [`StuffingStrategy::stuff_ptr`] and [`StuffingStrategy::extract_ptr`], /// For [`StuffingStrategy::stuff_ptr`] and [`StuffingStrategy::extract_ptr`],
/// `ptr == extract_ptr(stuff_ptr(ptr))` *must* hold true. /// `ptr == extract_ptr(stuff_ptr(ptr))` *must* hold true.
pub unsafe trait StuffingStrategy<I> { pub unsafe trait StuffingStrategy<B> {
/// The type of the extra. /// The type of the extra.
type Extra; type Extra;
/// Checks whether the `StufferPtr` data value contains an extra value. The result of this /// Checks whether the `StufferPtr` data value contains an extra value. The result of this
/// function can be trusted. /// 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 /// Stuff extra data into a usize that is then put into the pointer. This operation
/// must be infallible. /// must be infallible.
fn stuff_extra(inner: Self::Extra) -> I; fn stuff_extra(inner: Self::Extra) -> B;
/// Extract extra data from the data. /// Extract extra data from the data.
/// # Safety /// # Safety
/// `data` must contain data created by [`StuffingStrategy::stuff_extra`]. /// `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. /// Stuff a pointer address into the pointer sized integer.
/// ///
@ -38,12 +40,12 @@ pub unsafe trait StuffingStrategy<I> {
/// cursed things with it. /// cursed things with it.
/// ///
/// The default implementation just returns the address directly. /// 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. /// Extract the pointer address from the data.
/// ///
/// This function expects `inner` to come directly from [`StuffingStrategy::stuff_ptr`]. /// 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<usize> for () { unsafe impl StuffingStrategy<usize> for () {