tagged_box/
tagged_pointer.rs

1use crate::discriminant::{
2    Discriminant, DISCRIMINANT_MASK, MAX_DISCRIMINANT, MAX_POINTER_VALUE, POINTER_WIDTH,
3};
4use core::fmt;
5
6/// A pointer that holds a data pointer plus additional data stored as a [`Discriminant`]  
7/// Note: The discriminant must be <= [`MAX_DISCRIMINANT`], which is feature-dependent  
8///
9/// [`MAX_DISCRIMINANT`]: crate::discriminant::MAX_DISCRIMINANT
10#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
11#[repr(transparent)]
12pub struct TaggedPointer {
13    /// The tagged pointer, the upper bits are used to store arbitrary data  
14    /// With the `48bits` feature the upper 16 bits are usable, while with the
15    /// `57bits` feature only the upper 7 bits are usable
16    tagged_ptr: usize,
17}
18
19impl TaggedPointer {
20    /// Create a new tagged pointer from a pointer and a discriminant
21    ///
22    /// # Panics
23    ///
24    /// Panics if `discriminant` is greater than [`MAX_DISCRIMINANT`] or if
25    /// `ptr` is greater than [`MAX_POINTER_VALUE`]
26    ///
27    /// [`MAX_DISCRIMINANT`]: crate::discriminant::MAX_DISCRIMINANT
28    /// [`MAX_POINTER_VALUE`]: crate::discriminant::MAX_POINTER_VALUE
29    #[inline]
30    #[allow(clippy::absurd_extreme_comparisons)]
31    pub fn new(ptr: usize, discriminant: Discriminant) -> Self {
32        assert!(
33            discriminant <= MAX_DISCRIMINANT,
34            "Attempted to store a discriminant of {} while the max value is {}",
35            discriminant,
36            MAX_DISCRIMINANT,
37        );
38        assert!(
39            ptr <= MAX_POINTER_VALUE, 
40            "If you are receiving this error, then your hardware uses more than {} bits of a pointer to store addresses. \
41            It is recommended that you use a different feature for the `tagged-box` crate using `features = [\"{}bits\"]` or above.
42            ",
43            POINTER_WIDTH,
44            POINTER_WIDTH + 1,
45        );
46
47        // Safety: The check that discriminant <= MAX_DISCRIMINANT has already been preformed
48        let tagged_ptr = unsafe { Self::store_discriminant_unchecked(ptr, discriminant) };
49
50        Self { tagged_ptr }
51    }
52
53    /// Create a new tagged pointer from a pointer and a discriminant without
54    /// checking invariance
55    ///
56    /// # Safety
57    ///
58    /// `discriminant` must be <= [`MAX_DISCRIMINANT`] and `pointer` must be <= 
59    /// [`MAX_POINTER_VALUE`]
60    ///
61    /// [`MAX_DISCRIMINANT`]: crate::discriminant::MAX_DISCRIMINANT
62    /// [`MAX_POINTER_VALUE`]: crate::discriminant::MAX_POINTER_VALUE
63    #[inline]
64    pub unsafe fn new_unchecked(ptr: usize, discriminant: Discriminant) -> Self {
65        let tagged_ptr = Self::store_discriminant_unchecked(ptr, discriminant);
66
67        Self { tagged_ptr }
68    }
69
70    /// Fetches the discriminant of the tagged pointer
71    #[inline]
72    pub const fn discriminant(self) -> Discriminant {
73        Self::fetch_discriminant(self.tagged_ptr)
74    }
75
76    /// Gains a reference to the inner value of the pointer
77    ///
78    /// # Safety
79    ///
80    /// The pointer given to [`TaggedPointer::new`] must be properly aligned and non-null
81    ///
82    /// [`TaggedPointer::new`]: crate::TaggedPointer#new
83    #[inline]
84    #[allow(clippy::should_implement_trait)]
85    pub unsafe fn as_ref<T>(&self) -> &T {
86        &*(Self::strip_discriminant(self.tagged_ptr) as *const T)
87    }
88
89    /// Gains a mutable reference to the inner value of the pointer
90    ///
91    /// # Safety
92    ///
93    /// The pointer given to [`TaggedPointer::new`] must be properly aligned and non-null
94    ///
95    /// [`TaggedPointer::new`]: crate::TaggedPointer#new
96    #[inline]
97    pub unsafe fn as_mut_ref<T>(&mut self) -> &mut T {
98        &mut *(Self::strip_discriminant(self.tagged_ptr) as *mut T)
99    }
100
101    /// Returns the pointer as a usize, removing the discriminant
102    #[inline]
103    pub const fn as_usize(self) -> usize {
104        Self::strip_discriminant(self.tagged_ptr)
105    }
106
107    /// Returns the raw tagged pointer, without removing the discriminant
108    ///
109    /// # Warning
110    ///
111    /// Attempting to dereference this usize will not point to valid memory!
112    ///
113    #[inline]
114    pub const fn as_raw_usize(self) -> usize {
115        self.tagged_ptr
116    }
117
118    /// Converts a tagged pointer into a raw pointer, removing the discriminant
119    #[inline]
120    pub const fn as_ptr<T>(self) -> *const T {
121        Self::strip_discriminant(self.tagged_ptr) as *const T
122    }
123
124    /// Converts a tagged pointer into a raw pointer, removing the discriminant
125    #[inline]
126    pub fn as_mut_ptr<T>(self) -> *mut T {
127        Self::strip_discriminant(self.tagged_ptr) as *mut T
128    }
129
130    /// Store a [`Discriminant`] into a tagged pointer
131    ///
132    /// # Panics
133    /// 
134    /// Panics if `discriminant` is greater than [`MAX_DISCRIMINANT`] or if
135    /// `ptr` is greater than [`MAX_POINTER_VALUE`]
136    ///
137    /// [`Discriminant`]: crate::Discriminant
138    /// [`MAX_DISCRIMINANT`]: crate::discriminant::MAX_DISCRIMINANT
139    /// [`MAX_POINTER_VALUE`]: crate::discriminant::MAX_POINTER_VALUE
140    #[inline]
141    #[allow(clippy::absurd_extreme_comparisons)]
142    pub fn store_discriminant(pointer: usize, discriminant: Discriminant) -> usize {
143        assert!(
144            discriminant <= MAX_DISCRIMINANT,
145            "Attempted to store a discriminant of {} while the max value is {}",
146            discriminant,
147            MAX_DISCRIMINANT,
148        );
149        assert!(
150            pointer <= MAX_POINTER_VALUE, 
151            "If you are receiving this error, then your hardware uses more than {} bits of a pointer to store addresses. \
152            It is recommended that you use a different feature for the `tagged-box` crate using `features = [\"{}bits\"]` or above.
153            ",
154            POINTER_WIDTH,
155            POINTER_WIDTH + 1,
156        );
157
158        pointer | ((discriminant as usize) << POINTER_WIDTH)
159    }
160
161    /// Store a [`Discriminant`] into a tagged pointer without any checks
162    ///
163    /// # Safety
164    ///
165    /// `discriminant` must be <= [`MAX_DISCRIMINANT`] and `pointer` must be <= 
166    /// [`MAX_POINTER_VALUE`]
167    ///
168    /// [`Discriminant`]: crate::Discriminant
169    /// [`MAX_DISCRIMINANT`]: crate::discriminant::MAX_DISCRIMINANT
170    /// [`MAX_POINTER_VALUE`]: crate::discriminant::MAX_POINTER_VALUE
171    #[inline]
172    pub unsafe fn store_discriminant_unchecked(
173        pointer: usize,
174        discriminant: Discriminant,
175    ) -> usize {
176        pointer | ((discriminant as usize) << POINTER_WIDTH)
177    }
178
179    /// Fetch a [`Discriminant`] from a tagged pointer    
180    ///
181    /// [`Discriminant`]: crate::Discriminant
182    #[inline]
183    #[allow(clippy::cast_possible_truncation)]
184    pub const fn fetch_discriminant(pointer: usize) -> Discriminant {
185        (pointer >> POINTER_WIDTH) as Discriminant
186    }
187
188    /// Strip the [`Discriminant`] from a tagged pointer, returning only the valid pointer as a usize
189    ///
190    /// [`Discriminant`]: crate::Discriminant
191    #[inline]
192    pub const fn strip_discriminant(pointer: usize) -> usize {
193        pointer & DISCRIMINANT_MASK
194    }
195}
196
197impl fmt::Debug for TaggedPointer {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        f.debug_struct("TaggedPointer")
200            .field("raw", &(self.as_raw_usize() as *const ()))
201            .field("ptr", &self.as_ptr::<()>())
202            .field("discriminant", &self.discriminant())
203            .finish()
204    }
205}
206
207impl fmt::Pointer for TaggedPointer {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        fmt::Pointer::fmt(&self.as_ptr::<()>(), f)
210    }
211}
212
213impl_fmt!(TaggedPointer => LowerHex, UpperHex, Binary, Octal);
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::discriminant;
219    use alloc::string::String;
220    use core::slice;
221
222    #[test]
223    fn utility_functions() {
224        let ptr = 0xF00D_BEEF;
225        let discrim = discriminant::MAX_DISCRIMINANT / 2;
226        let stored = TaggedPointer::new(ptr, discrim).as_raw_usize();
227
228        assert_eq!(TaggedPointer::strip_discriminant(stored), ptr);
229        assert_eq!(TaggedPointer::fetch_discriminant(stored), discrim);
230        assert_eq!(TaggedPointer::store_discriminant(ptr, discrim), stored);
231    }
232
233    #[test]
234    fn tagged_pointer() {
235        let integer = 100i32;
236        let int_ptr = &integer as *const _ as usize;
237        let discriminant = 10;
238
239        let ptr = TaggedPointer::new(int_ptr, discriminant);
240
241        assert_eq!(ptr.discriminant(), discriminant);
242        assert_eq!(ptr.as_usize(), int_ptr);
243
244        unsafe {
245            assert_eq!(ptr.as_ref::<i32>(), &integer);
246        }
247    }
248
249    #[test]
250    fn max_pointer() {
251        let ptr = discriminant::MAX_POINTER_VALUE;
252        let discriminant = discriminant::MAX_DISCRIMINANT;
253
254        let tagged = TaggedPointer::new(ptr, discriminant);
255
256        assert_eq!(tagged.discriminant(), discriminant);
257        assert_eq!(tagged.as_usize(), ptr);
258    }
259
260    #[test]
261    fn min_pointer() {
262        let ptr: usize = 0;
263        let discriminant = discriminant::MAX_DISCRIMINANT;
264
265        let tagged = TaggedPointer::new(ptr, discriminant);
266
267        assert_eq!(tagged.discriminant(), discriminant);
268        assert_eq!(tagged.as_usize(), ptr);
269    }
270
271    #[test]
272    fn max_discriminant() {
273        let integer = 100usize;
274        let int_ptr = &integer as *const _ as usize;
275        let discriminant = discriminant::MAX_DISCRIMINANT;
276
277        let ptr = TaggedPointer::new(int_ptr, discriminant);
278
279        assert_eq!(ptr.discriminant(), discriminant);
280        assert_eq!(ptr.as_usize(), int_ptr);
281
282        unsafe {
283            assert_eq!(ptr.as_ref::<usize>(), &integer);
284        }
285    }
286
287    #[test]
288    fn min_discriminant() {
289        let integer = 100usize;
290        let int_ptr = &integer as *const _ as usize;
291        let discriminant = 0;
292
293        let ptr = TaggedPointer::new(int_ptr, discriminant);
294
295        assert_eq!(ptr.discriminant(), discriminant);
296        assert_eq!(ptr.as_usize(), int_ptr);
297
298        unsafe {
299            assert_eq!(ptr.as_ref::<usize>(), &integer);
300        }
301    }
302
303    #[test]
304    fn string_pointer() {
305        let string = String::from("Hello world!");
306        let str_ptr = string.as_ptr() as usize;
307        let discriminant = discriminant::MAX_DISCRIMINANT;
308
309        let ptr = TaggedPointer::new(str_ptr, discriminant);
310
311        assert_eq!(ptr.discriminant(), discriminant);
312        assert_eq!(ptr.as_usize(), str_ptr);
313
314        unsafe {
315            let temp_str = slice::from_raw_parts(ptr.as_ptr::<u8>(), string.len());
316            assert_eq!(core::str::from_utf8(temp_str).unwrap(), &string);
317        }
318    }
319
320    #[test]
321    #[should_panic]
322    #[cfg_attr(miri, ignore)]
323    fn oversized_discriminant() {
324        let pointer = 0xF00D_BEEF;
325        let discriminant = if let Some(discrim) = discriminant::MAX_DISCRIMINANT.checked_add(1) {
326            discrim
327        } else {
328            panic!("Adding one to discriminant would overflow type, aborting test");
329        };
330
331        TaggedPointer::new(pointer, discriminant);
332    }
333}