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