1 //! Converting Cranelift IR to text.
2 //!
3 //! The `write` module provides the `write_function` function which converts an IR `Function` to an
4 //! equivalent textual form. This textual form can be read back by the `cranelift-reader` crate.
5 
6 use crate::entity::SecondaryMap;
7 use crate::ir::entities::AnyEntity;
8 use crate::ir::{Block, DataFlowGraph, Function, Inst, SigRef, Type, Value, ValueDef};
9 use crate::packed_option::ReservedValue;
10 use alloc::string::{String, ToString};
11 use alloc::vec::Vec;
12 use core::fmt::{self, Write};
13 
14 /// A `FuncWriter` used to decorate functions during printing.
15 pub trait FuncWriter {
16     /// Write the basic block header for the current function.
17     fn write_block_header(
18         &mut self,
19         w: &mut dyn Write,
20         func: &Function,
21         block: Block,
22         indent: usize,
23     ) -> fmt::Result;
24 
25     /// Write the given `inst` to `w`.
26     fn write_instruction(
27         &mut self,
28         w: &mut dyn Write,
29         func: &Function,
30         aliases: &SecondaryMap<Value, Vec<Value>>,
31         inst: Inst,
32         indent: usize,
33     ) -> fmt::Result;
34 
35     /// Write the preamble to `w`. By default, this uses `write_entity_definition`.
36     fn write_preamble(&mut self, w: &mut dyn Write, func: &Function) -> Result<bool, fmt::Error> {
37         self.super_preamble(w, func)
38     }
39 
40     /// Default impl of `write_preamble`
41     fn super_preamble(&mut self, w: &mut dyn Write, func: &Function) -> Result<bool, fmt::Error> {
42         let mut any = false;
43 
44         for (ss, slot) in func.dynamic_stack_slots.iter() {
45             any = true;
46             self.write_entity_definition(w, func, ss.into(), slot)?;
47         }
48 
49         for (ss, slot) in func.sized_stack_slots.iter() {
50             any = true;
51             self.write_entity_definition(w, func, ss.into(), slot)?;
52         }
53 
54         for (gv, gv_data) in &func.global_values {
55             any = true;
56             self.write_entity_definition(w, func, gv.into(), gv_data)?;
57         }
58 
59         for (heap, heap_data) in &func.heaps {
60             if !heap_data.index_type.is_invalid() {
61                 any = true;
62                 self.write_entity_definition(w, func, heap.into(), heap_data)?;
63             }
64         }
65 
66         for (table, table_data) in &func.tables {
67             if !table_data.index_type.is_invalid() {
68                 any = true;
69                 self.write_entity_definition(w, func, table.into(), table_data)?;
70             }
71         }
72 
73         // Write out all signatures before functions since function declarations can refer to
74         // signatures.
75         for (sig, sig_data) in &func.dfg.signatures {
76             any = true;
77             self.write_entity_definition(w, func, sig.into(), &sig_data)?;
78         }
79 
80         for (fnref, ext_func) in &func.dfg.ext_funcs {
81             if ext_func.signature != SigRef::reserved_value() {
82                 any = true;
83                 self.write_entity_definition(
84                     w,
85                     func,
86                     fnref.into(),
87                     &ext_func.display(Some(&func.params)),
88                 )?;
89             }
90         }
91 
92         for (jt, jt_data) in &func.jump_tables {
93             any = true;
94             self.write_entity_definition(w, func, jt.into(), jt_data)?;
95         }
96 
97         for (&cref, cval) in func.dfg.constants.iter() {
98             any = true;
99             self.write_entity_definition(w, func, cref.into(), cval)?;
100         }
101 
102         if let Some(limit) = func.stack_limit {
103             any = true;
104             self.write_entity_definition(w, func, AnyEntity::StackLimit, &limit)?;
105         }
106 
107         Ok(any)
108     }
109 
110     /// Write an entity definition defined in the preamble to `w`.
111     fn write_entity_definition(
112         &mut self,
113         w: &mut dyn Write,
114         func: &Function,
115         entity: AnyEntity,
116         value: &dyn fmt::Display,
117     ) -> fmt::Result {
118         self.super_entity_definition(w, func, entity, value)
119     }
120 
121     /// Default impl of `write_entity_definition`
122     #[allow(unused_variables)]
123     fn super_entity_definition(
124         &mut self,
125         w: &mut dyn Write,
126         func: &Function,
127         entity: AnyEntity,
128         value: &dyn fmt::Display,
129     ) -> fmt::Result {
130         writeln!(w, "    {} = {}", entity, value)
131     }
132 }
133 
134 /// A `PlainWriter` that doesn't decorate the function.
135 pub struct PlainWriter;
136 
137 impl FuncWriter for PlainWriter {
138     fn write_instruction(
139         &mut self,
140         w: &mut dyn Write,
141         func: &Function,
142         aliases: &SecondaryMap<Value, Vec<Value>>,
143         inst: Inst,
144         indent: usize,
145     ) -> fmt::Result {
146         write_instruction(w, func, aliases, inst, indent)
147     }
148 
149     fn write_block_header(
150         &mut self,
151         w: &mut dyn Write,
152         func: &Function,
153         block: Block,
154         indent: usize,
155     ) -> fmt::Result {
156         write_block_header(w, func, block, indent)
157     }
158 }
159 
160 /// Write `func` to `w` as equivalent text.
161 /// Use `isa` to emit ISA-dependent annotations.
162 pub fn write_function(w: &mut dyn Write, func: &Function) -> fmt::Result {
163     decorate_function(&mut PlainWriter, w, func)
164 }
165 
166 /// Create a reverse-alias map from a value to all aliases having that value as a direct target
167 fn alias_map(func: &Function) -> SecondaryMap<Value, Vec<Value>> {
168     let mut aliases = SecondaryMap::<_, Vec<_>>::new();
169     for v in func.dfg.values() {
170         // VADFS returns the immediate target of an alias
171         if let Some(k) = func.dfg.value_alias_dest_for_serialization(v) {
172             aliases[k].push(v);
173         }
174     }
175     aliases
176 }
177 
178 /// Writes `func` to `w` as text.
179 /// write_function_plain is passed as 'closure' to print instructions as text.
180 /// pretty_function_error is passed as 'closure' to add error decoration.
181 pub fn decorate_function<FW: FuncWriter>(
182     func_w: &mut FW,
183     w: &mut dyn Write,
184     func: &Function,
185 ) -> fmt::Result {
186     write!(w, "function ")?;
187     write_spec(w, func)?;
188     writeln!(w, " {{")?;
189     let aliases = alias_map(func);
190     let mut any = func_w.write_preamble(w, func)?;
191     for block in &func.layout {
192         if any {
193             writeln!(w)?;
194         }
195         decorate_block(func_w, w, func, &aliases, block)?;
196         any = true;
197     }
198     writeln!(w, "}}")
199 }
200 
201 //----------------------------------------------------------------------
202 //
203 // Function spec.
204 
205 fn write_spec(w: &mut dyn Write, func: &Function) -> fmt::Result {
206     write!(w, "{}{}", func.name, func.signature)
207 }
208 
209 //----------------------------------------------------------------------
210 //
211 // Basic blocks
212 
213 fn write_arg(w: &mut dyn Write, func: &Function, arg: Value) -> fmt::Result {
214     write!(w, "{}: {}", arg, func.dfg.value_type(arg))
215 }
216 
217 /// Write out the basic block header, outdented:
218 ///
219 ///    block1:
220 ///    block1(v1: i32):
221 ///    block10(v4: f64, v5: b1):
222 ///
223 pub fn write_block_header(
224     w: &mut dyn Write,
225     func: &Function,
226     block: Block,
227     indent: usize,
228 ) -> fmt::Result {
229     let cold = if func.layout.is_cold(block) {
230         " cold"
231     } else {
232         ""
233     };
234 
235     // The `indent` is the instruction indentation. block headers are 4 spaces out from that.
236     write!(w, "{1:0$}{2}", indent - 4, "", block)?;
237 
238     let mut args = func.dfg.block_params(block).iter().cloned();
239     match args.next() {
240         None => return writeln!(w, "{}:", cold),
241         Some(arg) => {
242             write!(w, "(")?;
243             write_arg(w, func, arg)?;
244         }
245     }
246     // Remaining arguments.
247     for arg in args {
248         write!(w, ", ")?;
249         write_arg(w, func, arg)?;
250     }
251     writeln!(w, "){}:", cold)
252 }
253 
254 fn decorate_block<FW: FuncWriter>(
255     func_w: &mut FW,
256     w: &mut dyn Write,
257     func: &Function,
258     aliases: &SecondaryMap<Value, Vec<Value>>,
259     block: Block,
260 ) -> fmt::Result {
261     // Indent all instructions if any srclocs are present.
262     let indent = if func.rel_srclocs().is_empty() { 4 } else { 36 };
263 
264     func_w.write_block_header(w, func, block, indent)?;
265     for a in func.dfg.block_params(block).iter().cloned() {
266         write_value_aliases(w, aliases, a, indent)?;
267     }
268 
269     for inst in func.layout.block_insts(block) {
270         func_w.write_instruction(w, func, aliases, inst, indent)?;
271     }
272 
273     Ok(())
274 }
275 
276 //----------------------------------------------------------------------
277 //
278 // Instructions
279 
280 // Should `inst` be printed with a type suffix?
281 //
282 // Polymorphic instructions may need a suffix indicating the value of the controlling type variable
283 // if it can't be trivially inferred.
284 //
285 fn type_suffix(func: &Function, inst: Inst) -> Option<Type> {
286     let inst_data = &func.dfg[inst];
287     let constraints = inst_data.opcode().constraints();
288 
289     if !constraints.is_polymorphic() {
290         return None;
291     }
292 
293     // If the controlling type variable can be inferred from the type of the designated value input
294     // operand, we don't need the type suffix.
295     if constraints.use_typevar_operand() {
296         let ctrl_var = inst_data.typevar_operand(&func.dfg.value_lists).unwrap();
297         let def_block = match func.dfg.value_def(ctrl_var) {
298             ValueDef::Result(instr, _) => func.layout.inst_block(instr),
299             ValueDef::Param(block, _) => Some(block),
300         };
301         if def_block.is_some() && def_block == func.layout.inst_block(inst) {
302             return None;
303         }
304     }
305 
306     let rtype = func.dfg.ctrl_typevar(inst);
307     assert!(
308         !rtype.is_invalid(),
309         "Polymorphic instruction must produce a result"
310     );
311     Some(rtype)
312 }
313 
314 /// Write out any aliases to the given target, including indirect aliases
315 fn write_value_aliases(
316     w: &mut dyn Write,
317     aliases: &SecondaryMap<Value, Vec<Value>>,
318     target: Value,
319     indent: usize,
320 ) -> fmt::Result {
321     let mut todo_stack = vec![target];
322     while let Some(target) = todo_stack.pop() {
323         for &a in &aliases[target] {
324             writeln!(w, "{1:0$}{2} -> {3}", indent, "", a, target)?;
325             todo_stack.push(a);
326         }
327     }
328 
329     Ok(())
330 }
331 
332 fn write_instruction(
333     w: &mut dyn Write,
334     func: &Function,
335     aliases: &SecondaryMap<Value, Vec<Value>>,
336     inst: Inst,
337     indent: usize,
338 ) -> fmt::Result {
339     // Prefix containing source location, encoding, and value locations.
340     let mut s = String::with_capacity(16);
341 
342     // Source location goes first.
343     let srcloc = func.srcloc(inst);
344     if !srcloc.is_default() {
345         write!(s, "{} ", srcloc)?;
346     }
347 
348     // Write out prefix and indent the instruction.
349     write!(w, "{1:0$}", indent, s)?;
350 
351     // Write out the result values, if any.
352     let mut has_results = false;
353     for r in func.dfg.inst_results(inst) {
354         if !has_results {
355             has_results = true;
356             write!(w, "{}", r)?;
357         } else {
358             write!(w, ", {}", r)?;
359         }
360     }
361     if has_results {
362         write!(w, " = ")?;
363     }
364 
365     // Then the opcode, possibly with a '.type' suffix.
366     let opcode = func.dfg[inst].opcode();
367 
368     match type_suffix(func, inst) {
369         Some(suf) => write!(w, "{}.{}", opcode, suf)?,
370         None => write!(w, "{}", opcode)?,
371     }
372 
373     write_operands(w, &func.dfg, inst)?;
374     writeln!(w)?;
375 
376     // Value aliases come out on lines after the instruction defining the referent.
377     for r in func.dfg.inst_results(inst) {
378         write_value_aliases(w, aliases, *r, indent)?;
379     }
380     Ok(())
381 }
382 
383 /// Write the operands of `inst` to `w` with a prepended space.
384 pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {
385     let pool = &dfg.value_lists;
386     use crate::ir::instructions::InstructionData::*;
387     match dfg[inst] {
388         AtomicRmw { op, args, .. } => write!(w, " {} {}, {}", op, args[0], args[1]),
389         AtomicCas { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),
390         LoadNoOffset { flags, arg, .. } => write!(w, "{} {}", flags, arg),
391         StoreNoOffset { flags, args, .. } => write!(w, "{} {}, {}", flags, args[0], args[1]),
392         Unary { arg, .. } => write!(w, " {}", arg),
393         UnaryImm { imm, .. } => write!(w, " {}", imm),
394         UnaryIeee32 { imm, .. } => write!(w, " {}", imm),
395         UnaryIeee64 { imm, .. } => write!(w, " {}", imm),
396         UnaryBool { imm, .. } => write!(w, " {}", imm),
397         UnaryGlobalValue { global_value, .. } => write!(w, " {}", global_value),
398         UnaryConst {
399             constant_handle, ..
400         } => write!(w, " {}", constant_handle),
401         Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]),
402         BinaryImm8 { arg, imm, .. } => write!(w, " {}, {}", arg, imm),
403         BinaryImm64 { arg, imm, .. } => write!(w, " {}, {}", arg, imm),
404         Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),
405         MultiAry { ref args, .. } => {
406             if args.is_empty() {
407                 write!(w, "")
408             } else {
409                 write!(w, " {}", DisplayValues(args.as_slice(pool)))
410             }
411         }
412         NullAry { .. } => write!(w, " "),
413         TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm),
414         Shuffle { imm, args, .. } => {
415             let data = dfg.immediates.get(imm).expect(
416                 "Expected the shuffle mask to already be inserted into the immediates table",
417             );
418             write!(w, " {}, {}, {}", args[0], args[1], data)
419         }
420         IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
421         IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, imm),
422         IntCond { cond, arg, .. } => write!(w, " {} {}", cond, arg),
423         FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
424         FloatCond { cond, arg, .. } => write!(w, " {} {}", cond, arg),
425         IntSelect { cond, args, .. } => {
426             write!(w, " {} {}, {}, {}", cond, args[0], args[1], args[2])
427         }
428         Jump {
429             destination,
430             ref args,
431             ..
432         } => {
433             write!(w, " {}", destination)?;
434             write_block_args(w, args.as_slice(pool))
435         }
436         Branch {
437             destination,
438             ref args,
439             ..
440         } => {
441             let args = args.as_slice(pool);
442             write!(w, " {}, {}", args[0], destination)?;
443             write_block_args(w, &args[1..])
444         }
445         BranchInt {
446             cond,
447             destination,
448             ref args,
449             ..
450         } => {
451             let args = args.as_slice(pool);
452             write!(w, " {} {}, {}", cond, args[0], destination)?;
453             write_block_args(w, &args[1..])
454         }
455         BranchFloat {
456             cond,
457             destination,
458             ref args,
459             ..
460         } => {
461             let args = args.as_slice(pool);
462             write!(w, " {} {}, {}", cond, args[0], destination)?;
463             write_block_args(w, &args[1..])
464         }
465         BranchIcmp {
466             cond,
467             destination,
468             ref args,
469             ..
470         } => {
471             let args = args.as_slice(pool);
472             write!(w, " {} {}, {}, {}", cond, args[0], args[1], destination)?;
473             write_block_args(w, &args[2..])
474         }
475         BranchTable {
476             arg,
477             destination,
478             table,
479             ..
480         } => write!(w, " {}, {}, {}", arg, destination, table),
481         Call {
482             func_ref, ref args, ..
483         } => write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool))),
484         CallIndirect {
485             sig_ref, ref args, ..
486         } => {
487             let args = args.as_slice(pool);
488             write!(
489                 w,
490                 " {}, {}({})",
491                 sig_ref,
492                 args[0],
493                 DisplayValues(&args[1..])
494             )
495         }
496         FuncAddr { func_ref, .. } => write!(w, " {}", func_ref),
497         StackLoad {
498             stack_slot, offset, ..
499         } => write!(w, " {}{}", stack_slot, offset),
500         StackStore {
501             arg,
502             stack_slot,
503             offset,
504             ..
505         } => write!(w, " {}, {}{}", arg, stack_slot, offset),
506         DynamicStackLoad {
507             dynamic_stack_slot, ..
508         } => write!(w, " {}", dynamic_stack_slot),
509         DynamicStackStore {
510             arg,
511             dynamic_stack_slot,
512             ..
513         } => write!(w, " {}, {}", arg, dynamic_stack_slot),
514         HeapAddr { heap, arg, imm, .. } => write!(w, " {}, {}, {}", heap, arg, imm),
515         TableAddr { table, arg, .. } => write!(w, " {}, {}", table, arg),
516         Load {
517             flags, arg, offset, ..
518         } => write!(w, "{} {}{}", flags, arg, offset),
519         Store {
520             flags,
521             args,
522             offset,
523             ..
524         } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset),
525         Trap { code, .. } => write!(w, " {}", code),
526         CondTrap { arg, code, .. } => write!(w, " {}, {}", arg, code),
527         IntCondTrap {
528             cond, arg, code, ..
529         } => write!(w, " {} {}, {}", cond, arg, code),
530         FloatCondTrap {
531             cond, arg, code, ..
532         } => write!(w, " {} {}, {}", cond, arg, code),
533     }?;
534 
535     let mut sep = "  ; ";
536     for &arg in dfg.inst_args(inst) {
537         if let ValueDef::Result(src, _) = dfg.value_def(arg) {
538             let imm = match dfg[src] {
539                 UnaryImm { imm, .. } => imm.to_string(),
540                 UnaryIeee32 { imm, .. } => imm.to_string(),
541                 UnaryIeee64 { imm, .. } => imm.to_string(),
542                 UnaryBool { imm, .. } => imm.to_string(),
543                 UnaryConst {
544                     constant_handle, ..
545                 } => constant_handle.to_string(),
546                 _ => continue,
547             };
548             write!(w, "{}{} = {}", sep, arg, imm)?;
549             sep = ", ";
550         }
551     }
552     Ok(())
553 }
554 
555 /// Write block args using optional parantheses.
556 fn write_block_args(w: &mut dyn Write, args: &[Value]) -> fmt::Result {
557     if args.is_empty() {
558         Ok(())
559     } else {
560         write!(w, "({})", DisplayValues(args))
561     }
562 }
563 
564 /// Displayable slice of values.
565 struct DisplayValues<'a>(&'a [Value]);
566 
567 impl<'a> fmt::Display for DisplayValues<'a> {
568     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
569         for (i, val) in self.0.iter().enumerate() {
570             if i == 0 {
571                 write!(f, "{}", val)?;
572             } else {
573                 write!(f, ", {}", val)?;
574             }
575         }
576         Ok(())
577     }
578 }
579 
580 struct DisplayValuesWithDelimiter<'a>(&'a [Value], char);
581 
582 impl<'a> fmt::Display for DisplayValuesWithDelimiter<'a> {
583     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
584         for (i, val) in self.0.iter().enumerate() {
585             if i == 0 {
586                 write!(f, "{}", val)?;
587             } else {
588                 write!(f, "{}{}", self.1, val)?;
589             }
590         }
591         Ok(())
592     }
593 }
594 
595 #[cfg(test)]
596 mod tests {
597     use crate::cursor::{Cursor, CursorPosition, FuncCursor};
598     use crate::ir::types;
599     use crate::ir::{Function, InstBuilder, StackSlotData, StackSlotKind, UserFuncName};
600     use alloc::string::ToString;
601 
602     #[test]
603     fn basic() {
604         let mut f = Function::new();
605         assert_eq!(f.to_string(), "function u0:0() fast {\n}\n");
606 
607         f.name = UserFuncName::testcase("foo");
608         assert_eq!(f.to_string(), "function %foo() fast {\n}\n");
609 
610         f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4));
611         assert_eq!(
612             f.to_string(),
613             "function %foo() fast {\n    ss0 = explicit_slot 4\n}\n"
614         );
615 
616         let block = f.dfg.make_block();
617         f.layout.append_block(block);
618         assert_eq!(
619             f.to_string(),
620             "function %foo() fast {\n    ss0 = explicit_slot 4\n\nblock0:\n}\n"
621         );
622 
623         f.dfg.append_block_param(block, types::I8);
624         assert_eq!(
625             f.to_string(),
626             "function %foo() fast {\n    ss0 = explicit_slot 4\n\nblock0(v0: i8):\n}\n"
627         );
628 
629         f.dfg.append_block_param(block, types::F32.by(4).unwrap());
630         assert_eq!(
631             f.to_string(),
632             "function %foo() fast {\n    ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n}\n"
633         );
634 
635         {
636             let mut cursor = FuncCursor::new(&mut f);
637             cursor.set_position(CursorPosition::After(block));
638             cursor.ins().return_(&[])
639         };
640         assert_eq!(
641             f.to_string(),
642             "function %foo() fast {\n    ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n    return\n}\n"
643         );
644     }
645 
646     #[test]
647     fn aliases() {
648         use crate::ir::InstBuilder;
649 
650         let mut func = Function::new();
651         {
652             let block0 = func.dfg.make_block();
653             let mut pos = FuncCursor::new(&mut func);
654             pos.insert_block(block0);
655 
656             // make some detached values for change_to_alias
657             let v0 = pos.func.dfg.append_block_param(block0, types::I32);
658             let v1 = pos.func.dfg.append_block_param(block0, types::I32);
659             let v2 = pos.func.dfg.append_block_param(block0, types::I32);
660             pos.func.dfg.detach_block_params(block0);
661 
662             // alias to a param--will be printed at beginning of block defining param
663             let v3 = pos.func.dfg.append_block_param(block0, types::I32);
664             pos.func.dfg.change_to_alias(v0, v3);
665 
666             // alias to an alias--should print attached to alias, not ultimate target
667             pos.func.dfg.make_value_alias_for_serialization(v0, v2); // v0 <- v2
668 
669             // alias to a result--will be printed after instruction producing result
670             let _dummy0 = pos.ins().iconst(types::I32, 42);
671             let v4 = pos.ins().iadd(v0, v0);
672             pos.func.dfg.change_to_alias(v1, v4);
673             let _dummy1 = pos.ins().iconst(types::I32, 23);
674             let _v7 = pos.ins().iadd(v1, v1);
675         }
676         assert_eq!(
677             func.to_string(),
678             "function u0:0() fast {\nblock0(v3: i32):\n    v0 -> v3\n    v2 -> v0\n    v4 = iconst.i32 42\n    v5 = iadd v0, v0\n    v1 -> v5\n    v6 = iconst.i32 23\n    v7 = iadd v1, v1\n}\n"
679         );
680     }
681 
682     #[test]
683     fn cold_blocks() {
684         let mut func = Function::new();
685         {
686             let mut pos = FuncCursor::new(&mut func);
687 
688             let block0 = pos.func.dfg.make_block();
689             pos.insert_block(block0);
690             pos.func.layout.set_cold(block0);
691 
692             let block1 = pos.func.dfg.make_block();
693             pos.insert_block(block1);
694             pos.func.dfg.append_block_param(block1, types::I32);
695             pos.func.layout.set_cold(block1);
696         }
697 
698         assert_eq!(
699             func.to_string(),
700             "function u0:0() fast {\nblock0 cold:\n\nblock1(v0: i32) cold:\n}\n"
701         );
702     }
703 }
704