UnsafePinned: implement opsem effects of UnsafeUnpin

This commit is contained in:
Ralf Jung 2026-01-19 13:53:08 +01:00
parent 71dc761bfe
commit 590c1c9966
11 changed files with 113 additions and 31 deletions

View file

@ -1694,6 +1694,10 @@ rustc_queries! {
query is_freeze_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool { query is_freeze_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` is freeze", env.value } desc { "computing whether `{}` is freeze", env.value }
} }
/// Query backing `Ty::is_unsafe_unpin`.
query is_unsafe_unpin_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` is `UnsafeUnpin`", env.value }
}
/// Query backing `Ty::is_unpin`. /// Query backing `Ty::is_unpin`.
query is_unpin_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool { query is_unpin_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` is `Unpin`", env.value } desc { "computing whether `{}` is `Unpin`", env.value }

View file

@ -1049,9 +1049,11 @@ where
hir::Mutability::Not => { hir::Mutability::Not => {
PointerKind::SharedRef { frozen: optimize && ty.is_freeze(tcx, typing_env) } PointerKind::SharedRef { frozen: optimize && ty.is_freeze(tcx, typing_env) }
} }
hir::Mutability::Mut => { hir::Mutability::Mut => PointerKind::MutableRef {
PointerKind::MutableRef { unpin: optimize && ty.is_unpin(tcx, typing_env) } unpin: optimize
} && ty.is_unpin(tcx, typing_env)
&& ty.is_unsafe_unpin(tcx, typing_env),
},
}; };
tcx.layout_of(typing_env.as_query_input(ty)).ok().map(|layout| PointeeInfo { tcx.layout_of(typing_env.as_query_input(ty)).ok().map(|layout| PointeeInfo {
@ -1146,7 +1148,9 @@ where
debug_assert!(pointee.safe.is_none()); debug_assert!(pointee.safe.is_none());
let optimize = tcx.sess.opts.optimize != OptLevel::No; let optimize = tcx.sess.opts.optimize != OptLevel::No;
pointee.safe = Some(PointerKind::Box { pointee.safe = Some(PointerKind::Box {
unpin: optimize && boxed_ty.is_unpin(tcx, typing_env), unpin: optimize
&& boxed_ty.is_unpin(tcx, typing_env)
&& boxed_ty.is_unsafe_unpin(tcx, typing_env),
global: this.ty.is_box_global(tcx), global: this.ty.is_box_global(tcx),
}); });
} }

View file

@ -1189,14 +1189,23 @@ impl<'tcx> Ty<'tcx> {
} }
} }
/// Checks whether values of this type `T` implement the `UnsafeUnpin` trait.
pub fn is_unsafe_unpin(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool {
self.is_trivially_unpin() || tcx.is_unsafe_unpin_raw(typing_env.as_query_input(self))
}
/// Checks whether values of this type `T` implement the `Unpin` trait. /// Checks whether values of this type `T` implement the `Unpin` trait.
///
/// Note that this is a safe trait, so it cannot be very semantically meaningful.
/// However, as a hack to mitigate <https://github.com/rust-lang/rust/issues/63818> until a
/// proper solution is implemented, we do give special semantics to the `Unpin` trait.
pub fn is_unpin(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool { pub fn is_unpin(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool {
self.is_trivially_unpin() || tcx.is_unpin_raw(typing_env.as_query_input(self)) self.is_trivially_unpin() || tcx.is_unpin_raw(typing_env.as_query_input(self))
} }
/// Fast path helper for testing if a type is `Unpin`. /// Fast path helper for testing if a type is `Unpin` *and* `UnsafeUnpin`.
/// ///
/// Returning true means the type is known to be `Unpin`. Returning /// Returning true means the type is known to be `Unpin` and `UnsafeUnpin`. Returning
/// `false` means nothing -- could be `Unpin`, might not be. /// `false` means nothing -- could be `Unpin`, might not be.
fn is_trivially_unpin(self) -> bool { fn is_trivially_unpin(self) -> bool {
match self.kind() { match self.kind() {

View file

@ -306,8 +306,12 @@ fn arg_attrs_for_rust_scalar<'tcx>(
let kind = if let Some(kind) = pointee.safe { let kind = if let Some(kind) = pointee.safe {
Some(kind) Some(kind)
} else if let Some(pointee) = drop_target_pointee { } else if let Some(pointee) = drop_target_pointee {
assert_eq!(pointee, layout.ty.builtin_deref(true).unwrap());
assert_eq!(offset, Size::ZERO);
// The argument to `drop_in_place` is semantically equivalent to a mutable reference. // The argument to `drop_in_place` is semantically equivalent to a mutable reference.
Some(PointerKind::MutableRef { unpin: pointee.is_unpin(tcx, cx.typing_env) }) let mutref = Ty::new_mut_ref(tcx, tcx.lifetimes.re_erased, pointee);
let layout = cx.layout_of(mutref).unwrap();
layout.pointee_info_at(&cx, offset).and_then(|pi| pi.safe)
} else { } else {
None None
}; };

View file

@ -8,36 +8,43 @@ use rustc_span::DUMMY_SP;
use rustc_trait_selection::traits; use rustc_trait_selection::traits;
fn is_copy_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool { fn is_copy_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
is_item_raw(tcx, query, LangItem::Copy) is_trait_raw(tcx, query, LangItem::Copy)
} }
fn is_use_cloned_raw<'tcx>( fn is_use_cloned_raw<'tcx>(
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
) -> bool { ) -> bool {
is_item_raw(tcx, query, LangItem::UseCloned) is_trait_raw(tcx, query, LangItem::UseCloned)
} }
fn is_sized_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool { fn is_sized_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
is_item_raw(tcx, query, LangItem::Sized) is_trait_raw(tcx, query, LangItem::Sized)
} }
fn is_freeze_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool { fn is_freeze_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
is_item_raw(tcx, query, LangItem::Freeze) is_trait_raw(tcx, query, LangItem::Freeze)
}
fn is_unsafe_unpin_raw<'tcx>(
tcx: TyCtxt<'tcx>,
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
) -> bool {
is_trait_raw(tcx, query, LangItem::UnsafeUnpin)
} }
fn is_unpin_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool { fn is_unpin_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
is_item_raw(tcx, query, LangItem::Unpin) is_trait_raw(tcx, query, LangItem::Unpin)
} }
fn is_async_drop_raw<'tcx>( fn is_async_drop_raw<'tcx>(
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
) -> bool { ) -> bool {
is_item_raw(tcx, query, LangItem::AsyncDrop) is_trait_raw(tcx, query, LangItem::AsyncDrop)
} }
fn is_item_raw<'tcx>( fn is_trait_raw<'tcx>(
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
item: LangItem, item: LangItem,
@ -53,6 +60,7 @@ pub(crate) fn provide(providers: &mut Providers) {
is_use_cloned_raw, is_use_cloned_raw,
is_sized_raw, is_sized_raw,
is_freeze_raw, is_freeze_raw,
is_unsafe_unpin_raw,
is_unpin_raw, is_unpin_raw,
is_async_drop_raw, is_async_drop_raw,
..*providers ..*providers

View file

@ -927,14 +927,20 @@ marker_impls! {
/// This is part of [RFC 3467](https://rust-lang.github.io/rfcs/3467-unsafe-pinned.html), and is /// This is part of [RFC 3467](https://rust-lang.github.io/rfcs/3467-unsafe-pinned.html), and is
/// tracked by [#125735](https://github.com/rust-lang/rust/issues/125735). /// tracked by [#125735](https://github.com/rust-lang/rust/issues/125735).
#[lang = "unsafe_unpin"] #[lang = "unsafe_unpin"]
pub(crate) unsafe auto trait UnsafeUnpin {} #[unstable(feature = "unsafe_unpin", issue = "125735")]
pub unsafe auto trait UnsafeUnpin {}
#[unstable(feature = "unsafe_unpin", issue = "125735")]
impl<T: ?Sized> !UnsafeUnpin for UnsafePinned<T> {} impl<T: ?Sized> !UnsafeUnpin for UnsafePinned<T> {}
unsafe impl<T: ?Sized> UnsafeUnpin for PhantomData<T> {} marker_impls! {
unsafe impl<T: ?Sized> UnsafeUnpin for *const T {} #[unstable(feature = "unsafe_unpin", issue = "125735")]
unsafe impl<T: ?Sized> UnsafeUnpin for *mut T {} unsafe UnsafeUnpin for
unsafe impl<T: ?Sized> UnsafeUnpin for &T {} {T: ?Sized} PhantomData<T>,
unsafe impl<T: ?Sized> UnsafeUnpin for &mut T {} {T: ?Sized} *const T,
{T: ?Sized} *mut T,
{T: ?Sized} &T,
{T: ?Sized} &mut T,
}
/// Types that do not require any pinning guarantees. /// Types that do not require any pinning guarantees.
/// ///
@ -1027,6 +1033,7 @@ impl !Unpin for PhantomPinned {}
// continue working. Ideally PhantomPinned could just wrap an `UnsafePinned<()>` to get the same // continue working. Ideally PhantomPinned could just wrap an `UnsafePinned<()>` to get the same
// effect, but we can't add a new field to an already stable unit struct -- that would be a breaking // effect, but we can't add a new field to an already stable unit struct -- that would be a breaking
// change. // change.
#[unstable(feature = "unsafe_unpin", issue = "125735")]
impl !UnsafeUnpin for PhantomPinned {} impl !UnsafeUnpin for PhantomPinned {}
marker_impls! { marker_impls! {

View file

@ -71,7 +71,9 @@ impl NewPermission {
access: None, access: None,
protector: None, protector: None,
} }
} else if pointee.is_unpin(*cx.tcx, cx.typing_env()) { } else if pointee.is_unpin(*cx.tcx, cx.typing_env())
&& pointee.is_unsafe_unpin(*cx.tcx, cx.typing_env())
{
// A regular full mutable reference. On `FnEntry` this is `noalias` and `dereferenceable`. // A regular full mutable reference. On `FnEntry` this is `noalias` and `dereferenceable`.
NewPermission::Uniform { NewPermission::Uniform {
perm: Permission::Unique, perm: Permission::Unique,
@ -129,7 +131,9 @@ impl NewPermission {
fn from_box_ty<'tcx>(ty: Ty<'tcx>, kind: RetagKind, cx: &crate::MiriInterpCx<'tcx>) -> Self { fn from_box_ty<'tcx>(ty: Ty<'tcx>, kind: RetagKind, cx: &crate::MiriInterpCx<'tcx>) -> Self {
// `ty` is not the `Box` but the field of the Box with this pointer (due to allocator handling). // `ty` is not the `Box` but the field of the Box with this pointer (due to allocator handling).
let pointee = ty.builtin_deref(true).unwrap(); let pointee = ty.builtin_deref(true).unwrap();
if pointee.is_unpin(*cx.tcx, cx.typing_env()) { if pointee.is_unpin(*cx.tcx, cx.typing_env())
&& pointee.is_unsafe_unpin(*cx.tcx, cx.typing_env())
{
// A regular box. On `FnEntry` this is `noalias`, but not `dereferenceable` (hence only // A regular box. On `FnEntry` this is `noalias`, but not `dereferenceable` (hence only
// a weak protector). // a weak protector).
NewPermission::Uniform { NewPermission::Uniform {

View file

@ -133,7 +133,8 @@ impl<'tcx> NewPermission {
retag_kind: RetagKind, retag_kind: RetagKind,
cx: &crate::MiriInterpCx<'tcx>, cx: &crate::MiriInterpCx<'tcx>,
) -> Option<Self> { ) -> Option<Self> {
let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env()); let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env())
&& pointee.is_unsafe_unpin(*cx.tcx, cx.typing_env());
let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env()); let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env());
let is_protected = retag_kind == RetagKind::FnEntry; let is_protected = retag_kind == RetagKind::FnEntry;

View file

@ -9,8 +9,36 @@ fn mutate(x: &UnsafePinned<i32>) {
unsafe { ptr.write(42) }; unsafe { ptr.write(42) };
} }
fn main() { fn mut_alias(x: &mut UnsafePinned<i32>, y: &mut UnsafePinned<i32>) {
let x = UnsafePinned::new(0); unsafe {
mutate(&x); x.get().write(0);
assert_eq!(x.into_inner(), 42); y.get().write(0);
x.get().write(0);
y.get().write(0);
}
}
// Also try this with a type for which we implement `Unpin`, just to be extra mean.
struct MyUnsafePinned<T>(UnsafePinned<T>);
impl<T> Unpin for MyUnsafePinned<T> {}
fn my_mut_alias(x: &mut MyUnsafePinned<i32>, y: &mut MyUnsafePinned<i32>) {
unsafe {
x.0.get().write(0);
y.0.get().write(0);
x.0.get().write(0);
y.0.get().write(0);
}
}
fn main() {
let mut x = UnsafePinned::new(0i32);
mutate(&x);
assert_eq!(unsafe { x.get().read() }, 42);
let ptr = &raw mut x;
unsafe { mut_alias(&mut *ptr, &mut *ptr) };
let ptr = ptr.cast::<MyUnsafePinned<i32>>();
unsafe { my_mut_alias(&mut *ptr, &mut *ptr) };
} }

View file

@ -76,6 +76,9 @@ pub trait BikeshedGuaranteedNoDrop {}
#[lang = "freeze"] #[lang = "freeze"]
pub unsafe auto trait Freeze {} pub unsafe auto trait Freeze {}
#[lang = "unsafe_unpin"]
pub unsafe auto trait UnsafeUnpin {}
#[lang = "unpin"] #[lang = "unpin"]
#[diagnostic::on_unimplemented( #[diagnostic::on_unimplemented(
note = "consider using the `pin!` macro\nconsider using `Box::pin` if you need to access the pinned value outside of the current scope", note = "consider using the `pin!` macro\nconsider using `Box::pin` if you need to access the pinned value outside of the current scope",

View file

@ -1,9 +1,9 @@
//@ compile-flags: -Copt-level=3 -C no-prepopulate-passes //@ compile-flags: -Copt-level=3 -C no-prepopulate-passes
#![crate_type = "lib"] #![crate_type = "lib"]
#![feature(rustc_attrs)] #![feature(rustc_attrs)]
#![feature(allocator_api)] #![feature(allocator_api, unsafe_unpin)]
use std::marker::PhantomPinned; use std::marker::{PhantomPinned, UnsafeUnpin};
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::num::NonZero; use std::num::NonZero;
use std::ptr::NonNull; use std::ptr::NonNull;
@ -259,11 +259,21 @@ pub fn trait_raw(_: *const dyn Drop) {}
// CHECK: @trait_box(ptr noalias noundef nonnull align 1{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) // CHECK: @trait_box(ptr noalias noundef nonnull align 1{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}})
#[no_mangle] #[no_mangle]
pub fn trait_box(_: Box<dyn Drop + Unpin>) {} pub fn trait_box(_: Box<dyn Drop + Unpin + UnsafeUnpin>) {}
// Ensure that removing *either* `Unpin` or `UnsafeUnpin` is enough to lose the attribute.
// CHECK: @trait_box_pin1(ptr noundef nonnull align 1{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}})
#[no_mangle]
pub fn trait_box_pin1(_: Box<dyn Drop + Unpin>) {}
// CHECK: @trait_box_pin2(ptr noundef nonnull align 1{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}})
#[no_mangle]
pub fn trait_box_pin2(_: Box<dyn Drop + UnsafeUnpin>) {}
// CHECK: { ptr, ptr } @trait_option(ptr noalias noundef align 1 %x.0, ptr %x.1) // CHECK: { ptr, ptr } @trait_option(ptr noalias noundef align 1 %x.0, ptr %x.1)
#[no_mangle] #[no_mangle]
pub fn trait_option(x: Option<Box<dyn Drop + Unpin>>) -> Option<Box<dyn Drop + Unpin>> { pub fn trait_option(
x: Option<Box<dyn Drop + Unpin + UnsafeUnpin>>,
) -> Option<Box<dyn Drop + Unpin + UnsafeUnpin>> {
x x
} }