Skip to content

Commit

Permalink
Auto merge of #106180 - RalfJung:dereferenceable-generators, r=nbdd0121
Browse files Browse the repository at this point in the history
make &mut !Unpin not dereferenceable, and Box<!Unpin> not noalias

See rust-lang/unsafe-code-guidelines#381 and [this LLVM discussion](https://discourse.llvm.org/t/interaction-of-noalias-and-dereferenceable/66979). The exact semantics of how `noalias` and `dereferenceable` interact are unclear, and `@comex` found a case of LLVM actually exploiting that ambiguity for optimizations. I think for now we should treat LLVM `dereferenceable` as implying a "fake read" to happen immediately at the top of the function (standing in for the spurious reads that LLVM might introduce), and that fake read is subject to all the usual `noalias` restrictions. This means we cannot put `dereferenceable` on `&mut !Unpin` references as those references can alias with other references that are being read and written inside the function (e.g. for self-referential generators), meaning the fake read introduces aliasing conflicts with those other accesses.

For `&` this is already not a problem due to rust-lang/rust#98017 which removed the `dereferenceable` attribute for other reasons.

Regular `&mut Unpin` references are unaffected, so I hope the impact of this is going to be tiny.

The first commit does some refactoring of the `PointerKind` enum since I found the old code very confusing each time I had to touch it. It doesn't change behavior.

Fixes #2714

EDIT: Turns out our `Box<!Unpin>` treatment was incorrect, too, so the PR also fixes that now (in codegen and Miri): we do not put `noalias` on these boxes any more.
  • Loading branch information
bors committed Feb 7, 2023
2 parents 313a19e + c9f136b commit 79ccd0e
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 76 deletions.
43 changes: 31 additions & 12 deletions src/borrow_tracker/stacked_borrows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,18 @@ impl NewPermission {
protector: None,
}
} else if pointee.is_unpin(*cx.tcx, cx.param_env()) {
// A regular full mutable reference.
// A regular full mutable reference. On `FnEntry` this is `noalias` and `dereferenceable`.
NewPermission::Uniform {
perm: Permission::Unique,
access: Some(AccessKind::Write),
protector,
}
} else {
// `!Unpin` dereferences do not get `noalias` nor `dereferenceable`.
NewPermission::Uniform {
perm: Permission::SharedReadWrite,
// FIXME: We emit `dereferenceable` for `!Unpin` mutable references, so we
// should do fake accesses here. But then we run into
// <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>, so for now
// we don't do that.
access: None,
protector,
protector: None,
}
}
}
Expand All @@ -109,6 +106,7 @@ impl NewPermission {
}
}
ty::Ref(_, _pointee, Mutability::Not) => {
// Shared references. If frozen, these get `noalias` and `dereferenceable`; otherwise neither.
NewPermission::FreezeSensitive {
freeze_perm: Permission::SharedReadOnly,
freeze_access: Some(AccessKind::Read),
Expand Down Expand Up @@ -137,6 +135,32 @@ 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().ty;
if pointee.is_unpin(*cx.tcx, cx.param_env()) {
// A regular box. On `FnEntry` this is `noalias`, but not `dereferenceable` (hence only
// a weak protector).
NewPermission::Uniform {
perm: Permission::Unique,
access: Some(AccessKind::Write),
protector: (kind == RetagKind::FnEntry)
.then_some(ProtectorKind::WeakProtector),
}
} else {
// `!Unpin` boxes do not get `noalias` nor `dereferenceable`.
NewPermission::Uniform {
perm: Permission::SharedReadWrite,
access: None,
protector: None,
}
}
}

fn protector(&self) -> Option<ProtectorKind> {
match self {
NewPermission::Uniform { protector, .. } => *protector,
Expand Down Expand Up @@ -916,12 +940,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {

fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
// Boxes get a weak protectors, since they may be deallocated.
let new_perm = NewPermission::Uniform {
perm: Permission::Unique,
access: Some(AccessKind::Write),
protector: (self.kind == RetagKind::FnEntry)
.then_some(ProtectorKind::WeakProtector),
};
let new_perm = NewPermission::from_box_ty(place.layout.ty, self.kind, self.ecx);
self.retag_ptr_inplace(place, new_perm, self.retag_cause)
}

Expand Down
16 changes: 0 additions & 16 deletions tests/fail/stacked_borrows/deallocate_against_protector2.rs

This file was deleted.

38 changes: 0 additions & 38 deletions tests/fail/stacked_borrows/deallocate_against_protector2.stderr

This file was deleted.

54 changes: 44 additions & 10 deletions tests/pass/stacked-borrows/future-self-referential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ impl Future for Delay {
}
}

fn mk_waker() -> Waker {
use std::sync::Arc;

struct MyWaker;
impl Wake for MyWaker {
fn wake(self: Arc<Self>) {
unimplemented!()
}
}

Waker::from(Arc::new(MyWaker))
}

async fn do_stuff() {
(&mut Delay::new(1)).await;
}
Expand Down Expand Up @@ -73,16 +86,7 @@ impl Future for DoStuff {
}

fn run_fut<T>(fut: impl Future<Output = T>) -> T {
use std::sync::Arc;

struct MyWaker;
impl Wake for MyWaker {
fn wake(self: Arc<Self>) {
unimplemented!()
}
}

let waker = Waker::from(Arc::new(MyWaker));
let waker = mk_waker();
let mut context = Context::from_waker(&waker);

let mut pinned = pin!(fut);
Expand All @@ -94,7 +98,37 @@ fn run_fut<T>(fut: impl Future<Output = T>) -> T {
}
}

fn self_referential_box() {
let waker = mk_waker();
let cx = &mut Context::from_waker(&waker);

async fn my_fut() -> i32 {
let val = 10;
let val_ref = &val;

let _ = Delay::new(1).await;

*val_ref
}

fn box_poll<F: Future>(
mut f: Pin<Box<F>>,
cx: &mut Context<'_>,
) -> (Pin<Box<F>>, Poll<F::Output>) {
let p = f.as_mut().poll(cx);
(f, p)
}

let my_fut = Box::pin(my_fut());
let (my_fut, p1) = box_poll(my_fut, cx);
assert!(p1.is_pending());
let (my_fut, p2) = box_poll(my_fut, cx);
assert!(p2.is_ready());
drop(my_fut);
}

fn main() {
run_fut(do_stuff());
run_fut(DoStuff::new());
self_referential_box();
}
20 changes: 20 additions & 0 deletions tests/pass/stacked-borrows/stacked-borrows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ fn main() {
array_casts();
mut_below_shr();
wide_raw_ptr_in_tuple();
not_unpin_not_protected();
}

// Make sure that reading from an `&mut` does, like reborrowing to `&`,
Expand Down Expand Up @@ -219,3 +220,22 @@ fn wide_raw_ptr_in_tuple() {
// Make sure the fn ptr part of the vtable is still fine.
r.type_id();
}

fn not_unpin_not_protected() {
// `&mut !Unpin`, at least for now, does not get `noalias` nor `dereferenceable`, so we also
// don't add protectors. (We could, but until we have a better idea for where we want to go with
// the self-referntial-generator situation, it does not seem worth the potential trouble.)
use std::marker::PhantomPinned;

pub struct NotUnpin(i32, PhantomPinned);

fn inner(x: &mut NotUnpin, f: fn(&mut NotUnpin)) {
// `f` may mutate, but it may not deallocate!
f(x)
}

inner(Box::leak(Box::new(NotUnpin(0, PhantomPinned))), |x| {
let raw = x as *mut _;
drop(unsafe { Box::from_raw(raw) });
});
}

0 comments on commit 79ccd0e

Please sign in to comment.