Skip to content

Commit 32c654a

Browse files
committed
Auto merge of rust-lang#75590 - Mark-Simulacrum:tagged-ptr, r=ecstatic-morse
Add a packed/tagged pointer abstraction and utilize it for ParamEnv The intent here is mostly just to add the abstraction; I suspect that there are definitely more use cases for it, and we can explore those over time now that there's a (mostly) safe abstraction that can be used in rustc.
2 parents 9900178 + 107e290 commit 32c654a

File tree

6 files changed

+533
-60
lines changed

6 files changed

+533
-60
lines changed

src/librustc_data_structures/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//! This API is completely unstable and subject to change.
88
99
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/")]
10+
#![allow(incomplete_features)]
1011
#![feature(in_band_lifetimes)]
1112
#![feature(unboxed_closures)]
1213
#![feature(generators)]
@@ -23,6 +24,8 @@
2324
#![feature(associated_type_bounds)]
2425
#![feature(thread_id_value)]
2526
#![feature(extend_one)]
27+
#![feature(const_panic)]
28+
#![feature(const_generics)]
2629
#![allow(rustc::default_hash_types)]
2730

2831
#[macro_use]
@@ -97,6 +100,7 @@ pub mod vec_linked_list;
97100
pub mod work_queue;
98101
pub use atomic_ref::AtomicRef;
99102
pub mod frozen;
103+
pub mod tagged_ptr;
100104
pub mod temp_dir;
101105

102106
pub struct OnDrop<F: Fn()>(pub F);
+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//! This module implements tagged pointers.
2+
//!
3+
//! In order to utilize the pointer packing, you must have two types: a pointer,
4+
//! and a tag.
5+
//!
6+
//! The pointer must implement the `Pointer` trait, with the primary requirement
7+
//! being conversion to and from a usize. Note that the pointer must be
8+
//! dereferenceable, so raw pointers generally cannot implement the `Pointer`
9+
//! trait. This implies that the pointer must also be nonzero.
10+
//!
11+
//! Many common pointer types already implement the `Pointer` trait.
12+
//!
13+
//! The tag must implement the `Tag` trait. We assert that the tag and `Pointer`
14+
//! are compatible at compile time.
15+
16+
use std::mem::ManuallyDrop;
17+
use std::ops::Deref;
18+
use std::rc::Rc;
19+
use std::sync::Arc;
20+
21+
mod copy;
22+
mod drop;
23+
24+
pub use copy::CopyTaggedPtr;
25+
pub use drop::TaggedPtr;
26+
27+
/// This describes the pointer type encaspulated by TaggedPtr.
28+
///
29+
/// # Safety
30+
///
31+
/// The usize returned from `into_usize` must be a valid, dereferenceable,
32+
/// pointer to `<Self as Deref>::Target`. Note that pointers to `Pointee` must
33+
/// be thin, even though `Pointee` may not be sized.
34+
///
35+
/// Note that the returned pointer from `into_usize` should be castable to `&mut
36+
/// <Self as Deref>::Target` if `Pointer: DerefMut`.
37+
///
38+
/// The BITS constant must be correct. At least `BITS` bits, least-significant,
39+
/// must be zero on all returned pointers from `into_usize`.
40+
///
41+
/// For example, if the alignment of `Pointee` is 2, then `BITS` should be 1.
42+
pub unsafe trait Pointer: Deref {
43+
/// Most likely the value you want to use here is the following, unless
44+
/// your Pointee type is unsized (e.g., `ty::List<T>` in rustc) in which
45+
/// case you'll need to manually figure out what the right type to pass to
46+
/// align_of is.
47+
///
48+
/// ```rust
49+
/// std::mem::align_of::<<Self as Deref>::Target>().trailing_zeros() as usize;
50+
/// ```
51+
const BITS: usize;
52+
fn into_usize(self) -> usize;
53+
54+
/// # Safety
55+
///
56+
/// The passed `ptr` must be returned from `into_usize`.
57+
///
58+
/// This acts as `ptr::read` semantically, it should not be called more than
59+
/// once on non-`Copy` `Pointer`s.
60+
unsafe fn from_usize(ptr: usize) -> Self;
61+
62+
/// This provides a reference to the `Pointer` itself, rather than the
63+
/// `Deref::Target`. It is used for cases where we want to call methods that
64+
/// may be implement differently for the Pointer than the Pointee (e.g.,
65+
/// `Rc::clone` vs cloning the inner value).
66+
///
67+
/// # Safety
68+
///
69+
/// The passed `ptr` must be returned from `into_usize`.
70+
unsafe fn with_ref<R, F: FnOnce(&Self) -> R>(ptr: usize, f: F) -> R;
71+
}
72+
73+
/// This describes tags that the `TaggedPtr` struct can hold.
74+
///
75+
/// # Safety
76+
///
77+
/// The BITS constant must be correct.
78+
///
79+
/// No more than `BITS` least significant bits may be set in the returned usize.
80+
pub unsafe trait Tag: Copy {
81+
const BITS: usize;
82+
83+
fn into_usize(self) -> usize;
84+
85+
/// # Safety
86+
///
87+
/// The passed `tag` must be returned from `into_usize`.
88+
unsafe fn from_usize(tag: usize) -> Self;
89+
}
90+
91+
unsafe impl<T> Pointer for Box<T> {
92+
const BITS: usize = std::mem::align_of::<T>().trailing_zeros() as usize;
93+
fn into_usize(self) -> usize {
94+
Box::into_raw(self) as usize
95+
}
96+
unsafe fn from_usize(ptr: usize) -> Self {
97+
Box::from_raw(ptr as *mut T)
98+
}
99+
unsafe fn with_ref<R, F: FnOnce(&Self) -> R>(ptr: usize, f: F) -> R {
100+
let raw = ManuallyDrop::new(Self::from_usize(ptr));
101+
f(&raw)
102+
}
103+
}
104+
105+
unsafe impl<T> Pointer for Rc<T> {
106+
const BITS: usize = std::mem::align_of::<T>().trailing_zeros() as usize;
107+
fn into_usize(self) -> usize {
108+
Rc::into_raw(self) as usize
109+
}
110+
unsafe fn from_usize(ptr: usize) -> Self {
111+
Rc::from_raw(ptr as *const T)
112+
}
113+
unsafe fn with_ref<R, F: FnOnce(&Self) -> R>(ptr: usize, f: F) -> R {
114+
let raw = ManuallyDrop::new(Self::from_usize(ptr));
115+
f(&raw)
116+
}
117+
}
118+
119+
unsafe impl<T> Pointer for Arc<T> {
120+
const BITS: usize = std::mem::align_of::<T>().trailing_zeros() as usize;
121+
fn into_usize(self) -> usize {
122+
Arc::into_raw(self) as usize
123+
}
124+
unsafe fn from_usize(ptr: usize) -> Self {
125+
Arc::from_raw(ptr as *const T)
126+
}
127+
unsafe fn with_ref<R, F: FnOnce(&Self) -> R>(ptr: usize, f: F) -> R {
128+
let raw = ManuallyDrop::new(Self::from_usize(ptr));
129+
f(&raw)
130+
}
131+
}
132+
133+
unsafe impl<'a, T: 'a> Pointer for &'a T {
134+
const BITS: usize = std::mem::align_of::<T>().trailing_zeros() as usize;
135+
fn into_usize(self) -> usize {
136+
self as *const T as usize
137+
}
138+
unsafe fn from_usize(ptr: usize) -> Self {
139+
&*(ptr as *const T)
140+
}
141+
unsafe fn with_ref<R, F: FnOnce(&Self) -> R>(ptr: usize, f: F) -> R {
142+
f(&*(&ptr as *const usize as *const Self))
143+
}
144+
}
145+
146+
unsafe impl<'a, T: 'a> Pointer for &'a mut T {
147+
const BITS: usize = std::mem::align_of::<T>().trailing_zeros() as usize;
148+
fn into_usize(self) -> usize {
149+
self as *mut T as usize
150+
}
151+
unsafe fn from_usize(ptr: usize) -> Self {
152+
&mut *(ptr as *mut T)
153+
}
154+
unsafe fn with_ref<R, F: FnOnce(&Self) -> R>(ptr: usize, f: F) -> R {
155+
f(&*(&ptr as *const usize as *const Self))
156+
}
157+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
use super::{Pointer, Tag};
2+
use crate::stable_hasher::{HashStable, StableHasher};
3+
use std::fmt;
4+
use std::marker::PhantomData;
5+
use std::num::NonZeroUsize;
6+
7+
/// A `Copy` TaggedPtr.
8+
///
9+
/// You should use this instead of the `TaggedPtr` type in all cases where
10+
/// `P: Copy`.
11+
///
12+
/// If `COMPARE_PACKED` is true, then the pointers will be compared and hashed without
13+
/// unpacking. Otherwise we don't implement PartialEq/Eq/Hash; if you want that,
14+
/// wrap the TaggedPtr.
15+
pub struct CopyTaggedPtr<P, T, const COMPARE_PACKED: bool>
16+
where
17+
P: Pointer,
18+
T: Tag,
19+
{
20+
packed: NonZeroUsize,
21+
data: PhantomData<(P, T)>,
22+
}
23+
24+
impl<P, T, const COMPARE_PACKED: bool> Copy for CopyTaggedPtr<P, T, COMPARE_PACKED>
25+
where
26+
P: Pointer,
27+
T: Tag,
28+
P: Copy,
29+
{
30+
}
31+
32+
impl<P, T, const COMPARE_PACKED: bool> Clone for CopyTaggedPtr<P, T, COMPARE_PACKED>
33+
where
34+
P: Pointer,
35+
T: Tag,
36+
P: Copy,
37+
{
38+
fn clone(&self) -> Self {
39+
*self
40+
}
41+
}
42+
43+
// We pack the tag into the *upper* bits of the pointer to ease retrieval of the
44+
// value; a left shift is a multiplication and those are embeddable in
45+
// instruction encoding.
46+
impl<P, T, const COMPARE_PACKED: bool> CopyTaggedPtr<P, T, COMPARE_PACKED>
47+
where
48+
P: Pointer,
49+
T: Tag,
50+
{
51+
const TAG_BIT_SHIFT: usize = (8 * std::mem::size_of::<usize>()) - T::BITS;
52+
const ASSERTION: () = {
53+
assert!(T::BITS <= P::BITS);
54+
// Used for the transmute_copy's below
55+
assert!(std::mem::size_of::<&P::Target>() == std::mem::size_of::<usize>());
56+
};
57+
58+
pub fn new(pointer: P, tag: T) -> Self {
59+
// Trigger assert!
60+
let () = Self::ASSERTION;
61+
let packed_tag = tag.into_usize() << Self::TAG_BIT_SHIFT;
62+
63+
Self {
64+
// SAFETY: We know that the pointer is non-null, as it must be
65+
// dereferenceable per `Pointer` safety contract.
66+
packed: unsafe {
67+
NonZeroUsize::new_unchecked((P::into_usize(pointer) >> T::BITS) | packed_tag)
68+
},
69+
data: PhantomData,
70+
}
71+
}
72+
73+
pub(super) fn pointer_raw(&self) -> usize {
74+
self.packed.get() << T::BITS
75+
}
76+
pub fn pointer(self) -> P
77+
where
78+
P: Copy,
79+
{
80+
// SAFETY: pointer_raw returns the original pointer
81+
//
82+
// Note that this isn't going to double-drop or anything because we have
83+
// P: Copy
84+
unsafe { P::from_usize(self.pointer_raw()) }
85+
}
86+
pub fn pointer_ref(&self) -> &P::Target {
87+
// SAFETY: pointer_raw returns the original pointer
88+
unsafe { std::mem::transmute_copy(&self.pointer_raw()) }
89+
}
90+
pub fn pointer_mut(&mut self) -> &mut P::Target
91+
where
92+
P: std::ops::DerefMut,
93+
{
94+
// SAFETY: pointer_raw returns the original pointer
95+
unsafe { std::mem::transmute_copy(&self.pointer_raw()) }
96+
}
97+
pub fn tag(&self) -> T {
98+
unsafe { T::from_usize(self.packed.get() >> Self::TAG_BIT_SHIFT) }
99+
}
100+
pub fn set_tag(&mut self, tag: T) {
101+
let mut packed = self.packed.get();
102+
let new_tag = T::into_usize(tag) << Self::TAG_BIT_SHIFT;
103+
let tag_mask = (1 << T::BITS) - 1;
104+
packed &= !(tag_mask << Self::TAG_BIT_SHIFT);
105+
packed |= new_tag;
106+
self.packed = unsafe { NonZeroUsize::new_unchecked(packed) };
107+
}
108+
}
109+
110+
impl<P, T, const COMPARE_PACKED: bool> std::ops::Deref for CopyTaggedPtr<P, T, COMPARE_PACKED>
111+
where
112+
P: Pointer,
113+
T: Tag,
114+
{
115+
type Target = P::Target;
116+
fn deref(&self) -> &Self::Target {
117+
self.pointer_ref()
118+
}
119+
}
120+
121+
impl<P, T, const COMPARE_PACKED: bool> std::ops::DerefMut for CopyTaggedPtr<P, T, COMPARE_PACKED>
122+
where
123+
P: Pointer + std::ops::DerefMut,
124+
T: Tag,
125+
{
126+
fn deref_mut(&mut self) -> &mut Self::Target {
127+
self.pointer_mut()
128+
}
129+
}
130+
131+
impl<P, T, const COMPARE_PACKED: bool> fmt::Debug for CopyTaggedPtr<P, T, COMPARE_PACKED>
132+
where
133+
P: Pointer,
134+
P::Target: fmt::Debug,
135+
T: Tag + fmt::Debug,
136+
{
137+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138+
f.debug_struct("CopyTaggedPtr")
139+
.field("pointer", &self.pointer_ref())
140+
.field("tag", &self.tag())
141+
.finish()
142+
}
143+
}
144+
145+
impl<P, T> PartialEq for CopyTaggedPtr<P, T, true>
146+
where
147+
P: Pointer,
148+
T: Tag,
149+
{
150+
fn eq(&self, other: &Self) -> bool {
151+
self.packed == other.packed
152+
}
153+
}
154+
155+
impl<P, T> Eq for CopyTaggedPtr<P, T, true>
156+
where
157+
P: Pointer,
158+
T: Tag,
159+
{
160+
}
161+
162+
impl<P, T> std::hash::Hash for CopyTaggedPtr<P, T, true>
163+
where
164+
P: Pointer,
165+
T: Tag,
166+
{
167+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
168+
self.packed.hash(state);
169+
}
170+
}
171+
172+
impl<P, T, HCX, const COMPARE_PACKED: bool> HashStable<HCX> for CopyTaggedPtr<P, T, COMPARE_PACKED>
173+
where
174+
P: Pointer + HashStable<HCX>,
175+
T: Tag + HashStable<HCX>,
176+
{
177+
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
178+
unsafe {
179+
Pointer::with_ref(self.pointer_raw(), |p: &P| p.hash_stable(hcx, hasher));
180+
}
181+
self.tag().hash_stable(hcx, hasher);
182+
}
183+
}

0 commit comments

Comments
 (0)