Skip to main content

tagged_pointer/
lib.rs

1/*
2 * Copyright 2021-2024 taylor.fish <contact@taylor.fish>
3 *
4 * This file is part of tagged-pointer.
5 *
6 * tagged-pointer is licensed under the Apache License, Version 2.0
7 * (the "License"); you may not use tagged-pointer except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19#![no_std]
20#![cfg_attr(has_unsafe_op_in_unsafe_fn, deny(unsafe_op_in_unsafe_fn))]
21#![warn(clippy::undocumented_unsafe_blocks)]
22
23//! This crate provides an implementation of [tagged pointers]: a
24//! space-efficient representation of a pointer and integer tag. In particular,
25//! both [`TaggedPtr`] and [`Option<TaggedPtr>`] are the size of a pointer
26//! despite containing both a pointer and tag.
27//!
28//! [tagged pointers]: https://en.wikipedia.org/wiki/Tagged_pointer
29//!
30//! This crate depends only on [`core`], so it can be used in `no_std`
31//! environments.
32//!
33//! [`core`]: https://doc.rust-lang.org/core/
34//!
35//! Example
36//! -------
37//!
38//! ```rust
39//! # #[cfg(not(feature = "fallback"))]
40//! use core::mem::size_of;
41//! use core::ptr::NonNull;
42//! use tagged_pointer::TaggedPtr;
43//!
44//! #[repr(align(4))]
45//! struct Item(u32, u32);
46//!
47//! # #[cfg(not(feature = "fallback"))]
48//! # {
49//! // `TaggedPtr` and `Option<TaggedPtr>` are both the size of a pointer:
50//! assert_eq!(size_of::<TaggedPtr<Item, 2>>(), size_of::<*mut ()>());
51//! assert_eq!(size_of::<Option<TaggedPtr<Item, 2>>>(), size_of::<*mut ()>());
52//! # }
53//!
54//! let item1 = Item(1, 2);
55//! let item2 = Item(3, 4);
56//!
57//! // We can store two bits of the tag, since `Item` has an alignment of 4.
58//! let tp1 = TaggedPtr::<_, 2>::new(NonNull::from(&item1), 1);
59//! let tp2 = TaggedPtr::<_, 2>::new(NonNull::from(&item2), 3);
60//!
61//! let (ptr1, tag1) = tp1.get();
62//! let (ptr2, tag2) = tp2.get();
63//!
64//! assert_eq!((ptr1, tag1), (NonNull::from(&item1), 1));
65//! assert_eq!((ptr2, tag2), (NonNull::from(&item2), 3));
66//! ```
67//!
68//! Platform considerations
69//! -----------------------
70//!
71//! The number of tag bits that can be stored in a pointer of a given type
72//! depends on the type’s alignment. However, the alignment of many types is
73//! [platform-specific][primitive-align]: `u64`, for example, could have an
74//! alignment of 8 on one platform and 4 on another.
75//!
76//! Therefore, it is highly recommended to use [`#[repr(align)]`][repr-align]
77//! to guarantee a minimum alignment, defining a wrapper type if necessary:
78//!
79//! ```rust
80//! # use core::ptr::NonNull;
81//! # use tagged_pointer::TaggedPtr;
82//! // This won't work on systems where `u64` has an alignment of 4!
83//! # if false {
84//! let x: u64 = 123;
85//! let tp = TaggedPtr::<u64, 3>::new(NonNull::from(&x), 0b101);
86//! # }
87//!
88//! // Instead, do this:
89//! #[repr(align(8))]
90//! struct MyU64(pub u64);
91//!
92//! let x = MyU64(123);
93//! let tp = TaggedPtr::<MyU64, 3>::new(NonNull::from(&x), 0b101);
94//! ```
95//!
96#![doc = "[primitive-align]: https://doc.rust-lang.org/reference/\
97    type-layout.html#r-layout.primitive.align"]
98#![doc = "[repr-align]: https://doc.rust-lang.org/reference/\
99    type-layout.html#r-layout.repr.alignment"]
100//!
101//! Assumptions
102//! -----------
103//!
104//! This crate avoids making assumptions about the representations of pointers.
105//! In particular, it does not cast pointers to `usize` and assume that the
106//! lower bits of that number can be used for tagging. There exist
107//! architectures that do not allow reusing the lower bits of aligned pointers
108//! in this manner, and even if none are currently supported by Rust, that
109//! could change in the future. This crate’s approach also works better with
110//! [strict provenance].
111//!
112//! [strict provenance]: https://github.com/rust-lang/rust/issues/95228
113//!
114//! Previously, this crate relied on assumptions about the behavior of
115//! [`pointer::align_offset`][align_offset] in certain circumstances. These
116//! assumptions were effectively always true, but were not strictly guaranteed,
117//! so a fallback implementation was provided with the crate feature
118//! `fallback`, which would avoid relying on this assumption at the cost of
119//! space efficiency.
120//!
121//! However, as of Rust 1.78, this assumption is no longer necessary:
122//! `align_offset` is [guaranteed to behave as required][121201].
123//!
124//! [align_offset]:
125//!  https://doc.rust-lang.org/std/primitive.pointer.html#method.align_offset
126//! [121201]: https://github.com/rust-lang/rust/pull/121201/
127
128#[cfg(has_const_assert)]
129macro_rules! const_assert {
130    ($($tt:tt)*) => {
131        ::core::assert!($($tt)*)
132    };
133}
134
135/// NOTE: The assertions generated by this macro may be evaluated in code paths
136/// that aren't taken. Therefore, `$cond` must contain *all* conditions
137/// necessary and sufficient for the assertion to pass, independent of the flow
138/// of code.
139#[cfg(not(has_const_assert))]
140macro_rules! const_assert {
141    ($cond:expr $(,)?) => {
142        const_assert!($cond, "assertion failed")
143    };
144
145    ($cond:expr, $msg:literal $(,)?) => {{
146        let _ = [$msg][!$cond as usize];
147    }};
148}
149
150/// Documentation for the const `BITS` parameter.
151macro_rules! with_bits_doc {
152    ($(#[$attr:meta])* pub $($tt:tt)+) => {
153        $(#[$attr])*
154        ///
155        /// `BITS` specifies how many bits are used for the tag. The alignment
156        /// of `T` must be large enough to store this many bits: if `BITS` is
157        /// larger than the base-2 logarithm of the alignment of `T`[^bits],
158        /// panics or compilation errors will occur.
159        ///
160        /// [^bits]: Because alignment is always a power of 2, this is equal to
161        // Workaround for issues with links to Rust items in footnotes
162        #[doc = "<code>\
163            [align_of][core::mem::align_of]::\\<T>().\
164            [trailing_zeros][usize::trailing_zeros]\\()\
165            </code>."]
166        pub $($tt)*
167    };
168}
169
170mod ptr;
171mod reference;
172#[cfg(any(test, doctest))]
173mod tests;
174
175/// The type of the const `BITS` parameter. This could have been [`u32`], which
176/// would match the `BITS` constants in primitive types (e.g., [`u8::BITS`]),
177/// but [`usize`] is also suitable for storing any potential number of tag
178/// bits: because the tag itself is stored in a [`usize`], it cannot possibly
179/// consist of more than [`usize::MAX`] bits (or anywhere close to it).
180type Bits = usize;
181
182pub use ptr::TaggedPtr;
183pub use reference::{TaggedMutRef, TaggedRef};
184
185/// Alternate tagged pointer types without a `BITS` parameter.
186///
187/// The types in this module correspond to those in the [crate root][crate],
188/// but instead of taking a `BITS` parameter to determine how many tag bits
189/// are stored, they use the largest possible tag size for an aligned pointer
190/// to `T`.
191///
192/// See each type's documentation for more information.
193pub mod implied {
194    pub use super::ptr::implied::*;
195    pub use super::reference::implied::*;
196}