├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── backend.rs ├── lib.rs ├── strategy.rs └── tag.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | env: 12 | RUST_BACKTRACE: 1 13 | 14 | jobs: 15 | test: 16 | name: Test Rust - ${{ matrix.build }} 17 | runs-on: ${{ matrix.os }} 18 | env: 19 | CARGO: cargo 20 | TARGET: "" 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | build: 25 | - macos 26 | - linux 27 | - linux32 28 | - win64-msvc 29 | - win64-gnu 30 | - win32-msvc 31 | - win32-gnu 32 | - beta 33 | - nightly 34 | - arm32 35 | - arm64 36 | - mips32 37 | - mips64 38 | include: 39 | - build: linux 40 | os: ubuntu-latest 41 | rust: stable 42 | - build: macos 43 | os: macos-latest 44 | rust: stable 45 | - build: win64-msvc 46 | os: windows-2019 47 | rust: stable 48 | - build: win64-gnu 49 | os: windows-2019 50 | rust: stable-x86_64-gnu 51 | - build: win32-msvc 52 | os: windows-2019 53 | rust: stable-i686-msvc 54 | - build: win32-gnu 55 | os: windows-2019 56 | rust: stable-i686-gnu 57 | - build: beta 58 | os: ubuntu-latest 59 | rust: beta 60 | - build: nightly 61 | os: ubuntu-latest 62 | rust: nightly 63 | - build: linux32 64 | os: ubuntu-latest 65 | rust: stable 66 | target: i686-unknown-linux-gnu 67 | # These should prob. be more generic arm targets and not android. 68 | - build: arm32 69 | os: ubuntu-latest 70 | rust: stable 71 | target: armv7-linux-androideabi 72 | - build: arm64 73 | os: ubuntu-latest 74 | rust: stable 75 | target: aarch64-linux-android 76 | # Mips is big endian. 77 | - build: mips32 78 | os: ubuntu-latest 79 | rust: stable 80 | target: mips-unknown-linux-gnu 81 | - build: mips64 82 | os: ubuntu-latest 83 | rust: stable 84 | target: mips64-unknown-linux-gnuabi64 85 | - build: riscv 86 | os: ubuntu-latest 87 | rust: stable 88 | target: riscv64gc-unknown-linux-gnu 89 | 90 | steps: 91 | - uses: actions/checkout@v2 92 | - name: Install Rust 93 | uses: actions-rs/toolchain@v1 94 | with: 95 | toolchain: ${{ matrix.rust }} 96 | profile: minimal 97 | override: true 98 | 99 | - name: Setup cross if needed 100 | if: matrix.target != '' 101 | run: | 102 | cargo install cross 103 | echo "CARGO=cross" >> $GITHUB_ENV 104 | echo "TARGET=--target ${{ matrix.target }}" >> $GITHUB_ENV 105 | 106 | - name: Show command used for Cargo 107 | run: | 108 | echo "cargo command is: ${{ env.CARGO }}" 109 | echo "target flag is: ${{ env.TARGET }}" 110 | 111 | - name: Test 112 | run: ${{ env.CARGO }} test --verbose ${{ env.TARGET }} 113 | 114 | check-msrv: 115 | name: MSRV 116 | runs-on: ubuntu-latest 117 | steps: 118 | - uses: actions/checkout@v2 119 | - name: Install Rust 120 | uses: actions-rs/toolchain@v1 121 | with: 122 | toolchain: 1.34.2 123 | profile: minimal 124 | override: true 125 | - name: Cargo check 126 | run: cargo check --verbose 127 | 128 | miri: 129 | name: Miri 130 | runs-on: ubuntu-latest 131 | env: 132 | RUSTFLAGS: "-Zrandomize-layout" 133 | MIRIFLAGS: "-Zmiri-retag-fields" 134 | steps: 135 | - uses: actions/checkout@v2 136 | with: 137 | fetch-depth: 1 138 | - uses: hecrj/setup-rust-action@v1 139 | with: 140 | rust-version: nightly 141 | components: miri 142 | - name: Run tests 143 | run: cargo miri test 144 | 145 | cargo-clippy: 146 | name: Lint 147 | runs-on: ubuntu-latest 148 | env: 149 | RUSTFLAGS: -Dwarnings 150 | steps: 151 | - uses: actions/checkout@v2 152 | with: 153 | fetch-depth: 1 154 | - uses: hecrj/setup-rust-action@v1 155 | with: 156 | rust-version: stable 157 | - name: Run cargo clippy (default features) 158 | run: cargo clippy --all-targets --verbose -- -D clippy::all 159 | 160 | # Ensure patch is formatted. 161 | fmt: 162 | name: Format 163 | runs-on: ubuntu-latest 164 | steps: 165 | - uses: actions/checkout@v2 166 | with: 167 | fetch-depth: 1 168 | - uses: hecrj/setup-rust-action@v1 169 | with: 170 | rust-version: stable 171 | components: rustfmt 172 | - name: Check formatting 173 | run: cargo fmt --all -- --check 174 | 175 | # Check doc reference links are all valid. 176 | doc: 177 | name: Doc check 178 | runs-on: ubuntu-latest 179 | steps: 180 | - uses: actions/checkout@v2 181 | with: 182 | fetch-depth: 1 183 | - uses: hecrj/setup-rust-action@v1 184 | with: 185 | rust-version: nightly 186 | - name: Check docs 187 | # Note: needs cargo rustdoc, not cargo doc. 188 | run: cargo rustdoc --all-features -- -D warnings 189 | 190 | sanitizers: 191 | name: Test sanitizer ${{ matrix.sanitizer }} 192 | runs-on: ubuntu-latest 193 | env: 194 | RUST_BACKTRACE: 0 195 | # only used by asan, but we set it for all of them cuz its easy 196 | ASAN_OPTIONS: detect_stack_use_after_return=1 197 | 198 | strategy: 199 | fail-fast: false 200 | matrix: 201 | sanitizer: [address, memory] 202 | include: 203 | - sanitizer: memory 204 | extra_rustflags: "-Zsanitizer-memory-track-origins" 205 | steps: 206 | - uses: actions/checkout@v2 207 | with: 208 | fetch-depth: 1 209 | - uses: actions-rs/toolchain@v1 210 | with: 211 | profile: minimal 212 | toolchain: nightly 213 | override: true 214 | components: rust-src 215 | 216 | - name: Test with sanitizer 217 | env: 218 | RUSTFLAGS: -Zsanitizer=${{ matrix.sanitizer }} ${{ matrix.extra_rustflags }} 219 | RUSTDOCFLAGS: -Zsanitizer=${{ matrix.sanitizer }} ${{ matrix.extra_rustflags }} 220 | run: | 221 | echo "note: RUSTFLAGS='$RUSTFLAGS'" 222 | cargo -Zbuild-std test --target=x86_64-unknown-linux-gnu 223 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Crate" 2 | newline_style = "Unix" 3 | group_imports = "StdExternalCrate" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for `stuff` 2 | 3 | ## 0.2.0 4 | 5 | ### **Breaking changes** 6 | * Renamed all occurrences of `extra` to `other` (for example in methods) 7 | 8 | ### Improvements 9 | * Made `(): StuffingStrategy` generic over all backends (with some trait bounds) instead of just `usize`, `u64`, and `u128`. 10 | * Added an MSRV (minimum supported rust version) policy (MSRV of `1.31.0`) 11 | * Upgraded `sptr` dependency to `0.3.1` 12 | * Improve documentation -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stuff" 3 | version = "0.3.0-beta.0" 4 | edition = "2018" 5 | description = "Stuffing things into pointers." 6 | readme = "./README.md" 7 | homepage = "https://github.com/Nilstrieb/stuff" 8 | repository = "https://github.com/Nilstrieb/stuff" 9 | documentation = "https://docs.rs/stuff" 10 | license = "MIT" 11 | keywords = ["unsafe", "pointer", "bitpacking", "provenance"] 12 | categories = ["data-structures", "memory-management", "no-std"] 13 | include = ["Cargo.toml", "LICENSE", "src", "README.md"] 14 | rust-version = "1.34.2" 15 | 16 | [dependencies] 17 | sptr = "0.3.1" 18 | 19 | [dev-dependencies] 20 | paste = "1.0.7" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 nils 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stuff 2 | 3 | [![crates.io](https://img.shields.io/crates/v/stuff.svg)](https://crates.io/crates/stuff) 4 | [![docs.rs](https://img.shields.io/docsrs/stuff)](https://docs.rs/stuff) 5 | ![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2FNilstrieb%2Fstuff%2Fbadge%3Fref%3Dmain&style=flat) 6 | 7 | A crate for stuffing things into a pointer. 8 | 9 | This crate is tested using miri (with `-Zmiri-tag-raw-pointers`). 10 | 11 | `stuff` helps you to 12 | 13 | - Stuff arbitrary data into pointers 14 | - Stuff pointers or arbitrary data into fixed size storage (u64, u128) 15 | 16 | in a **portable and provenance friendly** way. 17 | 18 | It does by providing an abstraction around it, completely abstracting away the provenance and pointers from 19 | the user, allowing the user to do their bit stuffing only on integers (pointer addresses) themselves. 20 | 21 | `StuffedPtr` is the main type of this crate. It's a type whose size depends on the 22 | choice of `Backend` (defaults to `usize`, `u64` and `u128` are also possible). It can store a 23 | pointer or some `other` data. 24 | 25 | You can choose any arbitrary bitstuffing depending on the `StuffingStrategy`, a trait that governs 26 | how the `other` data (or the pointer itself) will be packed into the backend. 27 | 28 | # Example: NaN-Boxing 29 | Pointers are hidden in the NaN values of floats. NaN boxing often involves also hiding booleans 30 | or null in there, but we stay with floats and pointers (pointers to a `HashMap` that servers 31 | as our "object" type). 32 | 33 | See [crafting interpreters](https://craftinginterpreters.com/optimization.html#nan-boxing) 34 | for more details. 35 | 36 | ```rust 37 | use std::collections::HashMap; 38 | use std::convert::{TryFrom, TryInto}; 39 | use std::mem::ManuallyDrop; 40 | 41 | use stuff::{StuffedPtr, StuffingStrategy, Unstuffed}; 42 | 43 | // Create a unit struct for our strategy 44 | struct NanBoxStrategy; 45 | 46 | // implementation detail of NaN boxing, a quiet NaN mask 47 | const QNAN: u64 = 0x7ffc000000000000; 48 | // implementation detail of NaN boxing, the sign bit of an f64 49 | const SIGN_BIT: u64 = 0x8000000000000000; 50 | 51 | impl StuffingStrategy for NanBoxStrategy { 52 | type Other = f64; 53 | fn stuff_other(inner: Self::Other) -> u64 { 54 | unsafe { std::mem::transmute(inner) } // both are 64 bit POD's 55 | } 56 | fn extract(data: u64) -> Unstuffed { 57 | if (data & QNAN) != QNAN { 58 | Unstuffed::Other(f64::from_bits(data)) 59 | } else { 60 | Unstuffed::Ptr((data & !(SIGN_BIT | QNAN)).try_into().unwrap()) 61 | } 62 | } 63 | fn stuff_ptr(addr: usize) -> u64 { 64 | // add the QNAN and SIGN_BIT 65 | SIGN_BIT | QNAN | u64::try_from(addr).unwrap() 66 | } 67 | } 68 | 69 | // a very, very crude representation of an object 70 | type Object = HashMap; 71 | 72 | // our value type 73 | type Value = StuffedPtr; 74 | 75 | let float: Value = StuffedPtr::new_other(123.5); 76 | assert_eq!(float.other(), Some(123.5)); 77 | 78 | let object: Object = HashMap::from([("a".to_owned(), 457)]); 79 | let boxed = Box::new(object); 80 | let ptr: Value = StuffedPtr::new_ptr(Box::into_raw(boxed)); 81 | 82 | let object = unsafe { &*ptr.ptr().unwrap() }; 83 | assert_eq!(object.get("a"), Some(&457)); 84 | 85 | drop(unsafe { Box::from_raw(ptr.ptr().unwrap()) }); 86 | 87 | // be careful, `ptr` is a dangling pointer now! 88 | ``` 89 | 90 | # MSRV-Policy 91 | `stuff`s current MSRV is `1.34.2`. This version *can* get increased in a non-breaking change, but such changes 92 | are avoided unless necessary. Features requiring a newer Rust version might get gated behind optional features in the future. 93 | -------------------------------------------------------------------------------- /src/backend.rs: -------------------------------------------------------------------------------- 1 | use sptr::Strict; 2 | 3 | /// A backend where the stuffed pointer is stored. Must be bigger or equal to the pointer size. 4 | /// 5 | /// The `Backend` is a trait to define types that store the stuffed pointer. It's supposed to 6 | /// be implemented on `Copy` types like `usize`, `u64`, or `u128`. Note that the `Self` type here 7 | /// serves as the main interchange format between the `Backend` and [`StuffedPtr`](`crate::StuffedPtr`) 8 | /// but *not* the actual underlying storage, which always contains a pointer to keep provenance 9 | /// (for example `(*mut (), u32)` on 32 bit for `u64`). This implies that `Self` *should* have the same 10 | /// size as `Backend::Stored`. 11 | /// 12 | /// This trait is just exposed for convenience and flexibility, you are usually not expected to implement 13 | /// it yourself, although such occasions could occur (for example to have a bigger storage than `u128` 14 | /// or smaller storage that only works on 32-bit or 16-bit platforms. 15 | /// 16 | /// # Safety 17 | /// Implementers of this trait *must* keep provenance of pointers, so if a valid pointer address+provenance 18 | /// combination is set in `set_ptr`, `get_ptr` *must* return the exact same values and provenance. 19 | pub unsafe trait Backend { 20 | /// The underlying type where the data is stored. Often a tuple of a pointer (for the provenance) 21 | /// and some integers to fill up the bytes. 22 | type Stored: Copy; 23 | 24 | /// Get the pointer from the backed. Since the [`StuffingStrategy`](`crate::StuffingStrategy`) 25 | /// is able to use the full bytes to pack in the pointer address, the full address is returned 26 | /// in the second tuple field, as the integer. The provenance of the pointer is returned as 27 | /// the first tuple field, but its address should be ignored and may be invalid. 28 | fn get_ptr(s: Self::Stored) -> (*mut (), Self); 29 | 30 | /// Set a new pointer address. The provenance of the new pointer is transferred in the first argument, 31 | /// and the address in the second. See [`Backend::get_ptr`] for more details on the separation. 32 | fn set_ptr(provenance: *mut (), addr: Self) -> Self::Stored; 33 | 34 | /// Get the integer value from the backend. Note that this *must not* be used to create a pointer, 35 | /// for that use [`Backend::get_ptr`] to keep the provenance. 36 | fn get_int(s: Self::Stored) -> Self; 37 | } 38 | 39 | #[cfg(test)] // todo: this mustn't affect the msrv, fix this later 40 | mod backend_size_asserts { 41 | use core::mem; 42 | 43 | use super::Backend; 44 | 45 | #[allow(dead_code)] // :/ 46 | const fn assert_same_size() { 47 | let has_equal_size = mem::size_of::() == mem::size_of::(); 48 | assert!(has_equal_size); 49 | } 50 | 51 | #[cfg(not(target_pointer_width = "16"))] 52 | const _: () = assert_same_size::::Stored>(); 53 | const _: () = assert_same_size::::Stored>(); 54 | const _: () = assert_same_size::::Stored>(); 55 | } 56 | 57 | // SAFETY: We are careful around provenance 58 | unsafe impl Backend for usize { 59 | type Stored = *mut (); 60 | 61 | fn get_ptr(s: Self::Stored) -> (*mut (), Self) { 62 | (s, Strict::addr(s)) 63 | } 64 | 65 | fn set_ptr(provenance: *mut (), addr: Self) -> Self::Stored { 66 | Strict::with_addr(provenance, addr) 67 | } 68 | 69 | fn get_int(s: Self::Stored) -> Self { 70 | Strict::addr(s) 71 | } 72 | } 73 | 74 | #[cfg(target_pointer_width = "64")] 75 | /// on 64 bit, we can just treat u64/usize interchangeably, because uintptr_t == size_t in Rust 76 | // SAFETY: We are careful around provenance 77 | unsafe impl Backend for u64 { 78 | type Stored = *mut (); 79 | 80 | fn get_ptr(s: Self::Stored) -> (*mut (), Self) { 81 | (s, Strict::addr(s) as u64) 82 | } 83 | 84 | fn set_ptr(provenance: *mut (), addr: Self) -> Self::Stored { 85 | Strict::with_addr(provenance, addr as usize) 86 | } 87 | 88 | fn get_int(s: Self::Stored) -> Self { 89 | Strict::addr(s) as u64 90 | } 91 | } 92 | 93 | macro_rules! impl_backend_2_tuple { 94 | (impl for $ty:ty { (*mut (), $int:ident), $num:expr }) => { 95 | // SAFETY: We are careful around provenance 96 | unsafe impl Backend for $ty { 97 | // this one keeps the MSB in the pointer address, and the LSB in the integer 98 | 99 | type Stored = (*mut (), $int); 100 | 101 | fn get_ptr(s: Self::Stored) -> (*mut (), Self) { 102 | (s.0, Self::get_int(s)) 103 | } 104 | 105 | fn set_ptr(provenance: *mut (), addr: Self) -> Self::Stored { 106 | let ptr_addr = (addr >> $num) as usize; 107 | let int_addr = addr as $int; // truncate it 108 | (Strict::with_addr(provenance, ptr_addr), int_addr) 109 | } 110 | 111 | fn get_int(s: Self::Stored) -> Self { 112 | let ptr_addr = Strict::addr(s.0) as $int; 113 | (<$ty>::from(ptr_addr) << $num) | <$ty>::from(s.1) 114 | } 115 | } 116 | }; 117 | } 118 | 119 | /// num1 is ptr-sized, num2 is 2*ptr sized 120 | #[cfg_attr(target_pointer_width = "64", allow(unused))] // not required on 64 bit 121 | macro_rules! impl_backend_3_tuple { 122 | (impl for $ty:ty { (*mut (), $int1:ident, $int2:ident), $num1:expr, $num2:expr }) => { 123 | // SAFETY: We are careful around provenance 124 | unsafe impl Backend for $ty { 125 | // this one keeps the MSB in the pointer address, ISB in int1 and the LSB in the int2 126 | 127 | type Stored = (*mut (), $int1, $int2); 128 | 129 | fn get_ptr(s: Self::Stored) -> (*mut (), Self) { 130 | (s.0, Self::get_int(s)) 131 | } 132 | 133 | fn set_ptr(provenance: *mut (), addr: Self) -> Self::Stored { 134 | let ptr_addr = (addr >> ($num1 + $num2)) as usize; 135 | let num1_addr = (addr >> $num2) as $int1; // truncate it 136 | let num2_addr = addr as $int2; // truncate it 137 | ( 138 | Strict::with_addr(provenance, ptr_addr), 139 | num1_addr, 140 | num2_addr, 141 | ) 142 | } 143 | 144 | fn get_int(s: Self::Stored) -> Self { 145 | let ptr_addr = Strict::addr(s.0) as $ty; 146 | let num1_addr = s.1 as $ty; 147 | let num2_addr = s.2 as $ty; 148 | (ptr_addr << ($num1 + $num2)) | (num1_addr << ($num2)) | num2_addr 149 | } 150 | } 151 | }; 152 | } 153 | 154 | #[cfg(target_pointer_width = "64")] 155 | impl_backend_2_tuple!(impl for u128 { (*mut (), u64), 64 }); 156 | 157 | #[cfg(target_pointer_width = "32")] 158 | impl_backend_2_tuple!(impl for u64 { (*mut (), u32), 32 }); 159 | 160 | #[cfg(target_pointer_width = "32")] 161 | impl_backend_3_tuple!(impl for u128 { (*mut (), u32, u64), 32, 64 }); 162 | 163 | #[cfg(target_pointer_width = "16")] 164 | impl_backend_3_tuple!(impl for u64 { (*mut (), u16, u32), 16, 32 }); 165 | 166 | // no 128 on 16 bit for now 167 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![warn(rust_2018_idioms)] 3 | #![warn(missing_docs)] 4 | #![deny(clippy::undocumented_unsafe_blocks)] 5 | 6 | //! A crate for stuffing things into a pointer. 7 | //! 8 | //! `stuff` helps you to 9 | //! 10 | //! - Stuff arbitrary data into pointers 11 | //! - Stuff pointers or arbitrary data into fixed size storage (u64, u128) 12 | //! 13 | //! in a **portable and provenance friendly** way. 14 | //! 15 | //! [`StuffedPtr`] is the main type of this crate. You it's a type whose size depends on the 16 | //! choice of [`Backend`] (defaults to `usize`, `u64` and `u128` are also possible). It can store a 17 | //! pointer or some `other` data. 18 | //! 19 | //! You can choose any arbitrary bitstuffing depending on the [`StuffingStrategy`], a trait that governs 20 | //! how the `other` data (or the pointer itself) will be packed into the backend. 21 | //! 22 | //! # Example: NaN-Boxing 23 | //! Pointers are hidden in the NaN values of floats. NaN boxing often involves also hiding booleans 24 | //! or null in there, but we stay with floats and pointers (pointers to a `HashMap` that servers 25 | //! as our "object" type). 26 | //! 27 | //! See [crafting interpreters](https://craftinginterpreters.com/optimization.html#nan-boxing) 28 | //! for more details. 29 | //! ``` 30 | //! use std::collections::HashMap; 31 | //! # use std::convert::{TryFrom, TryInto}; 32 | //! use std::mem::ManuallyDrop; 33 | //! 34 | //! use stuff::{StuffedPtr, StuffingStrategy, Unstuffed}; 35 | //! 36 | //! // Create a unit struct for our strategy 37 | //! struct NanBoxStrategy; 38 | //! 39 | //! // implementation detail of NaN boxing, a quiet NaN mask 40 | //! const QNAN: u64 = 0x7ffc000000000000; 41 | //! // implementation detail of NaN boxing, the sign bit of an f64 42 | //! const SIGN_BIT: u64 = 0x8000000000000000; 43 | //! 44 | //! impl StuffingStrategy for NanBoxStrategy { 45 | //! type Other = f64; 46 | //! 47 | //! fn stuff_other(inner: Self::Other) -> u64 { 48 | //! unsafe { std::mem::transmute(inner) } // both are 64 bit POD's 49 | //! } 50 | //! 51 | //! fn extract(data: u64) -> Unstuffed { 52 | //! if (data & QNAN) != QNAN { 53 | //! Unstuffed::Other(f64::from_bits(data)) 54 | //! } else { 55 | //! Unstuffed::Ptr((data & !(SIGN_BIT | QNAN)).try_into().unwrap()) 56 | //! } 57 | //! } 58 | //! 59 | //! fn stuff_ptr(addr: usize) -> u64 { 60 | //! // add the QNAN and SIGN_BIT 61 | //! SIGN_BIT | QNAN | u64::try_from(addr).unwrap() 62 | //! } 63 | //! } 64 | //! 65 | //! // a very, very crude representation of an object 66 | //! type Object = HashMap; 67 | //! 68 | //! // our value type 69 | //! type Value = StuffedPtr; 70 | //! 71 | //! let float: Value = StuffedPtr::new_other(123.5); 72 | //! assert_eq!(float.other(), Some(123.5)); 73 | //! 74 | //! let object: Object = HashMap::from([("a".to_owned(), 457)]); 75 | //! let boxed = Box::new(object); 76 | //! let ptr: Value = StuffedPtr::new_ptr(Box::into_raw(boxed)); 77 | //! 78 | //! let object = unsafe { &*ptr.ptr().unwrap() }; 79 | //! assert_eq!(object.get("a"), Some(&457)); 80 | //! 81 | //! drop(unsafe { Box::from_raw(ptr.ptr().unwrap()) }); 82 | //! 83 | //! // be careful, `ptr` is a dangling pointer now! 84 | //! ``` 85 | 86 | #[cfg(test)] 87 | extern crate std; 88 | 89 | mod backend; 90 | mod strategy; 91 | 92 | #[cfg(any())] 93 | mod tag; 94 | 95 | use core::{ 96 | fmt::{Debug, Formatter}, 97 | hash::{Hash, Hasher}, 98 | marker::PhantomData, 99 | }; 100 | 101 | use sptr::Strict; 102 | 103 | pub use crate::{backend::Backend, either::Unstuffed, strategy::StuffingStrategy}; 104 | 105 | /// A union of a pointer or some `other` data, bitpacked into a value with the size depending on 106 | /// `B`. It defaults to `usize`, meaning pointer sized, but `u64` and `u128` are also provided 107 | /// by this crate. You can also provide your own [`Backend`] implementation 108 | /// 109 | /// The stuffing strategy is supplied as the second generic parameter `S`. 110 | /// 111 | /// The first generic parameter `T` is the type that the pointer is pointing to. 112 | /// 113 | /// For a usage example, view the crate level documentation. 114 | /// 115 | /// `StuffedPtr` implements most traits like `Hash` or `PartialEq` if the `other` type does. 116 | /// It's also always `Copy`, and therefore requires the other type to be `Copy` as well. 117 | /// 118 | /// This type is guaranteed to be `#[repr(transparent)]` to a `B::Stored`. 119 | #[repr(transparent)] 120 | pub struct StuffedPtr(B::Stored, PhantomData>) 121 | where 122 | B: Backend; 123 | 124 | impl StuffedPtr 125 | where 126 | S: StuffingStrategy, 127 | B: Backend, 128 | { 129 | /// Create a new `StuffedPtr` from a pointer 130 | pub fn new_ptr(ptr: *mut T) -> Self { 131 | let addr = Strict::addr(ptr); 132 | let stuffed = S::stuff_ptr(addr); 133 | StuffedPtr(B::set_ptr(ptr as *mut (), stuffed), PhantomData) 134 | } 135 | 136 | /// Create a new `StuffPtr` from `other` data 137 | pub fn new_other(other: S::Other) -> Self { 138 | // this doesn't have any provenance, which is ok, since it's never a pointer anyways. 139 | // if the user calls `set_ptr` it will use the new provenance from that ptr 140 | let ptr = core::ptr::null_mut(); 141 | let other = S::stuff_other(other); 142 | StuffedPtr(B::set_ptr(ptr, other), PhantomData) 143 | } 144 | 145 | /// Get the pointer data, or `None` if it contains `other` data 146 | pub fn ptr(&self) -> Option<*mut T> { 147 | let (provenance, stored) = B::get_ptr(self.0); 148 | let addr = S::extract(stored).ptr()?; 149 | Some(Strict::with_addr(provenance as *mut T, addr)) 150 | } 151 | 152 | /// Get `other` data from this, or `None` if it contains pointer data 153 | pub fn other(&self) -> Option { 154 | let data = self.addr(); 155 | S::extract(data).other() 156 | } 157 | 158 | /// Get out the unstuffed enum representation 159 | pub fn unstuff(&self) -> Unstuffed<*mut T, S::Other> { 160 | let (provenance, stored) = B::get_ptr(self.0); 161 | let either = S::extract(stored); 162 | either.map_ptr(|addr| Strict::with_addr(provenance as *mut T, addr)) 163 | } 164 | 165 | fn addr(&self) -> B { 166 | B::get_int(self.0) 167 | } 168 | } 169 | 170 | impl Debug for StuffedPtr 171 | where 172 | S: StuffingStrategy, 173 | S::Other: Debug, 174 | B: Backend, 175 | { 176 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 177 | match self.unstuff() { 178 | Unstuffed::Ptr(ptr) => f.debug_tuple("Ptr").field(&ptr).finish(), 179 | Unstuffed::Other(other) => f.debug_tuple("Other").field(&other).finish(), 180 | } 181 | } 182 | } 183 | 184 | impl Clone for StuffedPtr 185 | where 186 | S: StuffingStrategy, 187 | B: Backend, 188 | { 189 | fn clone(&self) -> Self { 190 | *self 191 | } 192 | } 193 | 194 | impl Copy for StuffedPtr 195 | where 196 | S: StuffingStrategy, 197 | B: Backend, 198 | { 199 | } 200 | 201 | impl PartialEq for StuffedPtr 202 | where 203 | S: StuffingStrategy, 204 | S::Other: PartialEq, 205 | B: Backend, 206 | { 207 | fn eq(&self, other: &Self) -> bool { 208 | match (self.unstuff(), other.unstuff()) { 209 | (Unstuffed::Ptr(a), Unstuffed::Ptr(b)) => core::ptr::eq(a, b), 210 | (Unstuffed::Other(a), Unstuffed::Other(b)) => a == b, 211 | _ => false, 212 | } 213 | } 214 | } 215 | 216 | impl Eq for StuffedPtr 217 | where 218 | S: StuffingStrategy, 219 | S::Other: PartialEq + Eq, 220 | B: Backend, 221 | { 222 | } 223 | 224 | impl Hash for StuffedPtr 225 | where 226 | S: StuffingStrategy, 227 | S::Other: Hash, 228 | B: Backend, 229 | { 230 | fn hash(&self, state: &mut H) { 231 | match self.unstuff() { 232 | Unstuffed::Ptr(ptr) => { 233 | ptr.hash(state); 234 | } 235 | Unstuffed::Other(other) => { 236 | other.hash(state); 237 | } 238 | } 239 | } 240 | } 241 | 242 | mod either { 243 | /// The enum representation of a `StuffedPtr` 244 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 245 | pub enum Unstuffed { 246 | /// The pointer or pointer address 247 | Ptr(P), 248 | /// The other type 249 | Other(O), 250 | } 251 | 252 | impl Unstuffed { 253 | /// Get the pointer, or `None` if it's the other 254 | pub fn ptr(&self) -> Option

{ 255 | match *self { 256 | Unstuffed::Ptr(ptr) => Some(ptr), 257 | Unstuffed::Other(_) => None, 258 | } 259 | } 260 | 261 | /// Get the other type, or `None` if it's the pointer 262 | pub fn other(self) -> Option { 263 | match self { 264 | Unstuffed::Ptr(_) => None, 265 | Unstuffed::Other(other) => Some(other), 266 | } 267 | } 268 | 269 | /// Maps the pointer type if it's a pointer, or does nothing if it's other 270 | pub fn map_ptr(self, f: impl FnOnce(P) -> U) -> Unstuffed { 271 | match self { 272 | Unstuffed::Ptr(ptr) => Unstuffed::Ptr(f(ptr)), 273 | Unstuffed::Other(other) => Unstuffed::Other(other), 274 | } 275 | } 276 | } 277 | } 278 | 279 | #[cfg(test)] 280 | mod tests { 281 | #![allow(non_snake_case, clippy::undocumented_unsafe_blocks)] 282 | 283 | use std::{boxed::Box, format, println}; 284 | 285 | use paste::paste; 286 | 287 | use crate::{ 288 | strategy::test_strategies::{EmptyInMax, HasDebug}, 289 | Backend, StuffedPtr, StuffingStrategy, 290 | }; 291 | 292 | fn from_box(boxed: Box) -> StuffedPtr 293 | where 294 | S: StuffingStrategy, 295 | B: Backend, 296 | { 297 | StuffedPtr::new_ptr(Box::into_raw(boxed)) 298 | } 299 | 300 | macro_rules! make_tests { 301 | ($backend:ident) => { 302 | paste! { 303 | #[test] 304 | fn []() { 305 | let boxed = Box::new(1); 306 | let stuffed_ptr: StuffedPtr = from_box(boxed); 307 | let ptr = stuffed_ptr.ptr().unwrap(); 308 | // SAFETY: We just allocated that one above 309 | let boxed = unsafe { Box::from_raw(ptr) }; 310 | assert_eq!(*boxed, 1); 311 | } 312 | 313 | 314 | #[test] 315 | fn []() { 316 | let stuffed_ptr: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_other(EmptyInMax); 317 | assert!(stuffed_ptr.other().is_some()); 318 | assert!(matches!(stuffed_ptr.other(), Some(EmptyInMax))); 319 | } 320 | 321 | #[test] 322 | fn []() { 323 | let boxed = Box::new(1); 324 | let stuffed_ptr: StuffedPtr = from_box(boxed); 325 | println!("{stuffed_ptr:?}"); 326 | assert!(format!("{stuffed_ptr:?}").starts_with("Ptr(")); 327 | 328 | // SAFETY: We just allocated that one above 329 | drop(unsafe { Box::from_raw(stuffed_ptr.ptr().unwrap()) }); 330 | 331 | let other = HasDebug; 332 | let stuffed_ptr: StuffedPtr = StuffedPtr::new_other(other); 333 | assert_eq!( 334 | format!("{stuffed_ptr:?}"), 335 | "Other(hello!)" 336 | ); 337 | } 338 | 339 | #[test] 340 | #[allow(clippy::redundant_clone)] 341 | fn []() { 342 | let mut unit = (); 343 | let stuffed_ptr1: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_ptr(&mut unit); 344 | let _ = stuffed_ptr1.clone(); 345 | 346 | let stuffed_ptr1: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_other(EmptyInMax); 347 | let _ = stuffed_ptr1.clone(); 348 | } 349 | 350 | 351 | #[test] 352 | fn []() { 353 | // two pointers 354 | let mut unit = (); 355 | let stuffed_ptr1: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_ptr(&mut unit); 356 | let stuffed_ptr2: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_ptr(&mut unit); 357 | 358 | assert_eq!(stuffed_ptr1, stuffed_ptr2); 359 | 360 | let stuffed_ptr1: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_ptr(&mut unit); 361 | let stuffed_ptr2: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_other(EmptyInMax); 362 | 363 | assert_ne!(stuffed_ptr1, stuffed_ptr2); 364 | } 365 | 366 | } 367 | }; 368 | } 369 | 370 | make_tests!(u128); 371 | make_tests!(u64); 372 | make_tests!(usize); 373 | } 374 | -------------------------------------------------------------------------------- /src/strategy.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryInto; 2 | 3 | use crate::{Backend, Unstuffed}; 4 | 5 | /// A trait that describes how to stuff others and pointers into the pointer sized object. 6 | /// 7 | /// This trait is what a user of this crate is expected to implement to use the crate for their own 8 | /// pointer stuffing. It's usually implemented on ZSTs that only serve as stuffing strategies, but 9 | /// it's also completely possible to implement it on the type in [`StuffingStrategy::Other`] directly 10 | /// if possible. 11 | /// 12 | /// The generic parameter `B` stands for the [`Backend`](`crate::Backend`) used by the strategy. 13 | pub trait StuffingStrategy { 14 | /// The type of the other. 15 | type Other: Copy; 16 | 17 | /// Stuff other data into a usize that is then put into the pointer. This operation 18 | /// must be infallible. 19 | fn stuff_other(inner: Self::Other) -> B; 20 | 21 | /// Extract the pointer data or other data 22 | /// # Safety 23 | /// `data` must contain data created by [`StuffingStrategy::stuff_other`]. 24 | fn extract(data: B) -> Unstuffed; 25 | 26 | /// Stuff a pointer address into the pointer sized integer. 27 | /// 28 | /// This can be used to optimize away some of the unnecessary parts of the pointer or do other 29 | /// cursed things with it. 30 | /// 31 | /// The default implementation just returns the address directly. 32 | fn stuff_ptr(addr: usize) -> B; 33 | } 34 | 35 | impl StuffingStrategy for () 36 | where 37 | B: Backend + Default + TryInto, 38 | usize: TryInto, 39 | { 40 | type Other = (); 41 | 42 | fn stuff_other(_inner: Self::Other) -> B { 43 | B::default() 44 | } 45 | 46 | fn extract(data: B) -> Unstuffed { 47 | Unstuffed::Ptr( 48 | data.try_into() 49 | // note: this can't happen 🤔 50 | .unwrap_or_else(|_| panic!("Pointer value too big for usize")), 51 | ) 52 | } 53 | 54 | fn stuff_ptr(addr: usize) -> B { 55 | addr.try_into() 56 | .unwrap_or_else(|_| panic!("Address in `stuff_ptr` too big")) 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | pub(crate) mod test_strategies { 62 | use core::fmt::{Debug, Formatter}; 63 | 64 | use super::StuffingStrategy; 65 | use crate::Unstuffed; 66 | 67 | macro_rules! impl_usize_max_zst { 68 | ($ty:ident) => { 69 | // this one lives in usize::MAX 70 | impl StuffingStrategy for $ty { 71 | type Other = Self; 72 | 73 | #[allow(clippy::forget_copy)] 74 | fn stuff_other(inner: Self::Other) -> usize { 75 | core::mem::forget(inner); 76 | usize::MAX 77 | } 78 | 79 | fn extract(data: usize) -> Unstuffed { 80 | match data == usize::MAX { 81 | true => Unstuffed::Other($ty), 82 | false => Unstuffed::Ptr(data), 83 | } 84 | } 85 | 86 | fn stuff_ptr(addr: usize) -> usize { 87 | addr 88 | } 89 | } 90 | 91 | impl StuffingStrategy for $ty { 92 | type Other = Self; 93 | 94 | #[allow(clippy::forget_copy)] 95 | fn stuff_other(inner: Self::Other) -> u64 { 96 | core::mem::forget(inner); 97 | u64::MAX 98 | } 99 | 100 | fn extract(data: u64) -> Unstuffed { 101 | match data == u64::MAX { 102 | true => Unstuffed::Other($ty), 103 | false => Unstuffed::Ptr(data as usize), 104 | } 105 | } 106 | 107 | fn stuff_ptr(addr: usize) -> u64 { 108 | addr as u64 109 | } 110 | } 111 | 112 | impl StuffingStrategy for $ty { 113 | type Other = Self; 114 | 115 | #[allow(clippy::forget_copy)] 116 | fn stuff_other(inner: Self::Other) -> u128 { 117 | core::mem::forget(inner); 118 | u128::MAX 119 | } 120 | 121 | fn extract(data: u128) -> Unstuffed { 122 | match data == u128::MAX { 123 | true => Unstuffed::Other($ty), 124 | false => Unstuffed::Ptr(data as usize), 125 | } 126 | } 127 | 128 | fn stuff_ptr(addr: usize) -> u128 { 129 | addr as u128 130 | } 131 | } 132 | }; 133 | } 134 | 135 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 136 | pub struct EmptyInMax; 137 | 138 | impl_usize_max_zst!(EmptyInMax); 139 | 140 | #[derive(Clone, Copy)] 141 | pub struct HasDebug; 142 | 143 | impl Debug for HasDebug { 144 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 145 | f.write_str("hello!") 146 | } 147 | } 148 | 149 | impl_usize_max_zst!(HasDebug); 150 | } 151 | -------------------------------------------------------------------------------- /src/tag.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use core::marker::PhantomData; 4 | 5 | use sptr::Strict; 6 | 7 | use crate::Backend; 8 | 9 | pub struct TaggedPtr(B::Stored, PhantomData<(S, *mut T)>) 10 | where 11 | B: Backend; 12 | 13 | impl TaggedPtr 14 | where 15 | S: TaggingStrategy, 16 | B: Backend, 17 | { 18 | pub fn new(ptr: *mut T, tag: S::Tag) -> Self { 19 | let addr = Strict::addr(ptr); 20 | let tagged = S::set(addr, tag); 21 | let stored = B::set_ptr(ptr, tagged); 22 | TaggedPtr(stored, PhantomData) 23 | } 24 | 25 | pub fn get_ptr(&self) -> *mut T { 26 | let (provenance, stored) = B::get_ptr(self.0); 27 | let addr = S::get_ptr_addr(stored); 28 | Strict::with_addr(provenance, addr) 29 | } 30 | 31 | pub fn get_tag(&self) -> S::Tag { 32 | let stored = B::get_int(self.0); 33 | S::get_tag(stored) 34 | } 35 | 36 | pub fn set_tag(&self, tag: S::Tag) -> Self { 37 | let (provenance, stored) = B::get_ptr(self.0); 38 | let ptr_addr = S::get_ptr_addr(stored); 39 | let addr = S::set(ptr_addr, tag); 40 | let stored = B::set_ptr(provenance, addr); 41 | TaggedPtr(stored, PhantomData) 42 | } 43 | } 44 | 45 | impl Clone for TaggedPtr 46 | where 47 | B: Backend, 48 | { 49 | fn clone(&self) -> Self { 50 | TaggedPtr(self.0, self.1) 51 | } 52 | } 53 | 54 | impl Copy for TaggedPtr where B: Backend {} 55 | 56 | pub trait TaggingStrategy { 57 | type Tag: Copy; 58 | 59 | fn get_tag(data: B) -> Self::Tag; 60 | 61 | fn get_ptr_addr(data: B) -> usize; 62 | 63 | fn set(addr: usize, tag: Self::Tag) -> B; 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests {} 68 | --------------------------------------------------------------------------------