Skip to content

Commit d672a4b

Browse files
committed
feat(call_graph): Use map of vecs to group calls from the same caller
1 parent e990e4f commit d672a4b

2 files changed

Lines changed: 90 additions & 87 deletions

File tree

mutest-driver/src/print.rs

Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,9 @@ pub fn print_call_graph<'tcx, 'trg>(tcx: TyCtxt<'tcx>, tests: &[Test], call_grap
123123

124124
println!("test {}", test_path_str);
125125

126-
let mut callees_in_print_order = call_graph.root_calls.iter()
127-
.filter(|(root_def_id, _)| *root_def_id == test.def_id)
126+
let mut callees_in_print_order = call_graph.root_calls.get(&test.def_id).map(|v| &**v).unwrap_or_default().into_iter()
128127
// HACK: Deduplicate multiple calls to the same callee.
129-
.map(|(_, call)| call.callee).collect::<FxHashSet<_>>().into_iter()
128+
.map(|call| call.callee).collect::<FxHashSet<_>>().into_iter()
130129
.map(|callee| (callee.display_str(tcx), tcx.def_span(callee.def_id), callee))
131130
.collect::<Vec<_>>();
132131
callees_in_print_order.sort_unstable_by(|(callee_a_display_str, callee_a_span, _), (callee_b_display_str, callee_b_span, _)| {
@@ -159,10 +158,9 @@ pub fn print_call_graph<'tcx, 'trg>(tcx: TyCtxt<'tcx>, tests: &[Test], call_grap
159158
for (caller_display_str, caller_span, caller) in callers_in_print_order {
160159
println!("{} at {:#?}", caller_display_str, caller_span);
161160

162-
let mut callees_in_print_order = calls.iter()
163-
.filter(|(this_caller, _)| this_caller == caller)
161+
let mut callees_in_print_order = calls.get(caller).map(|v| &**v).unwrap_or_default().into_iter()
164162
// HACK: Deduplicate multiple calls to the same callee.
165-
.map(|(_, call)| call.callee).collect::<FxHashSet<_>>().into_iter()
163+
.map(|call| call.callee).collect::<FxHashSet<_>>().into_iter()
166164
.map(|callee| (callee.display_str(tcx), tcx.def_span(callee.def_id), callee))
167165
.collect::<Vec<_>>();
168166
callees_in_print_order.sort_unstable_by(|(callee_a_display_str, callee_a_span, _), (callee_b_display_str, callee_b_span, _)| {
@@ -232,7 +230,8 @@ pub fn print_call_graph<'tcx, 'trg>(tcx: TyCtxt<'tcx>, tests: &[Test], call_grap
232230
println!(" {{ rank=same; 0;");
233231
let unique_root_callees = call_graph.root_calls.iter()
234232
.filter(|(root_def_id, _)| test_filters.is_empty() || filtered_nodes.contains(&Callee::new(root_def_id.to_def_id(), tcx.mk_args(&[]))))
235-
.map(|(_, call)| call.callee)
233+
.flat_map(|(_, calls)| calls)
234+
.map(|call| call.callee)
236235
.collect::<FxHashSet<_>>();
237236
for callee in unique_root_callees {
238237
if !defined_callees.insert(callee) { continue; }
@@ -254,33 +253,36 @@ pub fn print_call_graph<'tcx, 'trg>(tcx: TyCtxt<'tcx>, tests: &[Test], call_grap
254253
}
255254
}
256255
println!(" }}");
257-
for (root_def_id, call) in &call_graph.root_calls {
258-
if !test_filters.is_empty() {
259-
if !filtered_nodes.contains(&Callee::new(root_def_id.to_def_id(), tcx.mk_args(&[]))) { continue; }
260-
filtered_nodes.insert(call.callee);
261-
}
256+
for (root_def_id, calls) in &call_graph.root_calls {
257+
for call in calls {
258+
if !test_filters.is_empty() {
259+
if !filtered_nodes.contains(&Callee::new(root_def_id.to_def_id(), tcx.mk_args(&[]))) { continue; }
260+
filtered_nodes.insert(call.callee);
261+
}
262262

263-
// Override non-local root call edge rendering.
264-
if !call.callee.def_id.is_local() {
265-
match non_local_call_view {
266-
config::CallGraphNonLocalCallView::Collapse => {
267-
collapsed_call_paths.entry(call.callee).or_default().push(smallvec![Callee::new(root_def_id.to_def_id(), tcx.mk_args(&[])), call.callee]);
268-
}
269-
config::CallGraphNonLocalCallView::Expand => {
270-
println!(" {} -> {}:w [color=lightgray];", def_node_id(root_def_id.to_def_id()), callee_node_id(&call.callee));
263+
// Override non-local root call edge rendering.
264+
if !call.callee.def_id.is_local() {
265+
match non_local_call_view {
266+
config::CallGraphNonLocalCallView::Collapse => {
267+
collapsed_call_paths.entry(call.callee).or_default().push(smallvec![Callee::new(root_def_id.to_def_id(), tcx.mk_args(&[])), call.callee]);
268+
}
269+
config::CallGraphNonLocalCallView::Expand => {
270+
println!(" {} -> {}:w [color=lightgray];", def_node_id(root_def_id.to_def_id()), callee_node_id(&call.callee));
271+
}
271272
}
273+
continue;
272274
}
273-
continue;
274-
}
275275

276-
println!(" {} -> {}:w;", def_node_id(root_def_id.to_def_id()), callee_node_id(&call.callee));
276+
println!(" {} -> {}:w;", def_node_id(root_def_id.to_def_id()), callee_node_id(&call.callee));
277+
}
277278
}
278279

279280
for (distance, calls) in iter::zip(1.., &call_graph.nested_calls) {
280281
println!(" {{ rank=same; {};", distance);
281282
let unique_nested_callees = calls.iter()
282283
.filter(|(caller, _)| test_filters.is_empty() || filtered_nodes.contains(caller))
283-
.map(|(_, call)| call.callee)
284+
.flat_map(|(_, calls)| calls)
285+
.map(|call| call.callee)
284286
.collect::<FxHashSet<_>>();
285287
for callee in unique_nested_callees {
286288
if !defined_callees.insert(callee) { continue; }
@@ -302,56 +304,58 @@ pub fn print_call_graph<'tcx, 'trg>(tcx: TyCtxt<'tcx>, tests: &[Test], call_grap
302304
}
303305
}
304306
println!(" }}");
305-
for (caller, call) in calls {
306-
if !test_filters.is_empty() {
307-
if !filtered_nodes.contains(caller) { continue; }
308-
filtered_nodes.insert(call.callee);
309-
}
307+
for (caller, calls) in calls {
308+
for call in calls {
309+
if !test_filters.is_empty() {
310+
if !filtered_nodes.contains(caller) { continue; }
311+
filtered_nodes.insert(call.callee);
312+
}
310313

311-
// Override non-local deep call edge rendering.
312-
if !caller.def_id.is_local() || !call.callee.def_id.is_local() {
313-
match non_local_call_view {
314-
config::CallGraphNonLocalCallView::Collapse => {
315-
match (caller.def_id.is_local(), call.callee.def_id.is_local()) {
316-
(true, true) => unreachable!(),
317-
// Local definition calls into non-local definition.
318-
(true, false) => {
319-
// Record new collapsed non-local call path.
320-
collapsed_call_paths.entry(call.callee).or_default().push(smallvec![*caller, call.callee]);
321-
}
322-
// Non-local definition calls into other non-local definition.
323-
(false, false) => {
324-
// Extend collapsed non-local call paths.
325-
let Some(call_paths) = collapsed_call_paths.get(caller) else { unreachable!() };
326-
let mut call_paths = call_paths.clone();
327-
for call_path in &mut call_paths {
328-
call_path.push(call.callee);
314+
// Override non-local deep call edge rendering.
315+
if !caller.def_id.is_local() || !call.callee.def_id.is_local() {
316+
match non_local_call_view {
317+
config::CallGraphNonLocalCallView::Collapse => {
318+
match (caller.def_id.is_local(), call.callee.def_id.is_local()) {
319+
(true, true) => unreachable!(),
320+
// Local definition calls into non-local definition.
321+
(true, false) => {
322+
// Record new collapsed non-local call path.
323+
collapsed_call_paths.entry(call.callee).or_default().push(smallvec![*caller, call.callee]);
329324
}
330-
collapsed_call_paths.entry(call.callee).or_default().extend(call_paths);
331-
}
332-
// Non-local definition calls into local definition.
333-
(false, true) => {
334-
// Write out collapsed non-local call paths.
335-
let Some(call_paths) = collapsed_call_paths.get(caller) else { unreachable!(); };
336-
for call_path in call_paths {
337-
let [root_callee, ..] = &call_path[..] else { unreachable!() };
338-
let tooltip = call_path.iter().chain(iter::once(&call.callee))
339-
.map(|callee| callee.display_str(tcx))
340-
.intersperse("\n-> ".to_owned())
341-
.collect::<String>();
342-
println!(" {} -> {}:w [label=\"indirect\", color=lightgray, labeltooltip={}];", callee_node_id(root_callee), callee_node_id(&call.callee), escape_label_str(&tooltip));
325+
// Non-local definition calls into other non-local definition.
326+
(false, false) => {
327+
// Extend collapsed non-local call paths.
328+
let Some(call_paths) = collapsed_call_paths.get(caller) else { unreachable!() };
329+
let mut call_paths = call_paths.clone();
330+
for call_path in &mut call_paths {
331+
call_path.push(call.callee);
332+
}
333+
collapsed_call_paths.entry(call.callee).or_default().extend(call_paths);
334+
}
335+
// Non-local definition calls into local definition.
336+
(false, true) => {
337+
// Write out collapsed non-local call paths.
338+
let Some(call_paths) = collapsed_call_paths.get(caller) else { unreachable!(); };
339+
for call_path in call_paths {
340+
let [root_callee, ..] = &call_path[..] else { unreachable!() };
341+
let tooltip = call_path.iter().chain(iter::once(&call.callee))
342+
.map(|callee| callee.display_str(tcx))
343+
.intersperse("\n-> ".to_owned())
344+
.collect::<String>();
345+
println!(" {} -> {}:w [label=\"indirect\", color=lightgray, labeltooltip={}];", callee_node_id(root_callee), callee_node_id(&call.callee), escape_label_str(&tooltip));
346+
}
343347
}
344348
}
345349
}
350+
config::CallGraphNonLocalCallView::Expand => {
351+
println!(" {} -> {}:w [color=lightgray];", callee_node_id(caller), callee_node_id(&call.callee));
352+
}
346353
}
347-
config::CallGraphNonLocalCallView::Expand => {
348-
println!(" {} -> {}:w [color=lightgray];", callee_node_id(caller), callee_node_id(&call.callee));
349-
}
354+
continue;
350355
}
351-
continue;
352-
}
353356

354-
println!(" {} -> {}:w;", callee_node_id(caller), callee_node_id(&call.callee));
357+
println!(" {} -> {}:w;", callee_node_id(caller), callee_node_id(&call.callee));
358+
}
355359
}
356360
}
357361

mutest-emit/src/analysis/call_graph.rs

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -310,13 +310,14 @@ pub struct CallGraph<'tcx> {
310310
pub virtual_calls_count: usize,
311311
pub dynamic_calls_count: usize,
312312
pub foreign_calls_count: usize,
313-
pub root_calls: FxHashSet<(hir::LocalDefId, InstanceCall<'tcx>)>,
314-
pub nested_calls: Vec<FxHashSet<(Callee<'tcx>, InstanceCall<'tcx>)>>,
313+
pub root_calls: FxHashMap<hir::LocalDefId, Vec<InstanceCall<'tcx>>>,
314+
pub nested_calls: Vec<FxHashMap<Callee<'tcx>, Vec<InstanceCall<'tcx>>>>,
315315
}
316316

317317
impl<'tcx> CallGraph<'tcx> {
318318
pub fn total_calls_count(&self) -> usize {
319-
let mut total_calls_count = self.root_calls.len() + self.nested_calls.iter().map(|calls| calls.len()).sum::<usize>();
319+
let mut total_calls_count = self.root_calls.iter().map(|(_, calls)| calls.len()).sum::<usize>()
320+
+ self.nested_calls.iter().map(|calls| calls.iter().map(|(_, calls)| calls.len()).sum::<usize>()).sum::<usize>();
320321
// NOTE: Dynamic calls are currently not represented in the call graph, therefore
321322
// we have to add their count manually to the total.
322323
total_calls_count += self.dynamic_calls_count;
@@ -328,14 +329,8 @@ impl<'tcx> CallGraph<'tcx> {
328329
(!self.root_calls.is_empty() as usize) + self.nested_calls.len()
329330
}
330331

331-
pub fn callees_of_nested_caller(&self, caller: Callee<'tcx>) -> Option<impl Iterator<Item = InstanceCall<'tcx>> + '_> {
332-
let calls_of_caller = self.nested_calls.iter().find_map(|calls| {
333-
let mut calls_of_caller = calls.iter().filter(move |(this_caller, _)| *this_caller == caller).peekable();
334-
if let Some(_) = calls_of_caller.peek() { return Some(calls_of_caller); }
335-
None
336-
})?;
337-
338-
Some(calls_of_caller.map(|(_, call)| *call))
332+
pub fn callees_of_nested_caller(&self, caller: Callee<'tcx>) -> Option<&[InstanceCall<'tcx>]> {
333+
self.nested_calls.iter().find_map(|calls| calls.get(&caller).map(|v| &**v))
339334
}
340335
}
341336

@@ -432,7 +427,8 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>(
432427
}
433428
};
434429

435-
call_graph.root_calls.insert((test.def_id, InstanceCall { callee, safety: call.safety, span: call.span }));
430+
let test_calls = call_graph.root_calls.entry(test.def_id).or_default();
431+
test_calls.push(InstanceCall { callee, safety: call.safety, span: call.span });
436432

437433
previously_found_callees.insert(callee);
438434
}
@@ -554,7 +550,8 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>(
554550
}
555551
};
556552

557-
call_graph.nested_calls[distance].insert((caller, InstanceCall { callee, safety: call.safety, span: call.span }));
553+
let caller_calls = call_graph.nested_calls[distance].entry(caller).or_default();
554+
caller_calls.push(InstanceCall { callee, safety: call.safety, span: call.span });
558555

559556
newly_found_callees.insert(callee);
560557
}
@@ -589,7 +586,7 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>(
589586

590587
struct CalleeLookupCache<'tcx, 'a> {
591588
call_graph: &'a CallGraph<'tcx>,
592-
cache: UnsafeCell<FxHashMap<Callee<'tcx>, Box<[InstanceCall<'tcx>]>>>,
589+
cache: UnsafeCell<FxHashMap<Callee<'tcx>, Option<&'a [InstanceCall<'tcx>]>>>,
593590
}
594591

595592
impl<'tcx, 'a> CalleeLookupCache<'tcx, 'a> {
@@ -601,8 +598,8 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>(
601598
// SAFETY: The lookup cache is an append-only map; existing entries are never modified.
602599
let cache = unsafe { &mut *self.cache.get() };
603600

604-
let callees = cache.entry(caller).or_insert_with(|| self.call_graph.callees_of_nested_caller(caller).into_iter().flatten().collect::<Box<[_]>>());
605-
callees
601+
let callees = cache.entry(caller).or_insert_with(|| self.call_graph.callees_of_nested_caller(caller));
602+
callees.unwrap_or_default()
606603
}
607604
}
608605

@@ -692,11 +689,13 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>(
692689

693690
let callee_lookup_cache = CalleeLookupCache::new(&call_graph);
694691
let mut targets: FxHashMap<hir::LocalDefId, Target> = Default::default();
695-
for &(entry_point, call) in &call_graph.root_calls {
696-
let Some(test) = tests.iter().find(|test| test.def_id == entry_point) else { unreachable!() };
697-
let unsafety = None;
698-
let mut call_trace = CallTrace { root: entry_point, nested_calls: smallvec![call.callee] };
699-
record_targets(tcx, def_res, krate, &test_def_ids, &callee_lookup_cache, test, unsafety, &mut call_trace, &mut targets);
692+
for (&entry_point, calls) in &call_graph.root_calls {
693+
for call in calls {
694+
let Some(test) = tests.iter().find(|test| test.def_id == entry_point) else { unreachable!() };
695+
let unsafety = None;
696+
let mut call_trace = CallTrace { root: entry_point, nested_calls: smallvec![call.callee] };
697+
record_targets(tcx, def_res, krate, &test_def_ids, &callee_lookup_cache, test, unsafety, &mut call_trace, &mut targets);
698+
}
700699
}
701700

702701
(call_graph, targets.into_values().collect())

0 commit comments

Comments
 (0)