Skip to content

Commit d2141e4

Browse files
authored
Add loop scanner to tool-scanner (#3443)
This extend #3120 with loop scanner that counts the number of loops in each function and the number of functions that contain loops. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent 17dc239 commit d2141e4

File tree

5 files changed

+132
-12
lines changed

5 files changed

+132
-12
lines changed

Cargo.lock

+30
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
99
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
1010
dependencies = [
1111
"cfg-if",
12+
"getrandom",
1213
"once_cell",
1314
"version_check",
1415
"zerocopy",
@@ -349,6 +350,12 @@ version = "2.1.0"
349350
source = "registry+https://github.com/rust-lang/crates.io-index"
350351
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
351352

353+
[[package]]
354+
name = "fixedbitset"
355+
version = "0.4.2"
356+
source = "registry+https://github.com/rust-lang/crates.io-index"
357+
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
358+
352359
[[package]]
353360
name = "getopts"
354361
version = "0.2.21"
@@ -375,6 +382,17 @@ version = "0.3.1"
375382
source = "registry+https://github.com/rust-lang/crates.io-index"
376383
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
377384

385+
[[package]]
386+
name = "graph-cycles"
387+
version = "0.1.0"
388+
source = "registry+https://github.com/rust-lang/crates.io-index"
389+
checksum = "3a6ad932c6dd3cfaf16b66754a42f87bbeefd591530c4b6a8334270a7df3e853"
390+
dependencies = [
391+
"ahash",
392+
"petgraph",
393+
"thiserror",
394+
]
395+
378396
[[package]]
379397
name = "hashbrown"
380398
version = "0.14.5"
@@ -723,6 +741,16 @@ version = "0.2.1"
723741
source = "registry+https://github.com/rust-lang/crates.io-index"
724742
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
725743

744+
[[package]]
745+
name = "petgraph"
746+
version = "0.6.5"
747+
source = "registry+https://github.com/rust-lang/crates.io-index"
748+
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
749+
dependencies = [
750+
"fixedbitset",
751+
"indexmap",
752+
]
753+
726754
[[package]]
727755
name = "pin-project-lite"
728756
version = "0.2.14"
@@ -928,6 +956,8 @@ name = "scanner"
928956
version = "0.0.0"
929957
dependencies = [
930958
"csv",
959+
"graph-cycles",
960+
"petgraph",
931961
"serde",
932962
"strum",
933963
"strum_macros",
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
2 test_scan_fn_loops.csv
2-
16 test_scan_functions.csv
1+
5 test_scan_fn_loops.csv
2+
19 test_scan_functions.csv
33
5 test_scan_input_tys.csv
4-
14 test_scan_overall.csv
4+
16 test_scan_overall.csv
55
3 test_scan_recursion.csv
66
5 test_scan_unsafe_ops.csv

tests/script-based-pre/tool-scanner/test.rs

+28-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,34 @@ pub fn with_iterator(input: &[usize]) -> usize {
3333
.iter()
3434
.copied()
3535
.find(|e| *e == 0)
36-
.unwrap_or_else(|| input.iter().fold(0, |acc, i| acc + 1))
36+
.unwrap_or_else(|| input.iter().fold(0, |acc, _| acc + 1))
37+
}
38+
39+
pub fn with_for_loop(input: &[usize]) -> usize {
40+
let mut res = 0;
41+
for _ in input {
42+
res += 1;
43+
}
44+
res
45+
}
46+
47+
pub fn with_while_loop(input: &[usize]) -> usize {
48+
let mut res = 0;
49+
while res < input.len() {
50+
res += 1;
51+
}
52+
return res;
53+
}
54+
55+
pub fn with_loop_loop(input: &[usize]) -> usize {
56+
let mut res = 0;
57+
loop {
58+
if res == input.len() {
59+
break;
60+
}
61+
res += 1;
62+
}
63+
res
3764
}
3865

3966
static mut COUNTER: Option<usize> = Some(0);

tools/scanner/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ csv = "1.3"
1515
serde = {version = "1", features = ["derive"]}
1616
strum = "0.26"
1717
strum_macros = "0.26"
18+
petgraph = "0.6.5"
19+
graph-cycles = "0.1.0"
1820

1921
[package.metadata.rust-analyzer]
2022
# This crate uses rustc crates.

tools/scanner/src/analysis.rs

+69-8
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
66
use crate::info;
77
use csv::WriterBuilder;
8+
use graph_cycles::Cycles;
9+
use petgraph::graph::Graph;
810
use serde::{ser::SerializeStruct, Serialize, Serializer};
911
use stable_mir::mir::mono::Instance;
1012
use stable_mir::mir::visit::{Location, PlaceContext, PlaceRef};
1113
use stable_mir::mir::{
12-
Body, MirVisitor, Mutability, ProjectionElem, Safety, Terminator, TerminatorKind,
14+
BasicBlock, Body, MirVisitor, Mutability, ProjectionElem, Safety, Terminator, TerminatorKind,
1315
};
1416
use stable_mir::ty::{AdtDef, AdtKind, FnDef, GenericArgs, MirConst, RigidTy, Ty, TyKind};
1517
use stable_mir::visitor::{Visitable, Visitor};
@@ -159,6 +161,7 @@ impl OverallStats {
159161
pub fn loops(&mut self, filename: PathBuf) {
160162
let all_items = stable_mir::all_local_items();
161163
let (has_loops, no_loops) = all_items
164+
.clone()
162165
.into_iter()
163166
.filter_map(|item| {
164167
let kind = item.ty().kind();
@@ -168,9 +171,37 @@ impl OverallStats {
168171
Some(FnLoops::new(item.name()).collect(&item.body()))
169172
})
170173
.partition::<Vec<_>, _>(|props| props.has_loops());
171-
self.counters
172-
.extend_from_slice(&[("has_loops", has_loops.len()), ("no_loops", no_loops.len())]);
173-
dump_csv(filename, &has_loops);
174+
175+
let (has_iterators, no_iterators) = all_items
176+
.clone()
177+
.into_iter()
178+
.filter_map(|item| {
179+
let kind = item.ty().kind();
180+
if !kind.is_fn() {
181+
return None;
182+
};
183+
Some(FnLoops::new(item.name()).collect(&item.body()))
184+
})
185+
.partition::<Vec<_>, _>(|props| props.has_iterators());
186+
187+
let (has_either, _) = all_items
188+
.into_iter()
189+
.filter_map(|item| {
190+
let kind = item.ty().kind();
191+
if !kind.is_fn() {
192+
return None;
193+
};
194+
Some(FnLoops::new(item.name()).collect(&item.body()))
195+
})
196+
.partition::<Vec<_>, _>(|props| props.has_iterators() || props.has_loops());
197+
198+
self.counters.extend_from_slice(&[
199+
("has_loops", has_loops.len()),
200+
("no_loops", no_loops.len()),
201+
("has_iterators", has_iterators.len()),
202+
("no_iterators", no_iterators.len()),
203+
]);
204+
dump_csv(filename, &has_either);
174205
}
175206

176207
/// Create a callgraph for this crate and try to find recursive calls.
@@ -436,21 +467,26 @@ impl<'a> MirVisitor for BodyVisitor<'a> {
436467
fn_props! {
437468
struct FnLoops {
438469
iterators,
439-
nested_loops,
440-
/// TODO: Collect loops.
441470
loops,
471+
// TODO: Collect nested loops.
472+
nested_loops,
442473
}
443474
}
444475

445476
impl FnLoops {
446477
pub fn collect(self, body: &Body) -> FnLoops {
447-
let mut visitor = IteratorVisitor { props: self, body };
478+
let mut visitor =
479+
IteratorVisitor { props: self, body, graph: Vec::new(), current_bbidx: 0 };
448480
visitor.visit_body(body);
449481
visitor.props
450482
}
451483

452484
pub fn has_loops(&self) -> bool {
453-
(self.iterators + self.loops + self.nested_loops) > 0
485+
(self.loops + self.nested_loops) > 0
486+
}
487+
488+
pub fn has_iterators(&self) -> bool {
489+
(self.iterators) > 0
454490
}
455491
}
456492

@@ -461,12 +497,36 @@ impl FnLoops {
461497
struct IteratorVisitor<'a> {
462498
props: FnLoops,
463499
body: &'a Body,
500+
graph: Vec<(u32, u32)>,
501+
current_bbidx: u32,
464502
}
465503

466504
impl<'a> MirVisitor for IteratorVisitor<'a> {
505+
fn visit_body(&mut self, body: &Body) {
506+
// First visit the body to build the control flow graph
507+
self.super_body(body);
508+
// Build the petgraph from the adj vec
509+
let g = Graph::<(), ()>::from_edges(self.graph.clone());
510+
self.props.loops += g.cycles().len();
511+
}
512+
513+
fn visit_basic_block(&mut self, bb: &BasicBlock) {
514+
self.current_bbidx = self.body.blocks.iter().position(|b| *b == *bb).unwrap() as u32;
515+
self.super_basic_block(bb);
516+
}
517+
467518
fn visit_terminator(&mut self, term: &Terminator, location: Location) {
519+
// Add edges between basic block into the adj table
520+
let successors = term.kind.successors();
521+
for target in successors {
522+
self.graph.push((self.current_bbidx, target as u32));
523+
}
524+
468525
if let TerminatorKind::Call { func, .. } = &term.kind {
469526
let kind = func.ty(self.body.locals()).unwrap().kind();
527+
// Check if the target is a visited block.
528+
529+
// Check if the call is an iterator function that contains loops.
470530
if let TyKind::RigidTy(RigidTy::FnDef(def, _)) = kind {
471531
let fullname = def.name();
472532
let names = fullname.split("::").collect::<Vec<_>>();
@@ -505,6 +565,7 @@ impl<'a> MirVisitor for IteratorVisitor<'a> {
505565
}
506566
}
507567
}
568+
508569
self.super_terminator(term, location)
509570
}
510571
}

0 commit comments

Comments
 (0)