mirror of
https://github.com/Noratrieb/stuff.git
synced 2026-01-14 16:35:08 +01:00
tidy up docs
This commit is contained in:
parent
82cc768531
commit
fe9196eae0
4 changed files with 85 additions and 64 deletions
24
README.md
24
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 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};
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
99
src/lib.rs
99
src/lib.rs
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 () {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue