Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add impl_tag! macro to implement Tag for tagged pointer easily #110615

Merged
merged 6 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_data_structures/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#![feature(unwrap_infallible)]
#![feature(strict_provenance)]
#![feature(ptr_alignment_type)]
#![feature(macro_metavar_expr)]
#![allow(rustc::default_hash_types)]
#![allow(rustc::potential_query_instability)]
#![deny(rustc::untranslatable_diagnostic)]
Expand Down
31 changes: 25 additions & 6 deletions compiler/rustc_data_structures/src/tagged_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::aligned::Aligned;

mod copy;
mod drop;
mod impl_tag;

pub use copy::CopyTaggedPtr;
pub use drop::TaggedPtr;
Expand Down Expand Up @@ -141,6 +142,30 @@ pub unsafe trait Tag: Copy {
unsafe fn from_usize(tag: usize) -> Self;
}

/// Returns the number of bits available for use for tags in a pointer to `T`
/// (this is based on `T`'s alignment).
pub const fn bits_for<T: ?Sized + Aligned>() -> u32 {
crate::aligned::align_of::<T>().as_nonzero().trailing_zeros()
}

/// Returns the correct [`Tag::BITS`] constant for a set of tag values.
pub const fn bits_for_tags(mut tags: &[usize]) -> u32 {
let mut bits = 0;

while let &[tag, ref rest @ ..] = tags {
tags = rest;

// bits required to represent `tag`,
// position of the most significant 1
let b = usize::BITS - tag.leading_zeros();
if b > bits {
bits = b;
}
}

bits
}

unsafe impl<T: ?Sized + Aligned> Pointer for Box<T> {
const BITS: u32 = bits_for::<Self::Target>();

Expand Down Expand Up @@ -221,12 +246,6 @@ unsafe impl<'a, T: 'a + ?Sized + Aligned> Pointer for &'a mut T {
}
}

/// Returns the number of bits available for use for tags in a pointer to `T`
/// (this is based on `T`'s alignment).
pub const fn bits_for<T: ?Sized + Aligned>() -> u32 {
crate::aligned::align_of::<T>().as_nonzero().trailing_zeros()
}

/// A tag type used in [`CopyTaggedPtr`] and [`TaggedPtr`] tests.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg(test)]
Expand Down
144 changes: 144 additions & 0 deletions compiler/rustc_data_structures/src/tagged_ptr/impl_tag.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/// Implements [`Tag`] for a given type.
///
/// You can use `impl_tag` on structs and enums.
/// You need to specify the type and all its possible values,
/// which can only be paths with optional fields.
///
/// [`Tag`]: crate::tagged_ptr::Tag
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(macro_metavar_expr)]
/// use rustc_data_structures::{impl_tag, tagged_ptr::Tag};
///
/// #[derive(Copy, Clone, PartialEq, Debug)]
/// enum SomeTag {
/// A,
/// B,
/// X { v: bool },
/// Y(bool, bool),
/// }
///
/// impl_tag! {
/// // The type for which the `Tag` will be implemented
/// impl Tag for SomeTag;
/// // You need to specify all possible tag values:
/// SomeTag::A, // 0
/// SomeTag::B, // 1
/// // For variants with fields, you need to specify the fields:
/// SomeTag::X { v: true }, // 2
/// SomeTag::X { v: false }, // 3
/// // For tuple variants use named syntax:
/// SomeTag::Y { 0: true, 1: true }, // 4
/// SomeTag::Y { 0: false, 1: true }, // 5
/// SomeTag::Y { 0: true, 1: false }, // 6
/// SomeTag::Y { 0: false, 1: false }, // 7
/// }
///
/// // Tag values are assigned in order:
/// assert_eq!(SomeTag::A.into_usize(), 0);
/// assert_eq!(SomeTag::X { v: false }.into_usize(), 3);
/// assert_eq!(SomeTag::Y(false, true).into_usize(), 5);
///
/// assert_eq!(unsafe { SomeTag::from_usize(1) }, SomeTag::B);
/// assert_eq!(unsafe { SomeTag::from_usize(2) }, SomeTag::X { v: true });
/// assert_eq!(unsafe { SomeTag::from_usize(7) }, SomeTag::Y(false, false));
/// ```
///
/// Structs are supported:
///
/// ```
/// #![feature(macro_metavar_expr)]
/// # use rustc_data_structures::impl_tag;
/// #[derive(Copy, Clone)]
/// struct Flags { a: bool, b: bool }
///
/// impl_tag! {
/// impl Tag for Flags;
/// Flags { a: true, b: true },
/// Flags { a: false, b: true },
/// Flags { a: true, b: false },
/// Flags { a: false, b: false },
/// }
/// ```
///
/// Not specifying all values results in a compile error:
///
/// ```compile_fail,E0004
/// #![feature(macro_metavar_expr)]
/// # use rustc_data_structures::impl_tag;
/// #[derive(Copy, Clone)]
/// enum E {
/// A,
/// B,
/// }
///
/// impl_tag! {
/// impl Tag for E;
/// E::A,
/// }
/// ```
#[macro_export]
macro_rules! impl_tag {
(
impl Tag for $Self:ty;
$(
$($path:ident)::* $( { $( $fields:tt )* })?,
)*
) => {
// Safety:
// `bits_for_tags` is called on the same `${index()}`-es as
// `into_usize` returns, thus `BITS` constant is correct.
unsafe impl $crate::tagged_ptr::Tag for $Self {
const BITS: u32 = $crate::tagged_ptr::bits_for_tags(&[
$(
${index()},
$( ${ignore(path)} )*
)*
]);

#[inline]
fn into_usize(self) -> usize {
// This forbids use of repeating patterns (`Enum::V`&`Enum::V`, etc)
// (or at least it should, see <https://github.com/rust-lang/rust/issues/110613>)
#[forbid(unreachable_patterns)]
match self {
// `match` is doing heavy lifting here, by requiring exhaustiveness
$(
$($path)::* $( { $( $fields )* } )? => ${index()},
)*
}
}

#[inline]
unsafe fn from_usize(tag: usize) -> Self {
match tag {
$(
${index()} => $($path)::* $( { $( $fields )* } )?,
)*

// Safety:
// `into_usize` only returns `${index()}` of the same
// repetition as we are filtering above, thus if this is
// reached, the safety contract of this function was
// already breached.
_ => unsafe {
debug_assert!(
false,
"invalid tag: {tag}\
(this is a bug in the caller of `from_usize`)"
);
std::hint::unreachable_unchecked()
},
}
}

}
};
}

#[cfg(test)]
mod tests;
34 changes: 34 additions & 0 deletions compiler/rustc_data_structures/src/tagged_ptr/impl_tag/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#[test]
fn bits_constant() {
use crate::tagged_ptr::Tag;

#[derive(Copy, Clone)]
struct Unit;
impl_tag! { impl Tag for Unit; Unit, }
assert_eq!(Unit::BITS, 0);

#[derive(Copy, Clone)]
enum Enum3 {
A,
B,
C,
}
impl_tag! { impl Tag for Enum3; Enum3::A, Enum3::B, Enum3::C, }
assert_eq!(Enum3::BITS, 2);

#[derive(Copy, Clone)]
struct Eight(bool, bool, bool);
impl_tag! {
impl Tag for Eight;
Eight { 0: true, 1: true, 2: true },
Eight { 0: true, 1: true, 2: false },
Eight { 0: true, 1: false, 2: true },
Eight { 0: true, 1: false, 2: false },
Eight { 0: false, 1: true, 2: true },
Eight { 0: false, 1: true, 2: false },
Eight { 0: false, 1: false, 2: true },
Eight { 0: false, 1: false, 2: false },
}

assert_eq!(Eight::BITS, 3);
}
1 change: 1 addition & 0 deletions compiler/rustc_middle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#![feature(const_option)]
#![feature(trait_alias)]
#![feature(ptr_alignment_type)]
#![feature(macro_metavar_expr)]
#![recursion_limit = "512"]
#![allow(rustc::potential_query_instability)]

Expand Down
29 changes: 6 additions & 23 deletions compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1626,29 +1626,12 @@ struct ParamTag {
constness: hir::Constness,
}

unsafe impl rustc_data_structures::tagged_ptr::Tag for ParamTag {
const BITS: u32 = 2;

#[inline]
fn into_usize(self) -> usize {
match self {
Self { reveal: traits::Reveal::UserFacing, constness: hir::Constness::NotConst } => 0,
Self { reveal: traits::Reveal::All, constness: hir::Constness::NotConst } => 1,
Self { reveal: traits::Reveal::UserFacing, constness: hir::Constness::Const } => 2,
Self { reveal: traits::Reveal::All, constness: hir::Constness::Const } => 3,
}
}

#[inline]
unsafe fn from_usize(ptr: usize) -> Self {
match ptr {
0 => Self { reveal: traits::Reveal::UserFacing, constness: hir::Constness::NotConst },
1 => Self { reveal: traits::Reveal::All, constness: hir::Constness::NotConst },
2 => Self { reveal: traits::Reveal::UserFacing, constness: hir::Constness::Const },
3 => Self { reveal: traits::Reveal::All, constness: hir::Constness::Const },
_ => std::hint::unreachable_unchecked(),
}
}
impl_tag! {
impl Tag for ParamTag;
ParamTag { reveal: traits::Reveal::UserFacing, constness: hir::Constness::NotConst },
ParamTag { reveal: traits::Reveal::All, constness: hir::Constness::NotConst },
ParamTag { reveal: traits::Reveal::UserFacing, constness: hir::Constness::Const },
ParamTag { reveal: traits::Reveal::All, constness: hir::Constness::Const },
}

impl<'tcx> fmt::Debug for ParamEnv<'tcx> {
Expand Down