charon_lib/transform/add_missing_info/sccs.rs
1use indexmap::IndexSet;
2use std::collections::HashMap;
3use std::vec::Vec;
4
5/// A structure containing information about SCCs (Strongly Connected Components)
6pub struct SCCs<Id> {
7 /// The SCCs themselves
8 pub sccs: Vec<Vec<Id>>,
9}
10
11/// The order in which Tarjan's algorithm generates the SCCs is arbitrary,
12/// while we want to keep as much as possible the original order (the order
13/// in which the user generated the ids). For this, we iterate through
14/// the ordered ids and try to add the SCC containing the id to a new list of SCCs
15/// Of course, this SCC might have dependencies, so we need to add the dependencies
16/// first (in which case we have reordering to do). This is what this function
17/// does: we add an SCC and its dependencies to the list of reordered SCCs by
18/// doing a depth-first search.
19fn insert_scc_with_deps<Id: Copy + std::hash::Hash + Eq>(
20 get_id_dependencies: &dyn Fn(Id) -> Vec<Id>,
21 reordered_sccs: &mut IndexSet<usize>,
22 sccs: &Vec<Vec<Id>>,
23 id_to_scc: &HashMap<Id, usize>,
24 scc_id: usize,
25) {
26 // Check if the scc id has already been added
27 if reordered_sccs.contains(&scc_id) {
28 return;
29 }
30
31 // Add the dependencies.
32 // For every id in the dependencies, get the SCC containing this id.
33 // If this is the current SCC: continue. If it is a different one, it is
34 // a dependency: add it to the list of reordered SCCs.
35 let scc = &sccs[scc_id];
36 for id in scc.iter() {
37 let ids = get_id_dependencies(*id);
38 for dep_id in ids.iter() {
39 let dep_scc_id = id_to_scc.get(dep_id).unwrap();
40 if *dep_scc_id == scc_id {
41 continue;
42 } else {
43 // Explore the parent SCC
44 insert_scc_with_deps(
45 get_id_dependencies,
46 reordered_sccs,
47 sccs,
48 id_to_scc,
49 *dep_scc_id,
50 );
51 }
52 }
53 }
54
55 // Add the current SCC
56 reordered_sccs.insert(scc_id);
57}
58
59/// Provided we computed the SCCs (Strongly Connected Components) of a set of
60/// identifier, and those identifiers are ordered, compute the set of SCCs where
61/// the order of the SCCs and the order of the identifiers inside the SCCs attempt
62/// to respect as much as possible the original order between the identifiers.
63/// The `ids` vector gives the ordered set of identifiers.
64///
65///
66/// This is used in several places, for instance to generate the translated
67/// definitions in a consistent and stable manner. For instance, let's
68/// say we extract 4 definitions `f`, `g1`, `g2` and `h`, where:
69/// - `g1` and `g2` are mutually recursive
70/// - `h` calls `g1`
71///
72/// When translating those functions, we group together the mutually recursive
73/// functions, because they have to be extracted in one single group, and thus
74/// apply Tarjan's algorithm on the call graph to find out which functions are
75/// mutually recursive.
76/// The implementation of Tarjan's algorithm we use gives us the Strongly Connected
77/// SCCs of the call graph in an arbitrary order, so we can have:
78/// `[[f], [g1, g2], [h]]`, but also `[[h], [f], [g2, g1]]`, etc.
79///
80/// If the user defined those functions in the order: `f, g1, h, g2`, we want
81/// to reorder them into: `f, g1, g2, h`, so that we can translate the mutually
82/// recursive functions together, while performing a minimal amount of reordering.
83/// And if reordering is not needed, because the user defined those functions
84/// in the order `f, g1, g2, h`, or `g1, g2, f, h` or ... then we want to translate
85/// them in this exact order.
86///
87/// This function performs just that: provided the order in which the definitions
88/// were defined, and the SCCs of the call graph, return an order suitable for
89/// translation and where the amount of reorderings is minimal with regards to
90/// the initial order.
91pub fn reorder_sccs<Id: std::fmt::Debug + Copy + std::hash::Hash + Eq>(
92 get_id_dependencies: &dyn Fn(Id) -> Vec<Id>,
93 ids: &Vec<Id>,
94 sccs: &[Vec<Id>],
95) -> SCCs<Id> {
96 // Map the identifiers to the SCC indices
97 let mut id_to_scc = HashMap::<Id, usize>::new();
98 for (i, scc) in sccs.iter().enumerate() {
99 for id in scc {
100 id_to_scc.insert(*id, i);
101 }
102 }
103
104 // Reorder the identifiers inside the SCCs.
105 // We iterate over the identifiers (in the order in which we register them), and
106 // add them in their corresponding SCCs.
107 let mut reordered_sccs: Vec<Vec<Id>> = vec![];
108 sccs.iter().for_each(|_| reordered_sccs.push(vec![]));
109 for id in ids {
110 let scc_id = match id_to_scc.get(id) {
111 None => panic!("Could not find id: {:?}", id),
112 Some(id) => id,
113 };
114 reordered_sccs[*scc_id].push(*id);
115 }
116
117 // Reorder the SCCs themselves - just do a depth first search. Iterate over
118 // the def ids, and add the corresponding SCCs (with their dependencies).
119 let mut reordered_sccs_ids = IndexSet::<usize>::new();
120 for id in ids {
121 let scc_id = id_to_scc.get(id).unwrap();
122 insert_scc_with_deps(
123 get_id_dependencies,
124 &mut reordered_sccs_ids,
125 &reordered_sccs,
126 &id_to_scc,
127 *scc_id,
128 );
129 }
130 let reordered_sccs_ids: Vec<usize> = reordered_sccs_ids.into_iter().collect();
131
132 // Compute the map from the original SCC ids to the new SCC ids (after
133 // reordering).
134 let mut old_id_to_new_id: Vec<usize> = reordered_sccs_ids.iter().map(|_| 0).collect();
135 for (new_id, dep) in reordered_sccs_ids.iter().enumerate() {
136 old_id_to_new_id[*dep] = new_id;
137 }
138
139 // Generate the reordered SCCs
140 let tgt_sccs = reordered_sccs_ids
141 .iter()
142 .map(|scc_id| reordered_sccs[*scc_id].clone())
143 .collect();
144
145 SCCs { sccs: tgt_sccs }
146}