|
1 |
| -use super::{possible_origin::PossibleOriginVisitor, transitive_relation::TransitiveRelation}; |
| 1 | +use super::possible_origin::PossibleOriginVisitor; |
2 | 2 | use crate::ty::is_copy;
|
3 |
| -use rustc_data_structures::fx::FxHashMap; |
| 3 | +use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; |
4 | 4 | use rustc_index::bit_set::{BitSet, HybridBitSet};
|
5 | 5 | use rustc_lint::LateContext;
|
6 |
| -use rustc_middle::mir::{self, visit::Visitor as _, Mutability}; |
7 |
| -use rustc_middle::ty::{self, visit::TypeVisitor}; |
8 |
| -use rustc_mir_dataflow::{impls::MaybeStorageLive, Analysis, ResultsCursor}; |
| 6 | +use rustc_middle::mir::{ |
| 7 | + self, visit::Visitor as _, BasicBlock, Local, Location, Mutability, Statement, StatementKind, Terminator, |
| 8 | +}; |
| 9 | +use rustc_middle::ty::{self, visit::TypeVisitor, TyCtxt}; |
| 10 | +use rustc_mir_dataflow::{ |
| 11 | + fmt::DebugWithContext, impls::MaybeStorageLive, lattice::JoinSemiLattice, Analysis, AnalysisDomain, |
| 12 | + CallReturnPlaces, ResultsCursor, |
| 13 | +}; |
| 14 | +use std::collections::VecDeque; |
9 | 15 | use std::ops::ControlFlow;
|
10 | 16 |
|
11 | 17 | /// Collects the possible borrowers of each local.
|
12 | 18 | /// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
|
13 | 19 | /// possible borrowers of `a`.
|
14 | 20 | #[allow(clippy::module_name_repetitions)]
|
15 |
| -struct PossibleBorrowerVisitor<'a, 'b, 'tcx> { |
16 |
| - possible_borrower: TransitiveRelation, |
| 21 | +struct PossibleBorrowerAnalysis<'b, 'tcx> { |
| 22 | + tcx: TyCtxt<'tcx>, |
17 | 23 | body: &'b mir::Body<'tcx>,
|
18 |
| - cx: &'a LateContext<'tcx>, |
19 | 24 | possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
20 | 25 | }
|
21 | 26 |
|
22 |
| -impl<'a, 'b, 'tcx> PossibleBorrowerVisitor<'a, 'b, 'tcx> { |
23 |
| - fn new( |
24 |
| - cx: &'a LateContext<'tcx>, |
25 |
| - body: &'b mir::Body<'tcx>, |
26 |
| - possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>, |
27 |
| - ) -> Self { |
| 27 | +#[derive(Clone, Debug, Eq, PartialEq)] |
| 28 | +struct PossibleBorrowerState { |
| 29 | + map: FxIndexMap<Local, BitSet<Local>>, |
| 30 | + domain_size: usize, |
| 31 | +} |
| 32 | + |
| 33 | +impl PossibleBorrowerState { |
| 34 | + fn new(domain_size: usize) -> Self { |
28 | 35 | Self {
|
29 |
| - possible_borrower: TransitiveRelation::default(), |
30 |
| - cx, |
31 |
| - body, |
32 |
| - possible_origin, |
| 36 | + map: FxIndexMap::default(), |
| 37 | + domain_size, |
33 | 38 | }
|
34 | 39 | }
|
35 | 40 |
|
36 |
| - fn into_map( |
37 |
| - self, |
38 |
| - cx: &'a LateContext<'tcx>, |
39 |
| - maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive>, |
40 |
| - ) -> PossibleBorrowerMap<'b, 'tcx> { |
41 |
| - let mut map = FxHashMap::default(); |
42 |
| - for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) { |
43 |
| - if is_copy(cx, self.body.local_decls[row].ty) { |
44 |
| - continue; |
45 |
| - } |
| 41 | + #[allow(clippy::similar_names)] |
| 42 | + fn add(&mut self, borrowed: Local, borrower: Local) { |
| 43 | + self.map |
| 44 | + .entry(borrowed) |
| 45 | + .or_insert(BitSet::new_empty(self.domain_size)) |
| 46 | + .insert(borrower); |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +impl<C> DebugWithContext<C> for PossibleBorrowerState { |
| 51 | + fn fmt_with(&self, _ctxt: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 52 | + <_ as std::fmt::Debug>::fmt(self, f) |
| 53 | + } |
| 54 | + fn fmt_diff_with(&self, _old: &Self, _ctxt: &C, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 55 | + unimplemented!() |
| 56 | + } |
| 57 | +} |
46 | 58 |
|
47 |
| - let mut borrowers = self.possible_borrower.reachable_from(row, self.body.local_decls.len()); |
48 |
| - borrowers.remove(mir::Local::from_usize(0)); |
| 59 | +impl JoinSemiLattice for PossibleBorrowerState { |
| 60 | + fn join(&mut self, other: &Self) -> bool { |
| 61 | + let mut changed = false; |
| 62 | + for (&borrowed, borrowers) in other.map.iter() { |
49 | 63 | if !borrowers.is_empty() {
|
50 |
| - map.insert(row, borrowers); |
| 64 | + changed |= self |
| 65 | + .map |
| 66 | + .entry(borrowed) |
| 67 | + .or_insert(BitSet::new_empty(self.domain_size)) |
| 68 | + .union(borrowers); |
51 | 69 | }
|
52 | 70 | }
|
| 71 | + changed |
| 72 | + } |
| 73 | +} |
53 | 74 |
|
54 |
| - let bs = BitSet::new_empty(self.body.local_decls.len()); |
55 |
| - PossibleBorrowerMap { |
56 |
| - map, |
57 |
| - maybe_live, |
58 |
| - bitset: (bs.clone(), bs), |
| 75 | +impl<'b, 'tcx> AnalysisDomain<'tcx> for PossibleBorrowerAnalysis<'b, 'tcx> { |
| 76 | + type Domain = PossibleBorrowerState; |
| 77 | + |
| 78 | + const NAME: &'static str = "possible_borrower"; |
| 79 | + |
| 80 | + fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain { |
| 81 | + PossibleBorrowerState::new(body.local_decls.len()) |
| 82 | + } |
| 83 | + |
| 84 | + fn initialize_start_block(&self, _body: &mir::Body<'tcx>, _entry_set: &mut Self::Domain) {} |
| 85 | +} |
| 86 | + |
| 87 | +impl<'b, 'tcx> PossibleBorrowerAnalysis<'b, 'tcx> { |
| 88 | + fn new( |
| 89 | + tcx: TyCtxt<'tcx>, |
| 90 | + body: &'b mir::Body<'tcx>, |
| 91 | + possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>, |
| 92 | + ) -> Self { |
| 93 | + Self { |
| 94 | + tcx, |
| 95 | + body, |
| 96 | + possible_origin, |
59 | 97 | }
|
60 | 98 | }
|
61 | 99 | }
|
62 | 100 |
|
63 |
| -impl<'a, 'b, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'b, 'tcx> { |
64 |
| - fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) { |
65 |
| - let lhs = place.local; |
66 |
| - match rvalue { |
67 |
| - mir::Rvalue::Ref(_, _, borrowed) => { |
68 |
| - self.possible_borrower.add(borrowed.local, lhs); |
69 |
| - }, |
70 |
| - other => { |
71 |
| - if ContainsRegion |
72 |
| - .visit_ty(place.ty(&self.body.local_decls, self.cx.tcx).ty) |
73 |
| - .is_continue() |
74 |
| - { |
75 |
| - return; |
76 |
| - } |
77 |
| - rvalue_locals(other, |rhs| { |
78 |
| - if lhs != rhs { |
79 |
| - self.possible_borrower.add(rhs, lhs); |
| 101 | +impl<'b, 'tcx> Analysis<'tcx> for PossibleBorrowerAnalysis<'b, 'tcx> { |
| 102 | + fn apply_call_return_effect( |
| 103 | + &self, |
| 104 | + _state: &mut Self::Domain, |
| 105 | + _block: BasicBlock, |
| 106 | + _return_places: CallReturnPlaces<'_, 'tcx>, |
| 107 | + ) { |
| 108 | + } |
| 109 | + |
| 110 | + fn apply_statement_effect(&self, state: &mut Self::Domain, statement: &Statement<'tcx>, _location: Location) { |
| 111 | + if let StatementKind::Assign(box (place, rvalue)) = &statement.kind { |
| 112 | + let lhs = place.local; |
| 113 | + match rvalue { |
| 114 | + mir::Rvalue::Ref(_, _, borrowed) => { |
| 115 | + state.add(borrowed.local, lhs); |
| 116 | + }, |
| 117 | + other => { |
| 118 | + if ContainsRegion |
| 119 | + .visit_ty(place.ty(&self.body.local_decls, self.tcx).ty) |
| 120 | + .is_continue() |
| 121 | + { |
| 122 | + return; |
80 | 123 | }
|
81 |
| - }); |
82 |
| - }, |
| 124 | + rvalue_locals(other, |rhs| { |
| 125 | + if lhs != rhs { |
| 126 | + state.add(rhs, lhs); |
| 127 | + } |
| 128 | + }); |
| 129 | + }, |
| 130 | + } |
83 | 131 | }
|
84 | 132 | }
|
85 | 133 |
|
86 |
| - fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Location) { |
| 134 | + fn apply_terminator_effect(&self, state: &mut Self::Domain, terminator: &Terminator<'tcx>, _location: Location) { |
87 | 135 | if let mir::TerminatorKind::Call {
|
88 | 136 | args,
|
89 | 137 | destination: mir::Place { local: dest, .. },
|
@@ -123,10 +171,10 @@ impl<'a, 'b, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'b,
|
123 | 171 |
|
124 | 172 | for y in mutable_variables {
|
125 | 173 | for x in &immutable_borrowers {
|
126 |
| - self.possible_borrower.add(*x, y); |
| 174 | + state.add(*x, y); |
127 | 175 | }
|
128 | 176 | for x in &mutable_borrowers {
|
129 |
| - self.possible_borrower.add(*x, y); |
| 177 | + state.add(*x, y); |
130 | 178 | }
|
131 | 179 | }
|
132 | 180 | }
|
@@ -162,73 +210,94 @@ fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) {
|
162 | 210 | }
|
163 | 211 | }
|
164 | 212 |
|
165 |
| -/// Result of `PossibleBorrowerVisitor`. |
| 213 | +/// Result of `PossibleBorrowerAnalysis`. |
166 | 214 | #[allow(clippy::module_name_repetitions)]
|
167 | 215 | pub struct PossibleBorrowerMap<'b, 'tcx> {
|
168 |
| - /// Mapping `Local -> its possible borrowers` |
169 |
| - pub map: FxHashMap<mir::Local, HybridBitSet<mir::Local>>, |
| 216 | + body: &'b mir::Body<'tcx>, |
| 217 | + possible_borrower: ResultsCursor<'b, 'tcx, PossibleBorrowerAnalysis<'b, 'tcx>>, |
170 | 218 | maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive>,
|
171 |
| - // Caches to avoid allocation of `BitSet` on every query |
172 |
| - pub bitset: (BitSet<mir::Local>, BitSet<mir::Local>), |
173 | 219 | }
|
174 | 220 |
|
175 |
| -impl<'a, 'b, 'tcx> PossibleBorrowerMap<'b, 'tcx> { |
176 |
| - pub fn new(cx: &'a LateContext<'tcx>, mir: &'b mir::Body<'tcx>) -> Self { |
| 221 | +impl<'b, 'tcx> PossibleBorrowerMap<'b, 'tcx> { |
| 222 | + pub fn new(cx: &LateContext<'tcx>, mir: &'b mir::Body<'tcx>) -> Self { |
177 | 223 | let possible_origin = {
|
178 | 224 | let mut vis = PossibleOriginVisitor::new(mir);
|
179 | 225 | vis.visit_body(mir);
|
180 | 226 | vis.into_map(cx)
|
181 | 227 | };
|
182 |
| - let maybe_storage_live_result = MaybeStorageLive::new(BitSet::new_empty(mir.local_decls.len())) |
| 228 | + let possible_borrower = PossibleBorrowerAnalysis::new(cx.tcx, mir, possible_origin) |
183 | 229 | .into_engine(cx.tcx, mir)
|
184 |
| - .pass_name("redundant_clone") |
| 230 | + .pass_name("possible_borrower") |
185 | 231 | .iterate_to_fixpoint()
|
186 | 232 | .into_results_cursor(mir);
|
187 |
| - let mut vis = PossibleBorrowerVisitor::new(cx, mir, possible_origin); |
188 |
| - vis.visit_body(mir); |
189 |
| - vis.into_map(cx, maybe_storage_live_result) |
190 |
| - } |
191 |
| - |
192 |
| - /// Returns true if the set of borrowers of `borrowed` living at `at` matches with `borrowers`. |
193 |
| - pub fn only_borrowers(&mut self, borrowers: &[mir::Local], borrowed: mir::Local, at: mir::Location) -> bool { |
194 |
| - self.bounded_borrowers(borrowers, borrowers, borrowed, at) |
| 233 | + let maybe_live = MaybeStorageLive::new(BitSet::new_empty(mir.local_decls.len())) |
| 234 | + .into_engine(cx.tcx, mir) |
| 235 | + .pass_name("possible_borrower") |
| 236 | + .iterate_to_fixpoint() |
| 237 | + .into_results_cursor(mir); |
| 238 | + PossibleBorrowerMap { |
| 239 | + body: mir, |
| 240 | + possible_borrower, |
| 241 | + maybe_live, |
| 242 | + } |
195 | 243 | }
|
196 | 244 |
|
197 |
| - /// Returns true if the set of borrowers of `borrowed` living at `at` includes at least `below` |
198 |
| - /// but no more than `above`. |
199 |
| - pub fn bounded_borrowers( |
| 245 | + /// Returns true if the set of borrowers of `borrowed` living at `at` includes no more than |
| 246 | + /// `borrowers`. |
| 247 | + /// Notes: |
| 248 | + /// 1. It would be nice if `PossibleBorrowerMap` could store `cx` so that `at_most_borrowers` |
| 249 | + /// would not require it to be passed in. But a `PossibleBorrowerMap` is stored in `LintPass` |
| 250 | + /// `Dereferencing`, which outlives any `LateContext`. |
| 251 | + /// 2. In all current uses of `at_most_borrowers`, `borrowers` is a slice of at most two |
| 252 | + /// elements. Thus, `borrowers.contains(...)` is effectively a constant-time operation. If |
| 253 | + /// `at_most_borrowers`'s uses were to expand beyond this, its implementation might have to be |
| 254 | + /// adjusted. |
| 255 | + pub fn at_most_borrowers( |
200 | 256 | &mut self,
|
201 |
| - below: &[mir::Local], |
202 |
| - above: &[mir::Local], |
| 257 | + cx: &LateContext<'tcx>, |
| 258 | + borrowers: &[mir::Local], |
203 | 259 | borrowed: mir::Local,
|
204 | 260 | at: mir::Location,
|
205 | 261 | ) -> bool {
|
206 |
| - self.maybe_live.seek_after_primary_effect(at); |
| 262 | + if is_copy(cx, self.body.local_decls[borrowed].ty) { |
| 263 | + return true; |
| 264 | + } |
| 265 | + |
| 266 | + self.possible_borrower.seek_before_primary_effect(at); |
| 267 | + self.maybe_live.seek_before_primary_effect(at); |
207 | 268 |
|
208 |
| - self.bitset.0.clear(); |
209 |
| - let maybe_live = &mut self.maybe_live; |
210 |
| - if let Some(bitset) = self.map.get(&borrowed) { |
211 |
| - for b in bitset.iter().filter(move |b| maybe_live.contains(*b)) { |
212 |
| - self.bitset.0.insert(b); |
| 269 | + let possible_borrower = &self.possible_borrower.get().map; |
| 270 | + let maybe_live = &self.maybe_live; |
| 271 | + |
| 272 | + let mut queued = BitSet::new_empty(self.body.local_decls.len()); |
| 273 | + let mut deque = VecDeque::with_capacity(self.body.local_decls.len()); |
| 274 | + |
| 275 | + if let Some(borrowers) = possible_borrower.get(&borrowed) { |
| 276 | + for b in borrowers.iter() { |
| 277 | + if queued.insert(b) { |
| 278 | + deque.push_back(b); |
| 279 | + } |
213 | 280 | }
|
214 | 281 | } else {
|
215 |
| - return false; |
| 282 | + // Nothing borrows `borrowed` at `at`. |
| 283 | + return true; |
216 | 284 | }
|
217 | 285 |
|
218 |
| - self.bitset.1.clear(); |
219 |
| - for b in below { |
220 |
| - self.bitset.1.insert(*b); |
221 |
| - } |
222 |
| - |
223 |
| - if !self.bitset.0.superset(&self.bitset.1) { |
224 |
| - return false; |
225 |
| - } |
| 286 | + while let Some(borrower) = deque.pop_front() { |
| 287 | + if maybe_live.contains(borrower) && !borrowers.contains(&borrower) { |
| 288 | + return false; |
| 289 | + } |
226 | 290 |
|
227 |
| - for b in above { |
228 |
| - self.bitset.0.remove(*b); |
| 291 | + if let Some(borrowers) = possible_borrower.get(&borrower) { |
| 292 | + for b in borrowers.iter() { |
| 293 | + if queued.insert(b) { |
| 294 | + deque.push_back(b); |
| 295 | + } |
| 296 | + } |
| 297 | + } |
229 | 298 | }
|
230 | 299 |
|
231 |
| - self.bitset.0.is_empty() |
| 300 | + true |
232 | 301 | }
|
233 | 302 |
|
234 | 303 | pub fn local_is_alive_at(&mut self, local: mir::Local, at: mir::Location) -> bool {
|
|
0 commit comments