Skip to content

Commit 4615a9a

Browse files
nikomatsakismark-i-m
authored andcommitted
start filling out the constraint propagation chapter in more detail
1 parent 83ab6e4 commit 4615a9a

File tree

1 file changed

+144
-1
lines changed

1 file changed

+144
-1
lines changed
Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,146 @@
11
# Constraint propagation
22

3-
The main work of the region inference is **constraint propagation**.
3+
The main work of the region inference is **constraint
4+
propagation**. This means processing the set of constraints to compute
5+
the final values for all the region variables.
6+
7+
## Kinds of constraints
8+
9+
Each kind of constraint is handled somewhat differently by the region inferencer.
10+
11+
### Liveness constraints
12+
13+
A **liveness constraint** arises when some variable whose type
14+
includes a region R is live at some point P. This simply means that
15+
the value of R must include the point P. Liveness constraints are
16+
computed by the MIR type checker.
17+
18+
We represent them by keeping a (sparse) bitset for each region
19+
variable, which is the field [`liveness_constraints`], of type
20+
[`LivenessValues`]
21+
22+
[`liveness_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.liveness_constraints
23+
[`LivenessValues`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/values/struct.LivenessValues.html
24+
25+
### Outlives constraints
26+
27+
An outlives constraint `'a: 'b` indicates that the value of `'a` must
28+
be a **superset** of the value of `'b`. On creation, we are given a
29+
set of outlives constraints in the form of a
30+
[`ConstraintSet`]. However, to work more efficiently with outlives
31+
constraints, they are [converted into the form of a graph][graph-fn],
32+
where the nodes of the graph are region variables (`'a`, `'b`) and
33+
each constraint `'a: 'b` induces an edge `'a -> 'b`. This conversion
34+
happens in the [`RegionInferenceContext::new`] function that creates
35+
the inference context.
36+
37+
[`ConstraintSet`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/constraints/struct.ConstraintSet.html
38+
[graph-fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/constraints/struct.ConstraintSet.html#method.graph
39+
[`RegionInferenceContext::new`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#method.new
40+
41+
### Member constraints
42+
43+
A member constraint `'m member of ['c_1..'c_N]` expresses that the
44+
region `'m` must be *equal* to some **choice regions** `'c_i` (for
45+
some `i`). These constraints cannot be expressed by users, but they arise
46+
from `impl Trait` due to its lifetime capture rules. Consinder a function
47+
such as the following:
48+
49+
```rust
50+
fn make(a: &'a u32, b: &'b u32) -> impl Trait<'a, 'b> { .. }
51+
```
52+
53+
Here, the true return type (often called the "hidden type") is only
54+
permitted to capture the lifeimes `'a` or `'b`. You can kind of see
55+
this more clearly by desugaring that `impl Trait` return type into its
56+
more explicit form:
57+
58+
```rust
59+
type MakeReturn<'x, 'y> = impl Trait<'x, 'y>;
60+
fn make(a: &'a u32, b: &'b u32) -> MakeReturn<'a, 'b> { .. }
61+
```
62+
63+
Here, the idea is that the hidden type must be some type that could
64+
have been written in place of the `impl Trait<'x, 'y>` -- but clearly
65+
such a type can only reference the regions `'x` or `'y` (or
66+
`'static`!), as those are the only names in scope. This limitation is
67+
then translated into a restriction to only access `'a` or `'b` because
68+
we are returning `MakeReturn<'a, 'b>`, where `'x` and `'y` have been
69+
replaced with `'a` and `'b` respectively.
70+
71+
## SCCs in the outlives constraint graph
72+
73+
The most common sort of constraint in practice are outlives
74+
constraints like `'a: 'b`. Such a cosntraint means that `'a` is a
75+
superset of `'b`. So what happens if we have two regions `'a` and `'b`
76+
that mutually outlive one another, like so?
77+
78+
```
79+
'a: 'b
80+
'b: 'a
81+
```
82+
83+
In this case, we can conclude that `'a` and `'b` must be equal
84+
sets. In fact, it doesn't have to be just two regions. We could create
85+
an extended "chain" of outlives constraints:
86+
87+
```
88+
'a: 'b
89+
'b: 'c
90+
'c: 'd
91+
'd: 'a
92+
```
93+
94+
Here, we know that `'a..'d` are all equal to one another.
95+
96+
As mentioned above, an outlives constraint like `'a: 'b` can be viewed
97+
as an edge in a graph `'a -> 'b`. Cycles in this graph indicate regions
98+
that mutually outlive one another and hence must be equal.
99+
100+
Therefore, one of the first things that we do in propagating region
101+
values is to compute the **strongly connected components** (SCCs) in
102+
the constraint graph. The result is stored in the [`constraint_sccs`]
103+
field. You can then easily find the SCC that a region `r` is a part of
104+
by invoking `constraint_sccs.scc(r)`.
105+
106+
Working in terms of SCCs allows us to be more efficient: if we have a
107+
set of regions `'a...'d` that are part of a single SCC, we don't have
108+
to compute/store their values separarely. We can just store one value
109+
**for the SCC**, since they must all be equal.
110+
111+
If you look over the region inference code, you will see that a number
112+
of fields are defined in terms of SCCs. For example, the
113+
[`scc_values`] field stores the values of each SCC. To get the value
114+
of a specific region `'a` then, we first figure out the SCC that the
115+
region is a part of, and then find the value of that SCC.
116+
117+
[`constraint_sccs`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.constraint_sccs
118+
[`scc_values`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.scc_values
119+
120+
When we compute SCCs, we not only figure out which regions are a
121+
member of each SCC, we also figure out the edges between them. So for example
122+
consider this set of outlives constraints:
123+
124+
```
125+
'a: 'b
126+
'b: 'a
127+
128+
'a: 'c
129+
130+
'c: 'd
131+
'd: 'c
132+
```
133+
134+
Here we have two SCCs: S0 contains `'a` and `'b`, and S1 contains `'c`
135+
and `'d`. But these SCCs are not independent: because `'a: 'c`, that
136+
means that `S0: S1` as well. That is -- the value of `S0` must be a
137+
superset of the value of `S1`. One crucial thing is that this graph of
138+
SCCs is always a DAG -- that is, it never has cycles. This is because
139+
all the cycles have been removed to form the SCCs themselves.
140+
141+
## How constraint propagation works
142+
143+
The main work of constraint propagation is done in the
144+
`propagation_constraints` function.
145+
146+
[`propagate_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#method.propagate_constraints

0 commit comments

Comments
 (0)