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

Allow limited transmuting between types involving type parameters #86281

Closed
Closed
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
104 changes: 86 additions & 18 deletions compiler/rustc_middle/src/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,29 @@ pub enum SizeSkeleton<'tcx> {
/// depending on one, with regions erased.
tail: Ty<'tcx>,
},

/// A value whose size would be statically known after monomorphization, but may not yet be
/// knowable. The size must either be exactly one instance of a type, or an array of instances
/// of a type, because this variant isn't aware of other layout constraints such as padding.
///
/// This can be useful for comparing two types which can be known to be the same size because,
/// though individually their sizes aren't known, they can be easily proven to have the same
/// size. For instance, checking whether two arrays of the same type parameter have the same
/// size (even though the type parameter's size may not yet be known), or for comparing the
/// size of a value of a type parameter with a repr(transaprent) type which contains a value of
/// the same type parameter.
///
/// This is, however, currently very limited and conservative. For instance, two arrays of the
/// same type, where:
/// * one's size is a const generic parameter
/// * the other's is an const value which happens to be equal to that const generic parameter
/// will not be considered equal.
RepeatedTy {
/// The type of which this size is a known multiple (e.g. in an array).
ty: Ty<'tcx>,
/// How many copies of that type are stored.
repetitions: &'tcx ty::Const<'tcx>,
},
}

impl<'tcx> SizeSkeleton<'tcx> {
Expand Down Expand Up @@ -1906,7 +1929,7 @@ impl<'tcx> SizeSkeleton<'tcx> {
.fields
.iter()
.map(|field| SizeSkeleton::compute(field.ty(tcx, substs), tcx, param_env));
let mut ptr = None;
let mut ptr_or_transparent = None;
for field in fields {
let field = field?;
match field {
Expand All @@ -1916,33 +1939,54 @@ impl<'tcx> SizeSkeleton<'tcx> {
}
}
SizeSkeleton::Pointer { .. } => {
if ptr.is_some() {
if ptr_or_transparent.is_some() {
return Err(err);
}
ptr_or_transparent = Some(field);
}
SizeSkeleton::RepeatedTy { .. } => {
if ptr_or_transparent.is_some() {
return Err(err);
} else if def.repr.transparent() {
ptr_or_transparent = Some(field);
} else {
return Err(err);
}
ptr = Some(field);
}
}
}
Ok(ptr)
Ok(ptr_or_transparent)
};

let v0 = zero_or_ptr_variant(0)?;
// Newtype.
if def.variants.len() == 1 {
if let Some(SizeSkeleton::Pointer { non_zero, tail }) = v0 {
return Ok(SizeSkeleton::Pointer {
non_zero: non_zero
|| match tcx.layout_scalar_valid_range(def.did) {
(Bound::Included(start), Bound::Unbounded) => start > 0,
(Bound::Included(start), Bound::Included(end)) => {
0 < start && start < end
}
_ => false,
},
tail,
});
} else {
return Err(err);
match v0 {
Some(SizeSkeleton::Pointer { non_zero, tail }) => {
return Ok(SizeSkeleton::Pointer {
non_zero: non_zero
|| match tcx.layout_scalar_valid_range(def.did) {
(Bound::Included(start), Bound::Unbounded) => start > 0,
(Bound::Included(start), Bound::Included(end)) => {
0 < start && start < end
}
_ => false,
},
tail,
});
}
Some(SizeSkeleton::RepeatedTy {
ty: related_ty,
repetitions: multiple,
}) => {
return Ok(SizeSkeleton::RepeatedTy {
ty: related_ty,
repetitions: multiple,
});
}
_ => {
return Err(err);
}
}
}

Expand All @@ -1966,6 +2010,26 @@ impl<'tcx> SizeSkeleton<'tcx> {
}
}

ty::Array(array_ty, size) => {
if array_ty.is_sized(tcx.at(DUMMY_SP), param_env) {
Ok(SizeSkeleton::RepeatedTy { ty: array_ty, repetitions: size })
} else {
Err(err)
}
}

ty::Param(param_ty) => {
let param_ty = param_ty.to_ty(tcx);
if param_ty.is_sized(tcx.at(DUMMY_SP), param_env) {
Ok(SizeSkeleton::RepeatedTy {
ty: param_ty,
repetitions: ty::Const::from_usize(tcx, 1),
})
} else {
Err(err)
}
}

_ => Err(err),
}
}
Expand All @@ -1976,6 +2040,10 @@ impl<'tcx> SizeSkeleton<'tcx> {
(SizeSkeleton::Pointer { tail: a, .. }, SizeSkeleton::Pointer { tail: b, .. }) => {
a == b
}
(
SizeSkeleton::RepeatedTy { ty: related_ty, repetitions: multiple },
SizeSkeleton::RepeatedTy { ty: other_related_ty, repetitions: other_multiple },
) => related_ty == other_related_ty && multiple == other_multiple,
_ => false,
}
}
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_passes/src/intrinsicck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ impl ExprVisitor<'tcx> {
let skeleton_string = |ty: Ty<'tcx>, sk| match sk {
Ok(SizeSkeleton::Known(size)) => format!("{} bits", size.bits()),
Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{}`", tail),
Ok(SizeSkeleton::RepeatedTy { ty: related_ty, repetitions: _ }) => {
format!("size can vary because of {}", related_ty)
}
Err(LayoutError::Unknown(bad)) => {
if bad == ty {
"this type does not have a fixed size".to_owned()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Tests that `transmute` can be called in simple cases on types using type parameters with arrays.

// run-pass

use std::mem::transmute;

#[repr(transparent)]
struct Wrapper<T>([T; 10]);

#[repr(transparent)]
struct OtherWrapper<T>([T; 10]);

#[repr(transparent)]
struct ArbitrarySizedWrapper<T, const N: usize>([T; N]);

fn wrap<T>(unwrapped: [T; 10]) -> Wrapper<T> {
unsafe {
transmute(unwrapped)
}
}

fn rewrap<T>(wrapped: Wrapper<T>) -> OtherWrapper<T> {
unsafe {
transmute(wrapped)
}
}

fn unwrap<T>(wrapped: OtherWrapper<T>) -> [T; 10] {
unsafe {
transmute(wrapped)
}
}

fn wrap_arbitrary_size<T, const N: usize>(arr: [T; N]) -> ArbitrarySizedWrapper<T, N> {
unsafe { transmute(arr) }
}

fn main() {
let unwrapped = [5_u64; 10];
let wrapped = wrap(unwrapped);
assert_eq!([5_u64; 10], wrapped.0);

let rewrapped = rewrap(wrapped);
assert_eq!([5_u64; 10], rewrapped.0);

let unwrapped = unwrap(rewrapped);
assert_eq!([5_u64; 10], unwrapped);

let arbitrary_sized_wrapper = wrap_arbitrary_size(unwrapped);
assert_eq!([5_u64; 10], arbitrary_sized_wrapper.0);
}
29 changes: 29 additions & 0 deletions src/test/ui/transmute/transmute-related-type-parameters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Tests that `transmute` can be called in simple cases on types using type parameters.

// run-pass

use std::mem::transmute;

#[repr(transparent)]
struct Wrapper<T>(T);

fn wrap<T>(unwrapped: T) -> Wrapper<T> {
unsafe {
transmute(unwrapped)
}
}

fn unwrap<T>(wrapped: Wrapper<T>) -> T {
unsafe {
transmute(wrapped)
}
}

fn main() {
let unwrapped = 5_u64;
let wrapped = wrap(unwrapped);
assert_eq!(5_u64, wrapped.0);

let unwrapped = unwrap(wrapped);
assert_eq!(5_u64, unwrapped);
}
2 changes: 1 addition & 1 deletion src/test/ui/transmute/transmute-type-parameters.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Tests that `transmute` cannot be called on type parameters.
// Tests that `transmute` cannot be called on arbitrary type parameters.

use std::mem::transmute;

Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/transmute/transmute-type-parameters.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ error[E0512]: cannot transmute between types of different sizes, or dependently-
LL | let _: i32 = transmute(x);
| ^^^^^^^^^
|
= note: source type: `T` (this type does not have a fixed size)
= note: source type: `T` (size can vary because of T)
= note: target type: `i32` (32 bits)

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
Expand Down