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 //!     +--------------+     +--------------+
40 //!              \                /
41 //!          +-----------+ +-----------+
42 //!          |(edge 1->3)| |(edge 2->3)|
43 //!          +-----------+ +-----------+
44 //!                   \      /
45 //!                +------------+
46 //!                |CLIF block 3|
47 //!                +------------+
48 //! ```
49 //!
50 //! (note that the edges into CLIF blocks 1 and 2 could be merged with those
51 //! blocks' original bodies, but the out-edges could not because for simplicity
52 //! in the successor-function definition, we only ever merge an edge onto one
53 //! side of an original CLIF block.)
54 //!
55 //! Each `LoweredBlock` names just an original CLIF block, an original CLIF
56 //! block prepended or appended with an edge block (never both, though), or just
57 //! an edge block.
58 //!
59 //! To compute this lowering, we do a DFS over the CLIF-plus-edge-block graph
60 //! (never actually materialized, just defined by a "successors" function), and
61 //! compute the reverse postorder.
62 //!
63 //! This algorithm isn't perfect w.r.t. generated code quality: we don't, for
64 //! example, consider any information about whether edge blocks will actually
65 //! have content, because this computation happens as part of lowering *before*
66 //! regalloc, and regalloc may or may not insert moves/spills/reloads on any
67 //! particular edge. But it works relatively well and is conceptually simple.
68 //! Furthermore, the [MachBuffer] machine-code sink performs final peephole-like
69 //! branch editing that in practice elides empty blocks and simplifies some of
70 //! the other redundancies that this scheme produces.
71 
72 use crate::entity::SecondaryMap;
73 use crate::fx::{FxHashMap, FxHashSet};
74 use crate::ir::{Block, Function, Inst, Opcode};
75 use crate::machinst::lower::visit_block_succs;
76 use crate::machinst::*;
77 
78 use smallvec::SmallVec;
79 
80 /// Mapping from CLIF BBs to VCode BBs.
81 #[derive(Debug)]
82 pub struct BlockLoweringOrder {
83     /// Lowered blocks, in BlockIndex order. Each block is some combination of
84     /// (i) a CLIF block, and (ii) inserted crit-edge blocks before or after;
85     /// see [LoweredBlock] for details.
86     lowered_order: Vec<LoweredBlock>,
87     /// Successors for all lowered blocks, in one serialized vector. Indexed by
88     /// the ranges in `lowered_succ_ranges`.
89     #[allow(dead_code)]
90     lowered_succs: Vec<(Inst, LoweredBlock)>,
91     /// BlockIndex values for successors for all lowered blocks, in the same
92     /// order as `lowered_succs`.
93     lowered_succ_indices: Vec<(Inst, BlockIndex)>,
94     /// Ranges in `lowered_succs` giving the successor lists for each lowered
95     /// block. Indexed by lowering-order index (`BlockIndex`).
96     lowered_succ_ranges: Vec<(usize, usize)>,
97     /// Mapping from CLIF BB to BlockIndex (index in lowered order). Note that
98     /// some CLIF BBs may not be lowered; in particular, we skip unreachable
99     /// blocks.
100     #[allow(dead_code)]
101     orig_map: SecondaryMap<Block, Option<BlockIndex>>,
102     /// Cold blocks. These blocks are not reordered in the
103     /// `lowered_order` above; the lowered order must respect RPO
104     /// (uses after defs) in order for lowering to be
105     /// correct. Instead, this set is used to provide `is_cold()`,
106     /// which is used by VCode emission to sink the blocks at the last
107     /// moment (when we actually emit bytes into the MachBuffer).
108     cold_blocks: FxHashSet<BlockIndex>,
109 }
110 
111 /// The origin of a block in the lowered block-order: either an original CLIF
112 /// block, or an inserted edge-block, or a combination of the two if an edge is
113 /// non-critical.
114 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
115 pub enum LoweredBlock {
116     /// Block in original CLIF, with no merged edge-blocks.
117     Orig {
118         /// Original CLIF block.
119         block: Block,
120     },
121     /// Block in the original CLIF, plus edge-block to one succ (which is the
122     /// one successor of the original block).
123     OrigAndEdge {
124         /// The original CLIF block contained in this lowered block.
125         block: Block,
126         /// The edge (jump) instruction transitioning from this block
127         /// to the next, i.e., corresponding to the included edge-block. This
128         /// will be an instruction in `block`.
129         edge_inst: Inst,
130         /// The successor index in this edge, to distinguish multiple
131         /// edges between the same block pair.
132         succ_idx: usize,
133         /// The successor CLIF block.
134         succ: Block,
135     },
136     /// Block in the original CLIF, preceded by edge-block from one pred (which
137     /// is the one pred of the original block).
138     EdgeAndOrig {
139         /// The previous CLIF block, i.e., the edge block's predecessor.
140         pred: Block,
141         /// The edge (jump) instruction corresponding to the included
142         /// edge-block. This will be an instruction in `pred`.
143         edge_inst: Inst,
144         /// The successor index in this edge, to distinguish multiple
145         /// edges between the same block pair.
146         succ_idx: usize,
147         /// The original CLIF block included in this lowered block.
148         block: Block,
149     },
150     /// Split critical edge between two CLIF blocks. This lowered block does not
151     /// correspond to any original CLIF blocks; it only serves as an insertion
152     /// point for work to happen on the transition from `pred` to `succ`.
153     Edge {
154         /// The predecessor CLIF block.
155         pred: Block,
156         /// The edge (jump) instruction corresponding to this edge's transition.
157         /// This will be an instruction in `pred`.
158         edge_inst: Inst,
159         /// The successor index in this edge, to distinguish multiple
160         /// edges between the same block pair.
161         succ_idx: usize,
162         /// The successor CLIF block.
163         succ: Block,
164     },
165 }
166 
167 impl LoweredBlock {
168     /// The associated original (CLIF) block included in this lowered block, if
169     /// any.
170     pub fn orig_block(self) -> Option<Block> {
171         match self {
172             LoweredBlock::Orig { block, .. }
173             | LoweredBlock::OrigAndEdge { block, .. }
174             | LoweredBlock::EdgeAndOrig { block, .. } => Some(block),
175             LoweredBlock::Edge { .. } => None,
176         }
177     }
178 
179     /// The associated in-edge, if any.
180     #[cfg(test)]
181     pub fn in_edge(self) -> Option<(Block, Inst, Block)> {
182         match self {
183             LoweredBlock::EdgeAndOrig {
184                 pred,
185                 edge_inst,
186                 block,
187                 ..
188             } => Some((pred, edge_inst, block)),
189             _ => None,
190         }
191     }
192 
193     /// the associated out-edge, if any. Also includes edge-only blocks.
194     #[cfg(test)]
195     pub fn out_edge(self) -> Option<(Block, Inst, Block)> {
196         match self {
197             LoweredBlock::OrigAndEdge {
198                 block,
199                 edge_inst,
200                 succ,
201                 ..
202             } => Some((block, edge_inst, succ)),
203             LoweredBlock::Edge {
204                 pred,
205                 edge_inst,
206                 succ,
207                 ..
208             } => Some((pred, edge_inst, succ)),
209             _ => None,
210         }
211     }
212 }
213 
214 impl BlockLoweringOrder {
215     /// Compute and return a lowered block order for `f`.
216     pub fn new(f: &Function) -> BlockLoweringOrder {
217         log::trace!("BlockLoweringOrder: function body {:?}", f);
218 
219         // Step 1: compute the in-edge and out-edge count of every block.
220         let mut block_in_count = SecondaryMap::with_default(0);
221         let mut block_out_count = SecondaryMap::with_default(0);
222 
223         // Cache the block successors to avoid re-examining branches below.
224         let mut block_succs: SmallVec<[(Inst, usize, Block); 128]> = SmallVec::new();
225         let mut block_succ_range = SecondaryMap::with_default((0, 0));
226         let mut fallthrough_return_block = None;
227         for block in f.layout.blocks() {
228             let block_succ_start = block_succs.len();
229             let mut succ_idx = 0;
230             visit_block_succs(f, block, |inst, succ| {
231                 block_out_count[block] += 1;
232                 block_in_count[succ] += 1;
233                 block_succs.push((inst, succ_idx, succ));
234                 succ_idx += 1;
235             });
236             let block_succ_end = block_succs.len();
237             block_succ_range[block] = (block_succ_start, block_succ_end);
238 
239             for inst in f.layout.block_likely_branches(block) {
240                 if f.dfg[inst].opcode() == Opcode::Return {
241                     // Implicit output edge for any return.
242                     block_out_count[block] += 1;
243                 }
244                 if f.dfg[inst].opcode() == Opcode::FallthroughReturn {
245                     // Fallthrough return block must come last.
246                     debug_assert!(fallthrough_return_block == None);
247                     fallthrough_return_block = Some(block);
248                 }
249             }
250         }
251         // Implicit input edge for entry block.
252         if let Some(entry) = f.layout.entry_block() {
253             block_in_count[entry] += 1;
254         }
255 
256         // All blocks ending in conditional branches or br_tables must
257         // have edge-moves inserted at the top of successor blocks,
258         // not at the end of themselves. This is because the moves
259         // would have to be inserted prior to the branch's register
260         // use; but RA2's model is that the moves happen *on* the
261         // edge, after every def/use in the block. RA2 will check for
262         // "branch register use safety" and panic if such a problem
263         // occurs. To avoid this, we force the below algorithm to
264         // never merge the edge block onto the end of a block that
265         // ends in a conditional branch. We do this by "faking" more
266         // than one successor, even if there is only one.
267         //
268         // (One might ask, isn't that always the case already? It
269         // could not be, in cases of br_table with no table and just a
270         // default label, for example.)
271         for block in f.layout.blocks() {
272             for inst in f.layout.block_likely_branches(block) {
273                 // If the block has a branch with any "fixed args"
274                 // (not blockparam args) ...
275                 if f.dfg[inst].opcode().is_branch() && f.dfg.inst_fixed_args(inst).len() > 0 {
276                     // ... then force a minimum successor count of
277                     // two, so the below algorithm cannot put
278                     // edge-moves on the end of the block.
279                     block_out_count[block] = std::cmp::max(2, block_out_count[block]);
280                 }
281             }
282         }
283 
284         // Here we define the implicit CLIF-plus-edges graph. There are
285         // conceptually two such graphs: the original, with every edge explicit,
286         // and the merged one, with blocks (represented by `LoweredBlock`
287         // values) that contain original CLIF blocks, edges, or both. This
288         // function returns a lowered block's successors as per the latter, with
289         // consideration to edge-block merging.
290         //
291         // Note that there is a property of the block-merging rules below
292         // that is very important to ensure we don't miss any lowered blocks:
293         // any block in the implicit CLIF-plus-edges graph will *only* be
294         // included in one block in the merged graph.
295         //
296         // This, combined with the property that every edge block is reachable
297         // only from one predecessor (and hence cannot be reached by a DFS
298         // backedge), means that it is sufficient in our DFS below to track
299         // visited-bits per original CLIF block only, not per edge. This greatly
300         // simplifies the data structures (no need to keep a sparse hash-set of
301         // (block, block) tuples).
302         let compute_lowered_succs = |ret: &mut Vec<(Inst, LoweredBlock)>, block: LoweredBlock| {
303             let start_idx = ret.len();
304             match block {
305                 LoweredBlock::Orig { block } | LoweredBlock::EdgeAndOrig { block, .. } => {
306                     // At an orig block; successors are always edge blocks,
307                     // possibly with orig blocks following.
308                     let range = block_succ_range[block];
309                     for &(edge_inst, succ_idx, succ) in &block_succs[range.0..range.1] {
310                         if block_in_count[succ] == 1 {
311                             ret.push((
312                                 edge_inst,
313                                 LoweredBlock::EdgeAndOrig {
314                                     pred: block,
315                                     edge_inst,
316                                     succ_idx,
317                                     block: succ,
318                                 },
319                             ));
320                         } else {
321                             ret.push((
322                                 edge_inst,
323                                 LoweredBlock::Edge {
324                                     pred: block,
325                                     edge_inst,
326                                     succ_idx,
327                                     succ,
328                                 },
329                             ));
330                         }
331                     }
332                 }
333                 LoweredBlock::Edge {
334                     succ, edge_inst, ..
335                 }
336                 | LoweredBlock::OrigAndEdge {
337                     succ, edge_inst, ..
338                 } => {
339                     // At an edge block; successors are always orig blocks,
340                     // possibly with edge blocks following.
341                     if block_out_count[succ] == 1 {
342                         let range = block_succ_range[succ];
343                         // check if the one succ is a real CFG edge (vs.
344                         // implicit return succ).
345                         if range.1 - range.0 > 0 {
346                             debug_assert!(range.1 - range.0 == 1);
347                             let (succ_edge_inst, succ_succ_idx, succ_succ) = block_succs[range.0];
348                             ret.push((
349                                 edge_inst,
350                                 LoweredBlock::OrigAndEdge {
351                                     block: succ,
352                                     edge_inst: succ_edge_inst,
353                                     succ_idx: succ_succ_idx,
354                                     succ: succ_succ,
355                                 },
356                             ));
357                         } else {
358                             ret.push((edge_inst, LoweredBlock::Orig { block: succ }));
359                         }
360                     } else {
361                         ret.push((edge_inst, LoweredBlock::Orig { block: succ }));
362                     }
363                 }
364             }
365             let end_idx = ret.len();
366             (start_idx, end_idx)
367         };
368 
369         // Build the explicit LoweredBlock-to-LoweredBlock successors list.
370         let mut lowered_succs = vec![];
371         let mut lowered_succ_indices = vec![];
372 
373         // Step 2: Compute RPO traversal of the implicit CLIF-plus-edge-block graph. Use an
374         // explicit stack so we don't overflow the real stack with a deep DFS.
375         #[derive(Debug)]
376         struct StackEntry {
377             this: LoweredBlock,
378             succs: (usize, usize), // range in lowered_succs
379             cur_succ: usize,       // index in lowered_succs
380         }
381 
382         let mut stack: SmallVec<[StackEntry; 16]> = SmallVec::new();
383         let mut visited = FxHashSet::default();
384         let mut postorder = vec![];
385         if let Some(entry) = f.layout.entry_block() {
386             // FIXME(cfallin): we might be able to use OrigAndEdge. Find a way
387             // to not special-case the entry block here.
388             let block = LoweredBlock::Orig { block: entry };
389             visited.insert(block);
390             let range = compute_lowered_succs(&mut lowered_succs, block);
391             lowered_succ_indices.resize(lowered_succs.len(), 0);
392             stack.push(StackEntry {
393                 this: block,
394                 succs: range,
395                 cur_succ: range.1,
396             });
397         }
398 
399         let mut deferred_last = None;
400         while !stack.is_empty() {
401             let stack_entry = stack.last_mut().unwrap();
402             let range = stack_entry.succs;
403             if stack_entry.cur_succ == range.0 {
404                 let orig_block = stack_entry.this.orig_block();
405                 if orig_block.is_some() && orig_block == fallthrough_return_block {
406                     deferred_last = Some((stack_entry.this, range));
407                 } else {
408                     postorder.push((stack_entry.this, range));
409                 }
410                 stack.pop();
411             } else {
412                 // Heuristic: chase the children in reverse. This puts the first
413                 // successor block first in RPO, all other things being equal,
414                 // which tends to prioritize loop backedges over out-edges,
415                 // putting the edge-block closer to the loop body and minimizing
416                 // live-ranges in linear instruction space.
417                 let next = lowered_succs[stack_entry.cur_succ - 1].1;
418                 stack_entry.cur_succ -= 1;
419                 if visited.contains(&next) {
420                     continue;
421                 }
422                 visited.insert(next);
423                 let range = compute_lowered_succs(&mut lowered_succs, next);
424                 lowered_succ_indices.resize(lowered_succs.len(), 0);
425                 stack.push(StackEntry {
426                     this: next,
427                     succs: range,
428                     cur_succ: range.1,
429                 });
430             }
431         }
432 
433         postorder.reverse();
434         let mut rpo = postorder;
435         if let Some(d) = deferred_last {
436             rpo.push(d);
437         }
438 
439         // Step 3: now that we have RPO, build the BlockIndex/BB fwd/rev maps.
440         let mut lowered_order = vec![];
441         let mut cold_blocks = FxHashSet::default();
442         let mut lowered_succ_ranges = vec![];
443         let mut lb_to_bindex = FxHashMap::default();
444         for (block, succ_range) in rpo.into_iter() {
445             let index = BlockIndex::new(lowered_order.len());
446             lb_to_bindex.insert(block, index);
447             lowered_order.push(block);
448             lowered_succ_ranges.push(succ_range);
449 
450             if block
451                 .orig_block()
452                 .map(|b| f.layout.is_cold(b))
453                 .unwrap_or(false)
454             {
455                 cold_blocks.insert(index);
456             }
457         }
458 
459         let lowered_succ_indices = lowered_succs
460             .iter()
461             .map(|&(inst, succ)| (inst, lb_to_bindex.get(&succ).cloned().unwrap()))
462             .collect();
463 
464         let mut orig_map = SecondaryMap::with_default(None);
465         for (i, lb) in lowered_order.iter().enumerate() {
466             let i = BlockIndex::new(i);
467             if let Some(b) = lb.orig_block() {
468                 orig_map[b] = Some(i);
469             }
470         }
471 
472         let result = BlockLoweringOrder {
473             lowered_order,
474             lowered_succs,
475             lowered_succ_indices,
476             lowered_succ_ranges,
477             orig_map,
478             cold_blocks,
479         };
480         log::trace!("BlockLoweringOrder: {:?}", result);
481         result
482     }
483 
484     /// Get the lowered order of blocks.
485     pub fn lowered_order(&self) -> &[LoweredBlock] {
486         &self.lowered_order[..]
487     }
488 
489     /// Get the successor indices for a lowered block.
490     pub fn succ_indices(&self, block: BlockIndex) -> &[(Inst, BlockIndex)] {
491         let range = self.lowered_succ_ranges[block.index()];
492         &self.lowered_succ_indices[range.0..range.1]
493     }
494 
495     /// Determine whether the given lowered-block index is cold.
496     pub fn is_cold(&self, block: BlockIndex) -> bool {
497         self.cold_blocks.contains(&block)
498     }
499 }
500 
501 #[cfg(test)]
502 mod test {
503     use super::*;
504     use crate::cursor::{Cursor, FuncCursor};
505     use crate::ir::types::*;
506     use crate::ir::{AbiParam, ExternalName, Function, InstBuilder, Signature};
507     use crate::isa::CallConv;
508 
509     fn build_test_func(n_blocks: usize, edges: &[(usize, usize)]) -> Function {
510         assert!(n_blocks > 0);
511 
512         let name = ExternalName::testcase("test0");
513         let mut sig = Signature::new(CallConv::SystemV);
514         sig.params.push(AbiParam::new(I32));
515         let mut func = Function::with_name_signature(name, sig);
516         let blocks = (0..n_blocks)
517             .map(|i| {
518                 let bb = func.dfg.make_block();
519                 assert!(bb.as_u32() == i as u32);
520                 bb
521             })
522             .collect::<Vec<_>>();
523 
524         let arg0 = func.dfg.append_block_param(blocks[0], I32);
525 
526         let mut pos = FuncCursor::new(&mut func);
527 
528         let mut edge = 0;
529         for i in 0..n_blocks {
530             pos.insert_block(blocks[i]);
531             let mut succs = vec![];
532             while edge < edges.len() && edges[edge].0 == i {
533                 succs.push(edges[edge].1);
534                 edge += 1;
535             }
536             if succs.len() == 0 {
537                 pos.ins().return_(&[arg0]);
538             } else if succs.len() == 1 {
539                 pos.ins().jump(blocks[succs[0]], &[]);
540             } else if succs.len() == 2 {
541                 pos.ins().brnz(arg0, blocks[succs[0]], &[]);
542                 pos.ins().jump(blocks[succs[1]], &[]);
543             } else {
544                 panic!("Too many successors");
545             }
546         }
547 
548         func
549     }
550 
551     #[test]
552     fn test_blockorder_diamond() {
553         let func = build_test_func(4, &[(0, 1), (0, 2), (1, 3), (2, 3)]);
554         let order = BlockLoweringOrder::new(&func);
555 
556         assert_eq!(order.lowered_order.len(), 6);
557 
558         assert!(order.lowered_order[0].orig_block().unwrap().as_u32() == 0);
559         assert!(order.lowered_order[0].in_edge().is_none());
560         assert!(order.lowered_order[0].out_edge().is_none());
561 
562         assert!(order.lowered_order[1].orig_block().unwrap().as_u32() == 1);
563         assert!(order.lowered_order[1].in_edge().unwrap().0.as_u32() == 0);
564         assert!(order.lowered_order[1].in_edge().unwrap().2.as_u32() == 1);
565 
566         assert!(order.lowered_order[2].orig_block().is_none());
567         assert!(order.lowered_order[2].in_edge().is_none());
568         assert!(order.lowered_order[2].out_edge().unwrap().0.as_u32() == 1);
569         assert!(order.lowered_order[2].out_edge().unwrap().2.as_u32() == 3);
570 
571         assert!(order.lowered_order[3].orig_block().unwrap().as_u32() == 2);
572         assert!(order.lowered_order[3].in_edge().unwrap().0.as_u32() == 0);
573         assert!(order.lowered_order[3].in_edge().unwrap().2.as_u32() == 2);
574         assert!(order.lowered_order[3].out_edge().is_none());
575 
576         assert!(order.lowered_order[4].orig_block().is_none());
577         assert!(order.lowered_order[4].in_edge().is_none());
578         assert!(order.lowered_order[4].out_edge().unwrap().0.as_u32() == 2);
579         assert!(order.lowered_order[4].out_edge().unwrap().2.as_u32() == 3);
580 
581         assert!(order.lowered_order[5].orig_block().unwrap().as_u32() == 3);
582         assert!(order.lowered_order[5].in_edge().is_none());
583         assert!(order.lowered_order[5].out_edge().is_none());
584     }
585 
586     #[test]
587     fn test_blockorder_critedge() {
588         //            0
589         //          /   \
590         //         1     2
591         //        /  \     \
592         //       3    4    |
593         //       |\  _|____|
594         //       | \/ |
595         //       | /\ |
596         //       5    6
597         //
598         // (3 -> 5, 3 -> 6, 4 -> 6 are critical edges and must be split)
599         //
600         let func = build_test_func(
601             7,
602             &[
603                 (0, 1),
604                 (0, 2),
605                 (1, 3),
606                 (1, 4),
607                 (2, 5),
608                 (3, 5),
609                 (3, 6),
610                 (4, 6),
611             ],
612         );
613         let order = BlockLoweringOrder::new(&func);
614 
615         assert_eq!(order.lowered_order.len(), 11);
616         println!("ordered = {:?}", order.lowered_order);
617 
618         // block 0
619         assert!(order.lowered_order[0].orig_block().unwrap().as_u32() == 0);
620         assert!(order.lowered_order[0].in_edge().is_none());
621         assert!(order.lowered_order[0].out_edge().is_none());
622 
623         // edge 0->1 + block 1
624         assert!(order.lowered_order[1].orig_block().unwrap().as_u32() == 1);
625         assert!(order.lowered_order[1].in_edge().unwrap().0.as_u32() == 0);
626         assert!(order.lowered_order[1].in_edge().unwrap().2.as_u32() == 1);
627         assert!(order.lowered_order[1].out_edge().is_none());
628 
629         // edge 1->3 + block 3
630         assert!(order.lowered_order[2].orig_block().unwrap().as_u32() == 3);
631         assert!(order.lowered_order[2].in_edge().unwrap().0.as_u32() == 1);
632         assert!(order.lowered_order[2].in_edge().unwrap().2.as_u32() == 3);
633         assert!(order.lowered_order[2].out_edge().is_none());
634 
635         // edge 3->5
636         assert!(order.lowered_order[3].orig_block().is_none());
637         assert!(order.lowered_order[3].in_edge().is_none());
638         assert!(order.lowered_order[3].out_edge().unwrap().0.as_u32() == 3);
639         assert!(order.lowered_order[3].out_edge().unwrap().2.as_u32() == 5);
640 
641         // edge 3->6
642         assert!(order.lowered_order[4].orig_block().is_none());
643         assert!(order.lowered_order[4].in_edge().is_none());
644         assert!(order.lowered_order[4].out_edge().unwrap().0.as_u32() == 3);
645         assert!(order.lowered_order[4].out_edge().unwrap().2.as_u32() == 6);
646 
647         // edge 1->4 + block 4
648         assert!(order.lowered_order[5].orig_block().unwrap().as_u32() == 4);
649         assert!(order.lowered_order[5].in_edge().unwrap().0.as_u32() == 1);
650         assert!(order.lowered_order[5].in_edge().unwrap().2.as_u32() == 4);
651         assert!(order.lowered_order[5].out_edge().is_none());
652 
653         // edge 4->6
654         assert!(order.lowered_order[6].orig_block().is_none());
655         assert!(order.lowered_order[6].in_edge().is_none());
656         assert!(order.lowered_order[6].out_edge().unwrap().0.as_u32() == 4);
657         assert!(order.lowered_order[6].out_edge().unwrap().2.as_u32() == 6);
658 
659         // block 6
660         assert!(order.lowered_order[7].orig_block().unwrap().as_u32() == 6);
661         assert!(order.lowered_order[7].in_edge().is_none());
662         assert!(order.lowered_order[7].out_edge().is_none());
663 
664         // edge 0->2 + block 2
665         assert!(order.lowered_order[8].orig_block().unwrap().as_u32() == 2);
666         assert!(order.lowered_order[8].in_edge().unwrap().0.as_u32() == 0);
667         assert!(order.lowered_order[8].in_edge().unwrap().2.as_u32() == 2);
668         assert!(order.lowered_order[8].out_edge().is_none());
669 
670         // edge 2->5
671         assert!(order.lowered_order[9].orig_block().is_none());
672         assert!(order.lowered_order[9].in_edge().is_none());
673         assert!(order.lowered_order[9].out_edge().unwrap().0.as_u32() == 2);
674         assert!(order.lowered_order[9].out_edge().unwrap().2.as_u32() == 5);
675 
676         // block 5
677         assert!(order.lowered_order[10].orig_block().unwrap().as_u32() == 5);
678         assert!(order.lowered_order[10].in_edge().is_none());
679         assert!(order.lowered_order[10].out_edge().is_none());
680     }
681 }
682