1use std::fmt;
10
11use rustc_data_structures::fx::{FxHashMap, FxHashSet};
12use rustc_hir::def_id::DefId;
13use rustc_middle::{mir::BasicBlock, ty::TyCtxt};
14
15use crate::utils::scc::Scc;
16
17use super::helpers::{CFG, Callsite, CallsiteLocation};
18
19pub struct PathExtractor<'tcx> {
21 cfg: CFG,
22 callsites: Vec<Callsite<'tcx>>,
23 loops: Vec<LoopInfo>,
24 block_to_loop: FxHashMap<BasicBlock, LoopId>,
25 paths: FxHashMap<CallsiteLocation, Vec<Path>>,
26}
27
28impl<'tcx> PathExtractor<'tcx> {
29 pub fn new(tcx: TyCtxt<'tcx>, def_id: DefId, callsites: Vec<Callsite<'tcx>>) -> Self {
31 Self {
32 cfg: CFG::new(tcx, def_id),
33 callsites,
34 loops: Vec::new(),
35 block_to_loop: FxHashMap::default(),
36 paths: FxHashMap::default(),
37 }
38 }
39
40 pub fn run(mut self) -> PathResult<'tcx> {
42 self.find_loops();
43 self.find_paths();
44 PathResult {
45 callsites: self.callsites,
46 loops: self.loops,
47 paths: self.paths,
48 }
49 }
50
51 fn find_loops(&mut self) {
53 let (loops, block_to_loop) = find_loops(&self.cfg);
54 self.loops = loops;
55 self.block_to_loop = block_to_loop;
56 }
57
58 fn find_paths(&mut self) {
60 let by_block = self.callsites_by_block();
61 for index in 0..self.callsites.len() {
62 let callsite = self.callsites[index].clone();
63 let paths = self.find_paths_for_callsite(&callsite, &by_block);
64 self.paths.insert(callsite.location(), paths);
65 }
66 }
67
68 fn callsites_by_block(&self) -> FxHashMap<BasicBlock, CallsiteLocation> {
70 self.callsites
71 .iter()
72 .map(|callsite| (callsite.block, callsite.location()))
73 .collect()
74 }
75
76 fn find_paths_for_callsite(
78 &self,
79 callsite: &Callsite<'tcx>,
80 by_block: &FxHashMap<BasicBlock, CallsiteLocation>,
81 ) -> Vec<Path> {
82 let target = callsite.location();
83 if let Some(loop_id) = self.block_to_loop.get(&callsite.block).copied() {
84 self.find_loop_paths(loop_id, target, callsite.block)
85 } else {
86 self.find_entry_paths(target, callsite.block, by_block)
87 }
88 }
89
90 fn find_entry_paths(
96 &self,
97 target: CallsiteLocation,
98 target_block: BasicBlock,
99 by_block: &FxHashMap<BasicBlock, CallsiteLocation>,
100 ) -> Vec<Path> {
101 const PATH_LIMIT: usize = 1024;
102 let mut results = Vec::new();
103 let mut stack = vec![PathStep::Block(self.cfg.entry)];
104 let mut visited = FxHashSet::default();
105 visited.insert(self.cfg.entry);
106 self.dfs_entry_paths(
107 self.cfg.entry,
108 target,
109 target_block,
110 by_block,
111 &mut visited,
112 &mut stack,
113 &mut results,
114 PATH_LIMIT,
115 );
116 results
117 }
118
119 fn dfs_entry_paths(
125 &self,
126 current: BasicBlock,
127 target: CallsiteLocation,
128 target_block: BasicBlock,
129 by_block: &FxHashMap<BasicBlock, CallsiteLocation>,
130 visited: &mut FxHashSet<BasicBlock>,
131 stack: &mut Vec<PathStep>,
132 results: &mut Vec<Path>,
133 limit: usize,
134 ) {
135 if results.len() >= limit {
136 return;
137 }
138
139 if current == target_block {
140 stack.push(PathStep::Callsite(target));
141 results.push(Path {
142 target,
143 start: PathStart::FunctionEntry,
144 steps: stack.clone(),
145 });
146 stack.pop();
147 return;
148 }
149
150 for &next in self.cfg.successors(current) {
151 if results.len() >= limit {
152 break;
153 }
154
155 if let Some(loop_id) = self.block_to_loop.get(&next).copied() {
156 if self.block_to_loop.get(&target_block).copied() == Some(loop_id) {
157 continue;
158 }
159 self.follow_loop_exits(
160 loop_id,
161 target,
162 target_block,
163 by_block,
164 visited,
165 stack,
166 results,
167 limit,
168 );
169 continue;
170 }
171
172 if visited.contains(&next) || has_other_callsite(next, target, by_block) {
173 continue;
174 }
175
176 stack.push(PathStep::Block(next));
177 visited.insert(next);
178 self.dfs_entry_paths(
179 next,
180 target,
181 target_block,
182 by_block,
183 visited,
184 stack,
185 results,
186 limit,
187 );
188 visited.remove(&next);
189 stack.pop();
190 }
191 }
192
193 fn follow_loop_exits(
198 &self,
199 loop_id: LoopId,
200 target: CallsiteLocation,
201 target_block: BasicBlock,
202 by_block: &FxHashMap<BasicBlock, CallsiteLocation>,
203 visited: &mut FxHashSet<BasicBlock>,
204 stack: &mut Vec<PathStep>,
205 results: &mut Vec<Path>,
206 limit: usize,
207 ) {
208 let loop_info = &self.loops[loop_id.index()];
209 for exit in &loop_info.exits {
210 if results.len() >= limit {
211 break;
212 }
213 if visited.contains(&exit.to) {
214 continue;
215 }
216
217 stack.push(PathStep::LoopExit {
218 loop_id,
219 from: exit.from,
220 to: exit.to,
221 });
222 stack.push(PathStep::Block(exit.to));
223 visited.insert(exit.to);
224 self.dfs_entry_paths(
225 exit.to,
226 target,
227 target_block,
228 by_block,
229 visited,
230 stack,
231 results,
232 limit,
233 );
234 visited.remove(&exit.to);
235 stack.pop();
236 stack.pop();
237 }
238 }
239
240 fn find_loop_paths(
247 &self,
248 loop_id: LoopId,
249 target: CallsiteLocation,
250 target_block: BasicBlock,
251 ) -> Vec<Path> {
252 const PATH_LIMIT: usize = 1024;
253 let loop_info = &self.loops[loop_id.index()];
254 let loop_blocks: FxHashSet<BasicBlock> = loop_info.blocks.iter().copied().collect();
255 let mut results = Vec::new();
256 let mut stack = vec![PathStep::Block(loop_info.header)];
257 let mut visited = FxHashSet::default();
258 visited.insert(loop_info.header);
259 self.dfs_loop_paths(
260 loop_id,
261 loop_info.header,
262 target,
263 target_block,
264 &loop_blocks,
265 &mut visited,
266 &mut stack,
267 &mut results,
268 PATH_LIMIT,
269 );
270 results
271 }
272
273 fn dfs_loop_paths(
279 &self,
280 loop_id: LoopId,
281 current: BasicBlock,
282 target: CallsiteLocation,
283 target_block: BasicBlock,
284 loop_blocks: &FxHashSet<BasicBlock>,
285 visited: &mut FxHashSet<BasicBlock>,
286 stack: &mut Vec<PathStep>,
287 results: &mut Vec<Path>,
288 limit: usize,
289 ) {
290 if results.len() >= limit {
291 return;
292 }
293
294 if current == target_block {
295 stack.push(PathStep::Callsite(target));
296 results.push(Path {
297 target,
298 start: PathStart::LoopHeader { loop_id },
299 steps: stack.clone(),
300 });
301 stack.pop();
302 return;
303 }
304
305 for &next in self.cfg.successors(current) {
306 if !loop_blocks.contains(&next) || visited.contains(&next) {
307 continue;
308 }
309 stack.push(PathStep::Block(next));
310 visited.insert(next);
311 self.dfs_loop_paths(
312 loop_id,
313 next,
314 target,
315 target_block,
316 loop_blocks,
317 visited,
318 stack,
319 results,
320 limit,
321 );
322 visited.remove(&next);
323 stack.pop();
324 }
325 }
326}
327
328pub struct PathResult<'tcx> {
330 callsites: Vec<Callsite<'tcx>>,
331 loops: Vec<LoopInfo>,
332 paths: FxHashMap<CallsiteLocation, Vec<Path>>,
333}
334
335impl<'tcx> PathResult<'tcx> {
336 pub fn callsites(&self) -> &[Callsite<'tcx>] {
338 &self.callsites
339 }
340
341 pub fn loops(&self) -> &[LoopInfo] {
343 &self.loops
344 }
345
346 pub fn paths_for(&self, location: CallsiteLocation) -> &[Path] {
348 self.paths.get(&location).map(Vec::as_slice).unwrap_or(&[])
349 }
350}
351
352#[derive(Clone, Debug)]
354pub struct Path {
355 pub target: CallsiteLocation,
357 pub start: PathStart,
359 pub steps: Vec<PathStep>,
361}
362
363impl Path {
364 pub fn describe(&self) -> String {
366 self.steps
367 .iter()
368 .map(|step| match step {
369 PathStep::Block(bb) => format!("bb{}", bb.as_usize()),
370 PathStep::LoopExit { loop_id, from, to } => {
371 format!(
372 "Loop#{}.exit(bb{} -> bb{})",
373 loop_id.index(),
374 from.as_usize(),
375 to.as_usize()
376 )
377 }
378 PathStep::Callsite(location) => {
379 format!("callsite(bb{})", location.block.as_usize())
380 }
381 })
382 .collect::<Vec<_>>()
383 .join(" -> ")
384 }
385}
386
387#[derive(Clone, Copy, Debug, Eq, PartialEq)]
389pub enum PathStart {
390 FunctionEntry,
392 LoopHeader { loop_id: LoopId },
394}
395
396#[derive(Clone, Debug)]
398pub enum PathStep {
399 Block(BasicBlock),
401 LoopExit {
403 loop_id: LoopId,
404 from: BasicBlock,
405 to: BasicBlock,
406 },
407 Callsite(CallsiteLocation),
409}
410
411#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
413pub struct LoopId(usize);
414
415impl LoopId {
416 fn new(index: usize) -> Self {
418 Self(index)
419 }
420
421 pub fn index(self) -> usize {
423 self.0
424 }
425}
426
427impl fmt::Display for LoopId {
428 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 write!(f, "{}", self.0)
431 }
432}
433
434#[derive(Clone, Debug)]
436pub struct LoopInfo {
437 pub id: LoopId,
439 pub header: BasicBlock,
441 pub blocks: Vec<BasicBlock>,
443 pub exits: Vec<LoopExit>,
445 pub backedges: Vec<(BasicBlock, BasicBlock)>,
447 pub transfer: LoopTransfer,
449}
450
451#[derive(Clone, Debug)]
453pub struct LoopExit {
454 pub from: BasicBlock,
456 pub to: BasicBlock,
458}
459
460#[derive(Clone, Debug)]
462pub enum LoopTransfer {
463 Unknown,
465 Skip,
467 Havoc,
469}
470
471fn has_other_callsite(
473 block: BasicBlock,
474 target: CallsiteLocation,
475 by_block: &FxHashMap<BasicBlock, CallsiteLocation>,
476) -> bool {
477 by_block
478 .get(&block)
479 .map(|location| *location != target)
480 .unwrap_or(false)
481}
482
483fn find_loops(cfg: &CFG) -> (Vec<LoopInfo>, FxHashMap<BasicBlock, LoopId>) {
485 let mut detector = SccDetector::new(cfg.successors.clone());
486 detector.find_scc();
487
488 let mut loops = Vec::new();
489 let mut block_to_loop = FxHashMap::default();
490 for mut component in detector.components {
491 component.sort_unstable();
492 let is_self_loop = component.len() == 1
493 && cfg.successors[component[0]]
494 .iter()
495 .any(|succ| succ.as_usize() == component[0]);
496 if component.len() <= 1 && !is_self_loop {
497 continue;
498 }
499
500 let id = LoopId::new(loops.len());
501 let header = BasicBlock::from_usize(component[0]);
502 let block_set: FxHashSet<usize> = component.iter().copied().collect();
503 let mut exits = Vec::new();
504 let mut backedges = Vec::new();
505
506 for &block_idx in &component {
507 let block = BasicBlock::from_usize(block_idx);
508 for &succ in cfg.successors(block) {
509 let succ_idx = succ.as_usize();
510 if block_set.contains(&succ_idx) {
511 if succ_idx <= block_idx || succ == header {
512 backedges.push((block, succ));
513 }
514 } else {
515 exits.push(LoopExit {
516 from: block,
517 to: succ,
518 });
519 }
520 }
521 }
522
523 for &block_idx in &component {
524 block_to_loop.insert(BasicBlock::from_usize(block_idx), id);
525 }
526
527 loops.push(LoopInfo {
528 id,
529 header,
530 blocks: component.into_iter().map(BasicBlock::from_usize).collect(),
531 exits,
532 backedges,
533 transfer: LoopTransfer::Unknown,
534 });
535 }
536
537 (loops, block_to_loop)
538}
539
540struct SccDetector {
542 successors: Vec<Vec<BasicBlock>>,
543 components: Vec<Vec<usize>>,
544}
545
546impl SccDetector {
547 fn new(successors: Vec<Vec<BasicBlock>>) -> Self {
549 Self {
550 successors,
551 components: Vec::new(),
552 }
553 }
554}
555
556impl Scc for SccDetector {
557 fn on_scc_found(&mut self, _root: usize, scc_components: &[usize]) {
559 self.components.push(scc_components.to_vec());
560 }
561
562 fn get_next(&mut self, root: usize) -> FxHashSet<usize> {
564 self.successors
565 .get(root)
566 .into_iter()
567 .flat_map(|successors| successors.iter().map(|bb| bb.as_usize()))
568 .collect()
569 }
570
571 fn get_size(&mut self) -> usize {
573 self.successors.len()
574 }
575}