Skip to main content

cranelift_codegen/
write.rs

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