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-layout]: `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//! [primitive-layout]:
97//! https://doc.rust-lang.org/reference/type-layout.html#primitive-data-layout
98//! [repr-align]:
99#![doc = "https://doc.rust-lang.org/reference/type-layout.html\
100 #the-alignment-modifiers"]
101//!
102//! Assumptions
103//! -----------
104//!
105//! This crate avoids making assumptions about the representations of pointers.
106//! In particular, it does not cast pointers to `usize` and assume that the
107//! lower bits of that number can be used for tagging. There exist
108//! architectures that do not allow reusing the lower bits of aligned pointers
109//! in this manner, and even if none are currently supported by Rust, that
110//! could change in the future. This crate’s approach also works better with
111//! [strict provenance].
112//!
113//! [strict provenance]: https://github.com/rust-lang/rust/issues/95228
114//!
115//! Previously, this crate relied on assumptions about the behavior of
116//! [`pointer::align_offset`][align_offset] in certain circumstances. These
117//! assumptions were effectively always true, but were not strictly guaranteed,
118//! so a fallback implementation was provided with the crate feature
119//! `fallback`, which would avoid relying on this assumption at the cost of
120//! space efficiency.
121//!
122//! However, as of Rust 1.78, this assumption is no longer necessary:
123//! `align_offset` is [guaranteed to behave as required][121201].
124//!
125//! [align_offset]:
126//! https://doc.rust-lang.org/std/primitive.pointer.html#method.align_offset
127//! [121201]: https://github.com/rust-lang/rust/pull/121201/
128
129#[cfg(has_const_assert)]
130macro_rules! const_assert {
131 ($($tt:tt)*) => {
132 ::core::assert!($($tt)*)
133 };
134}
135
136/// NOTE: The assertions generated by this macro may be evaluated in code paths
137/// that aren't taken. Therefore, `$cond` must contain *all* conditions
138/// necessary and sufficient for the assertion to pass, independent of the flow
139/// of code.
140#[cfg(not(has_const_assert))]
141macro_rules! const_assert {
142 ($cond:expr $(,)?) => {
143 const_assert!($cond, "assertion failed")
144 };
145
146 ($cond:expr, $msg:literal $(,)?) => {{
147 let _ = [$msg][!$cond as usize];
148 }};
149}
150
151/// Documentation for the const `BITS` parameter.
152macro_rules! with_bits_doc {
153 ($(#[$attr:meta])* pub $($tt:tt)+) => {
154 $(#[$attr])*
155 ///
156 /// `BITS` specifies how many bits are used for the tag. The alignment
157 /// of `T` must be large enough to store this many bits: if `BITS` is
158 /// larger than the base-2 logarithm of the alignment of `T`[^bits],
159 /// panics or compilation errors will occur.
160 ///
161 /// [^bits]: Because alignment is always a power of 2, this is equal to
162 // Workaround for issues with links to Rust items in footnotes
163 #[doc = "<code>\
164 [align_of][core::mem::align_of]::\\<T>().\
165 [trailing_zeros][usize::trailing_zeros]\\()\
166 </code>."]
167 pub $($tt)*
168 };
169}
170
171mod ptr;
172mod reference;
173#[cfg(any(test, doctest))]
174mod tests;
175
176/// The type of the const `BITS` parameter. This could have been [`u32`], which
177/// would match the `BITS` constants in primitive types (e.g., [`u8::BITS`]),
178/// but [`usize`] is also suitable for storing any potential number of tag
179/// bits: because the tag itself is stored in a [`usize`], it cannot possibly
180/// consist of more than [`usize::MAX`] bits (or anywhere close to it).
181type Bits = usize;
182
183pub use ptr::TaggedPtr;
184pub use reference::{TaggedMutRef, TaggedRef};
185
186/// Alternate tagged pointer types without a `BITS` parameter.
187///
188/// The types in this module correspond to those in the [crate root][crate],
189/// but instead of taking a `BITS` parameter to determine how many tag bits
190/// are stored, they use the largest possible tag size for an aligned pointer
191/// to `T`.
192///
193/// See each type's documentation for more information.
194pub mod implied {
195 pub use super::ptr::implied::*;
196 pub use super::reference::implied::*;
197}