diff --git a/compiler/rustc_ast_ir/src/lib.rs b/compiler/rustc_ast_ir/src/lib.rs index 0898433a74c53..e1a4f3f75438f 100644 --- a/compiler/rustc_ast_ir/src/lib.rs +++ b/compiler/rustc_ast_ir/src/lib.rs @@ -17,16 +17,16 @@ use rustc_macros::{Decodable_NoContext, Encodable_NoContext, HashStable_NoContex pub mod visit; /// The movability of a coroutine / closure literal: -/// whether a coroutine contains self-references, causing it to be `!Unpin`. +/// whether a coroutine contains self-references, causing it to be `![Unsafe]Unpin`. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Copy)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, HashStable_NoContext) )] pub enum Movability { - /// May contain self-references, `!Unpin`. + /// May contain self-references, `!Unpin + !UnsafeUnpin`. Static, - /// Must not contain self-references, `Unpin`. + /// Must not contain self-references, `Unpin + UnsafeUnpin`. Movable, } diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index fe6dff7ff1b63..dfc8338b1c573 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -3385,7 +3385,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { Some(3) } else if string.starts_with("static") { // `static` is 6 chars long - // This is used for `!Unpin` coroutines + // This is used for immovable (self-referential) coroutines Some(6) } else { None diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs index 3fc05f2caf2ad..1028e0eba148a 100644 --- a/compiler/rustc_middle/src/mir/query.rs +++ b/compiler/rustc_middle/src/mir/query.rs @@ -29,6 +29,9 @@ pub struct CoroutineSavedTy<'tcx> { pub source_info: SourceInfo, /// Whether the local should be ignored for trait bound computations. pub ignore_for_traits: bool, + /// If this local is borrowed across a suspension point and thus is + /// "wrapped" in `UnsafePinned`. Always false for movable coroutines. + pub pinned: bool, } /// The layout of coroutine state. diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index fa2f1cf1a1c87..0f07d9aa6799d 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -335,6 +335,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.coroutine_hidden_types(def_id) } + fn coroutine_has_pinned_fields(self, def_id: DefId) -> Option { + self.coroutine_has_pinned_fields(def_id) + } + fn fn_sig(self, def_id: DefId) -> ty::EarlyBinder<'tcx, ty::PolyFnSig<'tcx>> { self.fn_sig(def_id) } @@ -734,6 +738,7 @@ bidirectional_lang_item_map! { TransmuteTrait, Tuple, Unpin, + UnsafeUnpin, Unsize, // tidy-alphabetical-end } diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index dfc11de283d80..56750b06f619a 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -790,6 +790,13 @@ impl<'tcx> TyCtxt<'tcx> { )) } + /// True if the given coroutine has any pinned fields. + /// `None` if the coroutine is tainted by errors. + pub fn coroutine_has_pinned_fields(self, def_id: DefId) -> Option { + self.mir_coroutine_witnesses(def_id) + .map(|layout| layout.field_tys.iter().any(|ty| ty.pinned)) + } + /// Expands the given impl trait type, stopping if the type is recursive. #[instrument(skip(self), level = "debug", ret)] pub fn try_expand_impl_trait_type( diff --git a/compiler/rustc_mir_dataflow/src/impls/coro_pinned_locals.rs b/compiler/rustc_mir_dataflow/src/impls/coro_pinned_locals.rs new file mode 100644 index 0000000000000..86726bdc36779 --- /dev/null +++ b/compiler/rustc_mir_dataflow/src/impls/coro_pinned_locals.rs @@ -0,0 +1,146 @@ +use rustc_index::bit_set::DenseBitSet; +use rustc_middle::mir::visit::Visitor; +use rustc_middle::mir::*; +use tracing::debug; + +use crate::{Analysis, GenKill}; + +#[derive(Clone)] +pub struct CoroutinePinnedLocals(pub Local); + +impl CoroutinePinnedLocals { + fn transfer_function<'a>(&self, domain: &'a mut DenseBitSet) -> TransferFunction<'a> { + TransferFunction { local: self.0, trans: domain } + } +} + +impl<'tcx> Analysis<'tcx> for CoroutinePinnedLocals { + type Domain = DenseBitSet; + const NAME: &'static str = "coro_pinned_locals"; + + fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain { + // bottom = unborrowed + DenseBitSet::new_empty(body.local_decls().len()) + } + + fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) { + // No locals are actively borrowing from other locals on function entry + } + + fn apply_primary_statement_effect( + &mut self, + state: &mut Self::Domain, + statement: &Statement<'tcx>, + location: Location, + ) { + self.transfer_function(state).visit_statement(statement, location); + } + + fn apply_primary_terminator_effect<'mir>( + &mut self, + state: &mut Self::Domain, + terminator: &'mir Terminator<'tcx>, + location: Location, + ) -> TerminatorEdges<'mir, 'tcx> { + self.transfer_function(state).visit_terminator(terminator, location); + + terminator.edges() + } +} + +/// A `Visitor` that defines the transfer function for `CoroutinePinnedLocals`. +pub(super) struct TransferFunction<'a> { + local: Local, + trans: &'a mut DenseBitSet, +} + +impl<'tcx> Visitor<'tcx> for TransferFunction<'_> { + fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { + self.super_statement(statement, location); + + if let StatementKind::StorageDead(local) = statement.kind { + debug!(for_ = ?self.local, KILL = ?local, ?statement, ?location); + self.trans.kill(local); + } + } + + fn visit_assign( + &mut self, + assigned_place: &Place<'tcx>, + rvalue: &Rvalue<'tcx>, + location: Location, + ) { + self.super_assign(assigned_place, rvalue, location); + + match rvalue { + Rvalue::Ref(_, BorrowKind::Mut { .. } | BorrowKind::Shared, place) + | Rvalue::RawPtr(RawPtrKind::Const | RawPtrKind::Mut, place) => { + if (!place.is_indirect() && place.local == self.local) + || self.trans.contains(place.local) + { + if assigned_place.is_indirect() { + debug!(for_ = ?self.local, GEN_ptr_indirect = ?assigned_place, borrowed_place = ?place, ?rvalue, ?location); + self.trans.gen_(self.local); + } else { + debug!(for_ = ?self.local, GEN_ptr_direct = ?assigned_place, borrowed_place = ?place, ?rvalue, ?location); + self.trans.gen_(assigned_place.local); + } + } + } + + // fake pointers don't count + Rvalue::Ref(_, BorrowKind::Fake(_), _) + | Rvalue::RawPtr(RawPtrKind::FakeForPtrMetadata, _) => {} + + Rvalue::Use(..) + | Rvalue::Repeat(..) + | Rvalue::ThreadLocalRef(..) + | Rvalue::Len(..) + | Rvalue::Cast(..) + | Rvalue::BinaryOp(..) + | Rvalue::NullaryOp(..) + | Rvalue::UnaryOp(..) + | Rvalue::Discriminant(..) + | Rvalue::Aggregate(..) + | Rvalue::ShallowInitBox(..) + | Rvalue::CopyForDeref(..) + | Rvalue::WrapUnsafeBinder(..) => {} + } + } + + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + self.super_terminator(terminator, location); + + match terminator.kind { + TerminatorKind::Drop { place: dropped_place, .. } => { + // Drop terminators may call custom drop glue (`Drop::drop`), which takes `&mut + // self` as a parameter. In the general case, a drop impl could launder that + // reference into the surrounding environment through a raw pointer, thus creating + // a valid `*mut` pointing to the dropped local. We are not yet willing to declare + // this particular case UB, so we must treat all dropped locals as mutably borrowed + // for now. See discussion on [#61069]. + // + // [#61069]: https://github.com/rust-lang/rust/pull/61069 + if !dropped_place.is_indirect() && dropped_place.local == self.local { + debug!(for_ = ?self.local, GEN_drop = ?dropped_place, ?terminator, ?location); + self.trans.gen_(self.local); + } + } + + TerminatorKind::Goto { .. } + | TerminatorKind::SwitchInt { .. } + | TerminatorKind::UnwindResume + | TerminatorKind::UnwindTerminate(_) + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Call { .. } + | TerminatorKind::TailCall { .. } + | TerminatorKind::Assert { .. } + | TerminatorKind::Yield { .. } + | TerminatorKind::CoroutineDrop + | TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } => {} + } + } +} diff --git a/compiler/rustc_mir_dataflow/src/impls/mod.rs b/compiler/rustc_mir_dataflow/src/impls/mod.rs index 3f29b819a6d18..2aaef5aab6921 100644 --- a/compiler/rustc_mir_dataflow/src/impls/mod.rs +++ b/compiler/rustc_mir_dataflow/src/impls/mod.rs @@ -1,9 +1,11 @@ mod borrowed_locals; +mod coro_pinned_locals; mod initialized; mod liveness; mod storage_liveness; pub use self::borrowed_locals::{MaybeBorrowedLocals, borrowed_locals}; +pub use self::coro_pinned_locals::CoroutinePinnedLocals; pub use self::initialized::{ EverInitializedPlaces, EverInitializedPlacesDomain, MaybeInitializedPlaces, MaybeUninitializedPlaces, MaybeUninitializedPlacesDomain, diff --git a/compiler/rustc_mir_transform/src/coroutine.rs b/compiler/rustc_mir_transform/src/coroutine.rs index 92d5e152f58e6..47eb7c73ccd1e 100644 --- a/compiler/rustc_mir_transform/src/coroutine.rs +++ b/compiler/rustc_mir_transform/src/coroutine.rs @@ -69,8 +69,8 @@ use rustc_middle::ty::{ }; use rustc_middle::{bug, span_bug}; use rustc_mir_dataflow::impls::{ - MaybeBorrowedLocals, MaybeLiveLocals, MaybeRequiresStorage, MaybeStorageLive, - always_storage_live_locals, + CoroutinePinnedLocals, MaybeBorrowedLocals, MaybeLiveLocals, MaybeRequiresStorage, + MaybeStorageLive, always_storage_live_locals, }; use rustc_mir_dataflow::{Analysis, Results, ResultsVisitor}; use rustc_span::def_id::{DefId, LocalDefId}; @@ -639,6 +639,15 @@ struct LivenessInfo { /// Parallel vec to the above with SourceInfo for each yield terminator. source_info_at_suspension_points: Vec, + /// Coroutine saved locals that are borrowed across a suspension point. + /// This corresponds to locals that are "wrapped" with `UnsafePinned`. + /// + /// Note that movable coroutines do not allow borrowing locals across + /// suspension points and thus will always have this set empty. + /// + /// For more information, see [RFC 3467](https://rust-lang.github.io/rfcs/3467-unsafe-pinned.html). + saved_locals_borrowed_across_suspension_points: DenseBitSet, + /// For every saved local, the set of other saved locals that are /// storage-live at the same time as this local. We cannot overlap locals in /// the layout which have conflicting storage. @@ -657,6 +666,9 @@ struct LivenessInfo { /// case none exist, the local is considered to be always live. /// - a local has to be stored if it is either directly used after the /// the suspend point, or if it is live and has been previously borrowed. +/// +/// We also compute locals which are "pinned" (borrowed across a suspension point). +/// These are "wrapped" in `UnsafePinned` and have their niche opts disabled. fn locals_live_across_suspend_points<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, @@ -686,10 +698,12 @@ fn locals_live_across_suspend_points<'tcx>( let mut liveness = MaybeLiveLocals.iterate_to_fixpoint(tcx, body, Some("coroutine")).into_results_cursor(body); + let mut pinned_locals_cache = IndexVec::from_fn_n(|_| None, body.local_decls.len()); let mut storage_liveness_map = IndexVec::from_elem(None, &body.basic_blocks); let mut live_locals_at_suspension_points = Vec::new(); let mut source_info_at_suspension_points = Vec::new(); let mut live_locals_at_any_suspension_point = DenseBitSet::new_empty(body.local_decls.len()); + let mut pinned_locals = DenseBitSet::new_empty(body.local_decls.len()); for (block, data) in body.basic_blocks.iter_enumerated() { if let TerminatorKind::Yield { .. } = data.terminator().kind { @@ -729,6 +743,27 @@ fn locals_live_across_suspend_points<'tcx>( debug!("loc = {:?}, live_locals = {:?}", loc, live_locals); + for live_local in live_locals.iter() { + let pinned_cursor = pinned_locals_cache[live_local].get_or_insert_with(|| { + CoroutinePinnedLocals(live_local) + .iterate_to_fixpoint(tcx, body, None) + .into_results_cursor(body) + }); + pinned_cursor.seek_to_block_end(block); + let mut pinned_by = pinned_cursor.get().clone(); + pinned_by.intersect(&live_locals); + + if !pinned_by.is_empty() { + assert!( + !movable, + "local {live_local:?} of movable coro shouldn't be pinned, yet it is pinned by {pinned_by:?}" + ); + + debug!("{live_local:?} pinned by {pinned_by:?} in {block:?}"); + pinned_locals.insert(live_local); + } + } + // Add the locals live at this suspension point to the set of locals which live across // any suspension points live_locals_at_any_suspension_point.union(&live_locals); @@ -738,7 +773,8 @@ fn locals_live_across_suspend_points<'tcx>( } } - debug!("live_locals_anywhere = {:?}", live_locals_at_any_suspension_point); + debug!(?pinned_locals); + debug!(live_locals_anywhere = ?live_locals_at_any_suspension_point); let saved_locals = CoroutineSavedLocals(live_locals_at_any_suspension_point); // Renumber our liveness_map bitsets to include only the locals we are @@ -748,6 +784,9 @@ fn locals_live_across_suspend_points<'tcx>( .map(|live_here| saved_locals.renumber_bitset(live_here)) .collect(); + let saved_locals_borrowed_across_suspension_points = + saved_locals.renumber_bitset(&pinned_locals); + let storage_conflicts = compute_storage_conflicts( body, &saved_locals, @@ -759,6 +798,7 @@ fn locals_live_across_suspend_points<'tcx>( saved_locals, live_locals_at_suspension_points, source_info_at_suspension_points, + saved_locals_borrowed_across_suspension_points, storage_conflicts, storage_liveness: storage_liveness_map, } @@ -931,6 +971,7 @@ fn compute_layout<'tcx>( saved_locals, live_locals_at_suspension_points, source_info_at_suspension_points, + saved_locals_borrowed_across_suspension_points, storage_conflicts, storage_liveness, } = liveness; @@ -960,8 +1001,14 @@ fn compute_layout<'tcx>( ClearCrossCrate::Set(box LocalInfo::FakeBorrow) => true, _ => false, }; - let decl = - CoroutineSavedTy { ty: decl.ty, source_info: decl.source_info, ignore_for_traits }; + let pinned = saved_locals_borrowed_across_suspension_points.contains(saved_local); + + let decl = CoroutineSavedTy { + ty: decl.ty, + source_info: decl.source_info, + ignore_for_traits, + pinned, + }; debug!(?decl); tys.push(decl); diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index 9262da2906d08..7076b8010bd2c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -1148,13 +1148,13 @@ where ty::Infer(_) | ty::Bound(_, _) => panic!("unexpected type `{self_ty:?}`"), - // Coroutines have one special built-in candidate, `Unpin`, which - // takes precedence over the structural auto trait candidate being - // assembled. + // Coroutines have two special built-in candidates, `Unpin` and `UnsafeUnpin`. + // These take precedence over the structural auto trait candidate being assembled. ty::Coroutine(def_id, _) if self.cx().is_lang_item(goal.predicate.def_id(), TraitSolverLangItem::Unpin) => { match self.cx().coroutine_movability(def_id) { + // immovable coroutines are *never* Unpin Movability::Static => Some(Err(NoSolution)), Movability::Movable => Some( self.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| { @@ -1163,6 +1163,21 @@ where ), } } + ty::Coroutine(def_id, _) + if self + .cx() + .is_lang_item(goal.predicate.def_id(), TraitSolverLangItem::UnsafeUnpin) => + { + match self.cx().coroutine_has_pinned_fields(def_id) { + Some(true) => Some(Err(NoSolution)), + Some(false) => Some( + self.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| { + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }), + ), + None => None, // coro tainted by errors + } + } // If we still have an alias here, it must be rigid. For opaques, it's always // okay to consider auto traits because that'll reveal its hidden type. For diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index cf6d2bc151fb0..60f0bad733de2 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -751,6 +751,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // The auto impl might apply; we don't know. candidates.ambiguous = true; } + ty::Coroutine(coroutine_def_id, _) if self.tcx().is_lang_item(def_id, LangItem::Unpin) => { @@ -766,6 +767,18 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } } + ty::Coroutine(coroutine_def_id, _) + if self.tcx().is_lang_item(def_id, LangItem::UnsafeUnpin) => + { + match self.tcx().coroutine_has_pinned_fields(coroutine_def_id) { + Some(true) => {} + Some(false) => { + candidates.vec.push(BuiltinCandidate { has_nested: false }); + } + // coro tainted by errors + None => candidates.vec.push(AutoImplCandidate), + } + } ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { bug!( diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 71bfeabfda878..b47a6bd9687a9 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -205,6 +205,8 @@ pub trait Interner: def_id: Self::DefId, ) -> ty::EarlyBinder>; + fn coroutine_has_pinned_fields(self, def_id: Self::DefId) -> Option; + fn fn_sig( self, def_id: Self::DefId, diff --git a/compiler/rustc_type_ir/src/lang_items.rs b/compiler/rustc_type_ir/src/lang_items.rs index 65f7cdf8f922b..2803539f249ff 100644 --- a/compiler/rustc_type_ir/src/lang_items.rs +++ b/compiler/rustc_type_ir/src/lang_items.rs @@ -38,6 +38,7 @@ pub enum TraitSolverLangItem { TransmuteTrait, Tuple, Unpin, + UnsafeUnpin, Unsize, // tidy-alphabetical-end } diff --git a/tests/mir-opt/building/async_await.b-{closure#0}.coroutine_resume.0.mir b/tests/mir-opt/building/async_await.b-{closure#0}.coroutine_resume.0.mir index 109a41d1ef907..c051d6fccb52e 100644 --- a/tests/mir-opt/building/async_await.b-{closure#0}.coroutine_resume.0.mir +++ b/tests/mir-opt/building/async_await.b-{closure#0}.coroutine_resume.0.mir @@ -21,6 +21,7 @@ scope: scope[0], }, ignore_for_traits: false, + pinned: false, }, _1: CoroutineSavedTy { ty: Coroutine( @@ -42,6 +43,7 @@ scope: scope[0], }, ignore_for_traits: false, + pinned: false, }, }, variant_fields: { diff --git a/tests/mir-opt/coroutine_pinned_fields.async_block-{closure#0}.coroutine_pre-elab.0.mir b/tests/mir-opt/coroutine_pinned_fields.async_block-{closure#0}.coroutine_pre-elab.0.mir new file mode 100644 index 0000000000000..04ca59b90ea2f --- /dev/null +++ b/tests/mir-opt/coroutine_pinned_fields.async_block-{closure#0}.coroutine_pre-elab.0.mir @@ -0,0 +1,421 @@ +// MIR for `async_block::{closure#0}` 0 coroutine_pre-elab +/* coroutine_layout = CoroutineLayout { + field_tys: { + _0: CoroutineSavedTy { + ty: i32, + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:49:13: 49:18 (#0), + scope: scope[0], + }, + ignore_for_traits: false, + pinned: true, + }, + _1: CoroutineSavedTy { + ty: &'{erased} mut i32, + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:50:13: 50:14 (#0), + scope: scope[1], + }, + ignore_for_traits: false, + pinned: false, + }, + _2: CoroutineSavedTy { + ty: Coroutine( + DefId(0:15 ~ coroutine_pinned_fields[f9b5]::nop::{closure#0}), + [ + (), + std::future::ResumeTy, + (), + (), + CoroutineWitness( + DefId(0:15 ~ coroutine_pinned_fields[f9b5]::nop::{closure#0}), + [], + ), + (), + ], + ), + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:51:9: 51:20 (#11), + scope: scope[2], + }, + ignore_for_traits: false, + pinned: false, + }, + _3: CoroutineSavedTy { + ty: Coroutine( + DefId(0:15 ~ coroutine_pinned_fields[f9b5]::nop::{closure#0}), + [ + (), + std::future::ResumeTy, + (), + (), + CoroutineWitness( + DefId(0:15 ~ coroutine_pinned_fields[f9b5]::nop::{closure#0}), + [], + ), + (), + ], + ), + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:53:9: 53:20 (#13), + scope: scope[2], + }, + ignore_for_traits: false, + pinned: false, + }, + }, + variant_fields: { + Unresumed(0): [], + Returned (1): [], + Panicked (2): [], + Suspend0 (3): [_0, _1, _2], + Suspend1 (4): [_0, _3], + }, + storage_conflicts: BitMatrix(4x4) { + (_0, _0), + (_0, _1), + (_0, _2), + (_0, _3), + (_1, _0), + (_1, _1), + (_1, _2), + (_1, _3), + (_2, _0), + (_2, _1), + (_2, _2), + (_3, _0), + (_3, _1), + (_3, _3), + }, +} */ + +fn async_block::{closure#0}(_1: {async block@$DIR/coroutine_pinned_fields.rs:48:5: 48:10}, _2: &mut Context<'_>) -> Poll { + debug _task_context => _41; + let mut _0: std::task::Poll; + let mut _3: i32; + let _5: (); + let mut _6: {async fn body of nop()}; + let mut _7: {async fn body of nop()}; + let mut _9: (); + let _10: (); + let mut _11: std::task::Poll<()>; + let mut _12: std::pin::Pin<&mut {async fn body of nop()}>; + let mut _13: &mut {async fn body of nop()}; + let mut _14: &mut {async fn body of nop()}; + let mut _15: &mut std::task::Context<'_>; + let mut _16: &mut std::task::Context<'_>; + let mut _17: &mut std::task::Context<'_>; + let mut _18: isize; + let mut _20: !; + let mut _21: &mut std::task::Context<'_>; + let mut _22: (); + let _23: (); + let mut _24: {async fn body of nop()}; + let mut _25: {async fn body of nop()}; + let _27: (); + let mut _28: std::task::Poll<()>; + let mut _29: std::pin::Pin<&mut {async fn body of nop()}>; + let mut _30: &mut {async fn body of nop()}; + let mut _31: &mut {async fn body of nop()}; + let mut _32: &mut std::task::Context<'_>; + let mut _33: &mut std::task::Context<'_>; + let mut _34: &mut std::task::Context<'_>; + let mut _35: isize; + let mut _37: !; + let mut _38: &mut std::task::Context<'_>; + let mut _39: (); + let mut _40: i32; + let mut _41: &mut std::task::Context<'_>; + scope 1 { + debug x => ((_1 as variant#4).0: i32); + let _4: &mut i32; + scope 2 { + debug y => ((_1 as variant#3).1: &mut i32); + let mut _8: {async fn body of nop()}; + let mut _26: {async fn body of nop()}; + scope 3 { + debug __awaitee => ((_1 as variant#3).2: {async fn body of nop()}); + let _19: (); + scope 4 { + debug result => _19; + } + } + scope 5 { + debug __awaitee => ((_1 as variant#4).1: {async fn body of nop()}); + let _36: (); + scope 6 { + debug result => _36; + } + } + } + } + + bb0: { + _41 = move _2; + nop; + ((_1 as variant#4).0: i32) = const 9_i32; + nop; + ((_1 as variant#3).1: &mut i32) = &mut ((_1 as variant#4).0: i32); + StorageLive(_5); + StorageLive(_6); + StorageLive(_7); + _7 = nop() -> [return: bb1, unwind unreachable]; + } + + bb1: { + _6 = <{async fn body of nop()} as IntoFuture>::into_future(move _7) -> [return: bb2, unwind unreachable]; + } + + bb2: { + StorageDead(_7); + PlaceMention(_6); + nop; + ((_1 as variant#3).2: {async fn body of nop()}) = move _6; + goto -> bb3; + } + + bb3: { + StorageLive(_10); + StorageLive(_11); + StorageLive(_12); + StorageLive(_13); + StorageLive(_14); + _14 = &mut ((_1 as variant#3).2: {async fn body of nop()}); + _13 = &mut (*_14); + _12 = Pin::<&mut {async fn body of nop()}>::new_unchecked(move _13) -> [return: bb4, unwind unreachable]; + } + + bb4: { + StorageDead(_13); + StorageLive(_15); + StorageLive(_16); + StorageLive(_17); + _17 = copy _41; + _16 = move _17; + goto -> bb5; + } + + bb5: { + _15 = &mut (*_16); + StorageDead(_17); + _11 = <{async fn body of nop()} as Future>::poll(move _12, move _15) -> [return: bb6, unwind unreachable]; + } + + bb6: { + StorageDead(_16); + StorageDead(_15); + StorageDead(_14); + StorageDead(_12); + PlaceMention(_11); + _18 = discriminant(_11); + switchInt(move _18) -> [0: bb9, 1: bb8, otherwise: bb7]; + } + + bb7: { + unreachable; + } + + bb8: { + _10 = const (); + StorageDead(_11); + StorageDead(_10); + StorageLive(_21); + StorageLive(_22); + _22 = (); + _0 = Poll::::Pending; + StorageDead(_5); + StorageDead(_6); + StorageDead(_21); + StorageDead(_22); + discriminant(_1) = 3; + return; + } + + bb9: { + StorageLive(_19); + _19 = copy ((_11 as Ready).0: ()); + _5 = copy _19; + StorageDead(_19); + StorageDead(_11); + StorageDead(_10); + drop(((_1 as variant#3).2: {async fn body of nop()})) -> [return: bb11, unwind unreachable]; + } + + bb10: { + StorageDead(_22); + _41 = move _21; + StorageDead(_21); + _9 = const (); + goto -> bb3; + } + + bb11: { + nop; + goto -> bb12; + } + + bb12: { + StorageDead(_6); + StorageDead(_5); + (*((_1 as variant#3).1: &mut i32)) = Add(copy (*((_1 as variant#3).1: &mut i32)), const 1_i32); + StorageLive(_23); + StorageLive(_24); + StorageLive(_25); + _25 = nop() -> [return: bb13, unwind unreachable]; + } + + bb13: { + _24 = <{async fn body of nop()} as IntoFuture>::into_future(move _25) -> [return: bb14, unwind unreachable]; + } + + bb14: { + StorageDead(_25); + PlaceMention(_24); + nop; + ((_1 as variant#4).1: {async fn body of nop()}) = move _24; + goto -> bb15; + } + + bb15: { + StorageLive(_27); + StorageLive(_28); + StorageLive(_29); + StorageLive(_30); + StorageLive(_31); + _31 = &mut ((_1 as variant#4).1: {async fn body of nop()}); + _30 = &mut (*_31); + _29 = Pin::<&mut {async fn body of nop()}>::new_unchecked(move _30) -> [return: bb16, unwind unreachable]; + } + + bb16: { + StorageDead(_30); + StorageLive(_32); + StorageLive(_33); + StorageLive(_34); + _34 = copy _41; + _33 = move _34; + goto -> bb17; + } + + bb17: { + _32 = &mut (*_33); + StorageDead(_34); + _28 = <{async fn body of nop()} as Future>::poll(move _29, move _32) -> [return: bb18, unwind unreachable]; + } + + bb18: { + StorageDead(_33); + StorageDead(_32); + StorageDead(_31); + StorageDead(_29); + PlaceMention(_28); + _35 = discriminant(_28); + switchInt(move _35) -> [0: bb20, 1: bb19, otherwise: bb7]; + } + + bb19: { + _27 = const (); + StorageDead(_28); + StorageDead(_27); + StorageLive(_38); + StorageLive(_39); + _39 = (); + _0 = Poll::::Pending; + StorageDead(_23); + StorageDead(_24); + StorageDead(_38); + StorageDead(_39); + discriminant(_1) = 4; + return; + } + + bb20: { + StorageLive(_36); + _36 = copy ((_28 as Ready).0: ()); + _23 = copy _36; + StorageDead(_36); + StorageDead(_28); + StorageDead(_27); + drop(((_1 as variant#4).1: {async fn body of nop()})) -> [return: bb22, unwind unreachable]; + } + + bb21: { + StorageDead(_39); + _41 = move _38; + StorageDead(_38); + _9 = const (); + goto -> bb15; + } + + bb22: { + nop; + goto -> bb23; + } + + bb23: { + StorageDead(_24); + StorageDead(_23); + _40 = copy ((_1 as variant#4).0: i32); + nop; + nop; + drop(_1) -> [return: bb24, unwind unreachable]; + } + + bb24: { + _0 = Poll::::Ready(move _40); + discriminant(_1) = 1; + return; + } + + bb25: { + StorageDead(_39); + StorageDead(_38); + drop(((_1 as variant#4).1: {async fn body of nop()})) -> [return: bb26, unwind unreachable]; + } + + bb26: { + nop; + goto -> bb27; + } + + bb27: { + StorageDead(_24); + StorageDead(_23); + goto -> bb31; + } + + bb28: { + StorageDead(_22); + StorageDead(_21); + drop(((_1 as variant#3).2: {async fn body of nop()})) -> [return: bb29, unwind unreachable]; + } + + bb29: { + nop; + goto -> bb30; + } + + bb30: { + StorageDead(_6); + StorageDead(_5); + goto -> bb31; + } + + bb31: { + nop; + nop; + drop(_1) -> [return: bb32, unwind unreachable]; + } + + bb32: { + coroutine_drop; + } + + bb33: { + return; + } + + bb34: { + drop(_1) -> [return: bb33, unwind continue]; + } +} diff --git a/tests/mir-opt/coroutine_pinned_fields.async_fn_borrow_not_used_after_yield-{closure#0}.coroutine_pre-elab.0.mir b/tests/mir-opt/coroutine_pinned_fields.async_fn_borrow_not_used_after_yield-{closure#0}.coroutine_pre-elab.0.mir new file mode 100644 index 0000000000000..1ec34d9595457 --- /dev/null +++ b/tests/mir-opt/coroutine_pinned_fields.async_fn_borrow_not_used_after_yield-{closure#0}.coroutine_pre-elab.0.mir @@ -0,0 +1,267 @@ +// MIR for `async_fn_borrow_not_used_after_yield::{closure#0}` 0 coroutine_pre-elab +/* coroutine_layout = CoroutineLayout { + field_tys: { + _0: CoroutineSavedTy { + ty: std::string::String, + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:60:9: 60:15 (#0), + scope: scope[0], + }, + ignore_for_traits: false, + pinned: false, + }, + _1: CoroutineSavedTy { + ty: Coroutine( + DefId(0:15 ~ coroutine_pinned_fields[f9b5]::nop::{closure#0}), + [ + (), + std::future::ResumeTy, + (), + (), + CoroutineWitness( + DefId(0:15 ~ coroutine_pinned_fields[f9b5]::nop::{closure#0}), + [], + ), + (), + ], + ), + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:64:5: 64:16 (#17), + scope: scope[2], + }, + ignore_for_traits: false, + pinned: false, + }, + }, + variant_fields: { + Unresumed(0): [], + Returned (1): [], + Panicked (2): [], + Suspend0 (3): [_0, _1], + }, + storage_conflicts: BitMatrix(2x2) { + (_0, _0), + (_0, _1), + (_1, _0), + (_1, _1), + }, +} */ + +fn async_fn_borrow_not_used_after_yield::{closure#0}(_1: {async fn body of async_fn_borrow_not_used_after_yield()}, _2: &mut Context<'_>) -> Poll<()> { + debug _task_context => _27; + let mut _0: std::task::Poll<()>; + let _3: std::string::String; + let mut _5: &std::string::String; + let _6: (); + let mut _7: &str; + let _8: (); + let mut _9: {async fn body of nop()}; + let mut _10: {async fn body of nop()}; + let mut _12: (); + let _13: (); + let mut _14: std::task::Poll<()>; + let mut _15: std::pin::Pin<&mut {async fn body of nop()}>; + let mut _16: &mut {async fn body of nop()}; + let mut _17: &mut {async fn body of nop()}; + let mut _18: &mut std::task::Context<'_>; + let mut _19: &mut std::task::Context<'_>; + let mut _20: &mut std::task::Context<'_>; + let mut _21: isize; + let mut _23: !; + let mut _24: &mut std::task::Context<'_>; + let mut _25: (); + let mut _26: (); + let mut _27: &mut std::task::Context<'_>; + scope 1 { + debug string => ((_1 as variant#3).0: std::string::String); + let _4: &str; + scope 2 { + debug str => _4; + let mut _11: {async fn body of nop()}; + scope 3 { + debug __awaitee => ((_1 as variant#3).1: {async fn body of nop()}); + let _22: (); + scope 4 { + debug result => _22; + } + } + } + } + + bb0: { + _27 = move _2; + nop; + ((_1 as variant#3).0: std::string::String) = >::from(const "abc123") -> [return: bb1, unwind unreachable]; + } + + bb1: { + StorageLive(_4); + StorageLive(_5); + _5 = &((_1 as variant#3).0: std::string::String); + _4 = String::as_str(move _5) -> [return: bb2, unwind unreachable]; + } + + bb2: { + StorageDead(_5); + StorageLive(_6); + StorageLive(_7); + _7 = copy _4; + _6 = do_thing::<&str>(move _7) -> [return: bb3, unwind unreachable]; + } + + bb3: { + StorageDead(_7); + StorageDead(_6); + StorageLive(_8); + StorageLive(_9); + StorageLive(_10); + _10 = nop() -> [return: bb4, unwind unreachable]; + } + + bb4: { + _9 = <{async fn body of nop()} as IntoFuture>::into_future(move _10) -> [return: bb5, unwind unreachable]; + } + + bb5: { + StorageDead(_10); + PlaceMention(_9); + nop; + ((_1 as variant#3).1: {async fn body of nop()}) = move _9; + goto -> bb6; + } + + bb6: { + StorageLive(_13); + StorageLive(_14); + StorageLive(_15); + StorageLive(_16); + StorageLive(_17); + _17 = &mut ((_1 as variant#3).1: {async fn body of nop()}); + _16 = &mut (*_17); + _15 = Pin::<&mut {async fn body of nop()}>::new_unchecked(move _16) -> [return: bb7, unwind unreachable]; + } + + bb7: { + StorageDead(_16); + StorageLive(_18); + StorageLive(_19); + StorageLive(_20); + _20 = copy _27; + _19 = move _20; + goto -> bb8; + } + + bb8: { + _18 = &mut (*_19); + StorageDead(_20); + _14 = <{async fn body of nop()} as Future>::poll(move _15, move _18) -> [return: bb9, unwind unreachable]; + } + + bb9: { + StorageDead(_19); + StorageDead(_18); + StorageDead(_17); + StorageDead(_15); + PlaceMention(_14); + _21 = discriminant(_14); + switchInt(move _21) -> [0: bb12, 1: bb11, otherwise: bb10]; + } + + bb10: { + unreachable; + } + + bb11: { + _13 = const (); + StorageDead(_14); + StorageDead(_13); + StorageLive(_24); + StorageLive(_25); + _25 = (); + _0 = Poll::<()>::Pending; + StorageDead(_4); + StorageDead(_8); + StorageDead(_9); + StorageDead(_24); + StorageDead(_25); + discriminant(_1) = 3; + return; + } + + bb12: { + StorageLive(_22); + _22 = copy ((_14 as Ready).0: ()); + _8 = copy _22; + StorageDead(_22); + StorageDead(_14); + StorageDead(_13); + drop(((_1 as variant#3).1: {async fn body of nop()})) -> [return: bb14, unwind unreachable]; + } + + bb13: { + StorageDead(_25); + _27 = move _24; + StorageDead(_24); + _12 = const (); + goto -> bb6; + } + + bb14: { + nop; + goto -> bb15; + } + + bb15: { + StorageDead(_9); + StorageDead(_8); + _26 = const (); + StorageDead(_4); + drop(((_1 as variant#3).0: std::string::String)) -> [return: bb16, unwind unreachable]; + } + + bb16: { + nop; + drop(_1) -> [return: bb17, unwind unreachable]; + } + + bb17: { + _0 = Poll::<()>::Ready(move _26); + discriminant(_1) = 1; + return; + } + + bb18: { + StorageDead(_25); + StorageDead(_24); + drop(((_1 as variant#3).1: {async fn body of nop()})) -> [return: bb19, unwind unreachable]; + } + + bb19: { + nop; + goto -> bb20; + } + + bb20: { + StorageDead(_9); + StorageDead(_8); + StorageDead(_4); + drop(((_1 as variant#3).0: std::string::String)) -> [return: bb21, unwind unreachable]; + } + + bb21: { + nop; + drop(_1) -> [return: bb22, unwind unreachable]; + } + + bb22: { + coroutine_drop; + } + + bb23: { + return; + } + + bb24: { + drop(_1) -> [return: bb23, unwind continue]; + } +} diff --git a/tests/mir-opt/coroutine_pinned_fields.borrow_not_held_across_yield-{closure#0}.coroutine_pre-elab.0.mir b/tests/mir-opt/coroutine_pinned_fields.borrow_not_held_across_yield-{closure#0}.coroutine_pre-elab.0.mir new file mode 100644 index 0000000000000..69f636e08d4fe --- /dev/null +++ b/tests/mir-opt/coroutine_pinned_fields.borrow_not_held_across_yield-{closure#0}.coroutine_pre-elab.0.mir @@ -0,0 +1,94 @@ +// MIR for `borrow_not_held_across_yield::{closure#0}` 0 coroutine_pre-elab +/* coroutine_layout = CoroutineLayout { + field_tys: { + _0: CoroutineSavedTy { + ty: i32, + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:34:13: 34:18 (#0), + scope: scope[0], + }, + ignore_for_traits: false, + pinned: false, + }, + }, + variant_fields: { + Unresumed(0): [], + Returned (1): [], + Panicked (2): [], + Suspend0 (3): [_0], + }, + storage_conflicts: BitMatrix(1x1) { + (_0, _0), + }, +} */ + +fn borrow_not_held_across_yield::{closure#0}(_1: {static coroutine@$DIR/coroutine_pinned_fields.rs:32:5: 32:14}, _2: ()) -> CoroutineState<(), i32> { + let mut _0: std::ops::CoroutineState<(), i32>; + let mut _3: i32; + let _4: (); + let _6: (); + let mut _7: (); + let mut _8: i32; + let mut _9: (); + scope 1 { + debug x => ((_1 as variant#3).0: i32); + let _5: &mut i32; + scope 2 { + debug y => _5; + } + } + + bb0: { + _9 = move _2; + nop; + ((_1 as variant#3).0: i32) = const 9_i32; + StorageLive(_4); + StorageLive(_5); + _5 = &mut ((_1 as variant#3).0: i32); + (*_5) = Add(copy (*_5), const 5_i32); + _4 = const (); + StorageDead(_5); + StorageDead(_4); + StorageLive(_6); + StorageLive(_7); + _7 = (); + _0 = CoroutineState::<(), i32>::Yielded(move _7); + StorageDead(_6); + StorageDead(_7); + discriminant(_1) = 3; + return; + } + + bb1: { + StorageDead(_7); + StorageDead(_6); + _8 = copy ((_1 as variant#3).0: i32); + nop; + drop(_1) -> [return: bb2, unwind unreachable]; + } + + bb2: { + _0 = CoroutineState::<(), i32>::Complete(move _8); + discriminant(_1) = 1; + return; + } + + bb3: { + StorageDead(_7); + StorageDead(_6); + nop; + drop(_1) -> [return: bb4, unwind unreachable]; + } + + bb4: { + coroutine_drop; + } + + bb5: { + return; + } + + bb6: { + drop(_1) -> [return: bb5, unwind continue]; + } +} diff --git a/tests/mir-opt/coroutine_pinned_fields.movable_is_never_pinned-{closure#0}.coroutine_pre-elab.0.mir b/tests/mir-opt/coroutine_pinned_fields.movable_is_never_pinned-{closure#0}.coroutine_pre-elab.0.mir new file mode 100644 index 0000000000000..c7f89ff827738 --- /dev/null +++ b/tests/mir-opt/coroutine_pinned_fields.movable_is_never_pinned-{closure#0}.coroutine_pre-elab.0.mir @@ -0,0 +1,109 @@ +// MIR for `movable_is_never_pinned::{closure#0}` 0 coroutine_pre-elab +/* coroutine_layout = CoroutineLayout { + field_tys: { + _0: CoroutineSavedTy { + ty: i32, + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:71:13: 71:20 (#0), + scope: scope[0], + }, + ignore_for_traits: false, + pinned: false, + }, + }, + variant_fields: { + Unresumed(0): [], + Returned (1): [], + Panicked (2): [], + Suspend0 (3): [_0], + Suspend1 (4): [_0], + }, + storage_conflicts: BitMatrix(1x1) { + (_0, _0), + }, +} */ + +fn movable_is_never_pinned::{closure#0}(_1: {coroutine@$DIR/coroutine_pinned_fields.rs:70:5: 70:7}, _2: ()) -> CoroutineState<(), i32> { + let mut _0: std::ops::CoroutineState<(), i32>; + let mut _3: i32; + let _4: (); + let mut _5: (); + let _6: (); + let mut _7: (); + let mut _8: i32; + let mut _9: (); + scope 1 { + debug bar => ((_1 as variant#4).0: i32); + } + + bb0: { + _9 = move _2; + nop; + ((_1 as variant#4).0: i32) = const 29_i32; + StorageLive(_4); + StorageLive(_5); + _5 = (); + _0 = CoroutineState::<(), i32>::Yielded(move _5); + StorageDead(_4); + StorageDead(_5); + discriminant(_1) = 3; + return; + } + + bb1: { + StorageDead(_5); + StorageDead(_4); + ((_1 as variant#4).0: i32) = Add(copy ((_1 as variant#4).0: i32), const 2_i32); + StorageLive(_6); + StorageLive(_7); + _7 = (); + _0 = CoroutineState::<(), i32>::Yielded(move _7); + StorageDead(_6); + StorageDead(_7); + discriminant(_1) = 4; + return; + } + + bb2: { + StorageDead(_7); + StorageDead(_6); + _8 = copy ((_1 as variant#4).0: i32); + nop; + drop(_1) -> [return: bb3, unwind unreachable]; + } + + bb3: { + _0 = CoroutineState::<(), i32>::Complete(move _8); + discriminant(_1) = 1; + return; + } + + bb4: { + StorageDead(_7); + StorageDead(_6); + goto -> bb6; + } + + bb5: { + StorageDead(_5); + StorageDead(_4); + goto -> bb6; + } + + bb6: { + nop; + drop(_1) -> [return: bb7, unwind unreachable]; + } + + bb7: { + coroutine_drop; + } + + bb8: { + return; + } + + bb9: { + drop(_1) -> [return: bb8, unwind continue]; + } +} diff --git a/tests/mir-opt/coroutine_pinned_fields.rs b/tests/mir-opt/coroutine_pinned_fields.rs new file mode 100644 index 0000000000000..20e50f72f7a80 --- /dev/null +++ b/tests/mir-opt/coroutine_pinned_fields.rs @@ -0,0 +1,77 @@ +// skip-filecheck + +//! Ensures pinned coroutine fields are marked correctly + +//@ compile-flags: -C panic=abort +//@ edition: 2024 + +#![crate_type = "lib"] +#![feature(coroutines, coroutine_trait, stmt_expr_attributes)] + +use std::future::Future; +use std::ops::Coroutine; + +fn do_thing(_: T) {} + +// EMIT_MIR coroutine_pinned_fields.use_borrow_across_yield-{closure#0}.coroutine_pre-elab.0.mir +fn use_borrow_across_yield() -> impl Coroutine { + #[coroutine] + static || { + let mut a = 19; // pinned + let b = &mut a; // not pinned + yield; + *b = 23; + yield; + a + } +} + +// EMIT_MIR coroutine_pinned_fields.borrow_not_held_across_yield-{closure#0}.coroutine_pre-elab.0.mir +fn borrow_not_held_across_yield() -> impl Coroutine { + #[coroutine] + static || { + // NOTE: unfortunately, this field is currently marked as pinned even though it shouldn't be + let mut x = 9; // not pinned + { + let y = &mut x; // not stored + *y += 5; + } + yield; + x + } +} + +async fn nop() {} + +// EMIT_MIR coroutine_pinned_fields.async_block-{closure#0}.coroutine_pre-elab.0.mir +fn async_block() -> impl Future { + async { + let mut x = 9; // pinned + let y = &mut x; // not pinned + nop().await; + *y += 1; + nop().await; + x + } +} + +// EMIT_MIR coroutine_pinned_fields.async_fn_borrow_not_used_after_yield-{closure#0}.coroutine_pre-elab.0.mir +async fn async_fn_borrow_not_used_after_yield() { + let string = String::from("abc123"); + let str = string.as_str(); + do_thing(str); + + nop().await; +} + +// EMIT_MIR coroutine_pinned_fields.movable_is_never_pinned-{closure#0}.coroutine_pre-elab.0.mir +fn movable_is_never_pinned() -> impl Coroutine { + #[coroutine] + || { + let mut bar = 29; + yield; + bar += 2; + yield; + bar + } +} diff --git a/tests/mir-opt/coroutine_pinned_fields.use_borrow_across_yield-{closure#0}.coroutine_pre-elab.0.mir b/tests/mir-opt/coroutine_pinned_fields.use_borrow_across_yield-{closure#0}.coroutine_pre-elab.0.mir new file mode 100644 index 0000000000000..e207c0c7faebc --- /dev/null +++ b/tests/mir-opt/coroutine_pinned_fields.use_borrow_across_yield-{closure#0}.coroutine_pre-elab.0.mir @@ -0,0 +1,129 @@ +// MIR for `use_borrow_across_yield::{closure#0}` 0 coroutine_pre-elab +/* coroutine_layout = CoroutineLayout { + field_tys: { + _0: CoroutineSavedTy { + ty: i32, + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:20:13: 20:18 (#0), + scope: scope[0], + }, + ignore_for_traits: false, + pinned: true, + }, + _1: CoroutineSavedTy { + ty: &'{erased} mut i32, + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:21:13: 21:14 (#0), + scope: scope[1], + }, + ignore_for_traits: false, + pinned: false, + }, + }, + variant_fields: { + Unresumed(0): [], + Returned (1): [], + Panicked (2): [], + Suspend0 (3): [_0, _1], + Suspend1 (4): [_0], + }, + storage_conflicts: BitMatrix(2x2) { + (_0, _0), + (_0, _1), + (_1, _0), + (_1, _1), + }, +} */ + +fn use_borrow_across_yield::{closure#0}(_1: {static coroutine@$DIR/coroutine_pinned_fields.rs:19:5: 19:14}, _2: ()) -> CoroutineState<(), i32> { + let mut _0: std::ops::CoroutineState<(), i32>; + let mut _3: i32; + let _5: (); + let mut _6: (); + let _7: (); + let mut _8: (); + let mut _9: i32; + let mut _10: (); + scope 1 { + debug a => ((_1 as variant#4).0: i32); + let _4: &mut i32; + scope 2 { + debug b => ((_1 as variant#3).1: &mut i32); + } + } + + bb0: { + _10 = move _2; + nop; + ((_1 as variant#4).0: i32) = const 19_i32; + nop; + ((_1 as variant#3).1: &mut i32) = &mut ((_1 as variant#4).0: i32); + StorageLive(_5); + StorageLive(_6); + _6 = (); + _0 = CoroutineState::<(), i32>::Yielded(move _6); + StorageDead(_5); + StorageDead(_6); + discriminant(_1) = 3; + return; + } + + bb1: { + StorageDead(_6); + StorageDead(_5); + (*((_1 as variant#3).1: &mut i32)) = const 23_i32; + StorageLive(_7); + StorageLive(_8); + _8 = (); + _0 = CoroutineState::<(), i32>::Yielded(move _8); + StorageDead(_7); + StorageDead(_8); + discriminant(_1) = 4; + return; + } + + bb2: { + StorageDead(_8); + StorageDead(_7); + _9 = copy ((_1 as variant#4).0: i32); + nop; + nop; + drop(_1) -> [return: bb3, unwind unreachable]; + } + + bb3: { + _0 = CoroutineState::<(), i32>::Complete(move _9); + discriminant(_1) = 1; + return; + } + + bb4: { + StorageDead(_8); + StorageDead(_7); + goto -> bb6; + } + + bb5: { + StorageDead(_6); + StorageDead(_5); + goto -> bb6; + } + + bb6: { + nop; + nop; + drop(_1) -> [return: bb7, unwind unreachable]; + } + + bb7: { + coroutine_drop; + } + + bb8: { + return; + } + + bb9: { + drop(_1) -> [return: bb8, unwind continue]; + } +} diff --git a/tests/mir-opt/coroutine_tiny.main-{closure#0}.coroutine_resume.0.mir b/tests/mir-opt/coroutine_tiny.main-{closure#0}.coroutine_resume.0.mir index f8b3f68d21e63..0d8f383e4a566 100644 --- a/tests/mir-opt/coroutine_tiny.main-{closure#0}.coroutine_resume.0.mir +++ b/tests/mir-opt/coroutine_tiny.main-{closure#0}.coroutine_resume.0.mir @@ -8,6 +8,7 @@ scope: scope[0], }, ignore_for_traits: false, + pinned: false, }, }, variant_fields: { diff --git a/tests/ui/coroutine/pin-fields-borrowed-across-suspensions.rs b/tests/ui/coroutine/pin-fields-borrowed-across-suspensions.rs new file mode 100644 index 0000000000000..eb40cc7d8714e --- /dev/null +++ b/tests/ui/coroutine/pin-fields-borrowed-across-suspensions.rs @@ -0,0 +1,29 @@ +//@ run-pass + +#![feature(coroutines)] +#![feature(coroutine_trait)] +#![feature(stmt_expr_attributes)] + +use std::ops::{Coroutine, CoroutineState}; +use std::pin::pin; + +fn coro() { + let c = #[coroutine] + static || { + let mut a = 19; + let b = &mut a; + yield; + *b = 23; + yield; + a + }; + + let mut c = pin!(c); + assert_eq!(c.as_mut().resume(()), CoroutineState::Yielded(())); + assert_eq!(c.as_mut().resume(()), CoroutineState::Yielded(())); + assert_eq!(c.as_mut().resume(()), CoroutineState::Complete(23)); +} + +fn main() { + coro(); +}