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