-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Make MIR drop terminators borrow the dropped location #61069
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Conversation
@bors rollup |
// Drop terminators borrows the location | ||
TerminatorKind::Drop { location, .. } | | ||
TerminatorKind::DropAndReplace { location, .. } => { | ||
if let Some(local) = find_local(location) { |
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.
BorrowedLocalsVisitor { | ||
sets, | ||
}.visit_terminator(self.mir[loc.block].terminator(), loc); | ||
}.visit_terminator(terminator, loc); | ||
match &terminator.kind { |
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.
Use if let
?
r? @pnkfelix cc @nikomatsakis |
|
@Zoxc Isn't it UB to capture |
@eddyb I don't understand the question. What is "capturing |
I don't see any grounds for making this UB. However, if the caller does a |
Also, why the question? What is the analysis/optimization that would benefit from that code being UB? Honestly I have no idea what this PR even does, |
@RalfJung Well, the issue is that in MIR it's I guess we shouldn't just assume it's UB, just like keeping a raw pointer to a moved-from location? It does throw a wrench into my plan of "assume borrows are cancelled by moves/drops" to limit the effects of borrows, but at least for some optimizations it all works out if there are no derefs/calls between |
Takes into account for what? The borrow checker?
Yeah, that is hard to realize. Even if we make moves write |
Just because I got a bit confused and thought this PR might not have any effect, here's an example: let mut x = foo();
x = bar(); // DropAndReplace
drop(x);
yield;
baz(); // may access `x` via raw pointers, `x` must be in generator state
// no Drop at the end of scope, only StorageDead Note that without the And I think that for regular end-of-scope cc @cramertj @tmandry How can we measure the impact this has on |
Maybe I'm similarly confused, but we primarily use That being said, it makes me nervous that we allow such things like capturing the pointer to a local inside a |
At the same time, keeping our semantics concrete and operational is important if we want it to be possible for mere mortals to write correct unsafe code. Right now, I consider "pointers may not escape from To give one example of what "escape" can not mean: it cannot mean "the address is not written to any memory outside the I also don't see the benefit in terms of optimization: if the compiler is supposed to know that some memory is not reachable any more through pointers "escaped" into somewhere, it should deallocate that memory (e.g. using |
Okay, after some puzzling and re-reading the dialogue here, I think I understand what this PR is doing. The important bit is what eddyb said up above; in terms of runtime semantics, the MIR operator
|
@tmandry wrote:
Well, at this point we have set down our drop order for e.g. struct fields. So one can imagine examples like this, which I think should be valid (play): use std::fmt::Debug;
struct D<A: Debug>(*const A);
struct Two<A: Debug> { d: D<A>, a: A }
impl<A: Debug> Drop for Two<A> {
fn drop(&mut self) {
println!("Dropping Two");
self.d.0 = &mut self.a as *const A;
}
}
impl<A: Debug> Drop for D<A> {
fn drop(&mut self) {
let a: A = unsafe { std::ptr::read(self.0) };
println!("Dropping D({:?})", a);
std::mem::forget(a);
}
}
fn main() {
let t = Two { d: D(std::ptr::null()), a: format!("Hello") };
} and examples like this, which I think are UB (play): use std::fmt::Debug;
struct D<A: Debug>(*const A);
struct Two<A: Debug> { a: A, d: D<A> }
impl<A: Debug> Drop for Two<A> {
fn drop(&mut self) {
println!("Dropping Two");
self.d.0 = &mut self.a as *const A;
}
}
impl<A: Debug> Drop for D<A> {
fn drop(&mut self) {
let a: A = unsafe { std::ptr::read(self.0) };
println!("Dropping D({:?})", a);
std::mem::forget(a);
}
}
fn main() {
let t = Two { d: D(std::ptr::null()), a: format!("Hello") };
} (Note that the only difference between those two examples is the order of the struct field declarations in |
Okay, what I'm gathering from this discussion is that we can use
Semantically I agree with this change then. I don't think it changes anything we do today, but it is more correct. I think this reasoning should be laid out a bit more explicitly in comments, since a few of us were confused by what the change was doing. |
Just to be clear, there's a significant hit from |
Yes; some benchmark results are here. Keep in mind that this does cause new basic blocks to be created. I have not tried suppressing those in the codegen yet. |
Just to be clear, this would happen during MIR construction when we know that this is really a "final" drop because something went out of scope, and not a "drop-and-replace", right? |
Yep. |
@tmandry are you saying I should close this PR and wait for you to implement the semantics you actually desire? As far as I can tell, this PR is putting in a fix, though its not 100% clear to me whether the fix is actually observable. (The lack of tests in the PR may be a reason to r- for the short term...) |
@pnkfelix No sorry, I agree with your conclusion that this PR does implement a fix, which probably isn't observable today (but should still be merged IMO). My work doesn't change that. Sorry for throwing the discussion off track a bit, I was just trying to clarify my own understanding. |
@bors r+ |
📌 Commit cb6beef has been approved by |
Rollup of 5 pull requests Successful merges: - #61069 (Make MIR drop terminators borrow the dropped location) - #61453 (Remove unneeded feature attr from atomic integers doctests) - #61488 (Fix NLL typeck ICEs) - #61500 (Fix regression 61475) - #61523 (Hide gen_future API from documentation) Failed merges: r? @ghost
…sleywiser Combine `HaveBeenBorrowedLocals` and `IndirectlyMutableLocals` into one dataflow analysis This PR began as an attempt to port `HaveBeenBorrowedLocals` to the new dataflow framework (see #68241 for prior art). Along the way, I noticed that it could share most of its code with `IndirectlyMutableLocals` and then found a few bugs in the two analyses: - Neither one marked locals as borrowed after an `Rvalue::AddressOf`. - `IndirectlyMutableLocals` was missing a minor fix that `HaveBeenBorrowedLocals` got in #61069. This is not a problem today since it is only used during const-checking, where custom drop glue is forbidden. However, this may change some day. I decided to combine the two analyses so that they wouldn't diverge in the future while ensuring that they remain distinct types (called `MaybeBorrowedLocals` and `MaybeMutBorrowedLocals` to be consistent with the `Maybe{Un,}InitializedPlaces` naming scheme). I fixed the bugs and switched to exhaustive matching where possible to make them less likely in the future. Finally, I added comments explaining some of the finer points of the transfer function for these analyses (see #61069 and #65006).
r? @eddyb
cc @tmandry