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