-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Make MIR drop terminators borrow the dropped location #61069
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