obeli_sk_tag_ptr/lib.rs
1//! Utility library that enables a pointer to be associated with a tag of type `usize`
2
3#![doc = include_str!("../ABOUT.md")]
4#![doc(
5 html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg",
6 html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg"
7)]
8
9use std::ptr::{self, NonNull};
10
11/// A pointer that can be tagged with an `usize`.
12///
13/// Only pointers with a minimum alignment of 2-bytes are valid, and the tag must have its most
14/// significant bit (MSB) unset. In other words, the tag must fit inside `usize::BITS - 1` bits.
15/// Using pointers that are not 2-byte aligned won't cause Undefined Behaviour, but it could cause
16/// logical errors where the pointer is interpreted as an `usize` instead.
17///
18/// # Representation
19///
20/// If the least significant bit (LSB) of the internal [`NonNull`] is set (1), then the pointer
21/// address represents a tag where the remaining bits store the tag. Otherwise, the whole pointer
22/// represents the pointer itself.
23///
24/// It uses [`NonNull`], which guarantees that [`Tagged`] can use the "null pointer optimization"
25/// to optimize the size of [`Option<Tagged>`].
26///
27/// # Provenance
28///
29/// This struct stores a [`NonNull<T>`] instead of a [`NonZeroUsize`][std::num::NonZeroUsize]
30/// in order to preserve the provenance of our valid heap pointers.
31/// On the other hand, all index values are just casted to invalid pointers, because we don't need to
32/// preserve the provenance of [`usize`] indices.
33///
34/// [tagged_wp]: https://en.wikipedia.org/wiki/Tagged_pointer
35#[derive(Debug)]
36pub struct Tagged<T>(NonNull<T>);
37
38impl<T> Clone for Tagged<T> {
39 fn clone(&self) -> Self {
40 *self
41 }
42}
43
44impl<T> Copy for Tagged<T> {}
45
46impl<T> Tagged<T> {
47 /// Creates a new, tagged `Tagged` pointer from an integer.
48 ///
49 /// # Requirements
50 ///
51 /// - `tag` must fit inside `usize::BITS - 1` bits
52 #[inline]
53 #[must_use]
54 pub const fn from_tag(tag: usize) -> Self {
55 let addr = (tag << 1) | 1;
56 // SAFETY: `addr` is never zero, since we always set its LSB to 1
57 unsafe { Self(NonNull::new_unchecked(ptr::without_provenance_mut(addr))) }
58 }
59
60 /// Creates a new `Tagged` pointer from a raw pointer.
61 ///
62 /// # Requirements
63 ///
64 /// - `ptr` must have an alignment of at least 2.
65 ///
66 /// # Safety
67 ///
68 /// - `ptr` must be non null.
69 #[inline]
70 pub const unsafe fn from_ptr(ptr: *mut T) -> Self {
71 // SAFETY: the caller must ensure the invariants hold.
72 unsafe { Self(NonNull::new_unchecked(ptr)) }
73 }
74
75 /// Creates a new `Tagged` pointer from a (possibly tagged) `NonNull` pointer.
76 #[inline]
77 #[must_use]
78 pub const fn from_non_null(ptr: NonNull<T>) -> Self {
79 Self(ptr)
80 }
81
82 /// Unwraps the `Tagged` pointer.
83 #[inline]
84 #[must_use]
85 pub fn unwrap(self) -> UnwrappedTagged<T> {
86 let addr = self.0.as_ptr().addr();
87 if addr & 1 == 0 {
88 UnwrappedTagged::Ptr(self.0)
89 } else {
90 UnwrappedTagged::Tag(addr >> 1)
91 }
92 }
93
94 /// Gets the address of the inner pointer.
95 #[inline]
96 #[must_use]
97 pub fn addr(self) -> usize {
98 self.0.as_ptr().addr()
99 }
100
101 /// Gets the inner pointer.
102 ///
103 /// This may be a pointer or a tag, you can use
104 /// [`Self::is_tagged()`] to check.
105 #[inline]
106 #[must_use]
107 pub const fn as_inner_ptr(&self) -> NonNull<T> {
108 self.0
109 }
110
111 /// Returns `true` if `self ` is a tagged pointer.
112 #[inline]
113 #[must_use]
114 pub fn is_tagged(self) -> bool {
115 self.0.as_ptr().addr() & 1 > 0
116 }
117}
118
119/// The unwrapped value of a [`Tagged`] pointer.
120#[derive(Debug, Clone, Copy)]
121pub enum UnwrappedTagged<T> {
122 /// Pointer variant.
123 Ptr(NonNull<T>),
124 /// Tag variant.
125 Tag(usize),
126}