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

safely transmute<&List<Ty<'tcx>>, &List<GenericArg<'tcx>>> #93505

Merged
merged 7 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
35 changes: 30 additions & 5 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ pub struct CtxtInterners<'tcx> {
// Specifically use a speedy hash algorithm for these hash sets, since
// they're accessed quite often.
type_: InternedSet<'tcx, TyS<'tcx>>,
type_list: InternedSet<'tcx, List<Ty<'tcx>>>,
substs: InternedSet<'tcx, InternalSubsts<'tcx>>,
canonical_var_infos: InternedSet<'tcx, List<CanonicalVarInfo<'tcx>>>,
region: InternedSet<'tcx, RegionKind>,
Expand Down Expand Up @@ -129,7 +128,6 @@ impl<'tcx> CtxtInterners<'tcx> {
CtxtInterners {
arena,
type_: Default::default(),
type_list: Default::default(),
substs: Default::default(),
region: Default::default(),
poly_existential_predicates: Default::default(),
Expand Down Expand Up @@ -1666,6 +1664,23 @@ macro_rules! nop_lift {
};
}

// Can't use the macros as we have reuse the `substs` here.
//
// See `intern_type_list` for more info.
impl<'a, 'tcx> Lift<'tcx> for &'a List<Ty<'a>> {
type Lifted = &'tcx List<Ty<'tcx>>;
fn lift_to_tcx(self, tcx: TyCtxt<'tcx>) -> Option<Self::Lifted> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the transmute inside it, in instances such as this one, I personally like to spell out the aliases explicitly:

Suggested change
fn lift_to_tcx(self, tcx: TyCtxt<'tcx>) -> Option<Self::Lifted> {
fn lift_to_tcx(self: &'a List<Ty<'a>>, tcx: TyCtxt<'tcx>) -> Option<&'tcx List<Ty<'tcx>>> {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i generally agree, though, considering that all other Lift impls also don't do that i am going to keep the code as is for now.

if self.is_empty() {
return Some(List::empty());
}
if tcx.interners.substs.contains_pointer_to(&InternedInSet(self.as_substs())) {
Some(unsafe { mem::transmute(self) })
} else {
None
}
}
}

macro_rules! nop_list_lift {
($set:ident; $ty:ty => $lifted:ty) => {
impl<'a, 'tcx> Lift<'tcx> for &'a List<$ty> {
Expand All @@ -1690,7 +1705,6 @@ nop_lift! {const_; Const<'a> => Const<'tcx>}
nop_lift_old! {const_allocation; &'a Allocation => &'tcx Allocation}
nop_lift! {predicate; Predicate<'a> => Predicate<'tcx>}

nop_list_lift! {type_list; Ty<'a> => Ty<'tcx>}
nop_list_lift! {poly_existential_predicates; ty::Binder<'a, ExistentialPredicate<'a>> => ty::Binder<'tcx, ExistentialPredicate<'tcx>>}
nop_list_lift! {predicates; Predicate<'a> => Predicate<'tcx>}
nop_list_lift! {canonical_var_infos; CanonicalVarInfo<'a> => CanonicalVarInfo<'tcx>}
Expand Down Expand Up @@ -2189,7 +2203,6 @@ macro_rules! slice_interners {
}

slice_interners!(
type_list: _intern_type_list(Ty<'tcx>),
substs: _intern_substs(GenericArg<'tcx>),
canonical_var_infos: _intern_canonical_var_infos(CanonicalVarInfo<'tcx>),
poly_existential_predicates:
Expand Down Expand Up @@ -2611,7 +2624,19 @@ impl<'tcx> TyCtxt<'tcx> {
}

pub fn intern_type_list(self, ts: &[Ty<'tcx>]) -> &'tcx List<Ty<'tcx>> {
if ts.is_empty() { List::empty() } else { self._intern_type_list(ts) }
if ts.is_empty() {
List::empty()
} else {
// Actually intern type lists as lists of `GenericArg`s.
//
// Transmuting from `Ty<'tcx>` to `GenericArg<'tcx>` is sound
// as explained in ty_slice_as_generic_arg`. With this,
// we guarantee that even when transmuting between `List<Ty<'tcx>>`
// and `List<GenericArg<'tcx>>`, the uniqueness requirement for
// lists is upheld.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heh, this is...a cute pun.

let substs = self._intern_substs(ty::subst::ty_slice_as_generic_args(ts));
substs.try_as_type_list().unwrap()
}
}

pub fn intern_substs(self, ts: &[GenericArg<'tcx>]) -> &'tcx List<GenericArg<'tcx>> {
Expand Down
22 changes: 22 additions & 0 deletions compiler/rustc_middle/src/ty/impls_ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,28 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for ty::subst::GenericArg<'t
}
}

impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for ty::subst::GenericArgKind<'tcx> {
fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
match self {
// WARNING: We dedup cache the `HashStable` results for `List`
// while ignoring types and freely transmute
// between `List<Ty<'tcx>>` and `List<GenericArg<'tcx>>`.
// See `fn intern_type_list` for more details.
//
// We therefore hash types without adding a hash for their discriminant.
ty::subst::GenericArgKind::Type(ty) => ty.hash_stable(hcx, hasher),
ty::subst::GenericArgKind::Const(ct) => {
mem::discriminant(self).hash_stable(hcx, hasher);
ct.hash_stable(hcx, hasher);
}
ty::subst::GenericArgKind::Lifetime(lt) => {
mem::discriminant(self).hash_stable(hcx, hasher);
lt.hash_stable(hcx, hasher);
}
}
lcnr marked this conversation as resolved.
Show resolved Hide resolved
}
}

impl<'a> HashStable<StableHashingContext<'a>> for ty::RegionKind {
fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
mem::discriminant(self).hash_stable(hcx, hasher);
Expand Down
38 changes: 37 additions & 1 deletion compiler/rustc_middle/src/ty/subst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use std::marker::PhantomData;
use std::mem;
use std::num::NonZeroUsize;
use std::ops::ControlFlow;
use std::slice;

/// An entity in the Rust type system, which can be one of
/// several kinds (types, lifetimes, and consts).
Expand All @@ -40,13 +41,37 @@ const TYPE_TAG: usize = 0b00;
const REGION_TAG: usize = 0b01;
const CONST_TAG: usize = 0b10;

#[derive(Debug, TyEncodable, TyDecodable, PartialEq, Eq, PartialOrd, Ord, HashStable)]
#[derive(Debug, TyEncodable, TyDecodable, PartialEq, Eq, PartialOrd, Ord)]
pub enum GenericArgKind<'tcx> {
Lifetime(ty::Region<'tcx>),
Type(Ty<'tcx>),
Const(ty::Const<'tcx>),
}

/// This function goes from `&'a [Ty<'tcx>]` to `&'a [GenericArg<'tcx>]`
///
/// This is sound as, for types, `GenericArg` is just
/// `NonZeroUsize::new_unchecked(ty as *const _ as usize)`.
pub fn ty_slice_as_generic_args<'a, 'tcx>(ts: &'a [Ty<'tcx>]) -> &'a [GenericArg<'tcx>] {
assert_eq!(TYPE_TAG, 0);
lcnr marked this conversation as resolved.
Show resolved Hide resolved
// SAFETY: the whole slice is valid and immutable.
// `Ty` and `GenericArg` is explained above.
unsafe { slice::from_raw_parts(ts.as_ptr().cast(), ts.len()) }
}

impl<'tcx> List<Ty<'tcx>> {
/// Allows to freely switch betwen `List<Ty<'tcx>>` and `List<GenericArg<'tcx>>`.
///
/// As lists are interned, `List<Ty<'tcx>>` and `List<GenericArg<'tcx>>` have
/// be interned together, see `intern_type_list` for more details.
#[inline]
pub fn as_substs(&'tcx self) -> SubstsRef<'tcx> {
assert_eq!(TYPE_TAG, 0);
// SAFETY: `List<T>` is `#[repr(C)]`. `Ty` and `GenericArg` is explained above.
unsafe { &*(self as *const List<Ty<'tcx>> as *const List<GenericArg<'tcx>>) }
}
}

impl<'tcx> GenericArgKind<'tcx> {
#[inline]
fn pack(self) -> GenericArg<'tcx> {
Expand Down Expand Up @@ -208,6 +233,17 @@ pub type InternalSubsts<'tcx> = List<GenericArg<'tcx>>;
pub type SubstsRef<'tcx> = &'tcx InternalSubsts<'tcx>;

impl<'a, 'tcx> InternalSubsts<'tcx> {
/// Checks whether all elements of this list are types, if so, transmute.
pub fn try_as_type_list(&'tcx self) -> Option<&'tcx List<Ty<'tcx>>> {
if self.iter().all(|arg| matches!(arg.unpack(), GenericArgKind::Type(_))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe checking the pointer tag directly would make it possible for LLVM to optimize this? Something like self.iter().all(|arg| arg.ptr.get() & TAG_MASK == TYPE_TAG)?

Adding #[inline] to this method is a good idea in any case.

assert_eq!(TYPE_TAG, 0);
// SAFETY: All elements are types, see `List<Ty<'tcx>>::as_substs`.
Some(unsafe { &*(self as *const List<GenericArg<'tcx>> as *const List<Ty<'tcx>>) })
} else {
None
}
}

/// Interpret these substitutions as the substitutions of a closure type.
/// Closure substitutions have a particular structure controlled by the
/// compiler that encodes information like the signature and closure kind;
Expand Down