Rollup merge of #151365 - RalfJung:unsafe-unpin-opsem, r=BoxyUwU

UnsafePinned: implement opsem effects of UnsafeUnpin

This implements the next step for https://github.com/rust-lang/rust/issues/125735: actually making `UnsafePinned` have special opsem effects by suppressing the `noalias` *even if* the type is wrapped in an `Unpin` wrapper.

For backwards compatibility we also still keep the `Unpin` hack, i.e. a type must be both `Unpin` and `UnsafeUnpin` to get `noalias`.
This commit is contained in:
Jonathan Brouwer 2026-02-14 22:11:53 +01:00 committed by GitHub
commit 33c2a6eba9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 123 additions and 36 deletions

View file

@ -1693,6 +1693,10 @@ rustc_queries! {
query is_freeze_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
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 is_unpin_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` is `Unpin`", env.value }

View file

@ -1046,9 +1046,11 @@ where
hir::Mutability::Not => {
PointerKind::SharedRef { frozen: optimize && ty.is_freeze(tcx, typing_env) }
}
hir::Mutability::Mut => {
PointerKind::MutableRef { unpin: optimize && ty.is_unpin(tcx, typing_env) }
}
hir::Mutability::Mut => PointerKind::MutableRef {
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 {
@ -1143,7 +1145,9 @@ where
debug_assert!(pointee.safe.is_none());
let optimize = tcx.sess.opts.optimize != OptLevel::No;
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),
});
}

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.
///
/// 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 {
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.
fn is_trivially_unpin(self) -> bool {
match self.kind() {

View file

@ -306,8 +306,12 @@ fn arg_attrs_for_rust_scalar<'tcx>(
let kind = if let Some(kind) = pointee.safe {
Some(kind)
} 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.
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 {
None
};

View file

@ -8,36 +8,43 @@ use rustc_span::DUMMY_SP;
use rustc_trait_selection::traits;
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>(
tcx: TyCtxt<'tcx>,
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
) -> 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 {
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 {
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 {
is_item_raw(tcx, query, LangItem::Unpin)
is_trait_raw(tcx, query, LangItem::Unpin)
}
fn is_async_drop_raw<'tcx>(
tcx: TyCtxt<'tcx>,
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
) -> 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>,
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
item: LangItem,
@ -53,6 +60,7 @@ pub(crate) fn provide(providers: &mut Providers) {
is_use_cloned_raw,
is_sized_raw,
is_freeze_raw,
is_unsafe_unpin_raw,
is_unpin_raw,
is_async_drop_raw,
..*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
/// tracked by [#125735](https://github.com/rust-lang/rust/issues/125735).
#[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> {}
unsafe impl<T: ?Sized> UnsafeUnpin for PhantomData<T> {}
unsafe impl<T: ?Sized> UnsafeUnpin for *const T {}
unsafe impl<T: ?Sized> UnsafeUnpin for *mut T {}
unsafe impl<T: ?Sized> UnsafeUnpin for &T {}
unsafe impl<T: ?Sized> UnsafeUnpin for &mut T {}
marker_impls! {
#[unstable(feature = "unsafe_unpin", issue = "125735")]
unsafe UnsafeUnpin for
{T: ?Sized} PhantomData<T>,
{T: ?Sized} *const T,
{T: ?Sized} *mut T,
{T: ?Sized} &T,
{T: ?Sized} &mut T,
}
/// 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
// effect, but we can't add a new field to an already stable unit struct -- that would be a breaking
// change.
#[unstable(feature = "unsafe_unpin", issue = "125735")]
impl !UnsafeUnpin for PhantomPinned {}
marker_impls! {

View file

@ -31,7 +31,7 @@ use crate::{fmt, intrinsics, ptr, ub_checks};
issue = "none"
)]
pub unsafe trait ZeroablePrimitive: Sized + Copy + private::Sealed {
#[doc(hidden)]
/// A type like `Self` but with a niche that includes zero.
type NonZeroInner: Sized + Copy;
}

View file

@ -71,7 +71,9 @@ impl NewPermission {
access: 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`.
NewPermission::Uniform {
perm: Permission::Unique,
@ -129,7 +131,9 @@ impl NewPermission {
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).
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 weak protector).
NewPermission::Uniform {

View file

@ -133,7 +133,8 @@ impl<'tcx> NewPermission {
retag_kind: RetagKind,
cx: &crate::MiriInterpCx<'tcx>,
) -> 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 is_protected = retag_kind == RetagKind::FnEntry;

View file

@ -9,8 +9,36 @@ fn mutate(x: &UnsafePinned<i32>) {
unsafe { ptr.write(42) };
}
fn main() {
let x = UnsafePinned::new(0);
mutate(&x);
assert_eq!(x.into_inner(), 42);
fn mut_alias(x: &mut UnsafePinned<i32>, y: &mut UnsafePinned<i32>) {
unsafe {
x.get().write(0);
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"]
pub unsafe auto trait Freeze {}
#[lang = "unsafe_unpin"]
pub unsafe auto trait UnsafeUnpin {}
#[lang = "unpin"]
#[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",

View file

@ -1,9 +1,9 @@
//@ compile-flags: -Copt-level=3 -C no-prepopulate-passes
#![crate_type = "lib"]
#![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::num::NonZero;
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)?}})
#[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)
#[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
}

View file

@ -20,7 +20,8 @@ where
//@ has - '//h3[@class="code-header"]' 'impl<B> Send for Switch<B>where <B as Signal>::Item: Send'
//@ has - '//h3[@class="code-header"]' 'impl<B> Sync for Switch<B>where <B as Signal>::Item: Sync'
//@ count - '//*[@id="implementations-list"]//*[@class="impl"]' 0
//@ count - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]' 6
//@ count - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]' 7
// The number here will need updating when new auto traits are added: ^
pub struct Switch<B: Signal> {
pub inner: <B as Signal2>::Item2,
}

View file

@ -1,13 +1,15 @@
#![crate_name = "foo"]
#![feature(negative_impls, freeze_impls, freeze)]
#![feature(negative_impls, freeze_impls, freeze, unsafe_unpin)]
pub struct Foo;
//@ has foo/struct.Foo.html
//@ !hasraw - 'Auto Trait Implementations'
// Manually un-implement all auto traits for Foo:
impl !Send for Foo {}
impl !Sync for Foo {}
impl !std::marker::Freeze for Foo {}
impl !std::marker::UnsafeUnpin for Foo {}
impl !std::marker::Unpin for Foo {}
impl !std::panic::RefUnwindSafe for Foo {}
impl !std::panic::UnwindSafe for Foo {}

View file

@ -2,7 +2,8 @@
//@ has - '//h3[@class="code-header"]' 'impl<T> Send for Foo<T>where T: Send'
//@ has - '//h3[@class="code-header"]' 'impl<T> Sync for Foo<T>where T: Sync'
//@ count - '//*[@id="implementations-list"]//*[@class="impl"]' 0
//@ count - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]' 6
//@ count - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]' 7
// The number here will need updating when new auto traits are added: ^
pub struct Foo<T> {
field: T,
}

View file

@ -6,7 +6,8 @@
// 'impl<T> Send for Foo<T>'
//
//@ count - '//*[@id="trait-implementations-list"]//*[@class="impl"]' 1
//@ count - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]' 5
//@ count - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]' 6
// The number here will need updating when new auto traits are added: ^
pub struct Foo<T> {
field: T,
}