start filling out the constraint propagation chapter in more detail
This commit is contained in:
parent
52adfece7a
commit
00be9ef01b
|
|
@ -1,3 +1,146 @@
|
||||||
# Constraint propagation
|
# Constraint propagation
|
||||||
|
|
||||||
The main work of the region inference is **constraint propagation**.
|
The main work of the region inference is **constraint
|
||||||
|
propagation**. This means processing the set of constraints to compute
|
||||||
|
the final values for all the region variables.
|
||||||
|
|
||||||
|
## Kinds of constraints
|
||||||
|
|
||||||
|
Each kind of constraint is handled somewhat differently by the region inferencer.
|
||||||
|
|
||||||
|
### Liveness constraints
|
||||||
|
|
||||||
|
A **liveness constraint** arises when some variable whose type
|
||||||
|
includes a region R is live at some point P. This simply means that
|
||||||
|
the value of R must include the point P. Liveness constraints are
|
||||||
|
computed by the MIR type checker.
|
||||||
|
|
||||||
|
We represent them by keeping a (sparse) bitset for each region
|
||||||
|
variable, which is the field [`liveness_constraints`], of type
|
||||||
|
[`LivenessValues`]
|
||||||
|
|
||||||
|
[`liveness_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.liveness_constraints
|
||||||
|
[`LivenessValues`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/values/struct.LivenessValues.html
|
||||||
|
|
||||||
|
### Outlives constraints
|
||||||
|
|
||||||
|
An outlives constraint `'a: 'b` indicates that the value of `'a` must
|
||||||
|
be a **superset** of the value of `'b`. On creation, we are given a
|
||||||
|
set of outlives constraints in the form of a
|
||||||
|
[`ConstraintSet`]. However, to work more efficiently with outlives
|
||||||
|
constraints, they are [converted into the form of a graph][graph-fn],
|
||||||
|
where the nodes of the graph are region variables (`'a`, `'b`) and
|
||||||
|
each constraint `'a: 'b` induces an edge `'a -> 'b`. This conversion
|
||||||
|
happens in the [`RegionInferenceContext::new`] function that creates
|
||||||
|
the inference context.
|
||||||
|
|
||||||
|
[`ConstraintSet`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/constraints/struct.ConstraintSet.html
|
||||||
|
[graph-fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/constraints/struct.ConstraintSet.html#method.graph
|
||||||
|
[`RegionInferenceContext::new`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#method.new
|
||||||
|
|
||||||
|
### Member constraints
|
||||||
|
|
||||||
|
A member constraint `'m member of ['c_1..'c_N]` expresses that the
|
||||||
|
region `'m` must be *equal* to some **choice regions** `'c_i` (for
|
||||||
|
some `i`). These constraints cannot be expressed by users, but they arise
|
||||||
|
from `impl Trait` due to its lifetime capture rules. Consinder a function
|
||||||
|
such as the following:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn make(a: &'a u32, b: &'b u32) -> impl Trait<'a, 'b> { .. }
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, the true return type (often called the "hidden type") is only
|
||||||
|
permitted to capture the lifeimes `'a` or `'b`. You can kind of see
|
||||||
|
this more clearly by desugaring that `impl Trait` return type into its
|
||||||
|
more explicit form:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
type MakeReturn<'x, 'y> = impl Trait<'x, 'y>;
|
||||||
|
fn make(a: &'a u32, b: &'b u32) -> MakeReturn<'a, 'b> { .. }
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, the idea is that the hidden type must be some type that could
|
||||||
|
have been written in place of the `impl Trait<'x, 'y>` -- but clearly
|
||||||
|
such a type can only reference the regions `'x` or `'y` (or
|
||||||
|
`'static`!), as those are the only names in scope. This limitation is
|
||||||
|
then translated into a restriction to only access `'a` or `'b` because
|
||||||
|
we are returning `MakeReturn<'a, 'b>`, where `'x` and `'y` have been
|
||||||
|
replaced with `'a` and `'b` respectively.
|
||||||
|
|
||||||
|
## SCCs in the outlives constraint graph
|
||||||
|
|
||||||
|
The most common sort of constraint in practice are outlives
|
||||||
|
constraints like `'a: 'b`. Such a cosntraint means that `'a` is a
|
||||||
|
superset of `'b`. So what happens if we have two regions `'a` and `'b`
|
||||||
|
that mutually outlive one another, like so?
|
||||||
|
|
||||||
|
```
|
||||||
|
'a: 'b
|
||||||
|
'b: 'a
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, we can conclude that `'a` and `'b` must be equal
|
||||||
|
sets. In fact, it doesn't have to be just two regions. We could create
|
||||||
|
an extended "chain" of outlives constraints:
|
||||||
|
|
||||||
|
```
|
||||||
|
'a: 'b
|
||||||
|
'b: 'c
|
||||||
|
'c: 'd
|
||||||
|
'd: 'a
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, we know that `'a..'d` are all equal to one another.
|
||||||
|
|
||||||
|
As mentioned above, an outlives constraint like `'a: 'b` can be viewed
|
||||||
|
as an edge in a graph `'a -> 'b`. Cycles in this graph indicate regions
|
||||||
|
that mutually outlive one another and hence must be equal.
|
||||||
|
|
||||||
|
Therefore, one of the first things that we do in propagating region
|
||||||
|
values is to compute the **strongly connected components** (SCCs) in
|
||||||
|
the constraint graph. The result is stored in the [`constraint_sccs`]
|
||||||
|
field. You can then easily find the SCC that a region `r` is a part of
|
||||||
|
by invoking `constraint_sccs.scc(r)`.
|
||||||
|
|
||||||
|
Working in terms of SCCs allows us to be more efficient: if we have a
|
||||||
|
set of regions `'a...'d` that are part of a single SCC, we don't have
|
||||||
|
to compute/store their values separarely. We can just store one value
|
||||||
|
**for the SCC**, since they must all be equal.
|
||||||
|
|
||||||
|
If you look over the region inference code, you will see that a number
|
||||||
|
of fields are defined in terms of SCCs. For example, the
|
||||||
|
[`scc_values`] field stores the values of each SCC. To get the value
|
||||||
|
of a specific region `'a` then, we first figure out the SCC that the
|
||||||
|
region is a part of, and then find the value of that SCC.
|
||||||
|
|
||||||
|
[`constraint_sccs`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.constraint_sccs
|
||||||
|
[`scc_values`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.scc_values
|
||||||
|
|
||||||
|
When we compute SCCs, we not only figure out which regions are a
|
||||||
|
member of each SCC, we also figure out the edges between them. So for example
|
||||||
|
consider this set of outlives constraints:
|
||||||
|
|
||||||
|
```
|
||||||
|
'a: 'b
|
||||||
|
'b: 'a
|
||||||
|
|
||||||
|
'a: 'c
|
||||||
|
|
||||||
|
'c: 'd
|
||||||
|
'd: 'c
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we have two SCCs: S0 contains `'a` and `'b`, and S1 contains `'c`
|
||||||
|
and `'d`. But these SCCs are not independent: because `'a: 'c`, that
|
||||||
|
means that `S0: S1` as well. That is -- the value of `S0` must be a
|
||||||
|
superset of the value of `S1`. One crucial thing is that this graph of
|
||||||
|
SCCs is always a DAG -- that is, it never has cycles. This is because
|
||||||
|
all the cycles have been removed to form the SCCs themselves.
|
||||||
|
|
||||||
|
## How constraint propagation works
|
||||||
|
|
||||||
|
The main work of constraint propagation is done in the
|
||||||
|
`propagation_constraints` function.
|
||||||
|
|
||||||
|
[`propagate_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#method.propagate_constraints
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue