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::inst_predicates::visit_block_succs; 75 use crate::ir::{Block, Function, Inst, Opcode}; 76 use crate::{machinst::*, trace}; 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 trace!("BlockLoweringOrder: function body {:?}", f); 218 219 // Make sure that we have an entry block, and the entry block is 220 // not marked as cold. (The verifier ensures this as well, but 221 // the user may not have run the verifier, and this property is 222 // critical to avoid a miscompile, so we assert it here too.) 223 let entry = f.layout.entry_block().expect("Must have entry block"); 224 assert!(!f.layout.is_cold(entry)); 225 226 // Step 1: compute the in-edge and out-edge count of every block. 227 let mut block_in_count = SecondaryMap::with_default(0); 228 let mut block_out_count = SecondaryMap::with_default(0); 229 230 // Cache the block successors to avoid re-examining branches below. 231 let mut block_succs: SmallVec<[(Inst, usize, Block); 128]> = SmallVec::new(); 232 let mut block_succ_range = SecondaryMap::with_default((0, 0)); 233 for block in f.layout.blocks() { 234 let block_succ_start = block_succs.len(); 235 let mut succ_idx = 0; 236 visit_block_succs(f, block, |inst, succ| { 237 block_out_count[block] += 1; 238 block_in_count[succ] += 1; 239 block_succs.push((inst, succ_idx, succ)); 240 succ_idx += 1; 241 }); 242 let block_succ_end = block_succs.len(); 243 block_succ_range[block] = (block_succ_start, block_succ_end); 244 245 for inst in f.layout.block_likely_branches(block) { 246 if f.dfg[inst].opcode() == Opcode::Return { 247 // Implicit output edge for any return. 248 block_out_count[block] += 1; 249 } 250 } 251 } 252 // Implicit input edge for entry block. 253 block_in_count[entry] += 1; 254 255 // All blocks ending in conditional branches or br_tables must 256 // have edge-moves inserted at the top of successor blocks, 257 // not at the end of themselves. This is because the moves 258 // would have to be inserted prior to the branch's register 259 // use; but RA2's model is that the moves happen *on* the 260 // edge, after every def/use in the block. RA2 will check for 261 // "branch register use safety" and panic if such a problem 262 // occurs. To avoid this, we force the below algorithm to 263 // never merge the edge block onto the end of a block that 264 // ends in a conditional branch. We do this by "faking" more 265 // than one successor, even if there is only one. 266 // 267 // (One might ask, isn't that always the case already? It 268 // could not be, in cases of br_table with no table and just a 269 // default label, for example.) 270 for block in f.layout.blocks() { 271 for inst in f.layout.block_likely_branches(block) { 272 // If the block has a branch with any "fixed args" 273 // (not blockparam args) ... 274 if f.dfg[inst].opcode().is_branch() && f.dfg.inst_fixed_args(inst).len() > 0 { 275 // ... then force a minimum successor count of 276 // two, so the below algorithm cannot put 277 // edge-moves on the end of the block. 278 block_out_count[block] = std::cmp::max(2, block_out_count[block]); 279 } 280 } 281 } 282 283 // Here we define the implicit CLIF-plus-edges graph. There are 284 // conceptually two such graphs: the original, with every edge explicit, 285 // and the merged one, with blocks (represented by `LoweredBlock` 286 // values) that contain original CLIF blocks, edges, or both. This 287 // function returns a lowered block's successors as per the latter, with 288 // consideration to edge-block merging. 289 // 290 // Note that there is a property of the block-merging rules below 291 // that is very important to ensure we don't miss any lowered blocks: 292 // any block in the implicit CLIF-plus-edges graph will *only* be 293 // included in one block in the merged graph. 294 // 295 // This, combined with the property that every edge block is reachable 296 // only from one predecessor (and hence cannot be reached by a DFS 297 // backedge), means that it is sufficient in our DFS below to track 298 // visited-bits per original CLIF block only, not per edge. This greatly 299 // simplifies the data structures (no need to keep a sparse hash-set of 300 // (block, block) tuples). 301 let compute_lowered_succs = |ret: &mut Vec<(Inst, LoweredBlock)>, block: LoweredBlock| { 302 let start_idx = ret.len(); 303 match block { 304 LoweredBlock::Orig { block } | LoweredBlock::EdgeAndOrig { block, .. } => { 305 // At an orig block; successors are always edge blocks, 306 // possibly with orig blocks following. 307 let range = block_succ_range[block]; 308 for &(edge_inst, succ_idx, succ) in &block_succs[range.0..range.1] { 309 if block_in_count[succ] == 1 { 310 ret.push(( 311 edge_inst, 312 LoweredBlock::EdgeAndOrig { 313 pred: block, 314 edge_inst, 315 succ_idx, 316 block: succ, 317 }, 318 )); 319 } else { 320 ret.push(( 321 edge_inst, 322 LoweredBlock::Edge { 323 pred: block, 324 edge_inst, 325 succ_idx, 326 succ, 327 }, 328 )); 329 } 330 } 331 } 332 LoweredBlock::Edge { 333 succ, edge_inst, .. 334 } 335 | LoweredBlock::OrigAndEdge { 336 succ, edge_inst, .. 337 } => { 338 // At an edge block; successors are always orig blocks, 339 // possibly with edge blocks following. 340 if block_out_count[succ] == 1 { 341 let range = block_succ_range[succ]; 342 // check if the one succ is a real CFG edge (vs. 343 // implicit return succ). 344 if range.1 - range.0 > 0 { 345 debug_assert!(range.1 - range.0 == 1); 346 let (succ_edge_inst, succ_succ_idx, succ_succ) = block_succs[range.0]; 347 ret.push(( 348 edge_inst, 349 LoweredBlock::OrigAndEdge { 350 block: succ, 351 edge_inst: succ_edge_inst, 352 succ_idx: succ_succ_idx, 353 succ: succ_succ, 354 }, 355 )); 356 } else { 357 ret.push((edge_inst, LoweredBlock::Orig { block: succ })); 358 } 359 } else { 360 ret.push((edge_inst, LoweredBlock::Orig { block: succ })); 361 } 362 } 363 } 364 let end_idx = ret.len(); 365 (start_idx, end_idx) 366 }; 367 368 // Build the explicit LoweredBlock-to-LoweredBlock successors list. 369 let mut lowered_succs = vec![]; 370 let mut lowered_succ_indices = vec![]; 371 372 // Step 2: Compute RPO traversal of the implicit CLIF-plus-edge-block graph. Use an 373 // explicit stack so we don't overflow the real stack with a deep DFS. 374 #[derive(Debug)] 375 struct StackEntry { 376 this: LoweredBlock, 377 succs: (usize, usize), // range in lowered_succs 378 cur_succ: usize, // index in lowered_succs 379 } 380 381 let mut stack: SmallVec<[StackEntry; 16]> = SmallVec::new(); 382 let mut visited = FxHashSet::default(); 383 let mut postorder = vec![]; 384 385 // Add the entry block. 386 // 387 // FIXME(cfallin): we might be able to use OrigAndEdge. Find a 388 // way to not special-case the entry block here. 389 let block = LoweredBlock::Orig { block: entry }; 390 visited.insert(block); 391 let range = compute_lowered_succs(&mut lowered_succs, block); 392 lowered_succ_indices.resize(lowered_succs.len(), 0); 393 stack.push(StackEntry { 394 this: block, 395 succs: range, 396 cur_succ: range.1, 397 }); 398 399 while !stack.is_empty() { 400 let stack_entry = stack.last_mut().unwrap(); 401 let range = stack_entry.succs; 402 if stack_entry.cur_succ == range.0 { 403 postorder.push((stack_entry.this, range)); 404 stack.pop(); 405 } else { 406 // Heuristic: chase the children in reverse. This puts the first 407 // successor block first in RPO, all other things being equal, 408 // which tends to prioritize loop backedges over out-edges, 409 // putting the edge-block closer to the loop body and minimizing 410 // live-ranges in linear instruction space. 411 let next = lowered_succs[stack_entry.cur_succ - 1].1; 412 stack_entry.cur_succ -= 1; 413 if visited.contains(&next) { 414 continue; 415 } 416 visited.insert(next); 417 let range = compute_lowered_succs(&mut lowered_succs, next); 418 lowered_succ_indices.resize(lowered_succs.len(), 0); 419 stack.push(StackEntry { 420 this: next, 421 succs: range, 422 cur_succ: range.1, 423 }); 424 } 425 } 426 427 postorder.reverse(); 428 let rpo = postorder; 429 430 // Step 3: now that we have RPO, build the BlockIndex/BB fwd/rev maps. 431 let mut lowered_order = vec![]; 432 let mut cold_blocks = FxHashSet::default(); 433 let mut lowered_succ_ranges = vec![]; 434 let mut lb_to_bindex = FxHashMap::default(); 435 for (block, succ_range) in rpo.into_iter() { 436 let index = BlockIndex::new(lowered_order.len()); 437 lb_to_bindex.insert(block, index); 438 lowered_order.push(block); 439 lowered_succ_ranges.push(succ_range); 440 441 match block { 442 LoweredBlock::Orig { block } 443 | LoweredBlock::OrigAndEdge { block, .. } 444 | LoweredBlock::EdgeAndOrig { block, .. } => { 445 if f.layout.is_cold(block) { 446 cold_blocks.insert(index); 447 } 448 } 449 LoweredBlock::Edge { pred, succ, .. } => { 450 if f.layout.is_cold(pred) || f.layout.is_cold(succ) { 451 cold_blocks.insert(index); 452 } 453 } 454 } 455 } 456 457 let lowered_succ_indices = lowered_succs 458 .iter() 459 .map(|&(inst, succ)| (inst, lb_to_bindex.get(&succ).cloned().unwrap())) 460 .collect(); 461 462 let mut orig_map = SecondaryMap::with_default(None); 463 for (i, lb) in lowered_order.iter().enumerate() { 464 let i = BlockIndex::new(i); 465 if let Some(b) = lb.orig_block() { 466 orig_map[b] = Some(i); 467 } 468 } 469 470 let result = BlockLoweringOrder { 471 lowered_order, 472 lowered_succs, 473 lowered_succ_indices, 474 lowered_succ_ranges, 475 orig_map, 476 cold_blocks, 477 }; 478 trace!("BlockLoweringOrder: {:?}", result); 479 result 480 } 481 482 /// Get the lowered order of blocks. 483 pub fn lowered_order(&self) -> &[LoweredBlock] { 484 &self.lowered_order[..] 485 } 486 487 /// Get the successor indices for a lowered block. 488 pub fn succ_indices(&self, block: BlockIndex) -> &[(Inst, BlockIndex)] { 489 let range = self.lowered_succ_ranges[block.index()]; 490 &self.lowered_succ_indices[range.0..range.1] 491 } 492 493 /// Determine whether the given lowered-block index is cold. 494 pub fn is_cold(&self, block: BlockIndex) -> bool { 495 self.cold_blocks.contains(&block) 496 } 497 } 498 499 #[cfg(test)] 500 mod test { 501 use super::*; 502 use crate::cursor::{Cursor, FuncCursor}; 503 use crate::ir::types::*; 504 use crate::ir::{AbiParam, ExternalName, Function, InstBuilder, Signature}; 505 use crate::isa::CallConv; 506 507 fn build_test_func(n_blocks: usize, edges: &[(usize, usize)]) -> Function { 508 assert!(n_blocks > 0); 509 510 let name = ExternalName::testcase("test0"); 511 let mut sig = Signature::new(CallConv::SystemV); 512 sig.params.push(AbiParam::new(I32)); 513 let mut func = Function::with_name_signature(name, sig); 514 let blocks = (0..n_blocks) 515 .map(|i| { 516 let bb = func.dfg.make_block(); 517 assert!(bb.as_u32() == i as u32); 518 bb 519 }) 520 .collect::<Vec<_>>(); 521 522 let arg0 = func.dfg.append_block_param(blocks[0], I32); 523 524 let mut pos = FuncCursor::new(&mut func); 525 526 let mut edge = 0; 527 for i in 0..n_blocks { 528 pos.insert_block(blocks[i]); 529 let mut succs = vec![]; 530 while edge < edges.len() && edges[edge].0 == i { 531 succs.push(edges[edge].1); 532 edge += 1; 533 } 534 if succs.len() == 0 { 535 pos.ins().return_(&[arg0]); 536 } else if succs.len() == 1 { 537 pos.ins().jump(blocks[succs[0]], &[]); 538 } else if succs.len() == 2 { 539 pos.ins().brnz(arg0, blocks[succs[0]], &[]); 540 pos.ins().jump(blocks[succs[1]], &[]); 541 } else { 542 panic!("Too many successors"); 543 } 544 } 545 546 func 547 } 548 549 #[test] 550 fn test_blockorder_diamond() { 551 let func = build_test_func(4, &[(0, 1), (0, 2), (1, 3), (2, 3)]); 552 let order = BlockLoweringOrder::new(&func); 553 554 assert_eq!(order.lowered_order.len(), 6); 555 556 assert!(order.lowered_order[0].orig_block().unwrap().as_u32() == 0); 557 assert!(order.lowered_order[0].in_edge().is_none()); 558 assert!(order.lowered_order[0].out_edge().is_none()); 559 560 assert!(order.lowered_order[1].orig_block().unwrap().as_u32() == 1); 561 assert!(order.lowered_order[1].in_edge().unwrap().0.as_u32() == 0); 562 assert!(order.lowered_order[1].in_edge().unwrap().2.as_u32() == 1); 563 564 assert!(order.lowered_order[2].orig_block().is_none()); 565 assert!(order.lowered_order[2].in_edge().is_none()); 566 assert!(order.lowered_order[2].out_edge().unwrap().0.as_u32() == 1); 567 assert!(order.lowered_order[2].out_edge().unwrap().2.as_u32() == 3); 568 569 assert!(order.lowered_order[3].orig_block().unwrap().as_u32() == 2); 570 assert!(order.lowered_order[3].in_edge().unwrap().0.as_u32() == 0); 571 assert!(order.lowered_order[3].in_edge().unwrap().2.as_u32() == 2); 572 assert!(order.lowered_order[3].out_edge().is_none()); 573 574 assert!(order.lowered_order[4].orig_block().is_none()); 575 assert!(order.lowered_order[4].in_edge().is_none()); 576 assert!(order.lowered_order[4].out_edge().unwrap().0.as_u32() == 2); 577 assert!(order.lowered_order[4].out_edge().unwrap().2.as_u32() == 3); 578 579 assert!(order.lowered_order[5].orig_block().unwrap().as_u32() == 3); 580 assert!(order.lowered_order[5].in_edge().is_none()); 581 assert!(order.lowered_order[5].out_edge().is_none()); 582 } 583 584 #[test] 585 fn test_blockorder_critedge() { 586 // 0 587 // / \ 588 // 1 2 589 // / \ \ 590 // 3 4 | 591 // |\ _|____| 592 // | \/ | 593 // | /\ | 594 // 5 6 595 // 596 // (3 -> 5, 3 -> 6, 4 -> 6 are critical edges and must be split) 597 // 598 let func = build_test_func( 599 7, 600 &[ 601 (0, 1), 602 (0, 2), 603 (1, 3), 604 (1, 4), 605 (2, 5), 606 (3, 5), 607 (3, 6), 608 (4, 6), 609 ], 610 ); 611 let order = BlockLoweringOrder::new(&func); 612 613 assert_eq!(order.lowered_order.len(), 11); 614 println!("ordered = {:?}", order.lowered_order); 615 616 // block 0 617 assert!(order.lowered_order[0].orig_block().unwrap().as_u32() == 0); 618 assert!(order.lowered_order[0].in_edge().is_none()); 619 assert!(order.lowered_order[0].out_edge().is_none()); 620 621 // edge 0->1 + block 1 622 assert!(order.lowered_order[1].orig_block().unwrap().as_u32() == 1); 623 assert!(order.lowered_order[1].in_edge().unwrap().0.as_u32() == 0); 624 assert!(order.lowered_order[1].in_edge().unwrap().2.as_u32() == 1); 625 assert!(order.lowered_order[1].out_edge().is_none()); 626 627 // edge 1->3 + block 3 628 assert!(order.lowered_order[2].orig_block().unwrap().as_u32() == 3); 629 assert!(order.lowered_order[2].in_edge().unwrap().0.as_u32() == 1); 630 assert!(order.lowered_order[2].in_edge().unwrap().2.as_u32() == 3); 631 assert!(order.lowered_order[2].out_edge().is_none()); 632 633 // edge 3->5 634 assert!(order.lowered_order[3].orig_block().is_none()); 635 assert!(order.lowered_order[3].in_edge().is_none()); 636 assert!(order.lowered_order[3].out_edge().unwrap().0.as_u32() == 3); 637 assert!(order.lowered_order[3].out_edge().unwrap().2.as_u32() == 5); 638 639 // edge 3->6 640 assert!(order.lowered_order[4].orig_block().is_none()); 641 assert!(order.lowered_order[4].in_edge().is_none()); 642 assert!(order.lowered_order[4].out_edge().unwrap().0.as_u32() == 3); 643 assert!(order.lowered_order[4].out_edge().unwrap().2.as_u32() == 6); 644 645 // edge 1->4 + block 4 646 assert!(order.lowered_order[5].orig_block().unwrap().as_u32() == 4); 647 assert!(order.lowered_order[5].in_edge().unwrap().0.as_u32() == 1); 648 assert!(order.lowered_order[5].in_edge().unwrap().2.as_u32() == 4); 649 assert!(order.lowered_order[5].out_edge().is_none()); 650 651 // edge 4->6 652 assert!(order.lowered_order[6].orig_block().is_none()); 653 assert!(order.lowered_order[6].in_edge().is_none()); 654 assert!(order.lowered_order[6].out_edge().unwrap().0.as_u32() == 4); 655 assert!(order.lowered_order[6].out_edge().unwrap().2.as_u32() == 6); 656 657 // block 6 658 assert!(order.lowered_order[7].orig_block().unwrap().as_u32() == 6); 659 assert!(order.lowered_order[7].in_edge().is_none()); 660 assert!(order.lowered_order[7].out_edge().is_none()); 661 662 // edge 0->2 + block 2 663 assert!(order.lowered_order[8].orig_block().unwrap().as_u32() == 2); 664 assert!(order.lowered_order[8].in_edge().unwrap().0.as_u32() == 0); 665 assert!(order.lowered_order[8].in_edge().unwrap().2.as_u32() == 2); 666 assert!(order.lowered_order[8].out_edge().is_none()); 667 668 // edge 2->5 669 assert!(order.lowered_order[9].orig_block().is_none()); 670 assert!(order.lowered_order[9].in_edge().is_none()); 671 assert!(order.lowered_order[9].out_edge().unwrap().0.as_u32() == 2); 672 assert!(order.lowered_order[9].out_edge().unwrap().2.as_u32() == 5); 673 674 // block 5 675 assert!(order.lowered_order[10].orig_block().unwrap().as_u32() == 5); 676 assert!(order.lowered_order[10].in_edge().is_none()); 677 assert!(order.lowered_order[10].out_edge().is_none()); 678 } 679 } 680