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