1 //! Traversals over the IR.
2 
3 use crate::ir;
4 use alloc::vec::Vec;
5 use core::fmt::Debug;
6 use core::hash::Hash;
7 use cranelift_entity::EntitySet;
8 
9 /// A low-level DFS traversal event: either entering or exiting the traversal of
10 /// a block.
11 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
12 pub enum Event {
13     /// Entering traversal of a block.
14     ///
15     /// Processing a block upon this event corresponds to a pre-order,
16     /// depth-first traversal.
17     Enter,
18 
19     /// Exiting traversal of a block.
20     ///
21     /// Processing a block upon this event corresponds to a post-order,
22     /// depth-first traversal.
23     Exit,
24 }
25 
26 /// A depth-first traversal.
27 ///
28 /// This is a fairly low-level traversal type, and is generally intended to be
29 /// used as a building block for making specific pre-order or post-order
30 /// traversals for whatever problem is at hand.
31 ///
32 /// This type may be reused multiple times across different passes or functions
33 /// and will internally reuse any heap allocations its already made.
34 ///
35 /// Traversal is not recursive.
36 #[derive(Debug, Default, Clone)]
37 pub struct Dfs {
38     stack: Vec<(Event, ir::Block)>,
39     seen: EntitySet<ir::Block>,
40 }
41 
42 impl Dfs {
43     /// Construct a new depth-first traversal.
44     pub fn new() -> Self {
45         Self::default()
46     }
47 
48     /// Perform a depth-first traversal over the given function.
49     ///
50     /// Yields pairs of `(Event, ir::Block)`.
51     ///
52     /// This iterator can be used to perform either pre- or post-order
53     /// traversals, or a combination of the two.
54     pub fn iter<'a>(&'a mut self, func: &'a ir::Function) -> DfsIter<'a> {
55         self.seen.clear();
56         self.stack.clear();
57         if let Some(e) = func.layout.entry_block() {
58             self.stack.push((Event::Enter, e));
59         }
60         DfsIter { dfs: self, func }
61     }
62 
63     /// Perform a pre-order traversal over the given function.
64     ///
65     /// Yields `ir::Block` items.
66     pub fn pre_order_iter<'a>(&'a mut self, func: &'a ir::Function) -> DfsPreOrderIter<'a> {
67         DfsPreOrderIter(self.iter(func))
68     }
69 
70     /// Perform a post-order traversal over the given function.
71     ///
72     /// Yields `ir::Block` items.
73     pub fn post_order_iter<'a>(&'a mut self, func: &'a ir::Function) -> DfsPostOrderIter<'a> {
74         DfsPostOrderIter(self.iter(func))
75     }
76 }
77 
78 /// An iterator that yields pairs of `(Event, ir::Block)` items as it performs a
79 /// depth-first traversal over its associated function.
80 pub struct DfsIter<'a> {
81     dfs: &'a mut Dfs,
82     func: &'a ir::Function,
83 }
84 
85 impl Iterator for DfsIter<'_> {
86     type Item = (Event, ir::Block);
87 
88     fn next(&mut self) -> Option<(Event, ir::Block)> {
89         let (event, block) = self.dfs.stack.pop()?;
90 
91         if event == Event::Enter && self.dfs.seen.insert(block) {
92             self.dfs.stack.push((Event::Exit, block));
93             if let Some(inst) = self.func.layout.last_inst(block) {
94                 self.dfs.stack.extend(
95                     self.func.dfg.insts[inst]
96                         .branch_destination(&self.func.dfg.jump_tables)
97                         .iter()
98                         // Heuristic: chase the children in reverse. This puts
99                         // the first successor block first in the postorder, all
100                         // other things being equal, which tends to prioritize
101                         // loop backedges over out-edges, putting the edge-block
102                         // closer to the loop body and minimizing live-ranges in
103                         // linear instruction space. This heuristic doesn't have
104                         // any effect on the computation of dominators, and is
105                         // purely for other consumers of the postorder we cache
106                         // here.
107                         .rev()
108                         .map(|block| block.block(&self.func.dfg.value_lists))
109                         // This is purely an optimization to avoid additional
110                         // iterations of the loop, and is not required; it's
111                         // merely inlining the check from the outer conditional
112                         // of this case to avoid the extra loop iteration. This
113                         // also avoids potential excess stack growth.
114                         .filter(|block| !self.dfs.seen.contains(*block))
115                         .map(|block| (Event::Enter, block)),
116                 );
117             }
118         }
119 
120         Some((event, block))
121     }
122 }
123 
124 /// An iterator that yields `ir::Block` items during a depth-first, pre-order
125 /// traversal over its associated function.
126 pub struct DfsPreOrderIter<'a>(DfsIter<'a>);
127 
128 impl Iterator for DfsPreOrderIter<'_> {
129     type Item = ir::Block;
130 
131     fn next(&mut self) -> Option<Self::Item> {
132         loop {
133             match self.0.next()? {
134                 (Event::Enter, b) => return Some(b),
135                 (Event::Exit, _) => continue,
136             }
137         }
138     }
139 }
140 
141 /// An iterator that yields `ir::Block` items during a depth-first, post-order
142 /// traversal over its associated function.
143 pub struct DfsPostOrderIter<'a>(DfsIter<'a>);
144 
145 impl Iterator for DfsPostOrderIter<'_> {
146     type Item = ir::Block;
147 
148     fn next(&mut self) -> Option<Self::Item> {
149         loop {
150             match self.0.next()? {
151                 (Event::Exit, b) => return Some(b),
152                 (Event::Enter, _) => continue,
153             }
154         }
155     }
156 }
157 
158 #[cfg(test)]
159 mod tests {
160     use super::*;
161     use crate::cursor::{Cursor, FuncCursor};
162     use crate::ir::{types::I32, Function, InstBuilder, TrapCode};
163 
164     #[test]
165     fn test_dfs_traversal() {
166         let _ = env_logger::try_init();
167 
168         let mut func = Function::new();
169 
170         let block0 = func.dfg.make_block();
171         let v0 = func.dfg.append_block_param(block0, I32);
172         let block1 = func.dfg.make_block();
173         let block2 = func.dfg.make_block();
174         let block3 = func.dfg.make_block();
175 
176         let mut cur = FuncCursor::new(&mut func);
177 
178         // block0(v0):
179         //   br_if v0, block2, trap_block
180         cur.insert_block(block0);
181         cur.ins().brif(v0, block2, &[], block3, &[]);
182 
183         // block3:
184         //   trap user0
185         cur.insert_block(block3);
186         cur.ins().trap(TrapCode::User(0));
187 
188         // block1:
189         //   v1 = iconst.i32 1
190         //   v2 = iadd v0, v1
191         //   jump block0(v2)
192         cur.insert_block(block1);
193         let v1 = cur.ins().iconst(I32, 1);
194         let v2 = cur.ins().iadd(v0, v1);
195         cur.ins().jump(block0, &[v2]);
196 
197         // block2:
198         //   return v0
199         cur.insert_block(block2);
200         cur.ins().return_(&[v0]);
201 
202         let mut dfs = Dfs::new();
203 
204         assert_eq!(
205             dfs.iter(&func).collect::<Vec<_>>(),
206             vec![
207                 (Event::Enter, block0),
208                 (Event::Enter, block2),
209                 (Event::Exit, block2),
210                 (Event::Enter, block3),
211                 (Event::Exit, block3),
212                 (Event::Exit, block0)
213             ],
214         );
215     }
216 }
217