1use std::{iter, mem};
2
3use rustc_data_structures::either::Either;
4use rustc_hir::{Expr, HirId};
5use rustc_index::IndexVec;
6use rustc_index::bit_set::DenseBitSet;
7use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
8use rustc_middle::mir::{
9 BasicBlock, BasicBlockData, Body, InlineAsmOperand, Local, Location, Place, START_BLOCK, StatementKind,
10 TerminatorKind,
11};
12use rustc_middle::ty::TyCtxt;
13
14mod possible_borrower;
15pub use possible_borrower::PossibleBorrowerMap;
16
17mod possible_origin;
18
19mod transitive_relation;
20
21#[derive(Clone, Debug, Default)]
22pub struct LocalUsage {
23 pub local_use_locs: Vec<Location>,
25 pub local_consume_or_mutate_locs: Vec<Location>,
27}
28
29pub fn visit_local_usage<const N: usize>(
36 locals: [Local; N],
37 mir: &Body<'_>,
38 location: Location,
39) -> Option<[LocalUsage; N]> {
40 let live_on_entry = reachable_while_storage_live(&locals, mir, location)?;
41
42 let mut v = V {
43 locals: &locals,
44 location,
45 results: [const {
46 LocalUsage {
47 local_use_locs: Vec::new(),
48 local_consume_or_mutate_locs: Vec::new(),
49 }
50 }; N],
51 };
52
53 for &tbb in mir.basic_blocks.reverse_postorder() {
54 if live_on_entry[tbb] != 0 {
55 v.visit_basic_block_data(tbb, &mir.basic_blocks[tbb]);
56 }
57 }
58
59 Some(v.results)
60}
61
62fn reachable_while_storage_live<const N: usize>(
71 locals: &[Local; N],
72 mir: &Body<'_>,
73 location: Location,
74) -> Option<IndexVec<BasicBlock, u8>> {
75 fn join(base: &mut u8, other: u8) -> bool {
76 let new = *base | other;
77 mem::replace(base, new) != new
78 }
79 fn enqueue(state: &mut u8) -> bool {
80 let new = *state | ENQUEUED_FLAG;
81 mem::replace(state, new) != new
82 }
83 fn dequeue(state: &mut u8) {
84 *state &= !ENQUEUED_FLAG;
85 }
86
87 const ENQUEUED_FLAG: u8 = 0b1000_0000;
88 const {
89 assert!(
90 N <= ENQUEUED_FLAG.trailing_zeros() as usize,
91 "implementation isn't well suited for handling a larger number locals nor do we have any reason to pass a larger number"
92 );
93 }
94
95 let apply_deaths = |bb_data: &BasicBlockData<'_>, start: usize, mut live: u8| {
97 for stmt in bb_data.statements.get(start..).unwrap_or_default() {
99 if let StatementKind::StorageDead(killed) = stmt.kind
100 && let Some(slot) = locals.iter().position(|&local| local == killed)
101 {
102 live &= !(1 << slot);
103 }
104 }
105 live
106 };
107
108 let mut states = IndexVec::from_raw(vec![0; mir.basic_blocks.len()]);
113 let mut queue = Vec::new();
114 let init = u8::MAX >> (u8::BITS as usize - N);
116 states[location.block] = init;
117
118 let bb_data = &mir.basic_blocks[location.block];
121 let result = apply_deaths(bb_data, location.statement_index + 1, init);
122 if result != 0 {
126 for succ in bb_data.terminator().successors() {
127 if succ == location.block {
128 return None;
129 }
130 if join(&mut states[succ], result) && enqueue(&mut states[succ]) {
131 queue.push(succ);
132 }
133 }
134 }
135
136 while let Some(bb) = queue.pop() {
137 dequeue(&mut states[bb]);
138 let bb_data = &mir.basic_blocks[bb];
139 let result = apply_deaths(bb_data, 0, states[bb]);
140 if result != 0 {
141 for succ in bb_data.terminator().successors() {
142 if succ == location.block {
143 return None;
144 }
145 if join(&mut states[succ], result) && enqueue(&mut states[succ]) {
146 queue.push(succ);
147 }
148 }
149 }
150 }
151
152 Some(states)
153}
154
155struct V<'a, const N: usize> {
156 locals: &'a [Local; N],
157 location: Location,
158 results: [LocalUsage; N],
159}
160
161impl<'tcx, const N: usize> Visitor<'tcx> for V<'_, N> {
162 fn visit_place(&mut self, place: &Place<'tcx>, ctx: PlaceContext, loc: Location) {
163 if loc.block == self.location.block && loc.statement_index <= self.location.statement_index {
164 return;
165 }
166
167 let local = place.local;
168
169 for (self_local, result) in iter::zip(self.locals, &mut self.results) {
170 if local == *self_local {
171 if !matches!(
172 ctx,
173 PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
174 ) {
175 result.local_use_locs.push(loc);
176 }
177 if matches!(
178 ctx,
179 PlaceContext::NonMutatingUse(NonMutatingUseContext::Move | NonMutatingUseContext::Inspect)
180 | PlaceContext::MutatingUse(MutatingUseContext::Borrow)
181 ) {
182 result.local_consume_or_mutate_locs.push(loc);
183 }
184 }
185 }
186 }
187}
188
189pub fn block_in_cycle(body: &Body<'_>, block: BasicBlock) -> bool {
191 let mut seen = DenseBitSet::new_empty(body.basic_blocks.len());
192 let mut to_visit = Vec::with_capacity(body.basic_blocks.len() / 2);
193
194 seen.insert(block);
195 let mut next = block;
196 loop {
197 for succ in body.basic_blocks[next].terminator().successors() {
198 if seen.insert(succ) {
199 to_visit.push(succ);
200 } else if succ == block {
201 return true;
202 }
203 }
204
205 if let Some(x) = to_visit.pop() {
206 next = x;
207 } else {
208 return false;
209 }
210 }
211}
212
213pub fn used_exactly_once(mir: &Body<'_>, local: Local) -> Option<bool> {
215 visit_local_usage(
216 [local],
217 mir,
218 Location {
219 block: START_BLOCK,
220 statement_index: 0,
221 },
222 )
223 .map(|[local_usage]| {
224 let mut locations = local_usage
225 .local_use_locs
226 .into_iter()
227 .filter(|&location| !is_local_assignment(mir, local, location));
228 if let Some(location) = locations.next() {
229 locations.next().is_none() && !block_in_cycle(mir, location.block)
230 } else {
231 false
232 }
233 })
234}
235
236#[expect(clippy::module_name_repetitions)]
238pub fn enclosing_mir(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<&Body<'_>> {
239 let body_owner_local_def_id = tcx.hir_enclosing_body_owner(hir_id);
240 if tcx.hir_body_owner_kind(body_owner_local_def_id).is_fn_or_closure() {
241 Some(tcx.optimized_mir(body_owner_local_def_id.to_def_id()))
242 } else {
243 None
244 }
245}
246
247pub fn expr_local(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Local> {
250 enclosing_mir(tcx, expr.hir_id).and_then(|mir| {
251 mir.local_decls.iter_enumerated().find_map(|(local, local_decl)| {
252 if local_decl.source_info.span == expr.span {
253 Some(local)
254 } else {
255 None
256 }
257 })
258 })
259}
260
261pub fn local_assignments(mir: &Body<'_>, local: Local) -> Vec<Location> {
263 let mut locations = Vec::new();
264 for (block, data) in mir.basic_blocks.iter_enumerated() {
265 for statement_index in 0..=data.statements.len() {
266 let location = Location { block, statement_index };
267 if is_local_assignment(mir, local, location) {
268 locations.push(location);
269 }
270 }
271 }
272 locations
273}
274
275fn is_local_assignment(mir: &Body<'_>, local: Local, location: Location) -> bool {
278 match mir.stmt_at(location) {
279 Either::Left(statement) => {
280 if let StatementKind::Assign((place, _)) = statement.kind {
281 place.as_local() == Some(local)
282 } else {
283 false
284 }
285 },
286 Either::Right(terminator) => match &terminator.kind {
287 TerminatorKind::Call { destination, .. } => destination.as_local() == Some(local),
288 TerminatorKind::InlineAsm { operands, .. } => operands.iter().any(|operand| {
289 if let InlineAsmOperand::Out { place: Some(place), .. } = operand {
290 place.as_local() == Some(local)
291 } else {
292 false
293 }
294 }),
295 _ => false,
296 },
297 }
298}
299
300pub fn function_call_basic_block(body: &Body<'_>, fun: &Expr<'_>) -> Option<BasicBlock> {
302 body.basic_blocks.iter_enumerated().find_map(|(block, data)| {
303 if let Some(terminator) = data.terminator.as_ref()
304 && let TerminatorKind::Call { fn_span, .. } = terminator.kind
305 && fn_span.lo() == fun.span.lo()
306 {
307 Some(block)
308 } else {
309 None
310 }
311 })
312}