Skip to content
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

Merged
merged 30 commits into from
Dec 4, 2017

Conversation

nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Nov 27, 2017

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

@bors
Copy link
Contributor

bors commented Nov 28, 2017

☔ The latest upstream changes (presumably #46312) made this pull request unmergeable. Please resolve the merge conflicts.

@kennytm kennytm added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Nov 28, 2017
@nikomatsakis nikomatsakis force-pushed the nll-master-to-rust-master-2 branch 2 times, most recently from 2160a5a to 1f8cc00 Compare December 1, 2017 10:13
@bors
Copy link
Contributor

bors commented Dec 1, 2017

☔ The latest upstream changes (presumably #46236) made this pull request unmergeable. Please resolve the merge conflicts.

@nikomatsakis nikomatsakis force-pushed the nll-master-to-rust-master-2 branch from 1f8cc00 to 105e8a3 Compare December 2, 2017 02:24
@bors
Copy link
Contributor

bors commented Dec 3, 2017

☔ The latest upstream changes (presumably #46320) made this pull request unmergeable. Please resolve the merge conflicts.

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
Copy link
Member

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
Copy link
Member

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 {
Copy link
Member

@pnkfelix pnkfelix Dec 4, 2017

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.)

Copy link
Member

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

@pnkfelix
Copy link
Member

pnkfelix commented Dec 4, 2017

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.

);
let flow_uninits = do_dataflow(
));
let flow_uninits = FlowInProgress::new(do_dataflow(
Copy link
Member

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...

Copy link
Member

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...)

nikomatsakis and others added 16 commits December 4, 2017 08:25
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.
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>,
Copy link
Member

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.

Copy link
Contributor Author

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.

) where
I: IntoIterator<Item = OutlivesBound<'tcx>>,
{
// But also record other relationships, such as `T:'x`,
Copy link
Member

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...

/// 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
Copy link
Member

@pnkfelix pnkfelix Dec 4, 2017

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
Copy link
Member

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. 😉

@nikomatsakis nikomatsakis force-pushed the nll-master-to-rust-master-2 branch from 105e8a3 to c1708ab Compare December 4, 2017 14:37
}
}

/// Tests whether `r_a <= sup`. Both must be free regions or
Copy link
Member

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
Copy link
Member

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) {
Copy link
Member

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>,
Copy link
Member

@pnkfelix pnkfelix Dec 4, 2017

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.

/// 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
Copy link
Member

@pnkfelix pnkfelix Dec 4, 2017

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.

@pnkfelix
Copy link
Member

pnkfelix commented Dec 4, 2017

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 ...)

@nikomatsakis nikomatsakis force-pushed the nll-master-to-rust-master-2 branch from 4a1bc10 to a6adc74 Compare December 4, 2017 16:30
@nikomatsakis
Copy link
Contributor Author

@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.

@bors
Copy link
Contributor

bors commented Dec 4, 2017

📌 Commit a6adc74 has been approved by pnkfelix

@kennytm kennytm added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Dec 4, 2017
@bors
Copy link
Contributor

bors commented Dec 4, 2017

⌛ Testing commit a6adc74 with merge 8503b3f...

bors added a commit that referenced this pull request Dec 4, 2017
…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
@bors
Copy link
Contributor

bors commented Dec 4, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: pnkfelix
Pushing 8503b3f to master...

@bors bors merged commit a6adc74 into rust-lang:master Dec 4, 2017
@nikomatsakis nikomatsakis deleted the nll-master-to-rust-master-2 branch December 5, 2017 09:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants