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}