|
1 | 1 | # Constraint propagation
|
2 | 2 |
|
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