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

extend NLL checker to understand 'empty combined with universes #70950

Merged
merged 5 commits into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/librustc_infer/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@ pub enum NLLRegionVariableOrigin {
/// from a `for<'a> T` binder). Meant to represent "any region".
Placeholder(ty::PlaceholderRegion),

/// The variable we create to represent `'empty(U0)`.
RootEmptyRegion,

Existential {
/// If this is true, then this variable was created to represent a lifetime
/// bound in a `for` binder. For example, it might have been created to
Expand All @@ -493,6 +496,7 @@ impl NLLRegionVariableOrigin {
NLLRegionVariableOrigin::FreeRegion => true,
NLLRegionVariableOrigin::Placeholder(..) => true,
NLLRegionVariableOrigin::Existential { .. } => false,
NLLRegionVariableOrigin::RootEmptyRegion => false,
}
}

Expand Down
191 changes: 148 additions & 43 deletions src/librustc_mir/borrow_check/region_infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ pub struct RegionInferenceContext<'tcx> {
/// compute the values of each region.
constraint_sccs: Rc<Sccs<RegionVid, ConstraintSccIndex>>,

/// SCCs in "dependency order" (or "post order"), meaning that if S1 -> S2,
/// then S2 appears first. If you process the SCCs in this order, then you
/// are always ensured that when you proces a given SCC, all of its
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
/// successors have been processed.
scc_dependency_order: Vec<ConstraintSccIndex>,

/// Reverse of the SCC constraint graph -- i.e., an edge `A -> B` exists if
/// `B: A`. This is used to compute the universal regions that are required
/// to outlive a given SCC. Computed lazily.
Expand Down Expand Up @@ -277,7 +283,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
scc_values.merge_liveness(scc, region, &liveness_constraints);
}

let scc_universes = Self::compute_scc_universes(&constraint_sccs, &definitions);
let scc_dependency_order = Self::compute_scc_dependency_order(&constraint_sccs);

let scc_universes =
Self::compute_scc_universes(&constraint_sccs, &scc_dependency_order, &definitions);

let scc_representatives = Self::compute_scc_representatives(&constraint_sccs, &definitions);

Expand All @@ -290,6 +299,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
constraints,
constraint_graph,
constraint_sccs,
scc_dependency_order,
rev_scc_graph: None,
member_constraints,
member_constraints_applied: Vec::new(),
Expand All @@ -307,6 +317,43 @@ impl<'tcx> RegionInferenceContext<'tcx> {
result
}

/// Returns a vector of all scc-ids in "dependency" or "post order". See the
/// `scc_dependency_order` field for more details.
fn compute_scc_dependency_order(
constraints_scc: &Sccs<RegionVid, ConstraintSccIndex>,
) -> Vec<ConstraintSccIndex> {
let mut visited = &mut BitSet::new_empty(constraints_scc.num_sccs());
let mut output = vec![];

for scc in constraints_scc.all_sccs() {
Self::compute_scc_dependency_order_if_new(
constraints_scc,
scc,
&mut visited,
&mut output,
);
}

output
}

fn compute_scc_dependency_order_if_new(
constraints_scc: &Sccs<RegionVid, ConstraintSccIndex>,
index: ConstraintSccIndex,
visited: &mut BitSet<ConstraintSccIndex>,
output: &mut Vec<ConstraintSccIndex>,
) {
if !visited.insert(index) {
return;
}

for &succ in constraints_scc.successors(index) {
Self::compute_scc_dependency_order_if_new(constraints_scc, succ, visited, output);
}

output.push(index);
}

/// Each SCC is the combination of many region variables which
/// have been equated. Therefore, we can associate a universe with
/// each SCC which is minimum of all the universes of its
Expand All @@ -315,16 +362,82 @@ impl<'tcx> RegionInferenceContext<'tcx> {
/// SCC could have as well. This implies that the SCC must have
/// the minimum, or narrowest, universe.
fn compute_scc_universes(
constraints_scc: &Sccs<RegionVid, ConstraintSccIndex>,
constraint_sccs: &Sccs<RegionVid, ConstraintSccIndex>,
scc_dependency_order: &[ConstraintSccIndex],
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
) -> IndexVec<ConstraintSccIndex, ty::UniverseIndex> {
let num_sccs = constraints_scc.num_sccs();
let num_sccs = constraint_sccs.num_sccs();
let mut scc_universes = IndexVec::from_elem_n(ty::UniverseIndex::MAX, num_sccs);

debug!("compute_scc_universes()");

// For each region R in universe U, ensure that the universe for the SCC
// that contains R is "no bigger" than U. This effectively sets the universe
// for each SCC to be the minimum of the regions within.
for (region_vid, region_definition) in definitions.iter_enumerated() {
let scc = constraints_scc.scc(region_vid);
let scc = constraint_sccs.scc(region_vid);
let scc_universe = &mut scc_universes[scc];
*scc_universe = ::std::cmp::min(*scc_universe, region_definition.universe);
let scc_min = std::cmp::min(region_definition.universe, *scc_universe);
if scc_min != *scc_universe {
*scc_universe = scc_min;
debug!(
"compute_scc_universes: lowered universe of {scc:?} to {scc_min:?} \
because it contains {region_vid:?} in {region_universe:?}",
scc = scc,
scc_min = scc_min,
region_vid = region_vid,
region_universe = region_definition.universe,
);
}
}

// Walk each SCC `A` and `B` such that `A: B`
// and ensure that universe(A) can see universe(B).
//
// This serves to enforce the 'empty/placeholder' hierarchy
// (described in more detail on `RegionKind`):
//
// ```
// static -----+
// | |
// empty(U0) placeholder(U1)
// | /
// empty(U1)
// ```
//
// In particular, imagine we have variables R0 in U0 and R1
// created in U1, and constraints like this;
//
// ```
// R1: !1 // R1 outlives the placeholder in U1
// R1: R0 // R1 outlives R0
// ```
//
// Here, we wish for R1 to be `'static`, because it
// cannot outlive `placeholder(U1)` and `empty(U0)` any other way.
//
// Thanks to this loop, what happens is that the `R1: R0`
// constraint lowers the universe of `R1` to `U0`, which in turn
// means that the `R1: !1` constraint will (later) cause
// `R1` to become `'static`.
for &scc_a in scc_dependency_order {
for &scc_b in constraint_sccs.successors(scc_a) {
let scc_universe_a = scc_universes[scc_a];
let scc_universe_b = scc_universes[scc_b];
let scc_universe_min = std::cmp::min(scc_universe_a, scc_universe_b);
if scc_universe_a != scc_universe_min {
scc_universes[scc_a] = scc_universe_min;

debug!(
"compute_scc_universes: lowered universe of {scc_a:?} to {scc_universe_min:?} \
because {scc_a:?}: {scc_b:?} and {scc_b:?} is in universe {scc_universe_b:?}",
scc_a = scc_a,
scc_b = scc_b,
scc_universe_min = scc_universe_min,
scc_universe_b = scc_universe_b
);
}
}
}

debug!("compute_scc_universes: scc_universe = {:#?}", scc_universes);
Expand Down Expand Up @@ -416,7 +529,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
}
}

NLLRegionVariableOrigin::Existential { .. } => {
NLLRegionVariableOrigin::RootEmptyRegion
| NLLRegionVariableOrigin::Existential { .. } => {
// For existential, regions, nothing to do.
}
}
Expand Down Expand Up @@ -550,47 +664,26 @@ impl<'tcx> RegionInferenceContext<'tcx> {
// SCC. For each SCC, we visit its successors and compute
// their values, then we union all those values to get our
// own.
let visited = &mut BitSet::new_empty(self.constraint_sccs.num_sccs());
for scc_index in self.constraint_sccs.all_sccs() {
self.propagate_constraint_sccs_if_new(scc_index, visited);
for i in 0..self.scc_dependency_order.len() {
self.compute_value_for_scc(self.scc_dependency_order[i]);
}

// Sort the applied member constraints so we can binary search
// through them later.
self.member_constraints_applied.sort_by_key(|applied| applied.member_region_scc);
}

/// Computes the value of the SCC `scc_a` if it has not already
/// been computed. The `visited` parameter is a bitset
#[inline]
fn propagate_constraint_sccs_if_new(
&mut self,
scc_a: ConstraintSccIndex,
visited: &mut BitSet<ConstraintSccIndex>,
) {
if visited.insert(scc_a) {
self.propagate_constraint_sccs_new(scc_a, visited);
}
}

/// Computes the value of the SCC `scc_a`, which has not yet been
/// computed. This works by first computing all successors of the
/// SCC (if they haven't been computed already) and then unioning
/// together their elements.
fn propagate_constraint_sccs_new(
&mut self,
scc_a: ConstraintSccIndex,
visited: &mut BitSet<ConstraintSccIndex>,
) {
/// computed, by unioning the values of its successors.
/// Assumes that all successors have been computed already
/// (which is assured by iterating over SCCs in dependency order).
fn compute_value_for_scc(&mut self, scc_a: ConstraintSccIndex) {
let constraint_sccs = self.constraint_sccs.clone();

// Walk each SCC `B` such that `A: B`...
for &scc_b in constraint_sccs.successors(scc_a) {
debug!("propagate_constraint_sccs: scc_a = {:?} scc_b = {:?}", scc_a, scc_b);

// ...compute the value of `B`...
self.propagate_constraint_sccs_if_new(scc_b, visited);

// ...and add elements from `B` into `A`. One complication
// arises because of universes: If `B` contains something
// that `A` cannot name, then `A` can only contain `B` if
Expand Down Expand Up @@ -1258,7 +1351,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
self.check_bound_universal_region(fr, placeholder, errors_buffer);
}

NLLRegionVariableOrigin::Existential { .. } => {
NLLRegionVariableOrigin::RootEmptyRegion
| NLLRegionVariableOrigin::Existential { .. } => {
// nothing to check here
}
}
Expand Down Expand Up @@ -1360,7 +1454,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
self.check_bound_universal_region(fr, placeholder, errors_buffer);
}

NLLRegionVariableOrigin::Existential { .. } => {
NLLRegionVariableOrigin::RootEmptyRegion
| NLLRegionVariableOrigin::Existential { .. } => {
// nothing to check here
}
}
Expand Down Expand Up @@ -1633,9 +1728,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
universe1.cannot_name(placeholder.universe)
}

NLLRegionVariableOrigin::FreeRegion | NLLRegionVariableOrigin::Existential { .. } => {
false
}
NLLRegionVariableOrigin::RootEmptyRegion
| NLLRegionVariableOrigin::FreeRegion
| NLLRegionVariableOrigin::Existential { .. } => false,
}
}

Expand Down Expand Up @@ -1773,6 +1868,12 @@ impl<'tcx> RegionInferenceContext<'tcx> {
/// Finds some region R such that `fr1: R` and `R` is live at `elem`.
crate fn find_sub_region_live_at(&self, fr1: RegionVid, elem: Location) -> RegionVid {
debug!("find_sub_region_live_at(fr1={:?}, elem={:?})", fr1, elem);
debug!("find_sub_region_live_at: {:?} is in scc {:?}", fr1, self.constraint_sccs.scc(fr1));
debug!(
"find_sub_region_live_at: {:?} is in universe {:?}",
fr1,
self.scc_universes[self.constraint_sccs.scc(fr1)]
);
self.find_constraint_paths_between_regions(fr1, |r| {
// First look for some `r` such that `fr1: r` and `r` is live at `elem`
debug!(
Expand All @@ -1794,13 +1895,16 @@ impl<'tcx> RegionInferenceContext<'tcx> {
.or_else(|| {
// If we fail to find THAT, it may be that `fr1` is a
// placeholder that cannot "fit" into its SCC. In that
// case, there should be some `r` where `fr1: r`, both
// `fr1` and `r` are in the same SCC, and `fr1` is a
// case, there should be some `r` where `fr1: r` and `fr1` is a
// placeholder that `r` cannot name. We can blame that
// edge.
//
// Remember that if `R1: R2`, then the universe of R1
// must be able to name the universe of R2, because R2 will
// be at least `'empty(Universe(R2))`, and `R1` must be at
// larger than that.
self.find_constraint_paths_between_regions(fr1, |r| {
self.constraint_sccs.scc(fr1) == self.constraint_sccs.scc(r)
&& self.cannot_name_placeholder(r, fr1)
self.cannot_name_placeholder(r, fr1)
})
})
.map(|(_path, r)| r)
Expand Down Expand Up @@ -1945,7 +2049,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
let blame_source = match from_region_origin {
NLLRegionVariableOrigin::FreeRegion
| NLLRegionVariableOrigin::Existential { from_forall: false } => true,
NLLRegionVariableOrigin::Placeholder(_)
NLLRegionVariableOrigin::RootEmptyRegion
| NLLRegionVariableOrigin::Placeholder(_)
| NLLRegionVariableOrigin::Existential { from_forall: true } => false,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,6 @@ impl<'a, 'b, 'tcx> TypeOutlivesDelegate<'tcx> for &'a mut ConstraintConversion<'
a: ty::Region<'tcx>,
b: ty::Region<'tcx>,
) {
// FIXME -- this is not the fix I would prefer
if let ty::ReEmpty(ty::UniverseIndex::ROOT) = a {
return;
}
let b = self.to_region_vid(b);
let a = self.to_region_vid(a);
self.add_outlives(b, a);
Expand All @@ -176,10 +172,6 @@ impl<'a, 'b, 'tcx> TypeOutlivesDelegate<'tcx> for &'a mut ConstraintConversion<'
a: ty::Region<'tcx>,
bound: VerifyBound<'tcx>,
) {
// FIXME: I'd prefer if NLL had a notion of empty
if let ty::ReEmpty(ty::UniverseIndex::ROOT) = a {
return;
}
let type_test = self.verify_to_type_test(kind, a, bound);
self.add_type_test(type_test);
}
Expand Down
19 changes: 18 additions & 1 deletion src/librustc_mir/borrow_check/universal_regions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ pub struct UniversalRegions<'tcx> {
/// The total number of universal region variables instantiated.
num_universals: usize,

/// A special region variable created for the `'empty(U0)` region.
/// Note that this is **not** a "universal" region, as it doesn't
/// represent a universally bound placeholder or any such thing.
/// But we do create it here in this type because it's a useful region
/// to have around in a few limited cases.
pub root_empty: RegionVid,

/// The "defining" type for this function, with all universal
/// regions instantiated. For a closure or generator, this is the
/// closure type, but for a top-level function it's the `FnDef`.
Expand Down Expand Up @@ -316,7 +323,11 @@ impl<'tcx> UniversalRegions<'tcx> {

/// See `UniversalRegionIndices::to_region_vid`.
pub fn to_region_vid(&self, r: ty::Region<'tcx>) -> RegionVid {
self.indices.to_region_vid(r)
if let ty::ReEmpty(ty::UniverseIndex::ROOT) = r {
self.root_empty
} else {
self.indices.to_region_vid(r)
}
}

/// As part of the NLL unit tests, you can annotate a function with
Expand Down Expand Up @@ -472,10 +483,16 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> {
_ => None,
};

let root_empty = self
.infcx
.next_nll_region_var(NLLRegionVariableOrigin::RootEmptyRegion)
.to_region_vid();

UniversalRegions {
indices,
fr_static,
fr_fn_body,
root_empty,
first_extern_index,
first_local_index,
num_universals,
Expand Down
Loading