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