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