UnsafePinned: implement coroutine lowering changes#152946
Conversation
|
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt |
|
r? @mati865 rustbot has assigned @mati865. Use Why was this reviewer chosen?The reviewer was selected based on:
|
This comment has been minimized.
This comment has been minimized.
50482bd to
b226e46
Compare
…rk-Simulacrum extend unpin noalias tests to cover mutable references rust-lang#152946 made a change to the logic for this attribute that the test should have flagged as problematic -- but the test only checked `Box`, not `&mut`, and those have independent code paths. So extend the test to also cover `&mut`. @b-naber would be nice if you could confirm that the added tests do fail with your PR.
Rollup merge of #152970 - RalfJung:unpin-noalias-tests, r=Mark-Simulacrum extend unpin noalias tests to cover mutable references #152946 made a change to the logic for this attribute that the test should have flagged as problematic -- but the test only checked `Box`, not `&mut`, and those have independent code paths. So extend the test to also cover `&mut`. @b-naber would be nice if you could confirm that the added tests do fail with your PR.
|
I'm not a good fit for this code, perhaps r? @RalfJung ? Feel free to reroll |
|
|
|
Definitely not, I don't know the coroutine lowering code at all. @rustbot reroll |
|
@cjgillot Could you maybe review this? |
b226e46 to
d8b3c3d
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
This comment has been minimized.
This comment has been minimized.
d8b3c3d to
3d40415
Compare
…crum extend unpin noalias tests to cover mutable references rust-lang/rust#152946 made a change to the logic for this attribute that the test should have flagged as problematic -- but the test only checked `Box`, not `&mut`, and those have independent code paths. So extend the test to also cover `&mut`. @b-naber would be nice if you could confirm that the added tests do fail with your PR.
|
@rustbot author |
This comment has been minimized.
This comment has been minimized.
|
That test failing is a great sign! Quoting from the test description |
|
The Miri subtree was changed cc @rust-lang/miri |
| //! FIXME: This test should pass! However, `async fn` does not yet use `UnsafePinned`. | ||
| //! This is a regression test for <https://github.com/rust-lang/rust/issues/137750>: | ||
| //! `UnsafePinned` must include the effects of `UnsafeCell`. | ||
| // run-pass |
There was a problem hiding this comment.
This comment does nothing, please remove it. (Why did you even add this? Even in the rustc ui test suite this would do nothing. It's spelled // @run-pass there. But the Miri test suite is a little different.)
There was a problem hiding this comment.
There's another test that uses this: https://github.com/rust-lang/rust/blob/main/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs, which is why I used this.
There was a problem hiding this comment.
Ah, not sure how that snuck in, thanks for pointing that out. But you'll notice most pass tests don't have such an annotation.
3733432 to
4998cf2
Compare
This comment has been minimized.
This comment has been minimized.
|
Some changes occurred in src/tools/clippy cc @rust-lang/clippy |
|
@rustbot ready |
This comment has been minimized.
This comment has been minimized.
f5e7edd to
3f2a21b
Compare
This comment has been minimized.
This comment has been minimized.
3f2a21b to
8129bdb
Compare
This comment has been minimized.
This comment has been minimized.
8129bdb to
4df37a6
Compare
|
|
||
| borrowed_locals_cursor2.seek_before_primary_effect(loc); | ||
| let borrowed_locals = borrowed_locals_cursor2.get(); | ||
| locals_live_due_to_borrow.union(borrowed_locals); |
There was a problem hiding this comment.
Wouldn't it make sense to update locals_live_due_to_borrow inside !movable? As the movable case can't have self-referential fields.
|
I am not making much sense of |
| let mut projection = base.projection.to_vec(); | ||
| // self.field (UnsafePinned<T>) | ||
| projection.push(ProjectionElem::Field(idx, ty)); | ||
| // value (UnsafeCell<T>) | ||
| projection.push(ProjectionElem::Field(FieldIdx::from_u32(0), unsafe_cell_ty)); | ||
| // value (T) | ||
| projection.push(ProjectionElem::Field(FieldIdx::from_u32(0), original_ty)); | ||
|
|
||
| let place = Place { local: base.local, projection: self.tcx.mk_place_elems(&projection) }; |
There was a problem hiding this comment.
Let's only intern one set of projections.
| let mut projection = base.projection.to_vec(); | |
| // self.field (UnsafePinned<T>) | |
| projection.push(ProjectionElem::Field(idx, ty)); | |
| // value (UnsafeCell<T>) | |
| projection.push(ProjectionElem::Field(FieldIdx::from_u32(0), unsafe_cell_ty)); | |
| // value (T) | |
| projection.push(ProjectionElem::Field(FieldIdx::from_u32(0), original_ty)); | |
| let place = Place { local: base.local, projection: self.tcx.mk_place_elems(&projection) }; | |
| let place = Place { | |
| local: SELF_ARG, | |
| projection: self.tcx.mk_place_elems(&[ | |
| // self as variant | |
| ProjectionElem::Variant(None, variant_index), | |
| // self.field (UnsafePinned<T>) | |
| ProjectionElem::Field(idx, ty), | |
| // value (UnsafeCell<T>) | |
| ProjectionElem::Field(FieldIdx::from_u32(0), unsafe_cell_ty), | |
| // value (T) | |
| ProjectionElem::Field(FieldIdx::from_u32(0), original_ty), | |
| ]), | |
| }; |
| storage_conflicts, | ||
| storage_liveness: storage_liveness_map, | ||
| }, | ||
| self_referential_fields, |
There was a problem hiding this comment.
Why not make this a field inside LivenessInfo?
|
|
||
| // Every Local that is live due to an outstanding borrow is a self-referential field of | ||
| // the coroutine struct. | ||
| let mut locals_live_due_to_borrow = DenseBitSet::new_empty(body.local_decls.len()); |
There was a problem hiding this comment.
Why not call it self_referential_fields directly?
| struct PinnedLocalsFinder { | ||
| pin_fn: DefId, | ||
| pinned_locals: Vec<(Local, Location)>, | ||
| } | ||
|
|
||
| impl PinnedLocalsFinder { | ||
| fn new(pin_fn: DefId) -> Self { | ||
| PinnedLocalsFinder { pin_fn, pinned_locals: vec![] } | ||
| } | ||
| } | ||
|
|
||
| impl<'tcx> Visitor<'tcx> for PinnedLocalsFinder { | ||
| fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { | ||
| match &terminator.kind { | ||
| TerminatorKind::Call { func, args, .. } => { | ||
| if let Some((fn_def, _)) = func.const_fn_def() { | ||
| if fn_def == self.pin_fn { | ||
| assert!(args.len() == 1); | ||
| match args[0].node { | ||
| Operand::Move(pinned_place) => { | ||
| self.pinned_locals.push((pinned_place.local, location)); | ||
| } | ||
| _ => bug!("expected Operand::Move for argument to Pin::new_unchecked"), | ||
| } | ||
| } | ||
| } | ||
| } | ||
| _ => {} | ||
| } | ||
| self.super_terminator(terminator, location); | ||
| } | ||
| } |
There was a problem hiding this comment.
A loop over basic blocks inside find_locals_needing_unsafe_pinned will be more concise and easier to understand.
| Operand::Move(pinned_place) => { | ||
| self.pinned_locals.push((pinned_place.local, location)); | ||
| } | ||
| _ => bug!("expected Operand::Move for argument to Pin::new_unchecked"), |
There was a problem hiding this comment.
Please avoid asserting too much about what happens to MIR before this pass. The operand could get demoted to a Copy and it will work just as well. And we could get a constant (why not), and this method would do nothing.
| } | ||
|
|
||
| /// Finds all `Local`s that need to be wrapped in `UnsafePinned`. These are all self-referential | ||
| /// fields of the coroutine struct which aren't pinned. |
There was a problem hiding this comment.
Do you mind expanding a bit? What kind of MIR pattern are you looking for?
| for &pred in body.basic_blocks.predecessors()[bb].iter() { | ||
| let pred_last_stmt_idx = body.basic_blocks[pred].statements.len(); | ||
| worklist.push((pred, pred_last_stmt_idx, tracked_local)); | ||
| } |
There was a problem hiding this comment.
What if this block has 2 predecessors, each borrowing a different local?
|
|
||
| let pinned_targets = | ||
| find_pinned_self_referential_fields(body, &pinned_locals[..], &self_referential_fields); | ||
| debug!(?pinned_targets); |
There was a problem hiding this comment.
IIUC, pinned_targets is the set of locals l for which we find a Pin::new_unchecked(&mut l) in MIR.
What if such l is directly accessed in another part of MIR?
View all comments
Implements the lowering part for #125735
This introduces a visitor that looks for self-referential fields in the coroutine struct and wraps all of these types into UnsafePinned. I've also included a commit where we change the condition for using noalias. That change is based on my understanding of the RFC, but is likely wrong given the change in #151365. I've included it for now to further discuss this.
cc @RalfJung