-
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
NLL: improve inference with flow results, represent regions with bitsets, and more #46319
NLL: improve inference with flow results, represent regions with bitsets, and more #46319
Conversation
☔ The latest upstream changes (presumably #46312) made this pull request unmergeable. Please resolve the merge conflicts. |
2160a5a
to
1f8cc00
Compare
☔ The latest upstream changes (presumably #46236) made this pull request unmergeable. Please resolve the merge conflicts. |
1f8cc00
to
105e8a3
Compare
☔ The latest upstream changes (presumably #46320) made this pull request unmergeable. Please resolve the merge conflicts. |
src/librustc/middle/free_region.rs
Outdated
self.relation.add(sub, sup) | ||
} | ||
} | ||
|
||
/// True if `r_a <= r_b` is known to hold. Both `r_a` and `r_b` | ||
/// must be free regions from the function header. | ||
/// Tests whether `r_a <= sup`. Both must be free regions or |
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.
... did you mean to alpha-rename r_b
to sup
in the signature below? Why change the name in the comment?
// and hence we establish (transitively) a constraint that | ||
// `'a: 'b`. The `propagate_constraints` code above will | ||
// therefore add `end('a)` into the region for `'b` -- but we | ||
// have no evidence that `'b` outlives `'a`, so we want to report |
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.
I think you mean to write either:
- "
'a
outlives'b
" or - "
'b
is a subregion of'a
"
|
||
// Find every region `o` such that `fr: o` | ||
// (because `fr` includes `end(o)`). | ||
for &outlived_fr in &fr_value.free_regions { |
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.
I dislike the name outlived_fr
(at least in this context, where there is another variable named fr
).
I'm spending a lot of time staring at the name, wondering: does this name mean "I outlive(d?) the region named fr
"? Or does the name mean "I am a free region (abbreviated "fr") that is outlived (by another free region that happens to be named "fr")?"
(You would think that the comment would clarify this, but since I know from 1:1 discussions and also comments elsewhere in this PR that niko sometimes mixes up the argument order for 'a: 'b
.)
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.
alternative name suggestions:
must_be_outlived
lower_bound
subregion
hmm apparently my strategy of giving comments commit-by-commit may be suboptimal for this particular commit series, since the points I had in earliest commits have been completely subsumed? Update: no, my strategy was sound; but github's not smart enough to recognize some kinds of code migrations. So I needed to do a second pass over my review and transcribe most of the "outdated" comments onto the diff, where they are attached to the up-to-date locations in the code. |
src/librustc_mir/borrow_check/mod.rs
Outdated
); | ||
let flow_uninits = do_dataflow( | ||
)); | ||
let flow_uninits = FlowInProgress::new(do_dataflow( |
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.
sort of a shame that some of these changes which strike me as refactorings, like pulling the FlowInProgress::new
calls out of InProgress::new
(which I assume were necessitated by lifetime issues...) were not factored into a separate commit, to ease identification of the specific semantic changes being made in this commit...
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.
(but since I took forever to get around to the review, I won't block this PR on requesting such changes...)
It will be useful later for diagnostics to be able to remember where things were live.
Rather than declaring some region variables to be constant, and reporting errors when they would have to change, we instead populate each free region X with a minimal set of points (the CFG plus end(X)), and then we let inference do its thing. This may add other `end(Y)` points into X; we can then check after the fact that indeed `X: Y` holds. This requires a bit of "blame" detection to find where the bad constraint came from: we are currently using a pretty dumb algorithm. Good place for later expansion.
In particular, if we see a variable is DROP-LIVE, but it is not MAYBE-INIT, then we can ignore the drop. This leavess attempt to use more complex refinements of the idea (e.g., for subpaths or subfields) to future work.
This should be more efficient than allocating two BTreeSets for every region variable?—as it is written in rust-lang#45670.
This revealed some shortcomings, one of which is fixed. Fixes rust-lang#45937.
Now it can be reused by the NLL code.
We now visit just the stuff in the CFG, and we add liveness constraints for all the random types, regions etc that appear within rvalues and statements.
/// A map from each MIR Location to its column index in | ||
/// `liveness_constraints`/`inferred_values`. (The first N columns are | ||
/// the free regions.) | ||
point_indices: BTreeMap<Location, usize>, |
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.
... hmm, maybe at some point we'll want an IndexBitMatrix
analogous to our IndexVec
(where I suppose it would have two type parameters instead of just one), so that we could use an index type here instead of usize
... But, that need not be part of this PR.
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.
I have done a big refactoring of this (in particular, encapsulating the BitMatrix
) on nll-master, but I'm not inclined to bother rebasing it back earlier at this point.
src/librustc/infer/outlives/env.rs
Outdated
) where | ||
I: IntoIterator<Item = OutlivesBound<'tcx>>, | ||
{ | ||
// But also record other relationships, such as `T:'x`, |
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.
This comment as written doesn't make sense in this new context.
In the old code, it came after let implied_bounds = infcx.implied_bounds(...)
, so it made sense to open with "But also record ..."; here, we are just jumping directly into this loop...
src/librustc/infer/outlives/env.rs
Outdated
/// Processes outlives bounds that are known to hold, whether from implied or other sources. | ||
/// | ||
/// The `infcx` parameter is optional; if the implied bounds may | ||
/// contain inference variables, it should be supplied, in which |
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.
I personally would say "it must be supplied" (rather than "should"), since the code below isn't going to just ignore the variables in that case, but rather, it will panic when the implied bounds contain inference variables.
/// Given an element A, returns the maximal set {B} of elements B | ||
/// such that | ||
/// | ||
/// - A != A |
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.
you probably want to write "A != B" here. 😉
105e8a3
to
c1708ab
Compare
} | ||
} | ||
|
||
/// Tests whether `r_a <= sup`. Both must be free regions or |
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.
Transcribed from bfc696a : ... did you mean to alpha-rename r_b
to sup
in the signature below? Why change the name in the comment?
// and hence we establish (transitively) a constraint that | ||
// `'a: 'b`. The `propagate_constraints` code above will | ||
// therefore add `end('a)` into the region for `'b` -- but we | ||
// have no evidence that `'b` outlives `'a`, so we want to report |
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.
Transcribed from bfc696a :
I think you mean to write either:
- "have no evidence that
'a
outlives'b
" or - "have no evidence that
'b
is a subregion of'a
"
(after all, in the code itself, we are in a situation where we need the region on the input parameter to outlive the region on the output return type.)
|
||
// Find every region `o` such that `fr: o` | ||
// (because `fr` includes `end(o)`). | ||
for outlived_fr in fr_value.take_while(|&i| i < self.num_universal_regions) { |
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.
Transcribed from bfc696a :
I dislike the name outlived_fr
(at least in this context, where there is another variable named fr
).
I'm spending a lot of time staring at the name, wondering: does this name mean "I outlive(d?) the region named fr
"? Or does the name mean "I am a free region (abbreviated "fr") that is outlived (by another free region that happens to be named "fr")?"
(You would think that the comment would clarify this, but since I know from 1:1 discussions and also comments elsewhere in this PR that niko sometimes mixes up the argument order for 'a: 'b
.)
alternative name suggestions:
must_be_outlived
lower_bound
subregion
/// A map from each MIR Location to its column index in | ||
/// `liveness_constraints`/`inferred_values`. (The first N columns are | ||
/// the free regions.) | ||
point_indices: BTreeMap<Location, usize>, |
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.
Transcribed from 22b3175 :
.. hmmm, maybe at some point we'll want an IndexBitMatrix
analogous to our IndexVec
(where I suppose it would have two type parameters instead of just one), so that we could use an index type here instead of usize
... But, that need not be part of this PR.
src/librustc/infer/outlives/env.rs
Outdated
/// Processes outlives bounds that are known to hold, whether from implied or other sources. | ||
/// | ||
/// The `infcx` parameter is optional; if the implied bounds may | ||
/// contain inference variables, it should be supplied, in which |
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.
Transcribed from #46319 (comment) since github's algorithm for marking comments "outdated" is not quite smart enough to recognize when code has just been moved around...
I personally would say "it must be supplied" (rather than "should"), since the code below isn't going to just ignore the variables in that case, but rather, it will panic when the implied bounds contain inference variables.
Okay I left various notes with nits on comments. But the code itself seems fine. r=me once you've addressed my comments (or decided to disregard them ...) |
4a1bc10
to
a6adc74
Compare
@bors r=pnkfelix p=1 Tests pass locally. I'm giving p=1 because NLL is a top priority and we need to clear way for next PR. |
📌 Commit a6adc74 has been approved by |
…kfelix NLL: improve inference with flow results, represent regions with bitsets, and more This PR begins with a number of edits to the NLL code and then includes a large number of smaller refactorings (these refactorings ought not to change behavior). There are a lot of commits here, but each is individually simple. The goal is to land everything up to but not including the changes to how we handle closures, which are conceptually more complex. The NLL specific changes are as follows (in order of appearance): **Modify the region inferencer's approach to free regions.** Previously, for each free region (lifetime parameter) `'a`, it would compute the set of other free regions that `'a` outlives (e.g., if we have `where 'a: 'b`, then this set would be `{'a, 'b}`). Then it would mark those free regions as "constants" and report an error if inference tried to extend `'a` to include any other region (e.g., `'c`) that is not in that outlives set. In this way, the value of `'a` would never grow beyond the maximum that could type check. The new approach is to allow `'a` to grow larger. Then, after the fact, we check over the value of `'a` and see what other free regions it is required to outlive, and we check that those outlives relationships are justified by the where clauses in scope etc. **Modify constraint generation to consider maybe-init.** When we have a "drop-live" variable `x` (i.e., a variable that will be dropped but will not be otherwise used), we now consider whether `x` is "maybe initialized" at that point. If not, then we know the drop is a no-op, and we can allow its regions to be dead. Due to limitations in the fragment code, this currently only works at the level of entire variables. **Change representation of regions to use a `BitMatrix`.** We used to use a `BTreeSet`, which was rather silly. We now use a MxN matrix of bits, where `M` is the number of variables and `N` is the number of possible elements in each set (size of the CFG + number of free regions). The remaining commits (starting from extract the `implied_bounds` code into a helper function ") are all "no-op" refactorings, I believe. ~~One concern I have is with the commit "with -Zverbose, print all details of closure substs"; this commit seems to include some "internal" stuff in the mir-dump files, such as internal interner numbers, that I fear may vary by platform. Annoying. I guess we will see.~~ (I removed this commit.) As for reviewer, @arielb1 has been reviewing the PRs, and they are certainly welcome to review this one too. But I figured it'd maybe be good to have more people taking a look and being familiar with this code, so I'll "nominate" @pnkfelix . r? @pnkfelix
☀️ Test successful - status-appveyor, status-travis |
This PR begins with a number of edits to the NLL code and then includes a large number of smaller refactorings (these refactorings ought not to change behavior). There are a lot of commits here, but each is individually simple. The goal is to land everything up to but not including the changes to how we handle closures, which are conceptually more complex.
The NLL specific changes are as follows (in order of appearance):
Modify the region inferencer's approach to free regions. Previously, for each free region (lifetime parameter)
'a
, it would compute the set of other free regions that'a
outlives (e.g., if we havewhere 'a: 'b
, then this set would be{'a, 'b}
). Then it would mark those free regions as "constants" and report an error if inference tried to extend'a
to include any other region (e.g.,'c
) that is not in that outlives set. In this way, the value of'a
would never grow beyond the maximum that could type check. The new approach is to allow'a
to grow larger. Then, after the fact, we check over the value of'a
and see what other free regions it is required to outlive, and we check that those outlives relationships are justified by the where clauses in scope etc.Modify constraint generation to consider maybe-init. When we have a "drop-live" variable
x
(i.e., a variable that will be dropped but will not be otherwise used), we now consider whetherx
is "maybe initialized" at that point. If not, then we know the drop is a no-op, and we can allow its regions to be dead. Due to limitations in the fragment code, this currently only works at the level of entire variables.Change representation of regions to use a
BitMatrix
. We used to use aBTreeSet
, which was rather silly. We now use a MxN matrix of bits, whereM
is the number of variables andN
is the number of possible elements in each set (size of the CFG + number of free regions).The remaining commits (starting from
extract the
implied_bounds
code into a helper function ") are all "no-op" refactorings, I believe.One concern I have is with the commit "with -Zverbose, print all details of closure substs"; this commit seems to include some "internal" stuff in the mir-dump files, such as internal interner numbers, that I fear may vary by platform. Annoying. I guess we will see.(I removed this commit.)As for reviewer, @arielb1 has been reviewing the PRs, and they are certainly welcome to review this one too. But I figured it'd maybe be good to have more people taking a look and being familiar with this code, so I'll "nominate" @pnkfelix .
r? @pnkfelix