Skip to content

Commit 77c83c0

Browse files
committed
Add impl_tag! macro to implement Tag for tagged pointer easily
1 parent 13fc33e commit 77c83c0

File tree

4 files changed

+244
-29
lines changed

4 files changed

+244
-29
lines changed

compiler/rustc_data_structures/src/tagged_ptr.rs

+35-6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::aligned::Aligned;
2424

2525
mod copy;
2626
mod drop;
27+
mod impl_tag;
2728

2829
pub use copy::CopyTaggedPtr;
2930
pub use drop::TaggedPtr;
@@ -141,6 +142,40 @@ pub unsafe trait Tag: Copy {
141142
unsafe fn from_usize(tag: usize) -> Self;
142143
}
143144

145+
/// Returns the number of bits available for use for tags in a pointer to `T`
146+
/// (this is based on `T`'s alignment).
147+
pub const fn bits_for<T: ?Sized + Aligned>() -> u32 {
148+
crate::aligned::align_of::<T>().as_nonzero().trailing_zeros()
149+
}
150+
151+
/// Returns the correct [`Tag::BITS`] constant for a set of tag values.
152+
pub const fn bits_for_tags(mut tags: &[usize]) -> u32 {
153+
let mut bits = 0;
154+
155+
while let &[tag, ref rest @ ..] = tags {
156+
tags = rest;
157+
158+
let b = bits_for_tag(tag);
159+
if b > bits {
160+
bits = b;
161+
}
162+
}
163+
164+
bits
165+
}
166+
167+
/// Returns `(size_of::<usize>() * 8) - tag.leading_zeros()`
168+
const fn bits_for_tag(mut tag: usize) -> u32 {
169+
let mut bits = 0;
170+
171+
while tag > 0 {
172+
bits += 1;
173+
tag >>= 1;
174+
}
175+
176+
bits
177+
}
178+
144179
unsafe impl<T: ?Sized + Aligned> Pointer for Box<T> {
145180
const BITS: u32 = bits_for::<Self::Target>();
146181

@@ -221,12 +256,6 @@ unsafe impl<'a, T: 'a + ?Sized + Aligned> Pointer for &'a mut T {
221256
}
222257
}
223258

224-
/// Returns the number of bits available for use for tags in a pointer to `T`
225-
/// (this is based on `T`'s alignment).
226-
pub const fn bits_for<T: ?Sized + Aligned>() -> u32 {
227-
crate::aligned::align_of::<T>().as_nonzero().trailing_zeros()
228-
}
229-
230259
/// A tag type used in [`CopyTaggedPtr`] and [`TaggedPtr`] tests.
231260
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
232261
#[cfg(test)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/// Implements [`Tag`] for a given type.
2+
///
3+
/// You can use `impl_tag` on structs and enums.
4+
/// You need to specify the type and all its possible values,
5+
/// which can only be paths with optional fields.
6+
///
7+
/// [`Tag`]: crate::tagged_ptr::Tag
8+
///
9+
/// # Examples
10+
///
11+
/// Basic usage:
12+
///
13+
/// ```
14+
/// use rustc_data_structures::{impl_tag, tagged_ptr::Tag};
15+
///
16+
/// #[derive(Copy, Clone, PartialEq, Debug)]
17+
/// enum SomeTag {
18+
/// A,
19+
/// B,
20+
/// X { v: bool },
21+
/// Y(bool, bool),
22+
/// }
23+
///
24+
/// impl_tag! {
25+
/// // The type for which the `Tag` will be implemented
26+
/// for SomeTag;
27+
/// // You need to specify the `{value_of_the_type} <=> {tag}` relationship
28+
/// SomeTag::A <=> 0,
29+
/// SomeTag::B <=> 1,
30+
/// // For variants with fields, you need to specify the fields:
31+
/// SomeTag::X { v: true } <=> 2,
32+
/// SomeTag::X { v: false } <=> 3,
33+
/// // For tuple variants use named syntax:
34+
/// SomeTag::Y { 0: true, 1: true } <=> 4,
35+
/// SomeTag::Y { 0: false, 1: true } <=> 5,
36+
/// SomeTag::Y { 0: true, 1: false } <=> 6,
37+
/// SomeTag::Y { 0: false, 1: false } <=> 7,
38+
/// }
39+
///
40+
/// assert_eq!(SomeTag::A.into_usize(), 0);
41+
/// assert_eq!(SomeTag::X { v: false }.into_usize(), 3);
42+
/// assert_eq!(SomeTag::Y(false, true).into_usize(), 5);
43+
///
44+
/// assert_eq!(unsafe { SomeTag::from_usize(1) }, SomeTag::B);
45+
/// assert_eq!(unsafe { SomeTag::from_usize(2) }, SomeTag::X { v: true });
46+
/// assert_eq!(unsafe { SomeTag::from_usize(7) }, SomeTag::Y(false, false));
47+
/// ```
48+
///
49+
/// Structs are supported:
50+
///
51+
/// ```
52+
/// # use rustc_data_structures::impl_tag;
53+
/// #[derive(Copy, Clone)]
54+
/// struct Flags { a: bool, b: bool }
55+
///
56+
/// impl_tag! {
57+
/// for Flags;
58+
/// Flags { a: true, b: true } <=> 3,
59+
/// Flags { a: false, b: true } <=> 2,
60+
/// Flags { a: true, b: false } <=> 1,
61+
/// Flags { a: false, b: false } <=> 0,
62+
/// }
63+
/// ```
64+
///
65+
// This is supposed to produce a compile error, but does not,
66+
// see <https://github.com/rust-lang/rust/issues/110613> for more information.
67+
//
68+
// Using the same pattern twice results in a compile error:
69+
//
70+
// ```compile_fail
71+
// # use rustc_data_structures::impl_tag;
72+
// #[derive(Copy, Clone)]
73+
// struct Unit;
74+
//
75+
// impl_tag! {
76+
// for Unit;
77+
// Unit <=> 0,
78+
// Unit <=> 1,
79+
// }
80+
// ```
81+
//
82+
// Using the same tag twice results in a compile error:
83+
//
84+
// ```compile_fail
85+
// # use rustc_data_structures::impl_tag;
86+
// #[derive(Copy, Clone)]
87+
// enum E { A, B };
88+
//
89+
// impl_tag! {
90+
// for E;
91+
// E::A <=> 0,
92+
// E::B <=> 0,
93+
// }
94+
// ```
95+
//
96+
/// Not specifying all values results in a compile error:
97+
///
98+
/// ```compile_fail,E0004
99+
/// # use rustc_data_structures::impl_tag;
100+
/// #[derive(Copy, Clone)]
101+
/// enum E {
102+
/// A,
103+
/// B,
104+
/// }
105+
///
106+
/// impl_tag! {
107+
/// for E;
108+
/// E::A <=> 0,
109+
/// }
110+
/// ```
111+
#[macro_export]
112+
macro_rules! impl_tag {
113+
(
114+
for $Self:ty;
115+
$(
116+
$($path:ident)::* $( { $( $fields:tt )* })? <=> $tag:literal,
117+
)*
118+
) => {
119+
// Safety:
120+
// `into_usize` only returns one of `$tag`s,
121+
// `bits_for_tags` is called on all `$tag`s,
122+
// thus `BITS` constant is correct.
123+
unsafe impl $crate::tagged_ptr::Tag for $Self {
124+
const BITS: u32 = $crate::tagged_ptr::bits_for_tags(&[
125+
$( $tag, )*
126+
]);
127+
128+
fn into_usize(self) -> usize {
129+
// This forbids use of repeating patterns (`Enum::V`&`Enum::V`, etc)
130+
// (or at least it should, see <https://github.com/rust-lang/rust/issues/110613>)
131+
#[forbid(unreachable_patterns)]
132+
match self {
133+
// `match` is doing heavy lifting here, by requiring exhaustiveness
134+
$(
135+
$($path)::* $( { $( $fields )* } )? => $tag,
136+
)*
137+
}
138+
}
139+
140+
unsafe fn from_usize(tag: usize) -> Self {
141+
// Similarly to the above, this forbids repeating tags
142+
// (or at least it should, see <https://github.com/rust-lang/rust/issues/110613>)
143+
#[forbid(unreachable_patterns)]
144+
match tag {
145+
$(
146+
$tag => $($path)::* $( { $( $fields )* } )?,
147+
)*
148+
149+
// Safety:
150+
// `into_usize` only returns one of `$tag`s,
151+
// all `$tag`s are filtered up above,
152+
// thus if this is reached, the safety contract of this
153+
// function was already breached.
154+
_ => unsafe {
155+
debug_assert!(
156+
false,
157+
"invalid tag: {tag}\
158+
(this is a bug in the caller of `from_usize`)"
159+
);
160+
std::hint::unreachable_unchecked()
161+
},
162+
}
163+
}
164+
165+
}
166+
};
167+
}
168+
169+
#[cfg(test)]
170+
mod tests;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#[test]
2+
fn bits_constant() {
3+
use crate::tagged_ptr::Tag;
4+
5+
#[derive(Copy, Clone)]
6+
struct Unit;
7+
impl_tag! { for Unit; Unit <=> 0, }
8+
assert_eq!(Unit::BITS, 0);
9+
10+
#[derive(Copy, Clone)]
11+
struct Unit1;
12+
impl_tag! { for Unit1; Unit1 <=> 1, }
13+
assert_eq!(Unit1::BITS, 1);
14+
15+
#[derive(Copy, Clone)]
16+
struct Unit2;
17+
impl_tag! { for Unit2; Unit2 <=> 0b10, }
18+
assert_eq!(Unit2::BITS, 2);
19+
20+
#[derive(Copy, Clone)]
21+
struct Unit3;
22+
impl_tag! { for Unit3; Unit3 <=> 0b100, }
23+
assert_eq!(Unit3::BITS, 3);
24+
25+
#[derive(Copy, Clone)]
26+
enum Enum {
27+
A,
28+
B,
29+
C,
30+
}
31+
impl_tag! { for Enum; Enum::A <=> 0b1, Enum::B <=> 0b1000, Enum::C <=> 0b10, }
32+
assert_eq!(Enum::BITS, 4);
33+
}

compiler/rustc_middle/src/ty/mod.rs

+6-23
Original file line numberDiff line numberDiff line change
@@ -1626,29 +1626,12 @@ struct ParamTag {
16261626
constness: hir::Constness,
16271627
}
16281628

1629-
unsafe impl rustc_data_structures::tagged_ptr::Tag for ParamTag {
1630-
const BITS: u32 = 2;
1631-
1632-
#[inline]
1633-
fn into_usize(self) -> usize {
1634-
match self {
1635-
Self { reveal: traits::Reveal::UserFacing, constness: hir::Constness::NotConst } => 0,
1636-
Self { reveal: traits::Reveal::All, constness: hir::Constness::NotConst } => 1,
1637-
Self { reveal: traits::Reveal::UserFacing, constness: hir::Constness::Const } => 2,
1638-
Self { reveal: traits::Reveal::All, constness: hir::Constness::Const } => 3,
1639-
}
1640-
}
1641-
1642-
#[inline]
1643-
unsafe fn from_usize(ptr: usize) -> Self {
1644-
match ptr {
1645-
0 => Self { reveal: traits::Reveal::UserFacing, constness: hir::Constness::NotConst },
1646-
1 => Self { reveal: traits::Reveal::All, constness: hir::Constness::NotConst },
1647-
2 => Self { reveal: traits::Reveal::UserFacing, constness: hir::Constness::Const },
1648-
3 => Self { reveal: traits::Reveal::All, constness: hir::Constness::Const },
1649-
_ => std::hint::unreachable_unchecked(),
1650-
}
1651-
}
1629+
impl_tag! {
1630+
for ParamTag;
1631+
ParamTag { reveal: traits::Reveal::UserFacing, constness: hir::Constness::NotConst } <=> 0,
1632+
ParamTag { reveal: traits::Reveal::All, constness: hir::Constness::NotConst } <=> 1,
1633+
ParamTag { reveal: traits::Reveal::UserFacing, constness: hir::Constness::Const } <=> 2,
1634+
ParamTag { reveal: traits::Reveal::All, constness: hir::Constness::Const } <=> 3,
16521635
}
16531636

16541637
impl<'tcx> fmt::Debug for ParamEnv<'tcx> {

0 commit comments

Comments
 (0)