|
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