cranelift_codegen/machinst/
blockorder.rs

1//! Computation of basic block order in emitted code.
2//!
3//! This module handles the translation from CLIF BBs to VCode BBs.
4//!
5//! The basic idea is that we compute a sequence of "lowered blocks" that
6//! correspond to one or more blocks in the graph: (CLIF CFG) `union` (implicit
7//! block on *every* edge). Conceptually, the lowering pipeline wants to insert
8//! moves for phi-nodes on every block-to-block transfer; these blocks always
9//! conceptually exist, but may be merged with an "original" CLIF block (and
10//! hence not actually exist; this is equivalent to inserting the blocks only on
11//! critical edges).
12//!
13//! In other words, starting from a CFG like this (where each "CLIF block" and
14//! "(edge N->M)" is a separate basic block):
15//!
16//! ```plain
17//!
18//!              CLIF block 0
19//!               /           \
20//!       (edge 0->1)         (edge 0->2)
21//!              |                |
22//!       CLIF block 1         CLIF block 2
23//!              \                /
24//!           (edge 1->3)   (edge 2->3)
25//!                   \      /
26//!                 CLIF block 3
27//! ```
28//!
29//! We can produce a CFG of lowered blocks like so:
30//!
31//! ```plain
32//!            +--------------+
33//!            | CLIF block 0 |
34//!            +--------------+
35//!               /           \
36//!     +--------------+     +--------------+
37//!     | (edge 0->1)  |     | (edge 0->2)  |
38//!     | CLIF block 1 |     | CLIF block 2 |
39//!     | (edge 1->3)  |     | (edge 2->3)  |
40//!     +--------------+     +--------------+
41//!                \           /
42//!                 \         /
43//!                +------------+
44//!                |CLIF block 3|
45//!                +------------+
46//! ```
47//!
48//! Each `LoweredBlock` names just an original CLIF block, or just an edge block.
49//!
50//! To compute this lowering, we do a DFS over the CLIF-plus-edge-block graph
51//! (never actually materialized, just defined by a "successors" function), and
52//! compute the reverse postorder.
53//!
54//! This algorithm isn't perfect w.r.t. generated code quality: we don't, for
55//! example, consider any information about whether edge blocks will actually
56//! have content, because this computation happens as part of lowering *before*
57//! regalloc, and regalloc may or may not insert moves/spills/reloads on any
58//! particular edge. But it works relatively well and is conceptually simple.
59//! Furthermore, the [MachBuffer] machine-code sink performs final peephole-like
60//! branch editing that in practice elides empty blocks and simplifies some of
61//! the other redundancies that this scheme produces.
62
63use crate::dominator_tree::DominatorTree;
64use crate::entity::SecondaryMap;
65use crate::fx::{FxHashMap, FxHashSet};
66use crate::inst_predicates::visit_block_succs;
67use crate::ir::{Block, Function, Inst, Opcode};
68use crate::{machinst::*, trace};
69
70use smallvec::SmallVec;
71
72/// Mapping from CLIF BBs to VCode BBs.
73#[derive(Debug)]
74pub struct BlockLoweringOrder {
75    /// Lowered blocks, in BlockIndex order. Each block is some combination of
76    /// (i) a CLIF block, and (ii) inserted crit-edge blocks before or after;
77    /// see [LoweredBlock] for details.
78    lowered_order: Vec<LoweredBlock>,
79    /// BlockIndex values for successors for all lowered blocks, indexing `lowered_order`.
80    lowered_succ_indices: Vec<BlockIndex>,
81    /// Ranges in `lowered_succ_indices` giving the successor lists for each lowered
82    /// block. Indexed by lowering-order index (`BlockIndex`).
83    lowered_succ_ranges: Vec<(Option<Inst>, std::ops::Range<usize>)>,
84    /// Cold blocks. These blocks are not reordered in the
85    /// `lowered_order` above; the lowered order must respect RPO
86    /// (uses after defs) in order for lowering to be
87    /// correct. Instead, this set is used to provide `is_cold()`,
88    /// which is used by VCode emission to sink the blocks at the last
89    /// moment (when we actually emit bytes into the MachBuffer).
90    cold_blocks: FxHashSet<BlockIndex>,
91    /// Lowered blocks that are indirect branch targets.
92    indirect_branch_targets: FxHashSet<BlockIndex>,
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
96pub enum LoweredBlock {
97    /// Block in original CLIF.
98    Orig {
99        /// Original CLIF block.
100        block: Block,
101    },
102
103    /// Critical edge between two CLIF blocks.
104    CriticalEdge {
105        /// The predecessor block.
106        pred: Block,
107
108        /// The successor block.
109        succ: Block,
110
111        /// The index of this branch in the successor edges from `pred`, following the same
112        /// indexing order as `inst_predicates::visit_block_succs`. This is used to distinguish
113        /// multiple edges between the same CLIF blocks.
114        succ_idx: u32,
115    },
116}
117
118impl LoweredBlock {
119    /// Unwrap an `Orig` block.
120    pub fn orig_block(&self) -> Option<Block> {
121        match self {
122            &LoweredBlock::Orig { block } => Some(block),
123            &LoweredBlock::CriticalEdge { .. } => None,
124        }
125    }
126
127    /// The associated in-edge predecessor, if this is a critical edge.
128    #[cfg(test)]
129    pub fn in_edge(&self) -> Option<Block> {
130        match self {
131            &LoweredBlock::CriticalEdge { pred, .. } => Some(pred),
132            &LoweredBlock::Orig { .. } => None,
133        }
134    }
135
136    /// The associated out-edge successor, if this is a critical edge.
137    #[cfg(test)]
138    pub fn out_edge(&self) -> Option<Block> {
139        match self {
140            &LoweredBlock::CriticalEdge { succ, .. } => Some(succ),
141            &LoweredBlock::Orig { .. } => None,
142        }
143    }
144}
145
146impl BlockLoweringOrder {
147    /// Compute and return a lowered block order for `f`.
148    pub fn new(f: &Function, domtree: &DominatorTree) -> BlockLoweringOrder {
149        trace!("BlockLoweringOrder: function body {:?}", f);
150
151        // Step 1: compute the in-edge and out-edge count of every block.
152        let mut block_in_count = SecondaryMap::with_default(0);
153        let mut block_out_count = SecondaryMap::with_default(0);
154
155        // Block successors are stored as `LoweredBlocks` to simplify the construction of
156        // `lowered_succs` in the final result. Initially, all entries are `Orig` values, and are
157        // updated to be `CriticalEdge` when those cases are identified in step 2 below.
158        let mut block_succs: SmallVec<[LoweredBlock; 128]> = SmallVec::new();
159        let mut block_succ_range = SecondaryMap::with_default(0..0);
160
161        let mut indirect_branch_target_clif_blocks = FxHashSet::default();
162
163        for block in f.layout.blocks() {
164            let start = block_succs.len();
165            visit_block_succs(f, block, |_, succ, from_table| {
166                block_out_count[block] += 1;
167                block_in_count[succ] += 1;
168                block_succs.push(LoweredBlock::Orig { block: succ });
169
170                if from_table {
171                    indirect_branch_target_clif_blocks.insert(succ);
172                }
173            });
174
175            // Ensure that blocks terminated by br_table instructions with an empty jump table are
176            // still treated like conditional blocks from the point of view of critical edge
177            // splitting.
178            if let Some(inst) = f.layout.last_inst(block) {
179                if Opcode::BrTable == f.dfg.insts[inst].opcode() {
180                    block_out_count[block] = block_out_count[block].max(2);
181                }
182            }
183
184            let end = block_succs.len();
185            block_succ_range[block] = start..end;
186        }
187
188        // Step 2: walk the postorder from the domtree in reverse to produce our desired node
189        // lowering order, identifying critical edges to split along the way.
190
191        let mut lb_to_bindex = FxHashMap::default();
192        let mut lowered_order = Vec::new();
193
194        for &block in domtree.cfg_postorder().iter().rev() {
195            let lb = LoweredBlock::Orig { block };
196            let bindex = BlockIndex::new(lowered_order.len());
197            lb_to_bindex.insert(lb.clone(), bindex);
198            lowered_order.push(lb);
199
200            if block_out_count[block] > 1 {
201                let range = block_succ_range[block].clone();
202                for (succ_ix, lb) in block_succs[range].iter_mut().enumerate() {
203                    let succ = lb.orig_block().unwrap();
204                    if block_in_count[succ] > 1 {
205                        // Mutate the successor to be a critical edge, as `block` has multiple
206                        // edges leaving it, and `succ` has multiple edges entering it.
207                        *lb = LoweredBlock::CriticalEdge {
208                            pred: block,
209                            succ,
210                            succ_idx: succ_ix as u32,
211                        };
212                        let bindex = BlockIndex::new(lowered_order.len());
213                        lb_to_bindex.insert(*lb, bindex);
214                        lowered_order.push(*lb);
215                    }
216                }
217            }
218        }
219
220        // Step 3: build the successor tables given the lowering order. We can't perform this step
221        // during the creation of `lowering_order`, as we need `lb_to_bindex` to be fully populated
222        // first.
223        let mut lowered_succ_indices = Vec::new();
224        let mut cold_blocks = FxHashSet::default();
225        let mut indirect_branch_targets = FxHashSet::default();
226        let lowered_succ_ranges =
227            Vec::from_iter(lowered_order.iter().enumerate().map(|(ix, lb)| {
228                let bindex = BlockIndex::new(ix);
229                let start = lowered_succ_indices.len();
230                let opt_inst = match lb {
231                    // Block successors are pulled directly over, as they'll have been mutated when
232                    // determining the block order already.
233                    &LoweredBlock::Orig { block } => {
234                        let range = block_succ_range[block].clone();
235                        lowered_succ_indices
236                            .extend(block_succs[range].iter().map(|lb| lb_to_bindex[lb]));
237
238                        if f.layout.is_cold(block) {
239                            cold_blocks.insert(bindex);
240                        }
241
242                        if indirect_branch_target_clif_blocks.contains(&block) {
243                            indirect_branch_targets.insert(bindex);
244                        }
245
246                        let last = f.layout.last_inst(block).unwrap();
247                        let opcode = f.dfg.insts[last].opcode();
248
249                        assert!(opcode.is_terminator());
250
251                        opcode.is_branch().then_some(last)
252                    }
253
254                    // Critical edges won't have successor information in block_succ_range, but
255                    // they only have a single known successor to record anyway.
256                    &LoweredBlock::CriticalEdge { succ, .. } => {
257                        let succ_index = lb_to_bindex[&LoweredBlock::Orig { block: succ }];
258                        lowered_succ_indices.push(succ_index);
259
260                        // Edges inherit indirect branch and cold block metadata from their
261                        // successor.
262
263                        if f.layout.is_cold(succ) {
264                            cold_blocks.insert(bindex);
265                        }
266
267                        if indirect_branch_target_clif_blocks.contains(&succ) {
268                            indirect_branch_targets.insert(bindex);
269                        }
270
271                        None
272                    }
273                };
274                let end = lowered_succ_indices.len();
275                (opt_inst, start..end)
276            }));
277
278        let result = BlockLoweringOrder {
279            lowered_order,
280            lowered_succ_indices,
281            lowered_succ_ranges,
282            cold_blocks,
283            indirect_branch_targets,
284        };
285
286        trace!("BlockLoweringOrder: {:#?}", result);
287        result
288    }
289
290    /// Get the lowered order of blocks.
291    pub fn lowered_order(&self) -> &[LoweredBlock] {
292        &self.lowered_order[..]
293    }
294
295    /// Get the successor indices for a lowered block.
296    pub fn succ_indices(&self, block: BlockIndex) -> (Option<Inst>, &[BlockIndex]) {
297        let (opt_inst, range) = &self.lowered_succ_ranges[block.index()];
298        (opt_inst.clone(), &self.lowered_succ_indices[range.clone()])
299    }
300
301    /// Determine whether the given lowered-block index is cold.
302    pub fn is_cold(&self, block: BlockIndex) -> bool {
303        self.cold_blocks.contains(&block)
304    }
305
306    /// Determine whether the given lowered block index is an indirect branch
307    /// target.
308    pub fn is_indirect_branch_target(&self, block: BlockIndex) -> bool {
309        self.indirect_branch_targets.contains(&block)
310    }
311}
312
313#[cfg(test)]
314mod test {
315    use super::*;
316    use crate::cursor::{Cursor, FuncCursor};
317    use crate::flowgraph::ControlFlowGraph;
318    use crate::ir::types::*;
319    use crate::ir::UserFuncName;
320    use crate::ir::{AbiParam, Function, InstBuilder, Signature};
321    use crate::isa::CallConv;
322
323    fn build_test_func(n_blocks: usize, edges: &[(usize, usize)]) -> BlockLoweringOrder {
324        assert!(n_blocks > 0);
325
326        let name = UserFuncName::testcase("test0");
327        let mut sig = Signature::new(CallConv::SystemV);
328        sig.params.push(AbiParam::new(I32));
329        let mut func = Function::with_name_signature(name, sig);
330        let blocks = (0..n_blocks)
331            .map(|i| {
332                let bb = func.dfg.make_block();
333                assert!(bb.as_u32() == i as u32);
334                bb
335            })
336            .collect::<Vec<_>>();
337
338        let arg0 = func.dfg.append_block_param(blocks[0], I32);
339
340        let mut pos = FuncCursor::new(&mut func);
341
342        let mut edge = 0;
343        for i in 0..n_blocks {
344            pos.insert_block(blocks[i]);
345            let mut succs = vec![];
346            while edge < edges.len() && edges[edge].0 == i {
347                succs.push(edges[edge].1);
348                edge += 1;
349            }
350            if succs.len() == 0 {
351                pos.ins().return_(&[arg0]);
352            } else if succs.len() == 1 {
353                pos.ins().jump(blocks[succs[0]], &[]);
354            } else if succs.len() == 2 {
355                pos.ins()
356                    .brif(arg0, blocks[succs[0]], &[], blocks[succs[1]], &[]);
357            } else {
358                panic!("Too many successors");
359            }
360        }
361
362        let mut cfg = ControlFlowGraph::new();
363        cfg.compute(&func);
364        let dom_tree = DominatorTree::with_function(&func, &cfg);
365
366        BlockLoweringOrder::new(&func, &dom_tree)
367    }
368
369    #[test]
370    fn test_blockorder_diamond() {
371        let order = build_test_func(4, &[(0, 1), (0, 2), (1, 3), (2, 3)]);
372
373        // This test case doesn't need to introduce any critical edges, as all regalloc allocations
374        // can sit on either the entry or exit of blocks 1 and 2.
375        assert_eq!(order.lowered_order.len(), 4);
376    }
377
378    #[test]
379    fn test_blockorder_critedge() {
380        //            0
381        //          /   \
382        //         1     2
383        //        /  \     \
384        //       3    4    |
385        //       |\  _|____|
386        //       | \/ |
387        //       | /\ |
388        //       5    6
389        //
390        // (3 -> 5, and 3 -> 6 are critical edges and must be split)
391        //
392        let order = build_test_func(
393            7,
394            &[
395                (0, 1),
396                (0, 2),
397                (1, 3),
398                (1, 4),
399                (2, 5),
400                (3, 5),
401                (3, 6),
402                (4, 6),
403            ],
404        );
405
406        assert_eq!(order.lowered_order.len(), 9);
407        println!("ordered = {:?}", order.lowered_order);
408
409        // block 0
410        assert_eq!(order.lowered_order[0].orig_block().unwrap().as_u32(), 0);
411        assert!(order.lowered_order[0].in_edge().is_none());
412        assert!(order.lowered_order[0].out_edge().is_none());
413
414        // block 2
415        assert_eq!(order.lowered_order[1].orig_block().unwrap().as_u32(), 2);
416        assert!(order.lowered_order[1].in_edge().is_none());
417        assert!(order.lowered_order[1].out_edge().is_none());
418
419        // block 1
420        assert_eq!(order.lowered_order[2].orig_block().unwrap().as_u32(), 1);
421        assert!(order.lowered_order[2].in_edge().is_none());
422        assert!(order.lowered_order[2].out_edge().is_none());
423
424        // block 4
425        assert_eq!(order.lowered_order[3].orig_block().unwrap().as_u32(), 4);
426        assert!(order.lowered_order[3].in_edge().is_none());
427        assert!(order.lowered_order[3].out_edge().is_none());
428
429        // block 3
430        assert_eq!(order.lowered_order[4].orig_block().unwrap().as_u32(), 3);
431        assert!(order.lowered_order[4].in_edge().is_none());
432        assert!(order.lowered_order[4].out_edge().is_none());
433
434        // critical edge 3 -> 5
435        assert!(order.lowered_order[5].orig_block().is_none());
436        assert_eq!(order.lowered_order[5].in_edge().unwrap().as_u32(), 3);
437        assert_eq!(order.lowered_order[5].out_edge().unwrap().as_u32(), 5);
438
439        // critical edge 3 -> 6
440        assert!(order.lowered_order[6].orig_block().is_none());
441        assert_eq!(order.lowered_order[6].in_edge().unwrap().as_u32(), 3);
442        assert_eq!(order.lowered_order[6].out_edge().unwrap().as_u32(), 6);
443
444        // block 6
445        assert_eq!(order.lowered_order[7].orig_block().unwrap().as_u32(), 6);
446        assert!(order.lowered_order[7].in_edge().is_none());
447        assert!(order.lowered_order[7].out_edge().is_none());
448
449        // block 5
450        assert_eq!(order.lowered_order[8].orig_block().unwrap().as_u32(), 5);
451        assert!(order.lowered_order[8].in_edge().is_none());
452        assert!(order.lowered_order[8].out_edge().is_none());
453    }
454}