-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Treat Drop as a rmw operation #107271
Treat Drop as a rmw operation #107271
Conversation
r? @davidtwco (rustbot has picked a reviewer for you, use r? to override) |
c38da15
to
49e68f1
Compare
r? @oli-obk |
@@ -390,10 +391,6 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { | |||
self.create_move_path(place); | |||
self.gather_init(place.as_ref(), InitKind::Deep); | |||
} | |||
|
|||
TerminatorKind::Drop { place, target: _, unwind: _ } => { | |||
self.gather_move(place); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it suffice to just do the first part here (create_move_path
) instead of having drop_flag_effects_for_location
to redo this work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand what you mean. This builder basically records moves and inits, and Drop
is neither of them.
drop_flag_effects_for_location
(which may be renamed to initialization_effects
) now complements this information with Drop
s.
What would creating a move path gain us?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I managed to confuse myself a bit. You're right. It's just a bit scary how far apart the initialization tracking and move checking are.
So the next change would be to change DropAndReplace
to stop moving out of the dropped value. The only operation we need to do in the move path builder is to gather the operand with which we are replacing the existing value. There's not even a need to handle this case in initialization tracking logic, as it will be initialized right after. Or do you plan to just remove DropAndReplace
in favor of a Drop
terminator followed by a regular assignment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or do you plan to just remove
DropAndReplace
in favor of a Drop terminator followed by a regular assignment?
yes, that's what I would like to have.
So the next change would be to change
DropAndReplace
to stop moving out of the dropped value
AFAIK DropAndReplace
does not move out of the dropped place.
Indeed, in the move path builder DropAndReplace
already does the exact same thing as an Assign
(barring the Operand
/ Rvalue
difference)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DropAndReplace already does the exact same thing as an Assign
Yes, as I understand it, DropAndReplace
is a hack that was added to work around the following issue: If you treat a drop as being a move, then in this code, x
is unnecessarily captured by value.
let mut x = String::new();
|| {
x = "foo".to_string();
}
686aeab
to
1401ddb
Compare
Can the two invariants you rely on be checked by MIR validation ? |
I thought a bit about it and I actually think that by making drop de-initializing a place we have this checked for free by the borrow checker.
That's already part of the borrowck tasks.
How can we reach that value?
To be precise, we also need to check that we only execute drops on initialized places, but we already relied on drop-elab to ensure that and this change should not introduce any further reliance or assumption. |
I think we should check that all other dataflow analyses properly handle So a few things:
|
I can try this but I'm not too familiar with all the stuff that's around the compiler or miri. Is there a way I can check/validate these assumptions?
I think for now it's just easier to keep the existing terminator. Also, executing the destructor has a bit of added meaning (i.e. de-initializing the value) compared to a general function, I guess we would need to check the function in every place that needs to handle drops specially, like dataflow analyses or I think even the borrowck in the current implementation. |
it is already a lang item. Though you're right, we'd end up treating regular calls to |
Very hard at this stage I think. The closest would probably be to use the |
So, not sure if it's helpful but trying to do a write-up for the impact of this change on all analyses that I could find so that someone expert can validate my reasoning. in rustc_mir_dataflow:
in the borrowck:
in const eval:
in rustc_mir_transform:
Generally speaking, I'm wondering whether drop de-initializing a place has broader implications. Was that already established? Maybe it's something we need to check in miri? |
yea this is a pre-existing concern in miri. Since we don't do it for moving out of things, I don't think we should do it for |
we should look at this one together with anyway, I think it's time to ping our current MIR semantics expert: @JakobDegen |
This comment has been minimized.
This comment has been minimized.
No. All these questions were conclusively (to the extent that's possibly) answered on the operational side in #103957 . That PR decided that drop terminators are - operationally -
This was discussed on Zulip the other day - if we wanted this, then it would have to be a property of
Sorry for the nits, but I think this is worth picking apart a little bit. This change is definitely "well motivated" in the sense that it moves borrowck towards agreeing with the opsem. The more precise question to ask is whether borrowck is still sound after this change. This is a question that T-types is probably most qualified to answer, especially because (as Oli identified) Edit: I previously thought I had an interesting example where this made a difference but I was wrong. There is this obvious case though, which would be nice to have as a test: #![feature(custom_mir, core_intrinsics)]
use core::intrinsics::mir::*;
#[custom_mir(dialect = "built")]
fn drop_term<T>(t: &mut T) {
mir!(
{
Drop(*t, exit)
}
exit = {
Return()
}
)
} |
So this looks like invalid MIR but I think I get your point. |
Write some documentation :) . The ICE here is potentially fine, but "what does drop elaboration expect Mir to look like" needs to be written down somewhere. People will accidentally violate those rules |
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt |
I've added a doc comment on Thinking of further steps, checking for valid move paths now safely ignores |
This comment has been minimized.
This comment has been minimized.
Previously, a Drop terminator was considered a move in MIR. This commit changes the behavior to only treat Drop as a mutable access to the dropped place. In order for this change to be correct, we need to guarantee that a) A dropped value won't be used again b) Places that appear in a drop won't be used again before a subsequent initialization. We can ensure this to be correct at MIR construction because Drop will only be emitted when a variable goes out of scope, thus having: (a) as there is no way of reaching the old value. drop-elaboration will also remove any uninitialized drop. (b) as the place can't be named following the end of the scope. However, the initialization status, previously tracked by moves, should also be tied to the execution of a Drop, hence the additional logic in the dataflow analyses.
@bors r+ |
Treat Drop as a rmw operation Previously, a Drop terminator was considered a move in MIR. This commit changes the behavior to only treat Drop as a mutable access to the dropped place. In order for this change to be correct, we need to guarantee that 1. A dropped value won't be used again 2. Places that appear in a drop won't be used again before a subsequent initialization. We can ensure this to be correct at MIR construction because Drop will only be emitted when a variable goes out of scope, thus having: * (1) as there is no way of reaching the old value. drop-elaboration will also remove any uninitialized drop. * (2) as the place can't be named following the end of the scope. However, the initialization status, previously tracked by moves, should also be tied to the execution of a Drop, hence the additional logic in the dataflow analyses. From discussion in [this thread](https://rust-lang.zulipchat.com/#narrow/stream/233931-t-compiler.2Fmajor-changes/topic/.60DROP.60.20to.20.60DROP_IF.60.20compiler-team.23558), originating from rust-lang/compiler-team#558. See also rust-lang#104488 (comment)
Treat Drop as a rmw operation Previously, a Drop terminator was considered a move in MIR. This commit changes the behavior to only treat Drop as a mutable access to the dropped place. In order for this change to be correct, we need to guarantee that 1. A dropped value won't be used again 2. Places that appear in a drop won't be used again before a subsequent initialization. We can ensure this to be correct at MIR construction because Drop will only be emitted when a variable goes out of scope, thus having: * (1) as there is no way of reaching the old value. drop-elaboration will also remove any uninitialized drop. * (2) as the place can't be named following the end of the scope. However, the initialization status, previously tracked by moves, should also be tied to the execution of a Drop, hence the additional logic in the dataflow analyses. From discussion in [this thread](https://rust-lang.zulipchat.com/#narrow/stream/233931-t-compiler.2Fmajor-changes/topic/.60DROP.60.20to.20.60DROP_IF.60.20compiler-team.23558), originating from rust-lang/compiler-team#558. See also rust-lang#104488 (comment)
…iaskrgr Rollup of 8 pull requests Successful merges: - rust-lang#105641 (Implement cursors for BTreeMap) - rust-lang#107271 (Treat Drop as a rmw operation) - rust-lang#107710 (Update strip-ansi-escapes and vte) - rust-lang#107758 (Change `arena_cache` to not alter the declared query result) - rust-lang#107777 (Make `derive_const` derive properly const-if-const impls) - rust-lang#107780 (Rename `replace_bound_vars_with_*` to `instantiate_binder_with_*`) - rust-lang#107793 (Add missing tracking issue for `RawOsError`) - rust-lang#107807 (Fix small debug typo) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
Previously, a Drop terminator was considered a move in MIR. This commit changes the behavior to only treat Drop as a mutable access to the dropped place.
In order for this change to be correct, we need to guarantee that
subsequent initialization.
We can ensure this to be correct at MIR construction because Drop will only be emitted when a variable goes out of scope, thus having:
will also remove any uninitialized drop.
However, the initialization status, previously tracked by moves, should also be tied to the execution of a Drop, hence the additional logic in the dataflow analyses.
From discussion in this thread, originating from rust-lang/compiler-team#558.
See also #104488 (comment)