-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
NLL: Redo two-phase borrows with conservative, narrow focus #48431
Comments
Some notes, giving my take on what @pnkfelix wrote:
I would rather say that, for any borrow I believe that to be true by construction, since we only enable 2-phase borrows for auto-refs on method calls (
I don't know how any use of TMP0 could be inserted in between its creation and the call itself -- but it could certainly happen that there is unwinding, since the other arguments may do fallible things. That unwinding should not access TMP0, though, particularly since dropping a We can (I believe) find this unique activation by doing the following:
If the borrow is not marked as 2-phase, then the unique activation is just the borrow itself. |
I laid this out in rust-lang/rfcs#2025 (comment). However, I understand that borrowck doesn't actually work the way I described there. I think that translating this into constraints the way NLL works (well, the way I think it works; I don't have a very good understanding of the actual implementation) would require a new kind of constraint: When a |
Thinking about this again, something like this must already be in NLL. How is reborrowing handled? In let x = &mut *foo; // foo: &mut T
*foo = ...;
x = ...; somehow NLL must notice that EDIT: Ah, of course -- this is handled by borrowck, whereas the region inference is done earlier. So I guess what I am proposing is that when a two-phase borrow is reserved ( |
I'm interested in working on this. From reading the writeups above it seems like my approach should be something along the lines of:
|
@bobtwinkles that sounds great! If you want to ask any questions or just discuss how things are going, please feel free to join the NLL-dedicated gitter chat at https://gitter.im/rust-impl-period/WG-compiler-nll |
Unfortunately I ended up having less time to work on this than expected so I suspect this will slip until sometime before Monday of next week. Sorry all. |
@bobtwinkles How far are you in this? |
I've taken a few stabs at it but nothing has panned out quite yet. My first two attempts failed due to the issue outlined in this comment (namely, the dataflow system doesn't supported modifying gen/kill sets during fixed point iteration). The next thing I'm going to try is modifying the |
I have things mostly working on this branch https://github.com/bobtwinkles/rust/tree/two_phase_borrows_rewrite. I'm still failing a handful of tests, but I think the issues are tractable. Hopefully I can get this wrapped up tomorrow. The main issue I'm fighting with is balancing a narrow focus with adding complexity that's just going to get removed later when 2-phase borrows are extended later. Specifically, the |
See rust-lang#48431 for discussion as to why this was necessary and what we hoped to accomplish. A brief summary: - the first implementation of 2-phase borrows was hard to limit in the way we wanted. That is, it was too good at accepting all 2-phase borrows rather than just autorefs =) - Numerous diagnostic regressions were introduced by 2-phase borrow support which were difficult to fix
I believe we can close this, now that #48770 has landed. |
The current implementation of two-phase borrows (PRs #46537, #47489, #48197) should be thrown out and replaced with something more tailored to specific goals of the original RFC 2025.
The current implementation was written as if we could apply the concept of two-phase borrowing to any
&mut
-borrow that we find in the MIR, and then we tacked on a restriction to certrain autorefs. The latter restriction was originally motivated by not wanting the borrowck analysis to be too tightly tied to the particular details of what strategy is used to construct MIR (the general form as implemented would accept or reject code based on whether one e.g. introduced extra moves of temps).However, in our attempts to get two-phase borrows working as we expected, we have run into some issues (e.g. #48070, #48418). These seem to mostly be arising because the existing code was trying to be general purpose, but there is not an obvious straight-forward way to fix the aforementioned bugs (which tend to be either regressions taking the form of rejecting code that is accepted w/o two-phase borrows or diagnostics regressions), at least not without injecting soundness bugs.
After some discussion with @nikomatsakis, we decided the best way forward would be to redo the two-phase borrow support.
In order to have some context, in the details immediately below here is an outline of how two-phase borrow support is currently implemented.
(note that the plan may be to throw away much of this code, at least the code in the
rustc_mir
crate. Don't worry too much about trying to preserve the overall algorithm outlined here.)&mut
borrows track whether they support two-phase borrows (i.e. they track whether they arose from method-call autoref adjustments); see the fieldallow_two_phase_borrow
inAutoBorrowMutability
.allow_two_phase_borrow
inmir::BorrowKind
.allow_two_phase_borrow
information carried in themir::BorrowKind
. The dataflow results are the same regardless of that setting; the only difference is in how the dataflow is interpreted. (pnkfelix's original motivation for this was that he thought this would make it easier to debug code, by ensuring that everyone is staring at the same dataflow results...)With those details out the way, in these additional details below I outline the basic idea of the two-phase borrow rewrite:
&mut
-borrows support two-phase borrows. Note that this flag is only saying that such a borrow may allow two-phases; further conditions need to hold in the constructed MIR for the phases to be observable.mir::dataflow::impls::borrows
to track activations more precisely, both in the sense of using theallow_two_phase_borrow
flag, and also in terms of encoding the following activation semantics:tmp = &mut place
that says it allows two-phase borrows, determine if there is a unique use oftmp
that post-dominates the borrow. Also determine if the borrow dominates that use.tmp
have solely that borrow as its definition, and does the definition have that use as its only use.)tmp = &mut place
, whentmp
does not have a uniquely determined "first use", causes the gen-bits for both the reservation and for the activation to be set to 1.tmp = &mut place
just reserves (i.e. the borrow statement sets the reservation gen-bit to 1), and the (uniquely determined) associated use activates (i.e. use statement sets the activation gen-bit to 1).tmp
does have a uniquely determined first-use, but there are also control-flow splits before that use is reached (e.g. due to unwind paths from function calls), then it may suffice to again says that the borrow immediately activates. But another option may be to say that the borrow solely reserves, and any control flow branch is not post-dominated by the unique first use causes an immediate activation.tmp
in question should be considered dead, and thus the NLL region won't cover the flow of such branches anyway. So this may be an irrelevant detail. But @pnkfelix just wanted to point it out in case it arises...rustc_mir::borrow_check
.rustc_mir::borrow_check
support for reservations and activations may be salvageable, depending on the details of how the dataflow changes are done. You would probably throw away the special-case reading ofallow_two_phase_borrows
inrustc_mir::borrowck
, since this information should now be influencing the dataflow results and there should be no need for the mir-borrowck to also incorporate it into its own analysis.The text was updated successfully, but these errors were encountered: