-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Description
Solving region constraints currently accounts for 8% of MIR borrowck time in clap-rs. However, we are doing more work than we need to do. This part of the inference engine processes outlives constraints like 'a: 'b
, which says that the region 'a
outlives 'b
and therefore should be a superset of the points in 'b
. To process such a constraint we therefore take all the points in 'b
and add them to 'a
:
rust/src/librustc_mir/borrow_check/nll/region_infer/values.rs
Lines 246 to 250 in 2808460
/// Add all elements in `r_from` to `r_to` (because e.g. `r_to: | |
/// r_from`). | |
pub(super) fn add_region(&mut self, r_to: RegionVid, r_from: RegionVid) -> bool { | |
self.matrix.merge(r_from, r_to) | |
} |
You can see therefore that a constraint like 'a: 'a
is pretty uninteresting. It is also uninterested to have two of the same constraints ('b: 'c
, 'b: 'c
), since processing the first one will also solve the second one.
However, a cursory examination of the constraints from many examples reveals that we have both a lot of duplicates and a lot of X: X
identity constraints. I find about ~30% of the constraints are duplicated.
Part of the reason for this is that, in the original NLL proposal and in Polonius, we distinguished not only 'a: 'b
but also the location where it occurs ('a: 'b @ P
). We are no longer using this P
information so it would be good to discard it (and the next gen region inference engine, polonius, uses a different data structures for its constraints anyway). (Interestingly, even if we take the location P
into account, it is still true that most of the constraints are duplicated, so there is likely a win here waiting for polonius as well at some future point.)
The constraints in question are instances of this struct:
rust/src/librustc_mir/borrow_check/nll/region_infer/mod.rs
Lines 120 to 146 in 2808460
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] | |
pub struct OutlivesConstraint { | |
// NB. The ordering here is not significant for correctness, but | |
// it is for convenience. Before we dump the constraints in the | |
// debugging logs, we sort them, and we'd like the "super region" | |
// to be first, etc. (In particular, span should remain last.) | |
/// The region SUP must outlive SUB... | |
pub sup: RegionVid, | |
/// Region that must be outlived. | |
pub sub: RegionVid, | |
/// At this location. | |
pub point: Location, | |
/// Later on, we thread the constraints onto a linked list | |
/// grouped by their `sub` field. So if you had: | |
/// | |
/// Index | Constraint | Next Field | |
/// ----- | ---------- | ---------- | |
/// 0 | `'a: 'b` | Some(2) | |
/// 1 | `'b: 'c` | None | |
/// 2 | `'c: 'b` | None | |
pub next: Option<ConstraintIndex>, | |
/// Where did this constraint arise? | |
pub span: Span, |
They are stored in a vector here:
rust/src/librustc_mir/borrow_check/nll/region_infer/mod.rs
Lines 70 to 71 in 2808460
/// The constraints we have accumulated and used during solving. | |
constraints: IndexVec<ConstraintIndex, OutlivesConstraint>, |
Most elements of this vector I believe are given in RegionInferenceContext::new
:
constraints: IndexVec::from_raw(outlives_constraints), |
but some are later added via add_outlives
:
rust/src/librustc_mir/borrow_check/nll/region_infer/mod.rs
Lines 390 to 407 in 2808460
/// Indicates that the region variable `sup` must outlive `sub` is live at the point `point`. | |
pub(super) fn add_outlives( | |
&mut self, | |
span: Span, | |
sup: RegionVid, | |
sub: RegionVid, | |
point: Location, | |
) { | |
debug!("add_outlives({:?}: {:?} @ {:?}", sup, sub, point); | |
assert!(self.inferred_values.is_none(), "values already inferred"); | |
self.constraints.push(OutlivesConstraint { | |
span, | |
sup, | |
sub, | |
point, | |
next: None, | |
}); | |
} |
The initial vector is populated by the MIR type check in this call here:
rust/src/librustc_mir/borrow_check/nll/type_check/mod.rs
Lines 759 to 768 in 7008a95
if let Some(borrowck_context) = &mut self.borrowck_context { | |
constraint_conversion::ConstraintConversion::new( | |
self.mir, | |
borrowck_context.universal_regions, | |
borrowck_context.location_table, | |
&mut self.constraints.outlives_constraints, | |
&mut self.constraints.type_tests, | |
&mut borrowck_context.all_facts, | |
).convert(locations, &data); | |
} |
So to solve this problem we would want to detect duplicates somehow, probably taking just sub+sup into account. The only reason to also include the location is that it may be useful in reporting errors later. It probably makes sense to do this by adding a FxHashSet<(RegionVid, RegionVid)>
to detect duplicates, though it might make sense to extract the vector itself from both the RegionInferenceContext
and the MIR type check into a common data structure (OutlivesConstraintSet
or something) that keeps the FxHashSet
and the Vec
in one.