From 03ef470792d9ae15f7c3334e836bc6d498dc65a5 Mon Sep 17 00:00:00 2001 From: Sky Date: Wed, 16 Apr 2025 02:55:25 -0400 Subject: [PATCH] Initial `UnsafePinned` impl [Part 2: Lowering] --- compiler/rustc_ast_ir/src/lib.rs | 6 +- .../src/diagnostics/conflict_errors.rs | 2 +- compiler/rustc_middle/src/mir/query.rs | 3 + compiler/rustc_middle/src/ty/context.rs | 5 + compiler/rustc_middle/src/ty/util.rs | 7 + compiler/rustc_mir_transform/src/coroutine.rs | 30 +- .../src/solve/trait_goals.rs | 21 +- .../src/traits/select/candidate_assembly.rs | 13 + compiler/rustc_type_ir/src/interner.rs | 2 + compiler/rustc_type_ir/src/lang_items.rs | 1 + ...await.b-{closure#0}.coroutine_resume.0.mir | 2 + ...block-{closure#0}.coroutine_pre-elab.0.mir | 421 ++++++++++++++++++ ...yield-{closure#0}.coroutine_pre-elab.0.mir | 94 ++++ ...inned-{closure#0}.coroutine_pre-elab.0.mir | 109 +++++ tests/mir-opt/coroutine_pinned_fields.rs | 66 +++ ...yield-{closure#0}.coroutine_pre-elab.0.mir | 129 ++++++ ...ny.main-{closure#0}.coroutine_resume.0.mir | 1 + .../pin-fields-borrowed-across-suspensions.rs | 29 ++ 18 files changed, 932 insertions(+), 9 deletions(-) create mode 100644 tests/mir-opt/coroutine_pinned_fields.async_block-{closure#0}.coroutine_pre-elab.0.mir create mode 100644 tests/mir-opt/coroutine_pinned_fields.borrow_not_held_across_yield-{closure#0}.coroutine_pre-elab.0.mir create mode 100644 tests/mir-opt/coroutine_pinned_fields.movable_is_never_pinned-{closure#0}.coroutine_pre-elab.0.mir create mode 100644 tests/mir-opt/coroutine_pinned_fields.rs create mode 100644 tests/mir-opt/coroutine_pinned_fields.use_borrow_across_yield-{closure#0}.coroutine_pre-elab.0.mir create mode 100644 tests/ui/coroutine/pin-fields-borrowed-across-suspensions.rs 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_transform/src/coroutine.rs b/compiler/rustc_mir_transform/src/coroutine.rs index 92d5e152f58e6..e2ccf1220c7f7 100644 --- a/compiler/rustc_mir_transform/src/coroutine.rs +++ b/compiler/rustc_mir_transform/src/coroutine.rs @@ -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. @@ -690,6 +699,8 @@ fn locals_live_across_suspend_points<'tcx>( 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 locals_borrowed_across_any_suspension_point = + DenseBitSet::new_empty(body.local_decls.len()); for (block, data) in body.basic_blocks.iter_enumerated() { if let TerminatorKind::Yield { .. } = data.terminator().kind { @@ -711,6 +722,7 @@ fn locals_live_across_suspend_points<'tcx>( // of the local, which happens using the `intersect` operation below. borrowed_locals_cursor.seek_before_primary_effect(loc); live_locals.union(borrowed_locals_cursor.get()); + locals_borrowed_across_any_suspension_point.union(borrowed_locals_cursor.get()); } // Store the storage liveness for later use so we can restore the state @@ -726,6 +738,7 @@ fn locals_live_across_suspend_points<'tcx>( // The coroutine argument is ignored. live_locals.remove(SELF_ARG); + locals_borrowed_across_any_suspension_point.remove(SELF_ARG); debug!("loc = {:?}, live_locals = {:?}", loc, live_locals); @@ -741,6 +754,8 @@ fn locals_live_across_suspend_points<'tcx>( debug!("live_locals_anywhere = {:?}", live_locals_at_any_suspension_point); let saved_locals = CoroutineSavedLocals(live_locals_at_any_suspension_point); + debug!("borrowed_locals = {:?}", locals_borrowed_across_any_suspension_point); + // Renumber our liveness_map bitsets to include only the locals we are // saving. let live_locals_at_suspension_points = live_locals_at_suspension_points @@ -748,6 +763,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(&locals_borrowed_across_any_suspension_point); + let storage_conflicts = compute_storage_conflicts( body, &saved_locals, @@ -759,6 +777,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 +950,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 +980,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..736765540bb9d 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: true, }, _1: CoroutineSavedTy { ty: Coroutine( @@ -42,6 +43,7 @@ scope: scope[0], }, ignore_for_traits: false, + pinned: true, }, }, 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..1dedd36599c2f --- /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:47:13: 47: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:48:13: 48:14 (#0), + scope: scope[1], + }, + ignore_for_traits: false, + pinned: false, + }, + _2: CoroutineSavedTy { + ty: Coroutine( + DefId(0:13 ~ coroutine_pinned_fields[f9b5]::nop::{closure#0}), + [ + (), + std::future::ResumeTy, + (), + (), + CoroutineWitness( + DefId(0:13 ~ coroutine_pinned_fields[f9b5]::nop::{closure#0}), + [], + ), + (), + ], + ), + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:49:9: 49:20 (#11), + scope: scope[2], + }, + ignore_for_traits: false, + pinned: true, + }, + _3: CoroutineSavedTy { + ty: Coroutine( + DefId(0:13 ~ coroutine_pinned_fields[f9b5]::nop::{closure#0}), + [ + (), + std::future::ResumeTy, + (), + (), + CoroutineWitness( + DefId(0:13 ~ coroutine_pinned_fields[f9b5]::nop::{closure#0}), + [], + ), + (), + ], + ), + source_info: SourceInfo { + span: $DIR/coroutine_pinned_fields.rs:51:9: 51:20 (#13), + scope: scope[2], + }, + ignore_for_traits: false, + pinned: true, + }, + }, + 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:46:5: 46: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.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..9d4be12c97cff --- /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:32:13: 32:18 (#0), + scope: scope[0], + }, + ignore_for_traits: false, + pinned: true, + }, + }, + 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:30:5: 30: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..c5bd413eba0d5 --- /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:60:13: 60: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:59:5: 59: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..bc86c25041d05 --- /dev/null +++ b/tests/mir-opt/coroutine_pinned_fields.rs @@ -0,0 +1,66 @@ +// 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; + +// 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.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..fed2b979a69c5 --- /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:18:13: 18: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:19:13: 19: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:17:5: 17: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(); +}