]> git.proxmox.com Git - rustc.git/blame - src/doc/rustc-dev-guide/src/borrow_check/region_inference/constraint_propagation.md
New upstream version 1.52.0~beta.3+dfsg1
[rustc.git] / src / doc / rustc-dev-guide / src / borrow_check / region_inference / constraint_propagation.md
CommitLineData
dc9dc135
XL
1# Constraint propagation
2
6a06907d
XL
3<!-- toc -->
4
dc9dc135
XL
5The main work of the region inference is **constraint propagation**,
6which is done in the [`propagate_constraints`] function. There are
7three sorts of constraints that are used in NLL, and we'll explain how
8`propagate_constraints` works by "layering" those sorts of constraints
9on one at a time (each of them is fairly independent from the others):
10
11- liveness constraints (`R live at E`), which arise from liveness;
12- outlives constraints (`R1: R2`), which arise from subtyping;
13- [member constraints][m_c] (`member R_m of [R_c...]`), which arise from impl Trait.
14
dfeec247 15[`propagate_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/region_infer/struct.RegionInferenceContext.html#method.propagate_constraints
416331ca 16[m_c]: ./member_constraints.md
dc9dc135
XL
17
18In this chapter, we'll explain the "heart" of constraint propagation,
19covering both liveness and outlives constraints.
20
21## Notation and high-level concepts
22
23Conceptually, region inference is a "fixed-point" computation. It is
24given some set of constraints `{C}` and it computes a set of values
25`Values: R -> {E}` that maps each region `R` to a set of elements
26`{E}` (see [here][riv] for more notes on region elements):
27
28- Initially, each region is mapped to an empty set, so `Values(R) =
29 {}` for all regions `R`.
30- Next, we process the constraints repeatedly until a fixed-point is reached:
31 - For each constraint C:
32 - Update `Values` as needed to satisfy the constraint
33
416331ca 34[riv]: ../region_inference.md#region-variables
dc9dc135
XL
35
36As a simple example, if we have a liveness constraint `R live at E`,
37then we can apply `Values(R) = Values(R) union {E}` to make the
38constraint be satisfied. Similarly, if we have an outlives constraints
39`R1: R2`, we can apply `Values(R1) = Values(R1) union Values(R2)`.
40(Member constraints are more complex and we discuss them [in this section][m_c].)
41
42In practice, however, we are a bit more clever. Instead of applying
43the constraints in a loop, we can analyze the constraints and figure
44out the correct order to apply them, so that we only have to apply
45each constraint once in order to find the final result.
46
47Similarly, in the implementation, the `Values` set is stored in the
48`scc_values` field, but they are indexed not by a *region* but by a
49*strongly connected component* (SCC). SCCs are an optimization that
50avoids a lot of redundant storage and computation. They are explained
51in the section on outlives constraints.
52
53## Liveness constraints
54
55A **liveness constraint** arises when some variable whose type
56includes a region R is live at some [point] P. This simply means that
57the value of R must include the point P. Liveness constraints are
58computed by the MIR type checker.
59
ba9703b0 60[point]: ../../appendix/glossary.md#point
dc9dc135
XL
61
62A liveness constraint `R live at E` is satisfied if `E` is a member of
63`Values(R)`. So to "apply" such a constraint to `Values`, we just have
64to compute `Values(R) = Values(R) union {E}`.
65
66The liveness values are computed in the type-check and passed to the
67region inference upon creation in the `liveness_constraints` argument.
68These are not represented as individual constraints like `R live at E`
69though; instead, we store a (sparse) bitset per region variable (of
70type [`LivenessValues`]). This way we only need a single bit for each
71liveness constraint.
72
dfeec247
XL
73[`liveness_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/region_infer/struct.RegionInferenceContext.html#structfield.liveness_constraints
74[`LivenessValues`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/region_infer/values/struct.LivenessValues.html
dc9dc135
XL
75
76One thing that is worth mentioning: All lifetime parameters are always
77considered to be live over the entire function body. This is because
78they correspond to some portion of the *caller's* execution, and that
79execution clearly includes the time spent in this function, since the
80caller is waiting for us to return.
81
82## Outlives constraints
83
84An outlives constraint `'a: 'b` indicates that the value of `'a` must
85be a **superset** of the value of `'b`. That is, an outlives
86constraint `R1: R2` is satisfied if `Values(R1)` is a superset of
87`Values(R2)`. So to "apply" such a constraint to `Values`, we just
88have to compute `Values(R1) = Values(R1) union Values(R2)`.
89
90One observation that follows from this is that if you have `R1: R2`
91and `R2: R1`, then `R1 = R2` must be true. Similarly, if you have:
92
416331ca 93```txt
dc9dc135
XL
94R1: R2
95R2: R3
96R3: R4
97R4: R1
98```
99
100then `R1 = R2 = R3 = R4` follows. We take advantage of this to make things
101much faster, as described shortly.
102
103In the code, the set of outlives constraints is given to the region
104inference context on creation in a parameter of type
74b04a01 105[`OutlivesConstraintSet`]. The constraint set is basically just a list of `'a:
dc9dc135
XL
106'b` constraints.
107
108### The outlives constraint graph and SCCs
109
110In order to work more efficiently with outlives constraints, they are
111[converted into the form of a graph][graph-fn], where the nodes of the
112graph are region variables (`'a`, `'b`) and each constraint `'a: 'b`
113induces an edge `'a -> 'b`. This conversion happens in the
114[`RegionInferenceContext::new`] function that creates the inference
115context.
116
74b04a01 117[`OutlivesConstraintSet`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/constraints/struct.OutlivesConstraintSet.html
dfeec247
XL
118[graph-fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/constraints/struct.OutlivesConstraintSet.html#method.graph
119[`RegionInferenceContext::new`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/region_infer/struct.RegionInferenceContext.html#method.new
dc9dc135
XL
120
121When using a graph representation, we can detect regions that must be equal
122by looking for cycles. That is, if you have a constraint like
123
416331ca 124```txt
dc9dc135
XL
125'a: 'b
126'b: 'c
127'c: 'd
128'd: 'a
129```
130
131then this will correspond to a cycle in the graph containing the
132elements `'a...'d`.
133
134Therefore, one of the first things that we do in propagating region
135values is to compute the **strongly connected components** (SCCs) in
136the constraint graph. The result is stored in the [`constraint_sccs`]
137field. You can then easily find the SCC that a region `r` is a part of
138by invoking `constraint_sccs.scc(r)`.
139
140Working in terms of SCCs allows us to be more efficient: if we have a
141set of regions `'a...'d` that are part of a single SCC, we don't have
416331ca 142to compute/store their values separately. We can just store one value
dc9dc135
XL
143**for the SCC**, since they must all be equal.
144
145If you look over the region inference code, you will see that a number
146of fields are defined in terms of SCCs. For example, the
147[`scc_values`] field stores the values of each SCC. To get the value
148of a specific region `'a` then, we first figure out the SCC that the
149region is a part of, and then find the value of that SCC.
150
dfeec247
XL
151[`constraint_sccs`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/region_infer/struct.RegionInferenceContext.html#structfield.constraint_sccs
152[`scc_values`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/region_infer/struct.RegionInferenceContext.html#structfield.scc_values
dc9dc135
XL
153
154When we compute SCCs, we not only figure out which regions are a
155member of each SCC, we also figure out the edges between them. So for example
156consider this set of outlives constraints:
157
416331ca 158```txt
dc9dc135
XL
159'a: 'b
160'b: 'a
161
162'a: 'c
163
164'c: 'd
165'd: 'c
166```
167
168Here we have two SCCs: S0 contains `'a` and `'b`, and S1 contains `'c`
169and `'d`. But these SCCs are not independent: because `'a: 'c`, that
170means that `S0: S1` as well. That is -- the value of `S0` must be a
171superset of the value of `S1`. One crucial thing is that this graph of
172SCCs is always a DAG -- that is, it never has cycles. This is because
173all the cycles have been removed to form the SCCs themselves.
174
175### Applying liveness constraints to SCCs
176
177The liveness constraints that come in from the type-checker are
178expressed in terms of regions -- that is, we have a map like
179`Liveness: R -> {E}`. But we want our final result to be expressed
180in terms of SCCs -- we can integrate these liveness constraints very
181easily just by taking the union:
182
416331ca 183```txt
dc9dc135
XL
184for each region R:
185 let S be the SCC that contains R
186 Values(S) = Values(S) union Liveness(R)
187```
188
189In the region inferencer, this step is done in [`RegionInferenceContext::new`].
190
191### Applying outlives constraints
192
193Once we have computed the DAG of SCCs, we use that to structure out
194entire computation. If we have an edge `S1 -> S2` between two SCCs,
195that means that `Values(S1) >= Values(S2)` must hold. So, to compute
196the value of `S1`, we first compute the values of each successor `S2`.
197Then we simply union all of those values together. To use a
198quasi-iterator-like notation:
199
416331ca 200```txt
dc9dc135
XL
201Values(S1) =
202 s1.successors()
203 .map(|s2| Values(s2))
204 .union()
205```
206
207In the code, this work starts in the [`propagate_constraints`]
208function, which iterates over all the SCCs. For each SCC `S1`, we
209compute its value by first computing the value of its
210successors. Since SCCs form a DAG, we don't have to be concerned about
211cycles, though we do need to keep a set around to track whether we
212have already processed a given SCC or not. For each successor `S2`, once
213we have computed `S2`'s value, we can union those elements into the
214value for `S1`. (Although we have to be careful in this process to
215properly handle [higher-ranked
216placeholders](./placeholders_and_universes.html). Note that the value
217for `S1` already contains the liveness constraints, since they were
218added in [`RegionInferenceContext::new`].
219
220Once that process is done, we now have the "minimal value" for `S1`,
221taking into account all of the liveness and outlives
222constraints. However, in order to complete the process, we must also
223consider [member constraints][m_c], which are described in [a later
224section][m_c].