• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use super::{Pointer, Tag};
2 use crate::stable_hasher::{HashStable, StableHasher};
3 use std::fmt;
4 use std::hash::{Hash, Hasher};
5 use std::marker::PhantomData;
6 use std::mem::ManuallyDrop;
7 use std::num::NonZeroUsize;
8 use std::ops::{Deref, DerefMut};
9 use std::ptr::NonNull;
10 
11 /// A [`Copy`] tagged pointer.
12 ///
13 /// This is essentially `{ pointer: P, tag: T }` packed in a single pointer.
14 ///
15 /// You should use this instead of the [`TaggedPtr`] type in all cases where
16 /// `P` implements [`Copy`].
17 ///
18 /// If `COMPARE_PACKED` is true, then the pointers will be compared and hashed without
19 /// unpacking. Otherwise we don't implement [`PartialEq`], [`Eq`] and [`Hash`];
20 /// if you want that, wrap the [`CopyTaggedPtr`].
21 ///
22 /// [`TaggedPtr`]: crate::tagged_ptr::TaggedPtr
23 pub struct CopyTaggedPtr<P, T, const COMPARE_PACKED: bool>
24 where
25     P: Pointer,
26     T: Tag,
27 {
28     /// This is semantically a pair of `pointer: P` and `tag: T` fields,
29     /// however we pack them in a single pointer, to save space.
30     ///
31     /// We pack the tag into the **most**-significant bits of the pointer to
32     /// ease retrieval of the value. A left shift is a multiplication and
33     /// those are embeddable in instruction encoding, for example:
34     ///
35     /// ```asm
36     /// // (<https://godbolt.org/z/jqcYPWEr3>)
37     /// example::shift_read3:
38     ///     mov     eax, dword ptr [8*rdi]
39     ///     ret
40     ///
41     /// example::mask_read3:
42     ///     and     rdi, -8
43     ///     mov     eax, dword ptr [rdi]
44     ///     ret
45     /// ```
46     ///
47     /// This is ASM outputted by rustc for reads of values behind tagged
48     /// pointers for different approaches of tagging:
49     /// - `shift_read3` uses `<< 3` (the tag is in the most-significant bits)
50     /// - `mask_read3` uses `& !0b111` (the tag is in the least-significant bits)
51     ///
52     /// The shift approach thus produces less instructions and is likely faster
53     /// (see <https://godbolt.org/z/Y913sMdWb>).
54     ///
55     /// Encoding diagram:
56     /// ```text
57     /// [ packed.addr                     ]
58     /// [ tag ] [ pointer.addr >> T::BITS ] <-- usize::BITS - T::BITS bits
59     ///    ^
60     ///    |
61     /// T::BITS bits
62     /// ```
63     ///
64     /// The tag can be retrieved by `packed.addr() >> T::BITS` and the pointer
65     /// can be retrieved by `packed.map_addr(|addr| addr << T::BITS)`.
66     packed: NonNull<P::Target>,
67     tag_ghost: PhantomData<T>,
68 }
69 
70 // Note that even though `CopyTaggedPtr` is only really expected to work with
71 // `P: Copy`, can't add `P: Copy` bound, because `CopyTaggedPtr` is used in the
72 // `TaggedPtr`'s implementation.
73 impl<P, T, const CP: bool> CopyTaggedPtr<P, T, CP>
74 where
75     P: Pointer,
76     T: Tag,
77 {
78     /// Tags `pointer` with `tag`.
79     ///
80     /// Note that this leaks `pointer`: it won't be dropped when
81     /// `CopyTaggedPtr` is dropped. If you have a pointer with a significant
82     /// drop, use [`TaggedPtr`] instead.
83     ///
84     /// [`TaggedPtr`]: crate::tagged_ptr::TaggedPtr
85     #[inline]
new(pointer: P, tag: T) -> Self86     pub fn new(pointer: P, tag: T) -> Self {
87         Self { packed: Self::pack(P::into_ptr(pointer), tag), tag_ghost: PhantomData }
88     }
89 
90     /// Retrieves the pointer.
91     #[inline]
pointer(self) -> P where P: Copy,92     pub fn pointer(self) -> P
93     where
94         P: Copy,
95     {
96         // SAFETY: pointer_raw returns the original pointer
97         //
98         // Note that this isn't going to double-drop or anything because we have
99         // P: Copy
100         unsafe { P::from_ptr(self.pointer_raw()) }
101     }
102 
103     /// Retrieves the tag.
104     #[inline]
tag(&self) -> T105     pub fn tag(&self) -> T {
106         // Unpack the tag, according to the `self.packed` encoding scheme
107         let tag = self.packed.addr().get() >> Self::TAG_BIT_SHIFT;
108 
109         // Safety:
110         // The shift retrieves the original value from `T::into_usize`,
111         // satisfying `T::from_usize`'s preconditions.
112         unsafe { T::from_usize(tag) }
113     }
114 
115     /// Sets the tag to a new value.
116     #[inline]
set_tag(&mut self, tag: T)117     pub fn set_tag(&mut self, tag: T) {
118         self.packed = Self::pack(self.pointer_raw(), tag);
119     }
120 
121     const TAG_BIT_SHIFT: u32 = usize::BITS - T::BITS;
122     const ASSERTION: () = { assert!(T::BITS <= P::BITS) };
123 
124     /// Pack pointer `ptr` that comes from [`P::into_ptr`] with a `tag`,
125     /// according to `self.packed` encoding scheme.
126     ///
127     /// [`P::into_ptr`]: Pointer::into_ptr
128     #[inline]
pack(ptr: NonNull<P::Target>, tag: T) -> NonNull<P::Target>129     fn pack(ptr: NonNull<P::Target>, tag: T) -> NonNull<P::Target> {
130         // Trigger assert!
131         let () = Self::ASSERTION;
132 
133         let packed_tag = tag.into_usize() << Self::TAG_BIT_SHIFT;
134 
135         ptr.map_addr(|addr| {
136             // Safety:
137             // - The pointer is `NonNull` => it's address is `NonZeroUsize`
138             // - `P::BITS` least significant bits are always zero (`Pointer` contract)
139             // - `T::BITS <= P::BITS` (from `Self::ASSERTION`)
140             //
141             // Thus `addr >> T::BITS` is guaranteed to be non-zero.
142             //
143             // `{non_zero} | packed_tag` can't make the value zero.
144 
145             let packed = (addr.get() >> T::BITS) | packed_tag;
146             unsafe { NonZeroUsize::new_unchecked(packed) }
147         })
148     }
149 
150     /// Retrieves the original raw pointer from `self.packed`.
151     #[inline]
pointer_raw(&self) -> NonNull<P::Target>152     pub(super) fn pointer_raw(&self) -> NonNull<P::Target> {
153         self.packed.map_addr(|addr| unsafe { NonZeroUsize::new_unchecked(addr.get() << T::BITS) })
154     }
155 
156     /// This provides a reference to the `P` pointer itself, rather than the
157     /// `Deref::Target`. It is used for cases where we want to call methods
158     /// that may be implement differently for the Pointer than the Pointee
159     /// (e.g., `Rc::clone` vs cloning the inner value).
with_pointer_ref<R>(&self, f: impl FnOnce(&P) -> R) -> R160     pub(super) fn with_pointer_ref<R>(&self, f: impl FnOnce(&P) -> R) -> R {
161         // Safety:
162         // - `self.raw.pointer_raw()` is originally returned from `P::into_ptr`
163         //   and as such is valid for `P::from_ptr`.
164         //   - This also allows us to not care whatever `f` panics or not.
165         // - Even though we create a copy of the pointer, we store it inside
166         //   `ManuallyDrop` and only access it by-ref, so we don't double-drop.
167         //
168         // Semantically this is just `f(&self.pointer)` (where `self.pointer`
169         // is non-packed original pointer).
170         //
171         // Note that even though `CopyTaggedPtr` is only really expected to
172         // work with `P: Copy`, we have to assume `P: ?Copy`, because
173         // `CopyTaggedPtr` is used in the `TaggedPtr`'s implementation.
174         let ptr = unsafe { ManuallyDrop::new(P::from_ptr(self.pointer_raw())) };
175         f(&ptr)
176     }
177 }
178 
179 impl<P, T, const CP: bool> Copy for CopyTaggedPtr<P, T, CP>
180 where
181     P: Pointer + Copy,
182     T: Tag,
183 {
184 }
185 
186 impl<P, T, const CP: bool> Clone for CopyTaggedPtr<P, T, CP>
187 where
188     P: Pointer + Copy,
189     T: Tag,
190 {
191     #[inline]
clone(&self) -> Self192     fn clone(&self) -> Self {
193         *self
194     }
195 }
196 
197 impl<P, T, const CP: bool> Deref for CopyTaggedPtr<P, T, CP>
198 where
199     P: Pointer,
200     T: Tag,
201 {
202     type Target = P::Target;
203 
204     #[inline]
deref(&self) -> &Self::Target205     fn deref(&self) -> &Self::Target {
206         // Safety:
207         // `pointer_raw` returns the original pointer from `P::into_ptr` which,
208         // by the `Pointer`'s contract, must be valid.
209         unsafe { self.pointer_raw().as_ref() }
210     }
211 }
212 
213 impl<P, T, const CP: bool> DerefMut for CopyTaggedPtr<P, T, CP>
214 where
215     P: Pointer + DerefMut,
216     T: Tag,
217 {
218     #[inline]
deref_mut(&mut self) -> &mut Self::Target219     fn deref_mut(&mut self) -> &mut Self::Target {
220         // Safety:
221         // `pointer_raw` returns the original pointer from `P::into_ptr` which,
222         // by the `Pointer`'s contract, must be valid for writes if
223         // `P: DerefMut`.
224         unsafe { self.pointer_raw().as_mut() }
225     }
226 }
227 
228 impl<P, T, const CP: bool> fmt::Debug for CopyTaggedPtr<P, T, CP>
229 where
230     P: Pointer + fmt::Debug,
231     T: Tag + fmt::Debug,
232 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result233     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234         self.with_pointer_ref(|ptr| {
235             f.debug_struct("CopyTaggedPtr").field("pointer", ptr).field("tag", &self.tag()).finish()
236         })
237     }
238 }
239 
240 impl<P, T> PartialEq for CopyTaggedPtr<P, T, true>
241 where
242     P: Pointer,
243     T: Tag,
244 {
245     #[inline]
eq(&self, other: &Self) -> bool246     fn eq(&self, other: &Self) -> bool {
247         self.packed == other.packed
248     }
249 }
250 
251 impl<P, T> Eq for CopyTaggedPtr<P, T, true>
252 where
253     P: Pointer,
254     T: Tag,
255 {
256 }
257 
258 impl<P, T> Hash for CopyTaggedPtr<P, T, true>
259 where
260     P: Pointer,
261     T: Tag,
262 {
263     #[inline]
hash<H: Hasher>(&self, state: &mut H)264     fn hash<H: Hasher>(&self, state: &mut H) {
265         self.packed.hash(state);
266     }
267 }
268 
269 impl<P, T, HCX, const CP: bool> HashStable<HCX> for CopyTaggedPtr<P, T, CP>
270 where
271     P: Pointer + HashStable<HCX>,
272     T: Tag + HashStable<HCX>,
273 {
hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher)274     fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
275         self.with_pointer_ref(|ptr| ptr.hash_stable(hcx, hasher));
276         self.tag().hash_stable(hcx, hasher);
277     }
278 }
279 
280 // Safety:
281 // `CopyTaggedPtr<P, T, ..>` is semantically just `{ ptr: P, tag: T }`, as such
282 // it's ok to implement `Sync` as long as `P: Sync, T: Sync`
283 unsafe impl<P, T, const CP: bool> Sync for CopyTaggedPtr<P, T, CP>
284 where
285     P: Sync + Pointer,
286     T: Sync + Tag,
287 {
288 }
289 
290 // Safety:
291 // `CopyTaggedPtr<P, T, ..>` is semantically just `{ ptr: P, tag: T }`, as such
292 // it's ok to implement `Send` as long as `P: Send, T: Send`
293 unsafe impl<P, T, const CP: bool> Send for CopyTaggedPtr<P, T, CP>
294 where
295     P: Send + Pointer,
296     T: Send + Tag,
297 {
298 }
299 
300 /// Test that `new` does not compile if there is not enough alignment for the
301 /// tag in the pointer.
302 ///
303 /// ```compile_fail,E0080
304 /// use rustc_data_structures::tagged_ptr::{CopyTaggedPtr, Tag};
305 ///
306 /// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
307 /// enum Tag2 { B00 = 0b00, B01 = 0b01, B10 = 0b10, B11 = 0b11 };
308 ///
309 /// unsafe impl Tag for Tag2 {
310 ///     const BITS: u32 = 2;
311 ///
312 ///     fn into_usize(self) -> usize { todo!() }
313 ///     unsafe fn from_usize(tag: usize) -> Self { todo!() }
314 /// }
315 ///
316 /// let value = 12u16;
317 /// let reference = &value;
318 /// let tag = Tag2::B01;
319 ///
320 /// let _ptr = CopyTaggedPtr::<_, _, true>::new(reference, tag);
321 /// ```
322 // For some reason miri does not get the compile error
323 // probably it `check`s instead of `build`ing?
324 #[cfg(not(miri))]
325 const _: () = ();
326 
327 #[cfg(test)]
328 mod tests;
329