1 //! This implements the VCode container: a CFG of Insts that have been lowered. 2 //! 3 //! VCode is virtual-register code. An instruction in VCode is almost a machine 4 //! instruction; however, its register slots can refer to virtual registers in 5 //! addition to real machine registers. 6 //! 7 //! VCode is structured with traditional basic blocks, and 8 //! each block must be terminated by an unconditional branch (one target), a 9 //! conditional branch (two targets), or a return (no targets). Note that this 10 //! slightly differs from the machine code of most ISAs: in most ISAs, a 11 //! conditional branch has one target (and the not-taken case falls through). 12 //! However, we expect that machine backends will elide branches to the following 13 //! block (i.e., zero-offset jumps), and will be able to codegen a branch-cond / 14 //! branch-uncond pair if *both* targets are not fallthrough. This allows us to 15 //! play with layout prior to final binary emission, as well, if we want. 16 //! 17 //! See the main module comment in `mod.rs` for more details on the VCode-based 18 //! backend pipeline. 19 20 use crate::ir::{self, types, SourceLoc}; 21 use crate::machinst::*; 22 use crate::settings; 23 use crate::timing; 24 25 use regalloc::Function as RegallocFunction; 26 use regalloc::Set as RegallocSet; 27 use regalloc::{ 28 BlockIx, InstIx, Range, RegAllocResult, RegClass, RegUsageCollector, RegUsageMapper, SpillSlot, 29 StackmapRequestInfo, 30 }; 31 32 use alloc::boxed::Box; 33 use alloc::{borrow::Cow, vec::Vec}; 34 use std::fmt; 35 use std::iter; 36 use std::string::String; 37 38 /// Index referring to an instruction in VCode. 39 pub type InsnIndex = u32; 40 /// Index referring to a basic block in VCode. 41 pub type BlockIndex = u32; 42 43 /// VCodeInst wraps all requirements for a MachInst to be in VCode: it must be 44 /// a `MachInst` and it must be able to emit itself at least to a `SizeCodeSink`. 45 pub trait VCodeInst: MachInst + MachInstEmit {} 46 impl<I: MachInst + MachInstEmit> VCodeInst for I {} 47 48 /// A function in "VCode" (virtualized-register code) form, after lowering. 49 /// This is essentially a standard CFG of basic blocks, where each basic block 50 /// consists of lowered instructions produced by the machine-specific backend. 51 pub struct VCode<I: VCodeInst> { 52 /// Function liveins. 53 liveins: RegallocSet<RealReg>, 54 55 /// Function liveouts. 56 liveouts: RegallocSet<RealReg>, 57 58 /// VReg IR-level types. 59 vreg_types: Vec<Type>, 60 61 /// Do we have any ref values among our vregs? 62 have_ref_values: bool, 63 64 /// Lowered machine instructions in order corresponding to the original IR. 65 insts: Vec<I>, 66 67 /// Source locations for each instruction. (`SourceLoc` is a `u32`, so it is 68 /// reasonable to keep one of these per instruction.) 69 srclocs: Vec<SourceLoc>, 70 71 /// Entry block. 72 entry: BlockIndex, 73 74 /// Block instruction indices. 75 block_ranges: Vec<(InsnIndex, InsnIndex)>, 76 77 /// Block successors: index range in the successor-list below. 78 block_succ_range: Vec<(usize, usize)>, 79 80 /// Block successor lists, concatenated into one Vec. The `block_succ_range` 81 /// list of tuples above gives (start, end) ranges within this list that 82 /// correspond to each basic block's successors. 83 block_succs: Vec<BlockIx>, 84 85 /// Block-order information. 86 block_order: BlockLoweringOrder, 87 88 /// ABI object. 89 abi: Box<dyn ABIBody<I = I>>, 90 91 /// Safepoint instruction indices. Filled in post-regalloc. (Prior to 92 /// regalloc, the safepoint instructions are listed in the separate 93 /// `StackmapRequestInfo` held separate from the `VCode`.) 94 safepoint_insns: Vec<InsnIndex>, 95 96 /// For each safepoint entry in `safepoint_insns`, a list of `SpillSlot`s. 97 /// These are used to generate actual stack maps at emission. Filled in 98 /// post-regalloc. 99 safepoint_slots: Vec<Vec<SpillSlot>>, 100 } 101 102 /// A builder for a VCode function body. This builder is designed for the 103 /// lowering approach that we take: we traverse basic blocks in forward 104 /// (original IR) order, but within each basic block, we generate code from 105 /// bottom to top; and within each IR instruction that we visit in this reverse 106 /// order, we emit machine instructions in *forward* order again. 107 /// 108 /// Hence, to produce the final instructions in proper order, we perform two 109 /// swaps. First, the machine instructions (`I` instances) are produced in 110 /// forward order for an individual IR instruction. Then these are *reversed* 111 /// and concatenated to `bb_insns` at the end of the IR instruction lowering. 112 /// The `bb_insns` vec will thus contain all machine instructions for a basic 113 /// block, in reverse order. Finally, when we're done with a basic block, we 114 /// reverse the whole block's vec of instructions again, and concatenate onto 115 /// the VCode's insts. 116 pub struct VCodeBuilder<I: VCodeInst> { 117 /// In-progress VCode. 118 vcode: VCode<I>, 119 120 /// In-progress stack map-request info. 121 stack_map_info: StackmapRequestInfo, 122 123 /// Index of the last block-start in the vcode. 124 block_start: InsnIndex, 125 126 /// Start of succs for the current block in the concatenated succs list. 127 succ_start: usize, 128 129 /// Current source location. 130 cur_srcloc: SourceLoc, 131 } 132 133 impl<I: VCodeInst> VCodeBuilder<I> { 134 /// Create a new VCodeBuilder. 135 pub fn new(abi: Box<dyn ABIBody<I = I>>, block_order: BlockLoweringOrder) -> VCodeBuilder<I> { 136 let reftype_class = I::ref_type_regclass(abi.flags()); 137 let vcode = VCode::new(abi, block_order); 138 let stack_map_info = StackmapRequestInfo { 139 reftype_class, 140 reftyped_vregs: vec![], 141 safepoint_insns: vec![], 142 }; 143 144 VCodeBuilder { 145 vcode, 146 stack_map_info, 147 block_start: 0, 148 succ_start: 0, 149 cur_srcloc: SourceLoc::default(), 150 } 151 } 152 153 /// Access the ABI object. 154 pub fn abi(&mut self) -> &mut dyn ABIBody<I = I> { 155 &mut *self.vcode.abi 156 } 157 158 /// Access to the BlockLoweringOrder object. 159 pub fn block_order(&self) -> &BlockLoweringOrder { 160 &self.vcode.block_order 161 } 162 163 /// Set the type of a VReg. 164 pub fn set_vreg_type(&mut self, vreg: VirtualReg, ty: Type) { 165 if self.vcode.vreg_types.len() <= vreg.get_index() { 166 self.vcode 167 .vreg_types 168 .resize(vreg.get_index() + 1, ir::types::I8); 169 } 170 self.vcode.vreg_types[vreg.get_index()] = ty; 171 if is_reftype(ty) { 172 self.stack_map_info.reftyped_vregs.push(vreg); 173 self.vcode.have_ref_values = true; 174 } 175 } 176 177 /// Are there any reference-typed values at all among the vregs? 178 pub fn have_ref_values(&self) -> bool { 179 self.vcode.have_ref_values() 180 } 181 182 /// Set the current block as the entry block. 183 pub fn set_entry(&mut self, block: BlockIndex) { 184 self.vcode.entry = block; 185 } 186 187 /// End the current basic block. Must be called after emitting vcode insts 188 /// for IR insts and prior to ending the function (building the VCode). 189 pub fn end_bb(&mut self) { 190 let start_idx = self.block_start; 191 let end_idx = self.vcode.insts.len() as InsnIndex; 192 self.block_start = end_idx; 193 // Add the instruction index range to the list of blocks. 194 self.vcode.block_ranges.push((start_idx, end_idx)); 195 // End the successors list. 196 let succ_end = self.vcode.block_succs.len(); 197 self.vcode 198 .block_succ_range 199 .push((self.succ_start, succ_end)); 200 self.succ_start = succ_end; 201 } 202 203 /// Push an instruction for the current BB and current IR inst within the BB. 204 pub fn push(&mut self, insn: I, is_safepoint: bool) { 205 match insn.is_term() { 206 MachTerminator::None | MachTerminator::Ret => {} 207 MachTerminator::Uncond(target) => { 208 self.vcode.block_succs.push(BlockIx::new(target.get())); 209 } 210 MachTerminator::Cond(true_branch, false_branch) => { 211 self.vcode.block_succs.push(BlockIx::new(true_branch.get())); 212 self.vcode 213 .block_succs 214 .push(BlockIx::new(false_branch.get())); 215 } 216 MachTerminator::Indirect(targets) => { 217 for target in targets { 218 self.vcode.block_succs.push(BlockIx::new(target.get())); 219 } 220 } 221 } 222 self.vcode.insts.push(insn); 223 self.vcode.srclocs.push(self.cur_srcloc); 224 if is_safepoint { 225 self.stack_map_info 226 .safepoint_insns 227 .push(InstIx::new((self.vcode.insts.len() - 1) as u32)); 228 } 229 } 230 231 /// Get the current source location. 232 pub fn get_srcloc(&self) -> SourceLoc { 233 self.cur_srcloc 234 } 235 236 /// Set the current source location. 237 pub fn set_srcloc(&mut self, srcloc: SourceLoc) { 238 self.cur_srcloc = srcloc; 239 } 240 241 /// Build the final VCode, returning the vcode itself as well as auxiliary 242 /// information, such as the stack map request information. 243 pub fn build(self) -> (VCode<I>, StackmapRequestInfo) { 244 // TODO: come up with an abstraction for "vcode and auxiliary data". The 245 // auxiliary data needs to be separate from the vcode so that it can be 246 // referenced as the vcode is mutated (e.g. by the register allocator). 247 (self.vcode, self.stack_map_info) 248 } 249 } 250 251 fn is_redundant_move<I: VCodeInst>(insn: &I) -> bool { 252 if let Some((to, from)) = insn.is_move() { 253 to.to_reg() == from 254 } else { 255 false 256 } 257 } 258 259 /// Is this type a reference type? 260 fn is_reftype(ty: Type) -> bool { 261 ty == types::R64 || ty == types::R32 262 } 263 264 impl<I: VCodeInst> VCode<I> { 265 /// New empty VCode. 266 fn new(abi: Box<dyn ABIBody<I = I>>, block_order: BlockLoweringOrder) -> VCode<I> { 267 VCode { 268 liveins: abi.liveins(), 269 liveouts: abi.liveouts(), 270 vreg_types: vec![], 271 have_ref_values: false, 272 insts: vec![], 273 srclocs: vec![], 274 entry: 0, 275 block_ranges: vec![], 276 block_succ_range: vec![], 277 block_succs: vec![], 278 block_order, 279 abi, 280 safepoint_insns: vec![], 281 safepoint_slots: vec![], 282 } 283 } 284 285 /// Returns the flags controlling this function's compilation. 286 pub fn flags(&self) -> &settings::Flags { 287 self.abi.flags() 288 } 289 290 /// Get the IR-level type of a VReg. 291 pub fn vreg_type(&self, vreg: VirtualReg) -> Type { 292 self.vreg_types[vreg.get_index()] 293 } 294 295 /// Are there any reference-typed values at all among the vregs? 296 pub fn have_ref_values(&self) -> bool { 297 self.have_ref_values 298 } 299 300 /// Get the entry block. 301 pub fn entry(&self) -> BlockIndex { 302 self.entry 303 } 304 305 /// Get the number of blocks. Block indices will be in the range `0 .. 306 /// (self.num_blocks() - 1)`. 307 pub fn num_blocks(&self) -> usize { 308 self.block_ranges.len() 309 } 310 311 /// Stack frame size for the full function's body. 312 pub fn frame_size(&self) -> u32 { 313 self.abi.frame_size() 314 } 315 316 /// Inbound stack-args size. 317 pub fn stack_args_size(&self) -> u32 { 318 self.abi.stack_args_size() 319 } 320 321 /// Get the successors for a block. 322 pub fn succs(&self, block: BlockIndex) -> &[BlockIx] { 323 let (start, end) = self.block_succ_range[block as usize]; 324 &self.block_succs[start..end] 325 } 326 327 /// Take the results of register allocation, with a sequence of 328 /// instructions including spliced fill/reload/move instructions, and replace 329 /// the VCode with them. 330 pub fn replace_insns_from_regalloc(&mut self, result: RegAllocResult<Self>) { 331 // Record the spillslot count and clobbered registers for the ABI/stack 332 // setup code. 333 self.abi.set_num_spillslots(result.num_spill_slots as usize); 334 self.abi 335 .set_clobbered(result.clobbered_registers.map(|r| Writable::from_reg(*r))); 336 337 let mut final_insns = vec![]; 338 let mut final_block_ranges = vec![(0, 0); self.num_blocks()]; 339 let mut final_srclocs = vec![]; 340 let mut final_safepoint_insns = vec![]; 341 let mut safept_idx = 0; 342 343 assert!(result.target_map.elems().len() == self.num_blocks()); 344 for block in 0..self.num_blocks() { 345 let start = result.target_map.elems()[block].get() as usize; 346 let end = if block == self.num_blocks() - 1 { 347 result.insns.len() 348 } else { 349 result.target_map.elems()[block + 1].get() as usize 350 }; 351 let block = block as BlockIndex; 352 let final_start = final_insns.len() as InsnIndex; 353 354 if block == self.entry { 355 // Start with the prologue. 356 let prologue = self.abi.gen_prologue(); 357 let len = prologue.len(); 358 final_insns.extend(prologue.into_iter()); 359 final_srclocs.extend(iter::repeat(SourceLoc::default()).take(len)); 360 } 361 362 for i in start..end { 363 let insn = &result.insns[i]; 364 365 // Elide redundant moves at this point (we only know what is 366 // redundant once registers are allocated). 367 if is_redundant_move(insn) { 368 continue; 369 } 370 371 // Is there a srcloc associated with this insn? Look it up based on original 372 // instruction index (if new insn corresponds to some original insn, i.e., is not 373 // an inserted load/spill/move). 374 let orig_iix = result.orig_insn_map[InstIx::new(i as u32)]; 375 let srcloc = if orig_iix.is_invalid() { 376 SourceLoc::default() 377 } else { 378 self.srclocs[orig_iix.get() as usize] 379 }; 380 381 // Whenever encountering a return instruction, replace it 382 // with the epilogue. 383 let is_ret = insn.is_term() == MachTerminator::Ret; 384 if is_ret { 385 let epilogue = self.abi.gen_epilogue(); 386 let len = epilogue.len(); 387 final_insns.extend(epilogue.into_iter()); 388 final_srclocs.extend(iter::repeat(srcloc).take(len)); 389 } else { 390 final_insns.push(insn.clone()); 391 final_srclocs.push(srcloc); 392 } 393 394 // Was this instruction a safepoint instruction? Add its final 395 // index to the safepoint insn-index list if so. 396 if safept_idx < result.new_safepoint_insns.len() 397 && (result.new_safepoint_insns[safept_idx].get() as usize) == i 398 { 399 let idx = final_insns.len() - 1; 400 final_safepoint_insns.push(idx as InsnIndex); 401 safept_idx += 1; 402 } 403 } 404 405 let final_end = final_insns.len() as InsnIndex; 406 final_block_ranges[block as usize] = (final_start, final_end); 407 } 408 409 debug_assert!(final_insns.len() == final_srclocs.len()); 410 411 self.insts = final_insns; 412 self.srclocs = final_srclocs; 413 self.block_ranges = final_block_ranges; 414 self.safepoint_insns = final_safepoint_insns; 415 416 // Save safepoint slot-lists. These will be passed to the `EmitState` 417 // for the machine backend during emission so that it can do 418 // target-specific translations of slot numbers to stack offsets. 419 self.safepoint_slots = result.stackmaps; 420 } 421 422 /// Emit the instructions to a `MachBuffer`, containing fixed-up code and external 423 /// reloc/trap/etc. records ready for use. 424 pub fn emit(&self) -> MachBuffer<I> 425 where 426 I: MachInstEmit, 427 { 428 let _tt = timing::vcode_emit(); 429 let mut buffer = MachBuffer::new(); 430 let mut state = I::State::new(&*self.abi); 431 432 buffer.reserve_labels_for_blocks(self.num_blocks() as BlockIndex); // first N MachLabels are simply block indices. 433 434 let flags = self.abi.flags(); 435 let mut safepoint_idx = 0; 436 let mut cur_srcloc = None; 437 for block in 0..self.num_blocks() { 438 let block = block as BlockIndex; 439 let new_offset = I::align_basic_block(buffer.cur_offset()); 440 while new_offset > buffer.cur_offset() { 441 // Pad with NOPs up to the aligned block offset. 442 let nop = I::gen_nop((new_offset - buffer.cur_offset()) as usize); 443 nop.emit(&mut buffer, flags, &mut Default::default()); 444 } 445 assert_eq!(buffer.cur_offset(), new_offset); 446 447 let (start, end) = self.block_ranges[block as usize]; 448 buffer.bind_label(MachLabel::from_block(block)); 449 for iix in start..end { 450 let srcloc = self.srclocs[iix as usize]; 451 if cur_srcloc != Some(srcloc) { 452 if cur_srcloc.is_some() { 453 buffer.end_srcloc(); 454 } 455 buffer.start_srcloc(srcloc); 456 cur_srcloc = Some(srcloc); 457 } 458 459 if safepoint_idx < self.safepoint_insns.len() 460 && self.safepoint_insns[safepoint_idx] == iix 461 { 462 if self.safepoint_slots[safepoint_idx].len() > 0 { 463 let stack_map = self.abi.spillslots_to_stack_map( 464 &self.safepoint_slots[safepoint_idx][..], 465 &state, 466 ); 467 state.pre_safepoint(stack_map); 468 } 469 safepoint_idx += 1; 470 } 471 472 self.insts[iix as usize].emit(&mut buffer, flags, &mut state); 473 } 474 475 if cur_srcloc.is_some() { 476 buffer.end_srcloc(); 477 cur_srcloc = None; 478 } 479 480 // Do we need an island? Get the worst-case size of the next BB and see if, having 481 // emitted that many bytes, we will be beyond the deadline. 482 if block < (self.num_blocks() - 1) as BlockIndex { 483 let next_block = block + 1; 484 let next_block_range = self.block_ranges[next_block as usize]; 485 let next_block_size = next_block_range.1 - next_block_range.0; 486 let worst_case_next_bb = I::worst_case_size() * next_block_size; 487 if buffer.island_needed(worst_case_next_bb) { 488 buffer.emit_island(); 489 } 490 } 491 } 492 493 buffer 494 } 495 496 /// Get the IR block for a BlockIndex, if one exists. 497 pub fn bindex_to_bb(&self, block: BlockIndex) -> Option<ir::Block> { 498 self.block_order.lowered_order()[block as usize].orig_block() 499 } 500 } 501 502 impl<I: VCodeInst> RegallocFunction for VCode<I> { 503 type Inst = I; 504 505 fn insns(&self) -> &[I] { 506 &self.insts[..] 507 } 508 509 fn insns_mut(&mut self) -> &mut [I] { 510 &mut self.insts[..] 511 } 512 513 fn get_insn(&self, insn: InstIx) -> &I { 514 &self.insts[insn.get() as usize] 515 } 516 517 fn get_insn_mut(&mut self, insn: InstIx) -> &mut I { 518 &mut self.insts[insn.get() as usize] 519 } 520 521 fn blocks(&self) -> Range<BlockIx> { 522 Range::new(BlockIx::new(0), self.block_ranges.len()) 523 } 524 525 fn entry_block(&self) -> BlockIx { 526 BlockIx::new(self.entry) 527 } 528 529 fn block_insns(&self, block: BlockIx) -> Range<InstIx> { 530 let (start, end) = self.block_ranges[block.get() as usize]; 531 Range::new(InstIx::new(start), (end - start) as usize) 532 } 533 534 fn block_succs(&self, block: BlockIx) -> Cow<[BlockIx]> { 535 let (start, end) = self.block_succ_range[block.get() as usize]; 536 Cow::Borrowed(&self.block_succs[start..end]) 537 } 538 539 fn is_ret(&self, insn: InstIx) -> bool { 540 match self.insts[insn.get() as usize].is_term() { 541 MachTerminator::Ret => true, 542 _ => false, 543 } 544 } 545 546 fn get_regs(insn: &I, collector: &mut RegUsageCollector) { 547 insn.get_regs(collector) 548 } 549 550 fn map_regs<RUM: RegUsageMapper>(insn: &mut I, mapper: &RUM) { 551 insn.map_regs(mapper); 552 } 553 554 fn is_move(&self, insn: &I) -> Option<(Writable<Reg>, Reg)> { 555 insn.is_move() 556 } 557 558 fn get_num_vregs(&self) -> usize { 559 self.vreg_types.len() 560 } 561 562 fn get_spillslot_size(&self, regclass: RegClass, vreg: VirtualReg) -> u32 { 563 let ty = self.vreg_type(vreg); 564 self.abi.get_spillslot_size(regclass, ty) 565 } 566 567 fn gen_spill(&self, to_slot: SpillSlot, from_reg: RealReg, vreg: Option<VirtualReg>) -> I { 568 let ty = vreg.map(|v| self.vreg_type(v)); 569 self.abi.gen_spill(to_slot, from_reg, ty) 570 } 571 572 fn gen_reload( 573 &self, 574 to_reg: Writable<RealReg>, 575 from_slot: SpillSlot, 576 vreg: Option<VirtualReg>, 577 ) -> I { 578 let ty = vreg.map(|v| self.vreg_type(v)); 579 self.abi.gen_reload(to_reg, from_slot, ty) 580 } 581 582 fn gen_move(&self, to_reg: Writable<RealReg>, from_reg: RealReg, vreg: VirtualReg) -> I { 583 let ty = self.vreg_type(vreg); 584 I::gen_move(to_reg.map(|r| r.to_reg()), from_reg.to_reg(), ty) 585 } 586 587 fn gen_zero_len_nop(&self) -> I { 588 I::gen_zero_len_nop() 589 } 590 591 fn maybe_direct_reload(&self, insn: &I, reg: VirtualReg, slot: SpillSlot) -> Option<I> { 592 insn.maybe_direct_reload(reg, slot) 593 } 594 595 fn func_liveins(&self) -> RegallocSet<RealReg> { 596 self.liveins.clone() 597 } 598 599 fn func_liveouts(&self) -> RegallocSet<RealReg> { 600 self.liveouts.clone() 601 } 602 } 603 604 impl<I: VCodeInst> fmt::Debug for VCode<I> { 605 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 606 writeln!(f, "VCode_Debug {{")?; 607 writeln!(f, " Entry block: {}", self.entry)?; 608 609 for block in 0..self.num_blocks() { 610 writeln!(f, "Block {}:", block,)?; 611 for succ in self.succs(block as BlockIndex) { 612 writeln!(f, " (successor: Block {})", succ.get())?; 613 } 614 let (start, end) = self.block_ranges[block]; 615 writeln!(f, " (instruction range: {} .. {})", start, end)?; 616 for inst in start..end { 617 writeln!(f, " Inst {}: {:?}", inst, self.insts[inst as usize])?; 618 } 619 } 620 621 writeln!(f, "}}")?; 622 Ok(()) 623 } 624 } 625 626 /// Pretty-printing with `RealRegUniverse` context. 627 impl<I: VCodeInst> ShowWithRRU for VCode<I> { 628 fn show_rru(&self, mb_rru: Option<&RealRegUniverse>) -> String { 629 use std::fmt::Write; 630 631 let mut s = String::new(); 632 write!(&mut s, "VCode_ShowWithRRU {{{{\n").unwrap(); 633 write!(&mut s, " Entry block: {}\n", self.entry).unwrap(); 634 635 let mut state = Default::default(); 636 let mut safepoint_idx = 0; 637 for i in 0..self.num_blocks() { 638 let block = i as BlockIndex; 639 640 write!(&mut s, "Block {}:\n", block).unwrap(); 641 if let Some(bb) = self.bindex_to_bb(block) { 642 write!(&mut s, " (original IR block: {})\n", bb).unwrap(); 643 } 644 for succ in self.succs(block) { 645 write!(&mut s, " (successor: Block {})\n", succ.get()).unwrap(); 646 } 647 let (start, end) = self.block_ranges[block as usize]; 648 write!(&mut s, " (instruction range: {} .. {})\n", start, end).unwrap(); 649 for inst in start..end { 650 if safepoint_idx < self.safepoint_insns.len() 651 && self.safepoint_insns[safepoint_idx] == inst 652 { 653 write!( 654 &mut s, 655 " (safepoint: slots {:?} with EmitState {:?})\n", 656 self.safepoint_slots[safepoint_idx], state, 657 ) 658 .unwrap(); 659 safepoint_idx += 1; 660 } 661 write!( 662 &mut s, 663 " Inst {}: {}\n", 664 inst, 665 self.insts[inst as usize].pretty_print(mb_rru, &mut state) 666 ) 667 .unwrap(); 668 } 669 } 670 671 write!(&mut s, "}}}}\n").unwrap(); 672 673 s 674 } 675 } 676