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