-
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 iterating and updating &mut
fails for non-local Place.
#62007
Comments
I'm not 100% sure what action is best here. I think my game plan is roughly:
|
&mut ref
fails when in non-local Place.&mut ref
fails for non-local Place.
&mut ref
fails for non-local Place.&mut
ref fails for non-local Place.
&mut
ref fails for non-local Place.&mut
fails for non-local Place.
cc @davidtwco |
Currently, in the "special dodge", we loop over all borrows of that same local. Could we simply filter that list using |
@ecstatic-morse I'm not sure if I understood the question, but: to support the "special dodge", we specifically build up a We don't have an analogous map for all places in a given MIR. (and I suspect we do not want to pay the time+memory expense of building up such a map.) |
or are you saying we should map each |
Ah, so |
(I think the |
Resolves rust-lang#62007. Due to a bug, the previous version of this check did not actually kill any conflicting borrows unless the borrowed place had no projections. Specifically, `entry_set` will always be empty when `statement_effect` is called. It does not contain the set of borrows which are live at this point in the program.
…, r=pnkfelix Kill conflicting borrows of places with projections. Resolves rust-lang#62007. Due to a bug, the previous version of this check did not actually kill all conflicting borrows unless the borrowed place had no projections. Specifically, `sets.on_entry` will always be empty when `statement_effect` is called. It does not contain the set of borrows which are live at this point in the program. @pnkfelix describes why this was not caught before in rust-lang#62007, and created an example where the current borrow checker failed unnecessarily. This PR adds their example as a test, but they will likely want to add some additional ones. r? @pnkfelix
…, r=pnkfelix Kill conflicting borrows of places with projections. Resolves rust-lang#62007. Due to a bug, the previous version of this check did not actually kill all conflicting borrows unless the borrowed place had no projections. Specifically, `sets.on_entry` will always be empty when `statement_effect` is called. It does not contain the set of borrows which are live at this point in the program. @pnkfelix describes why this was not caught before in rust-lang#62007, and created an example where the current borrow checker failed unnecessarily. This PR adds their example as a test, but they will likely want to add some additional ones. r? @pnkfelix
Kill conflicting borrows of places with projections. Resolves #62007. Due to a bug, the previous version of this check did not actually kill all conflicting borrows unless the borrowed place had no projections. Specifically, `sets.on_entry` will always be empty when `statement_effect` is called. It does not contain the set of borrows which are live at this point in the program. @pnkfelix describes why this was not caught before in #62007, and created an example where the current borrow checker failed unnecessarily. This PR adds their example as a test, but they will likely want to add some additional ones. r? @pnkfelix
This commit makes `sets.on_entry` inaccessible in `{before_,}{statement,terminator}_effect`. This field was meant to allow implementors of `BitDenotation` to access the initial state for each block (optionally with the effect of all previous statements applied via `accumulates_intrablock_state`) while defining transfer functions. However, the ability to set the initial value for the entry set of each basic block (except for START_BLOCK) no longer exists. As a result, this functionality is mostly useless, and when it *was* used it was used erroneously (see rust-lang#62007). Since `on_entry` is now useless, we can also remove `BlockSets`, which held the `gen`, `kill`, and `on_entry` bitvectors and replace it with a `GenKill` struct. Variables of this type are called `trans` since they represent a transfer function. `GenKill`s are stored contiguously in `AllSets`, which reduces the number of bounds checks and may improve cache performance: one is almost never accessed without the other. Replacing `BlockSets` with `GenKill` allows us to define some new helper functions which streamline dataflow iteration and the dataflow-at-location APIs. Notably, `state_for_location` used a subtle side-effect of the `kill`/`kill_all` setters to apply the transfer function, and could be incorrect if a transfer function depended on effects of previous statements in the block on `gen_set`.
…kfelix rustc_mir: Hide initial block state when defining transfer functions This PR addresses [this FIXME](https://github.com/rust-lang/rust/blob/2887008e0ce0824be4e0e9562c22ea397b165c97/src/librustc_mir/dataflow/mod.rs#L594-L596). This makes `sets.on_entry` inaccessible in `{before_,}{statement,terminator}_effect`. This field was meant to allow implementors of `BitDenotation` to access the initial state for each block (optionally with the effect of all previous statements applied via `accumulates_intrablock_state`) while defining transfer functions. However, the ability to set the initial value for the entry set of each basic block (except for START_BLOCK) no longer exists. As a result, this functionality is mostly useless, and when it *was* used it was used erroneously (see #62007). Since `on_entry` is now useless, we can also remove `BlockSets`, which held the `gen`, `kill`, and `on_entry` bitvectors and replace it with a `GenKill` struct. Variables of this type are called `trans` since they represent a transfer function. `GenKill`s are stored contiguously in `AllSets`, which reduces the number of bounds checks and may improve cache performance: one is almost never accessed without the other. Replacing `BlockSets` with `GenKill` allows us to define some new helper functions which streamline dataflow iteration and the dataflow-at-location APIs. Notably, `state_for_location` used a subtle side-effect of the `kill`/`kill_all` setters to apply the transfer function, and could be incorrect if a transfer function depended on effects of previous statements in the block on `gen_set`. Additionally, this PR merges `BitSetOperator` and `InitialFlow` into one trait. Since the value of `InitialFlow` defines the semantics of the `join` operation, there's no reason to have seperate traits for each. We can add a default impl of `join` which branches based on `BOTTOM_VALUE`. This should get optimized away.
Spawned off of discussion with @ecstatic-morse and their work on PR #61787
There is a dataflow subroutine for
Borrows
,kill_borrows_on_place
, added in PR #56649 to resolve #46589.kill_borrows_on_place
reads from theon_entry
field ofBlockSets
(even though I am pretty sure no one was ever supposed to do so; its inclusion in this part of the API was a wart):rust/src/librustc_mir/dataflow/impls/borrows.rs
Lines 205 to 214 in f0c2bdf
I think this was written with the assumption that
sets.on_entry.clone().union(sets.gen_set)
(aka on-entry U gen-set) would represent an upper-bound on the things that should be in the kill set. (And then the subsequent loop determines which of those things to actually put into the kill set.)The problem with that is that when we are computing the transfer-function initially,
on_entry
is going to be the empty-set, at least for almost all basic blocks. That all you have to work with in dataflow. So you end up with a kill-set here that is an under-approximation to what it should actually be, if you are assuming that we're supposed to start with the complete (post dataflow) on-entry and combine that with gen-set to get the upper bound on what to use for the kill-set.Why hasn't this been a problem for us so far? Well, here's my theory:
With respect to soundness: if the kill-set for
Borrows
is a subset of what it should "truly be", then I think the compiler ends up treating borrows as living longer than they should, and so it ends up detecting conflicts that are not actually there, and so the compiler rejects code that you might have other expected it to accept.With respect to expressiveness: We don't actually get into the problematic part of
kill_borrows_on_place
as often as you might think, because there's a special dodge at the start of the function:rust/src/librustc_mir/dataflow/impls/borrows.rs
Lines 196 to 203 in f0c2bdf
You combine the two points made above, and you end up with things like the following example: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=3d29327853729ec280799760d5e58003
Click to see the example Rust code
(In that example,
to_refs2
is basically the same asto_refs1
, except that its wrapped in a unary tuple(T,)
. I think the intention is that ifto_refs1
is accepted, then so shouldto_refs2
. But NLL currently acceptsto_refs1
and )The text was updated successfully, but these errors were encountered: