|
3 | 3 | //! issues. Exposed raw pointers (i.e. those cast to `usize`) and function calls would make this simple |
4 | 4 | //! analysis unsound, so we have to handle them as follows: |
5 | 5 | //! |
| 6 | +//! ```text |
6 | 7 | //! * exposed pointers (i.e. a cast of a raw pointer to `usize`) |
7 | 8 | //! => These count towards the liveness of the `Local` that is behind the raw pointer, e.g. for |
8 | 9 | //! `_5 = _4 as usize` (where `_4 = AddressOf(_3)`), we keep `_3` alive on any use site of _5. |
|
28 | 29 | //! Let `Locals2` be the set of `Local`s that need to be kept alive due to the borrows corresponding to `Op2` |
29 | 30 | //! Then we need to start tracking all `Local`s in `Locals2` for the borrow corresponding to `Op1`, since the `Place` |
30 | 31 | //! corresponding to `Op2` might be moved into `Op1` in then call. |
| 32 | +//! ``` |
31 | 33 | //! |
32 | 34 | //! As an example for what this analysis does: |
33 | 35 | //! |
|
67 | 69 | //! yield 3; // live locals: [`Local4`] |
68 | 70 | //! |
69 | 71 | //! takes_and_returns_ref(bar3_ref); // live locals: [`Local4`] |
70 | | -//! } |
| 72 | +//! } |
71 | 73 | //! } |
72 | 74 | //! ``` |
73 | 75 | //! |
74 | 76 | //! Following is a description of the algorithm: |
| 77 | +//! ```text |
75 | 78 | //! 1. build a dependency graph that relates `Local`s based on their borrowing relationships. |
76 | 79 | //! As an example if we have something like this (in a simplified MIR representation): |
77 | 80 | //! |
78 | | -//! ```ignore |
79 | 81 | //! _4 = Bar {} |
80 | 82 | //! _5 = Ref(_4) |
81 | 83 | //! ... |
82 | 84 | //! _10 = f(_5) |
83 | | -//! ``` |
84 | 85 | //! |
85 | 86 | //! Then we add edges from `_5` to `_4` and `_4` to `_5` (for an explanation of why the edge |
86 | 87 | //! from `_4` to `_5` is necessary see the comment in `handle_ravlue_or_ptr`) and from |
|
95 | 96 | //! in that range we traverse our dependency graph and look for nodes that correspond to borrowed |
96 | 97 | //! `Local`s ("leaf nodes"). In our example we would find an edge from `_5` to `_4`, which is a leaf |
97 | 98 | //! node and hence we keep `_4` live over that range. |
| 99 | +//! ``` |
98 | 100 | //! |
99 | 101 | //! There are some corner cases we need to look out for to make this analysis sound. Let's look |
100 | 102 | //! at each of the three steps in more detail and elaborate how these steps deal with these corner |
|
104 | 106 | //! |
105 | 107 | //! The `Node`s in the dependency graph include data values of type `NodeKind`. `NodeKind` has |
106 | 108 | //! three variants: `Local`, `Borrow` and `LocalWithRefs`. |
| 109 | +//! ```text |
107 | 110 | //! * `NodeKind::Local` is used for `Local`s that are borrowed somewhere (`_4` in our example), but aren't |
108 | 111 | //! themselves references or pointers. |
109 | 112 | //! * `NodeKind::Borrow` is used for `Local`s that correspond to borrows (`_5` in our example). We equate |
110 | 113 | //! re-borrows with the `Node` that corresponds to the original borrow. |
111 | 114 | //! * `NodeKind::LocalWithRefs` is used for `Local`s that aren't themselves refs/ptrs, but contain |
112 | 115 | //! `Local`s that correspond to refs/ptrs or other `Local`s with `Node`s of kind `NodeKind::LocalWithRef`s. |
113 | 116 | //! `LocalWithRefs` is also used for exposed pointers. |
114 | | -//! Let's look at an example: |
| 117 | +//! ``` |
115 | 118 | //! |
116 | | -//! ```ignore |
117 | | -//! _4 = Bar {} |
118 | | -//! _5 = Ref(_4) |
119 | | -//! _6 = Foo(..)(move _5) |
120 | | -//! ... |
121 | | -//! _7 = (_6.0) |
122 | | -//! ``` |
| 119 | +//! Let's look at an example: |
123 | 120 | //! |
124 | | -//! In this example `_6` would be given `NodeKind::LocalWithRefs` and our graph would look |
125 | | -//! as follows: |
| 121 | +//! ```text |
| 122 | +//! _4 = Bar {} |
| 123 | +//! _5 = Ref(_4) |
| 124 | +//! _6 = Foo(..)(move _5) |
| 125 | +//! ... |
| 126 | +//! _7 = (_6.0) |
126 | 127 | //! |
127 | | -//! `_7` (NodeKind::Borrow) <-> `_6` (NodeKind::LocalWithRefs) <-> `_5` (NodeKind::Borrow) <-> `_4` (NodeKind::Local) |
| 128 | +//! In this example `_6` would be given `NodeKind::LocalWithRefs` and our graph would look |
| 129 | +//! as follows: |
128 | 130 | //! |
129 | | -//! On the one hand we need to treat `Local`s with `Node`s of kind `NodeKind::LocalWithRefs` similarly |
130 | | -//! to how we treat `Local`s with `Node`s of kind `NodeKind::Local`, in the sense that if they are |
131 | | -//! borrowed we want to keep them live over the live range of the borrow. But on the other hand we |
132 | | -//! want to also treat them like `Local`s with `Node`s of kind `NodeKind::Borrow` as they ultimately |
133 | | -//! could also contain references or pointers that refer to other `Local`s. So we want a |
134 | | -//! path in the graph from a `NodeKind::LocalWithRef`s node to the `NodeKind::Local` nodes, whose borrows |
135 | | -//! they might contain. |
| 131 | +//! `_7` (NodeKind::Borrow) <-> `_6` (NodeKind::LocalWithRefs) <-> `_5` (NodeKind::Borrow) <-> `_4` (NodeKind::Local) |
136 | 132 | //! |
137 | | -//! Additionally `NodeKind::LocalWithRefs` is also used for raw pointers that are cast to |
138 | | -//! `usize`: |
| 133 | +//! On the one hand we need to treat `Local`s with `Node`s of kind `NodeKind::LocalWithRefs` similarly |
| 134 | +//! to how we treat `Local`s with `Node`s of kind `NodeKind::Local`, in the sense that if they are |
| 135 | +//! borrowed we want to keep them live over the live range of the borrow. But on the other hand we |
| 136 | +//! want to also treat them like `Local`s with `Node`s of kind `NodeKind::Borrow` as they ultimately |
| 137 | +//! could also contain references or pointers that refer to other `Local`s. So we want a |
| 138 | +//! path in the graph from a `NodeKind::LocalWithRef`s node to the `NodeKind::Local` nodes, whose borrows |
| 139 | +//! they might contain. |
139 | 140 | //! |
140 | | -//! ```ignore |
141 | | -//! _4 = Bar {} |
142 | | -//! _5 = AddressOf(_4) |
143 | | -//! _6 = _5 as usize |
144 | | -//! _7 = Aggregate(..) (move _6) |
145 | | -//! _8 = (_7.0) |
146 | | -//! ``` |
| 141 | +//! Additionally `NodeKind::LocalWithRefs` is also used for raw pointers that are cast to |
| 142 | +//! `usize`: |
147 | 143 | //! |
148 | | -//! In this example our graph would have the following edges: |
149 | | -//! * `_5` (Borrow) <-> `_4` (Local) |
150 | | -//! * `_6` (LocalWithRefs) <-> `_5` (Borrow) |
151 | | -//! * `_7` (LocalWithRefs) <-> `_6` (LocalWithRefs) |
152 | | -//! * `_8` (LocalWithRefs) <-> `_7` (LocalWithRefs) |
| 144 | +//! _4 = Bar {} |
| 145 | +//! _5 = AddressOf(_4) |
| 146 | +//! _6 = _5 as usize |
| 147 | +//! _7 = Aggregate(..) (move _6) |
| 148 | +//! _8 = (_7.0) |
153 | 149 | //! |
154 | | -//! We also have to be careful about dealing with `Terminator`s. Whenever we pass references, |
155 | | -//! pointers or `Local`s with `NodeKind::LocalWithRefs` to a `TerminatorKind::Call` or |
156 | | -//! `TerminatorKind::Yield`, the destination `Place` or resume place, resp., might contain |
157 | | -//! these references, pointers or `LocalWithRefs`, hence we have to be conservative |
158 | | -//! and keep the `destination` `Local` and `resume_arg` `Local` live. |
| 150 | +//! In this example our graph would have the following edges: |
| 151 | +//! * `_5` (Borrow) <-> `_4` (Local) |
| 152 | +//! * `_6` (LocalWithRefs) <-> `_5` (Borrow) |
| 153 | +//! * `_7` (LocalWithRefs) <-> `_6` (LocalWithRefs) |
| 154 | +//! * `_8` (LocalWithRefs) <-> `_7` (LocalWithRefs) |
| 155 | +//! |
| 156 | +//! We also have to be careful about dealing with `Terminator`s. Whenever we pass references, |
| 157 | +//! pointers or `Local`s with `NodeKind::LocalWithRefs` to a `TerminatorKind::Call` or |
| 158 | +//! `TerminatorKind::Yield`, the destination `Place` or resume place, resp., might contain |
| 159 | +//! these references, pointers or `LocalWithRefs`, hence we have to be conservative |
| 160 | +//! and keep the `destination` `Local` and `resume_arg` `Local` live. |
| 161 | +//! ``` |
159 | 162 | //! |
160 | 163 | //! 2. Liveness analysis for borrows |
161 | 164 | //! |
162 | 165 | //! We perform a standard liveness analysis on any outstanding references, pointers or `LocalWithRefs` |
163 | 166 | //! So we `gen` at any use site, which are either direct uses of these `Local`s or projections that contain |
164 | 167 | //! these `Local`s. So e.g.: |
165 | 168 | //! |
166 | | -//! ```ignore |
| 169 | +//! ```text |
167 | 170 | //! 1. _3 = Foo {} |
168 | 171 | //! 2. _4 = Bar {} |
169 | 172 | //! 3. _5 = Ref(_3) |
|
173 | 176 | //! 7. _9 = (_8.0) |
174 | 177 | //! 8. _10 = const 5 |
175 | 178 | //! 9. (_7.0) = move _10 |
176 | | -//! ``` |
177 | 179 | //! |
178 | 180 | //! * `_5` is live from stmt 3 to stmt 9 |
179 | 181 | //! * `_6` is live from stmt 4 to stmt 7 |
180 | 182 | //! * `_7` is a `Local` of kind `LocalWithRefs` so needs to be taken into account in the |
181 | 183 | //! analyis. It's live from stmt 5 to stmt 9 |
182 | 184 | //! * `_8` is a `Local` of kind `LocalWithRefs`. It's live from 6. to 7. |
183 | 185 | //! * `_9` is a `Local` of kind `LocalWithRefs`. It's live at 7. |
| 186 | +//! ``` |
184 | 187 | //! |
185 | 188 | //! 3. Determining which `Local`s are borrowed |
186 | 189 | //! |
187 | 190 | //! Let's use our last example again. The dependency graph for that example looks as follows: |
188 | 191 | //! |
| 192 | +//! ```text |
189 | 193 | //! `_5` (Borrow) <-> `_3` (Local) |
190 | 194 | //! `_6` (Borrow) <-> `_4` (Local) |
191 | 195 | //! `_7` (LocalWithRef) <-> `_5` (Borrow) |
192 | 196 | //! `_8` (LocalWithRef) -> `_6` (Borrow) |
193 | 197 | //! `_9` (LocalWithRef) <-> `_8` (LocalWithRef) |
194 | 198 | //! `_7` (LocalWithRef) <-> `_10` (Local) |
| 199 | +//! ``` |
195 | 200 | //! |
196 | 201 | //! We then construct a strongly connected components graph from the dependency graph, yielding: |
197 | 202 | //! |
| 203 | +//! ```text |
198 | 204 | //! SCC1: [_3, _5, _7, _10] |
199 | 205 | //! SCC2: [_4, _6, _8, _9] |
| 206 | +//! ``` |
200 | 207 | //! |
201 | 208 | //! Now for each statement in the `Body` we check which refs/ptrs or `LocalWithRefs` are live at that statement |
202 | 209 | //! and then perform a depth-first search in the scc graph, collecting all `Local`s that need to be kept alive |
203 | 210 | //! (`Local`s that have `Node`s in the graph of either `NodeKind::Local` or `NodeKind::LocalWithRefs`). |
204 | 211 | //! So at each of those statements we have the following `Local`s that are live due to borrows: |
205 | 212 | //! |
| 213 | +//! ```text |
206 | 214 | //! 1. {} |
207 | 215 | //! 2. {} |
208 | 216 | //! 3. {_3} |
|
212 | 220 | //! 7. {_3, _4, _7, _8, _9} |
213 | 221 | //! 8. {_3, _7} |
214 | 222 | //! 9. {_3, _7, _10} |
| 223 | +//! ``` |
215 | 224 | //! |
216 | 225 | //! Ensuring soundness in all cases requires us to be more conservative (i.e. keeping more `Local`s alive) than necessary |
217 | 226 | //! in most situations. To eliminate all the unnecessary `Local`s we use the fact that the analysis performed by |
218 | 227 | //! `MaybeBorrowedLocals` functions as an upper bound for which `Local`s need to be kept alive. Hence we take the intersection |
219 | 228 | //! of the two analyses at each statement. The results of `MaybeBorrowedLocals` for our example are: |
220 | 229 | //! |
| 230 | +//! ```text |
221 | 231 | //! 1. {} |
222 | 232 | //! 2. {} |
223 | 233 | //! 3. {_3} |
|
227 | 237 | //! 7. {_3, _4,} |
228 | 238 | //! 8. {_3, _4} |
229 | 239 | //! 9. {_3, _4} |
| 240 | +//! ``` |
230 | 241 | //! |
231 | 242 | //! Taking the intersection hence yields: |
232 | 243 | //! |
| 244 | +//! ```text |
233 | 245 | //! 1. {} |
234 | 246 | //! 2. {} |
235 | 247 | //! 3. {_3} |
|
239 | 251 | //! 7. {_3, _4} |
240 | 252 | //! 8. {_3} |
241 | 253 | //! 9. {_3} |
| 254 | +//! ``` |
242 | 255 |
|
243 | 256 | use super::*; |
244 | 257 |
|
|
0 commit comments