c2rust_transpile/cfg/
loops.rs

1//! This module contains stuff related to choosing better loops in Relooper. The problem is simple:
2//! there is a point in the relooper algorithm where we commit to making a `Loop` structure. At that
3//! point, we partition blocks into those that go into the loop and those that go after the loop.
4//!
5//! Relooper gives us a handful of blocks which _must_ go in the loop, but we have some freedom to
6//! choose which other blocks we want to also push into the loop.
7//!
8//! Our choice can be informed by one of two strategies:
9//!
10//!   * _Do what C does_. We can try to match what C does by keeping track of which blocks in CFG
11//!     formed loops in the initial C source. This information needs to be built up when initially
12//!     building the CFG .
13//!
14//!   * _Try to avoid jump-table's_. The more entries we push into the loop, the more we risk
15//!     creating a massive loop that starts with a jump table. OTOH, the more entries we put after
16//!     the loop, the more likely we are to have a jump table right after the loop.
17//!
18
19#![deny(missing_docs)]
20
21use super::*;
22use indexmap::{IndexMap, IndexSet};
23
24/// Modifies the `body_blocks`, `follow_blocks`, and `follow_entries` to try to get all of the
25/// `desired_body` labels into the body. If it is not possible to do this, returns `false` (and the
26/// mutable references passed in cannot be used).
27///
28/// Also return `false` if the loop body ends up having follow blocks pointing into it.
29pub fn match_loop_body(
30    mut desired_body: IndexSet<Label>,
31    strict_reachable_from: &IndexMap<Label, IndexSet<Label>>,
32    body_blocks: &mut IndexMap<Label, BasicBlock<StructureLabel<StmtOrDecl>, StmtOrDecl>>,
33    follow_blocks: &mut IndexMap<Label, BasicBlock<StructureLabel<StmtOrDecl>, StmtOrDecl>>,
34    follow_entries: &mut IndexSet<Label>,
35) -> bool {
36    // Keep moving `follow_entries` that are also in `desired_body` into the loop's `body_blocks`
37    let mut something_happened = true;
38    while something_happened {
39        something_happened = false;
40
41        let to_move: Vec<Label> = follow_entries
42            .intersection(&desired_body)
43            .cloned()
44            .collect();
45
46        for following in to_move {
47            let bb = if let Some(bb) = follow_blocks.swap_remove(&following) {
48                bb
49            } else {
50                continue;
51            };
52            something_happened = true;
53
54            desired_body.swap_remove(&following);
55
56            follow_entries.swap_remove(&following);
57            follow_entries.extend(bb.successors());
58            follow_entries.retain(|e| !body_blocks.contains_key(e));
59            body_blocks.insert(following, bb);
60        }
61    }
62
63    desired_body.is_empty()
64        && body_blocks.keys().all(|body_lbl| {
65            // check that no body block can be reached from a block _not_ in the loop
66            match strict_reachable_from.get(body_lbl) {
67                None => true,
68                Some(reachable_froms) => reachable_froms
69                    .iter()
70                    .all(|lbl| body_blocks.contains_key(lbl)),
71            }
72        })
73}
74
75/// Use heuristics to decide which blocks to move into the loop body.
76///
77///  1. Don't do anything if `follow_entries` is zero or one (since that means whatever
78///     follows the loop will be nice looking).
79///  2. Otherwise, recursively push into the loop `follow_entries` as long as they have no
80///     more than 1 successor (the hope is that some of the chains will join).
81///
82/// This always succeeds.
83pub fn heuristic_loop_body(
84    predecessor_map: &IndexMap<Label, IndexSet<Label>>,
85    body_blocks: &mut IndexMap<Label, BasicBlock<StructureLabel<StmtOrDecl>, StmtOrDecl>>,
86    follow_blocks: &mut IndexMap<Label, BasicBlock<StructureLabel<StmtOrDecl>, StmtOrDecl>>,
87    follow_entries: &mut IndexSet<Label>,
88) {
89    if follow_entries.len() > 1 {
90        for follow_entry in follow_entries.clone().iter() {
91            let mut following: Label = follow_entry.clone();
92
93            loop {
94                // If this block might have come from 2 places, give up
95                if predecessor_map[&following].len() != 1 {
96                    break;
97                }
98
99                // Otherwise, move it into the loop
100                let bb = if let Some(bb) = follow_blocks.swap_remove(&following) {
101                    bb
102                } else {
103                    break;
104                };
105                let succs = bb.successors();
106
107                body_blocks.insert(following.clone(), bb);
108                follow_entries.swap_remove(&following);
109                follow_entries.extend(succs.clone());
110
111                // If it has more than one successor, don't try following the successor
112                if succs.len() != 1 {
113                    break;
114                }
115
116                following = succs.iter().next().unwrap().clone();
117            }
118        }
119    }
120}
121
122/// These IDs identify groups of basic blocks corresponding to loops in a CFG.
123#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
124pub struct LoopId(u64);
125
126impl LoopId {
127    /// Create a new loop id from (presumably) fresh number.
128    pub fn new(id: u64) -> LoopId {
129        LoopId(id)
130    }
131
132    /// Turn the loop id into an identifier. Note that there one needs to add on a tick mark for
133    /// this to be usable as a loop label.
134    pub fn pretty_print(&self) -> String {
135        let &LoopId(loop_id) = self;
136        format!("l_{}", loop_id)
137    }
138}
139
140/// Stores information about loops in a CFG.
141#[derive(Clone, Debug)]
142pub struct LoopInfo<Lbl: Hash + Eq> {
143    /// Given a node, find the tightest enclosing loop
144    node_loops: IndexMap<Lbl, LoopId>,
145
146    /// Given a loop, find all the nodes in it, along with the next tighest loop around it.
147    loops: IndexMap<LoopId, (IndexSet<Lbl>, Option<LoopId>)>,
148}
149
150/// Cannot `#[derive(Default)]` because of the `Lbl` generic.
151/// See <https://github.com/rust-lang/rust/issues/26925>.
152impl<Lbl: Hash + Eq> Default for LoopInfo<Lbl> {
153    fn default() -> Self {
154        Self {
155            node_loops: Default::default(),
156            loops: Default::default(),
157        }
158    }
159}
160
161impl<Lbl: Hash + Eq + Clone> LoopInfo<Lbl> {
162    #[allow(missing_docs)]
163    pub fn new() -> Self {
164        Self::default()
165    }
166
167    /// Merge the information from another `LoopInfo` into this `LoopInfo`
168    pub fn absorb(&mut self, other: LoopInfo<Lbl>) {
169        self.node_loops.extend(other.node_loops);
170        self.loops.extend(other.loops);
171    }
172
173    /// Find the smallest possible loop that contains all of the items
174    pub fn tightest_common_loop<E: Iterator<Item = Lbl>>(&self, mut entries: E) -> Option<LoopId> {
175        let first = entries.next()?;
176
177        let mut loop_id = *self.node_loops.get(&first)?;
178
179        for entry in entries {
180            // Widen the loop until it contains the `entry`, or it can no longer be widened.
181            loop {
182                match self.loops.get(&loop_id) {
183                    Some((ref in_loop, parent_id)) => {
184                        if in_loop.contains(&entry) {
185                            break;
186                        }
187                        loop_id = if let Some(i) = parent_id {
188                            *i
189                        } else {
190                            return None;
191                        };
192                    }
193
194                    None => return None,
195                }
196            }
197        }
198
199        Some(loop_id)
200    }
201
202    /// Filter out any nodes which need to be pruned from the entire CFG due to being unreachable.
203    pub fn filter_unreachable(&mut self, reachable: &IndexSet<Lbl>) {
204        self.node_loops.retain(|lbl, _| reachable.contains(lbl));
205        for (_, &mut (ref mut set, _)) in self.loops.iter_mut() {
206            set.retain(|lbl| reachable.contains(lbl));
207        }
208    }
209
210    /// Rewrite nodes to take into account a node remapping. Note that the remapping is usually
211    /// going to be very much _not_ injective - the whole point of remapping is to merge some nodes.
212    pub fn rewrite_blocks(&mut self, rewrites: &IndexMap<Lbl, Lbl>) {
213        self.node_loops.retain(|lbl, _| rewrites.get(lbl).is_none());
214        for (_, &mut (ref mut set, _)) in self.loops.iter_mut() {
215            set.retain(|lbl| rewrites.get(lbl).is_none());
216        }
217    }
218
219    /// Add in information about a new loop
220    pub fn add_loop(&mut self, id: LoopId, contents: IndexSet<Lbl>, outer_id: Option<LoopId>) {
221        for elem in &contents {
222            if !self.node_loops.contains_key(elem) {
223                self.node_loops.insert(elem.clone(), id);
224            }
225        }
226
227        self.loops.insert(id, (contents, outer_id));
228    }
229
230    /// Get all of the `LoopId`'s corresponding to loops of increasing size around the given label
231    pub fn enclosing_loops(&self, lbl: &Lbl) -> Vec<LoopId> {
232        let mut loop_id_opt: Option<LoopId> = self.node_loops.get(lbl).cloned();
233        let mut loop_ids = vec![];
234        while let Some(loop_id) = loop_id_opt {
235            loop_ids.push(loop_id);
236            loop_id_opt = self.loops[&loop_id].1;
237        }
238        loop_ids
239    }
240
241    /// Get all of the nodes contained in a given loop
242    pub fn get_loop_contents(&self, id: LoopId) -> &IndexSet<Lbl> {
243        &self
244            .loops
245            .get(&id)
246            .unwrap_or_else(|| panic!("There is no loop with id {:?}", id))
247            .0
248    }
249}