Skip to content

Commit

Permalink
supress niches in coroutines
Browse files Browse the repository at this point in the history
  • Loading branch information
RalfJung committed Aug 20, 2024
1 parent a971212 commit a281f93
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 1 deletion.
8 changes: 7 additions & 1 deletion compiler/rustc_ty_utils/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1001,7 +1001,13 @@ fn coroutine_layout<'tcx>(
},
fields: outer_fields,
abi,
largest_niche: prefix.largest_niche,
// Suppress niches inside coroutines. If the niche is inside a field that is aliased (due to
// self-referentiality), getting the discriminant can cause aliasing violations.
// `UnsafeCell` blocks niches for the same reason, but we don't yet have `UnsafePinned` that
// would do the same for us here.
// See <https://github.com/rust-lang/rust/issues/63818>, <https://github.com/rust-lang/miri/issues/3780>.
// FIXME: Remove when <https://github.com/rust-lang/rust/issues/125735> is implemented and aliased coroutine fields are wrapped in `UnsafePinned`.
largest_niche: None,
size,
align,
max_repr_align: None,
Expand Down
66 changes: 66 additions & 0 deletions src/tools/miri/tests/pass/async-niche-aliasing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows

use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll, Wake},
mem::MaybeUninit,
};

struct ThingAdder<'a> {
// Using `MaybeUninit` to ensure there are no niches here.
thing: MaybeUninit<&'a mut String>,
}

impl Future for ThingAdder<'_> {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe {
**self.get_unchecked_mut().thing.assume_init_mut() += ", world";
}
Poll::Pending
}
}

fn main() {
let mut thing = "hello".to_owned();
// This future has (at least) two fields, a String (`thing`) and a ThingAdder pointing to that string.
let fut = async move { ThingAdder { thing: MaybeUninit::new(&mut thing) }.await };

let mut fut = MaybeDone::Future(fut);
let mut fut = unsafe { Pin::new_unchecked(&mut fut) };

let waker = Arc::new(DummyWaker).into();
let mut ctx = Context::from_waker(&waker);
// This ends up reading the discriminant of the `MaybeDone`. If that is stored inside the
// `thing: String` as a niche optimization, that causes aliasing conflicts with the reference
// stored in `ThingAdder`.
assert_eq!(fut.as_mut().poll(&mut ctx), Poll::Pending);
assert_eq!(fut.as_mut().poll(&mut ctx), Poll::Pending);
}

struct DummyWaker;

impl Wake for DummyWaker {
fn wake(self: Arc<Self>) {}
}

pub enum MaybeDone<F: Future> {
Future(F),
Done,
}
impl<F: Future<Output = ()>> Future for MaybeDone<F> {
type Output = ();

fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe {
match *self.as_mut().get_unchecked_mut() {
MaybeDone::Future(ref mut f) => Pin::new_unchecked(f).poll(cx),
MaybeDone::Done => unreachable!(),
}
}
}
}

0 comments on commit a281f93

Please sign in to comment.