|
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