| //! Converting Cranelift IR to text. |
| //! |
| //! The `write` module provides the `write_function` function which converts an IR `Function` to an |
| //! equivalent textual form. This textual form can be read back by the `cranelift-reader` crate. |
| |
| use crate::entity::SecondaryMap; |
| use crate::ir::entities::AnyEntity; |
| use crate::ir::immediates::Ieee128; |
| use crate::ir::pcc::Fact; |
| use crate::ir::{Block, DataFlowGraph, Function, Inst, Opcode, SigRef, Type, Value, ValueDef}; |
| use crate::packed_option::ReservedValue; |
| use alloc::string::{String, ToString}; |
| use alloc::vec::Vec; |
| use core::fmt::{self, Write}; |
| |
| /// A `FuncWriter` used to decorate functions during printing. |
| pub trait FuncWriter { |
| /// Write the basic block header for the current function. |
| fn write_block_header( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| block: Block, |
| indent: usize, |
| ) -> fmt::Result; |
| |
| /// Write the given `inst` to `w`. |
| fn write_instruction( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| aliases: &SecondaryMap<Value, Vec<Value>>, |
| inst: Inst, |
| indent: usize, |
| ) -> fmt::Result; |
| |
| /// Write the preamble to `w`. By default, this uses `write_entity_definition`. |
| fn write_preamble(&mut self, w: &mut dyn Write, func: &Function) -> Result<bool, fmt::Error> { |
| self.super_preamble(w, func) |
| } |
| |
| /// Default impl of `write_preamble` |
| fn super_preamble(&mut self, w: &mut dyn Write, func: &Function) -> Result<bool, fmt::Error> { |
| let mut any = false; |
| |
| for (ss, slot) in func.dynamic_stack_slots.iter() { |
| any = true; |
| self.write_entity_definition(w, func, ss.into(), slot, None)?; |
| } |
| |
| for (ss, slot) in func.sized_stack_slots.iter() { |
| any = true; |
| self.write_entity_definition(w, func, ss.into(), slot, None)?; |
| } |
| |
| for (gv, gv_data) in &func.global_values { |
| any = true; |
| let maybe_fact = func.global_value_facts[gv].as_ref(); |
| self.write_entity_definition(w, func, gv.into(), gv_data, maybe_fact)?; |
| } |
| |
| for (mt, mt_data) in &func.memory_types { |
| any = true; |
| self.write_entity_definition(w, func, mt.into(), mt_data, None)?; |
| } |
| |
| // Write out all signatures before functions since function declarations can refer to |
| // signatures. |
| for (sig, sig_data) in &func.dfg.signatures { |
| any = true; |
| self.write_entity_definition(w, func, sig.into(), &sig_data, None)?; |
| } |
| |
| for (fnref, ext_func) in &func.dfg.ext_funcs { |
| if ext_func.signature != SigRef::reserved_value() { |
| any = true; |
| self.write_entity_definition( |
| w, |
| func, |
| fnref.into(), |
| &ext_func.display(Some(&func.params)), |
| None, |
| )?; |
| } |
| } |
| |
| for (&cref, cval) in func.dfg.constants.iter() { |
| any = true; |
| self.write_entity_definition(w, func, cref.into(), cval, None)?; |
| } |
| |
| if let Some(limit) = func.stack_limit { |
| any = true; |
| self.write_entity_definition(w, func, AnyEntity::StackLimit, &limit, None)?; |
| } |
| |
| Ok(any) |
| } |
| |
| /// Write an entity definition defined in the preamble to `w`. |
| fn write_entity_definition( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| entity: AnyEntity, |
| value: &dyn fmt::Display, |
| maybe_fact: Option<&Fact>, |
| ) -> fmt::Result { |
| self.super_entity_definition(w, func, entity, value, maybe_fact) |
| } |
| |
| /// Default impl of `write_entity_definition` |
| #[allow(unused_variables)] |
| fn super_entity_definition( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| entity: AnyEntity, |
| value: &dyn fmt::Display, |
| maybe_fact: Option<&Fact>, |
| ) -> fmt::Result { |
| if let Some(fact) = maybe_fact { |
| writeln!(w, " {} ! {} = {}", entity, fact, value) |
| } else { |
| writeln!(w, " {} = {}", entity, value) |
| } |
| } |
| } |
| |
| /// A `PlainWriter` that doesn't decorate the function. |
| pub struct PlainWriter; |
| |
| impl FuncWriter for PlainWriter { |
| fn write_instruction( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| aliases: &SecondaryMap<Value, Vec<Value>>, |
| inst: Inst, |
| indent: usize, |
| ) -> fmt::Result { |
| write_instruction(w, func, aliases, inst, indent) |
| } |
| |
| fn write_block_header( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| block: Block, |
| indent: usize, |
| ) -> fmt::Result { |
| write_block_header(w, func, block, indent) |
| } |
| } |
| |
| /// Write `func` to `w` as equivalent text. |
| /// Use `isa` to emit ISA-dependent annotations. |
| pub fn write_function(w: &mut dyn Write, func: &Function) -> fmt::Result { |
| decorate_function(&mut PlainWriter, w, func) |
| } |
| |
| /// Create a reverse-alias map from a value to all aliases having that value as a direct target |
| fn alias_map(func: &Function) -> SecondaryMap<Value, Vec<Value>> { |
| let mut aliases = SecondaryMap::<_, Vec<_>>::new(); |
| for v in func.dfg.values() { |
| // VADFS returns the immediate target of an alias |
| if let Some(k) = func.dfg.value_alias_dest_for_serialization(v) { |
| aliases[k].push(v); |
| } |
| } |
| aliases |
| } |
| |
| /// Writes `func` to `w` as text. |
| /// write_function_plain is passed as 'closure' to print instructions as text. |
| /// pretty_function_error is passed as 'closure' to add error decoration. |
| pub fn decorate_function<FW: FuncWriter>( |
| func_w: &mut FW, |
| w: &mut dyn Write, |
| func: &Function, |
| ) -> fmt::Result { |
| write!(w, "function ")?; |
| write_spec(w, func)?; |
| writeln!(w, " {{")?; |
| let aliases = alias_map(func); |
| let mut any = func_w.write_preamble(w, func)?; |
| for block in &func.layout { |
| if any { |
| writeln!(w)?; |
| } |
| decorate_block(func_w, w, func, &aliases, block)?; |
| any = true; |
| } |
| writeln!(w, "}}") |
| } |
| |
| //---------------------------------------------------------------------- |
| // |
| // Function spec. |
| |
| fn write_spec(w: &mut dyn Write, func: &Function) -> fmt::Result { |
| write!(w, "{}{}", func.name, func.signature) |
| } |
| |
| //---------------------------------------------------------------------- |
| // |
| // Basic blocks |
| |
| fn write_arg(w: &mut dyn Write, func: &Function, arg: Value) -> fmt::Result { |
| let ty = func.dfg.value_type(arg); |
| if let Some(f) = &func.dfg.facts[arg] { |
| write!(w, "{} ! {}: {}", arg, f, ty) |
| } else { |
| write!(w, "{}: {}", arg, ty) |
| } |
| } |
| |
| /// Write out the basic block header, outdented: |
| /// |
| /// block1: |
| /// block1(v1: i32): |
| /// block10(v4: f64, v5: i8): |
| /// |
| pub fn write_block_header( |
| w: &mut dyn Write, |
| func: &Function, |
| block: Block, |
| indent: usize, |
| ) -> fmt::Result { |
| let cold = if func.layout.is_cold(block) { |
| " cold" |
| } else { |
| "" |
| }; |
| |
| // The `indent` is the instruction indentation. block headers are 4 spaces out from that. |
| write!(w, "{1:0$}{2}", indent - 4, "", block)?; |
| |
| let mut args = func.dfg.block_params(block).iter().cloned(); |
| match args.next() { |
| None => return writeln!(w, "{}:", cold), |
| Some(arg) => { |
| write!(w, "(")?; |
| write_arg(w, func, arg)?; |
| } |
| } |
| // Remaining arguments. |
| for arg in args { |
| write!(w, ", ")?; |
| write_arg(w, func, arg)?; |
| } |
| writeln!(w, "){}:", cold) |
| } |
| |
| fn decorate_block<FW: FuncWriter>( |
| func_w: &mut FW, |
| w: &mut dyn Write, |
| func: &Function, |
| aliases: &SecondaryMap<Value, Vec<Value>>, |
| block: Block, |
| ) -> fmt::Result { |
| // Indent all instructions if any srclocs are present. |
| let indent = if func.rel_srclocs().is_empty() { 4 } else { 36 }; |
| |
| func_w.write_block_header(w, func, block, indent)?; |
| for a in func.dfg.block_params(block).iter().cloned() { |
| write_value_aliases(w, aliases, a, indent)?; |
| } |
| |
| for inst in func.layout.block_insts(block) { |
| func_w.write_instruction(w, func, aliases, inst, indent)?; |
| } |
| |
| Ok(()) |
| } |
| |
| //---------------------------------------------------------------------- |
| // |
| // Instructions |
| |
| // Should `inst` be printed with a type suffix? |
| // |
| // Polymorphic instructions may need a suffix indicating the value of the controlling type variable |
| // if it can't be trivially inferred. |
| // |
| fn type_suffix(func: &Function, inst: Inst) -> Option<Type> { |
| let inst_data = &func.dfg.insts[inst]; |
| let constraints = inst_data.opcode().constraints(); |
| |
| if !constraints.is_polymorphic() { |
| return None; |
| } |
| |
| // If the controlling type variable can be inferred from the type of the designated value input |
| // operand, we don't need the type suffix. |
| if constraints.use_typevar_operand() { |
| let ctrl_var = inst_data.typevar_operand(&func.dfg.value_lists).unwrap(); |
| let def_block = match func.dfg.value_def(ctrl_var) { |
| ValueDef::Result(instr, _) => func.layout.inst_block(instr), |
| ValueDef::Param(block, _) => Some(block), |
| ValueDef::Union(..) => None, |
| }; |
| if def_block.is_some() && def_block == func.layout.inst_block(inst) { |
| return None; |
| } |
| } |
| |
| let rtype = func.dfg.ctrl_typevar(inst); |
| assert!( |
| !rtype.is_invalid(), |
| "Polymorphic instruction must produce a result" |
| ); |
| Some(rtype) |
| } |
| |
| /// Write out any aliases to the given target, including indirect aliases |
| fn write_value_aliases( |
| w: &mut dyn Write, |
| aliases: &SecondaryMap<Value, Vec<Value>>, |
| target: Value, |
| indent: usize, |
| ) -> fmt::Result { |
| let mut todo_stack = vec![target]; |
| while let Some(target) = todo_stack.pop() { |
| for &a in &aliases[target] { |
| writeln!(w, "{1:0$}{2} -> {3}", indent, "", a, target)?; |
| todo_stack.push(a); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn write_instruction( |
| w: &mut dyn Write, |
| func: &Function, |
| aliases: &SecondaryMap<Value, Vec<Value>>, |
| inst: Inst, |
| indent: usize, |
| ) -> fmt::Result { |
| // Prefix containing source location, encoding, and value locations. |
| let mut s = String::with_capacity(16); |
| |
| // Source location goes first. |
| let srcloc = func.srcloc(inst); |
| if !srcloc.is_default() { |
| write!(s, "{} ", srcloc)?; |
| } |
| |
| // Write out prefix and indent the instruction. |
| write!(w, "{1:0$}", indent, s)?; |
| |
| // Write out the result values, if any. |
| let mut has_results = false; |
| for r in func.dfg.inst_results(inst) { |
| if !has_results { |
| has_results = true; |
| write!(w, "{}", r)?; |
| } else { |
| write!(w, ", {}", r)?; |
| } |
| if let Some(f) = &func.dfg.facts[*r] { |
| write!(w, " ! {}", f)?; |
| } |
| } |
| if has_results { |
| write!(w, " = ")?; |
| } |
| |
| // Then the opcode, possibly with a '.type' suffix. |
| let opcode = func.dfg.insts[inst].opcode(); |
| |
| match type_suffix(func, inst) { |
| Some(suf) => write!(w, "{}.{}", opcode, suf)?, |
| None => write!(w, "{}", opcode)?, |
| } |
| |
| write_operands(w, &func.dfg, inst)?; |
| writeln!(w)?; |
| |
| // Value aliases come out on lines after the instruction defining the referent. |
| for r in func.dfg.inst_results(inst) { |
| write_value_aliases(w, aliases, *r, indent)?; |
| } |
| Ok(()) |
| } |
| |
| /// Write the operands of `inst` to `w` with a prepended space. |
| pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result { |
| let pool = &dfg.value_lists; |
| let jump_tables = &dfg.jump_tables; |
| use crate::ir::instructions::InstructionData::*; |
| let ctrl_ty = dfg.ctrl_typevar(inst); |
| match dfg.insts[inst] { |
| AtomicRmw { op, args, .. } => write!(w, " {} {}, {}", op, args[0], args[1]), |
| AtomicCas { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), |
| LoadNoOffset { flags, arg, .. } => write!(w, "{} {}", flags, arg), |
| StoreNoOffset { flags, args, .. } => write!(w, "{} {}, {}", flags, args[0], args[1]), |
| Unary { arg, .. } => write!(w, " {}", arg), |
| UnaryImm { imm, .. } => write!(w, " {}", { |
| let mut imm = imm; |
| if ctrl_ty.bits() != 0 { |
| imm = imm.sign_extend_from_width(ctrl_ty.bits()); |
| } |
| imm |
| }), |
| UnaryIeee16 { imm, .. } => write!(w, " {}", imm), |
| UnaryIeee32 { imm, .. } => write!(w, " {}", imm), |
| UnaryIeee64 { imm, .. } => write!(w, " {}", imm), |
| UnaryGlobalValue { global_value, .. } => write!(w, " {}", global_value), |
| UnaryConst { |
| constant_handle, .. |
| } => write!(w, " {}", constant_handle), |
| Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]), |
| BinaryImm8 { arg, imm, .. } => write!(w, " {}, {}", arg, imm), |
| BinaryImm64 { arg, imm, .. } => write!(w, " {}, {}", arg, { |
| let mut imm = imm; |
| if ctrl_ty.bits() != 0 { |
| imm = imm.sign_extend_from_width(ctrl_ty.bits()); |
| } |
| imm |
| }), |
| Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), |
| MultiAry { ref args, .. } => { |
| if args.is_empty() { |
| write!(w, "") |
| } else { |
| write!(w, " {}", DisplayValues(args.as_slice(pool))) |
| } |
| } |
| NullAry { .. } => write!(w, " "), |
| TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm), |
| Shuffle { imm, args, .. } => { |
| let data = dfg.immediates.get(imm).expect( |
| "Expected the shuffle mask to already be inserted into the immediates table", |
| ); |
| write!(w, " {}, {}, {}", args[0], args[1], data) |
| } |
| IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), |
| IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, { |
| let mut imm = imm; |
| if ctrl_ty.bits() != 0 { |
| imm = imm.sign_extend_from_width(ctrl_ty.bits()); |
| } |
| imm |
| }), |
| IntAddTrap { args, code, .. } => write!(w, " {}, {}, {}", args[0], args[1], code), |
| FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), |
| Jump { destination, .. } => { |
| write!(w, " {}", destination.display(pool)) |
| } |
| Brif { |
| arg, |
| blocks: [block_then, block_else], |
| .. |
| } => { |
| write!(w, " {}, {}", arg, block_then.display(pool))?; |
| write!(w, ", {}", block_else.display(pool)) |
| } |
| BranchTable { arg, table, .. } => { |
| write!(w, " {}, {}", arg, jump_tables[table].display(pool)) |
| } |
| Call { |
| func_ref, ref args, .. |
| } => { |
| write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool)))?; |
| write_user_stack_map_entries(w, dfg, inst) |
| } |
| CallIndirect { |
| sig_ref, ref args, .. |
| } => { |
| let args = args.as_slice(pool); |
| write!( |
| w, |
| " {}, {}({})", |
| sig_ref, |
| args[0], |
| DisplayValues(&args[1..]) |
| )?; |
| write_user_stack_map_entries(w, dfg, inst) |
| } |
| FuncAddr { func_ref, .. } => write!(w, " {}", func_ref), |
| StackLoad { |
| stack_slot, offset, .. |
| } => write!(w, " {}{}", stack_slot, offset), |
| StackStore { |
| arg, |
| stack_slot, |
| offset, |
| .. |
| } => write!(w, " {}, {}{}", arg, stack_slot, offset), |
| DynamicStackLoad { |
| dynamic_stack_slot, .. |
| } => write!(w, " {}", dynamic_stack_slot), |
| DynamicStackStore { |
| arg, |
| dynamic_stack_slot, |
| .. |
| } => write!(w, " {}, {}", arg, dynamic_stack_slot), |
| Load { |
| flags, arg, offset, .. |
| } => write!(w, "{} {}{}", flags, arg, offset), |
| Store { |
| flags, |
| args, |
| offset, |
| .. |
| } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset), |
| Trap { code, .. } => write!(w, " {}", code), |
| CondTrap { arg, code, .. } => write!(w, " {}, {}", arg, code), |
| }?; |
| |
| let mut sep = " ; "; |
| for arg in dfg.inst_values(inst) { |
| if let ValueDef::Result(src, _) = dfg.value_def(arg) { |
| let imm = match dfg.insts[src] { |
| UnaryImm { imm, .. } => { |
| let mut imm = imm; |
| if dfg.ctrl_typevar(src).bits() != 0 { |
| imm = imm.sign_extend_from_width(dfg.ctrl_typevar(src).bits()); |
| } |
| imm.to_string() |
| } |
| UnaryIeee16 { imm, .. } => imm.to_string(), |
| UnaryIeee32 { imm, .. } => imm.to_string(), |
| UnaryIeee64 { imm, .. } => imm.to_string(), |
| UnaryConst { |
| constant_handle, |
| opcode: Opcode::F128const, |
| } => Ieee128::try_from(dfg.constants.get(constant_handle)) |
| .expect("16-byte f128 constant") |
| .to_string(), |
| UnaryConst { |
| constant_handle, .. |
| } => constant_handle.to_string(), |
| _ => continue, |
| }; |
| write!(w, "{}{} = {}", sep, arg, imm)?; |
| sep = ", "; |
| } |
| } |
| Ok(()) |
| } |
| |
| fn write_user_stack_map_entries(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result { |
| let entries = match dfg.user_stack_map_entries(inst) { |
| None => return Ok(()), |
| Some(es) => es, |
| }; |
| write!(w, ", stack_map=[")?; |
| let mut need_comma = false; |
| for entry in entries { |
| if need_comma { |
| write!(w, ", ")?; |
| } |
| write!(w, "{} @ {}+{}", entry.ty, entry.slot, entry.offset)?; |
| need_comma = true; |
| } |
| write!(w, "]")?; |
| Ok(()) |
| } |
| |
| /// Displayable slice of values. |
| struct DisplayValues<'a>(&'a [Value]); |
| |
| impl<'a> fmt::Display for DisplayValues<'a> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| for (i, val) in self.0.iter().enumerate() { |
| if i == 0 { |
| write!(f, "{}", val)?; |
| } else { |
| write!(f, ", {}", val)?; |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::cursor::{Cursor, CursorPosition, FuncCursor}; |
| use crate::ir::types; |
| use crate::ir::{Function, InstBuilder, StackSlotData, StackSlotKind, UserFuncName}; |
| use alloc::string::ToString; |
| |
| #[test] |
| fn basic() { |
| let mut f = Function::new(); |
| assert_eq!(f.to_string(), "function u0:0() fast {\n}\n"); |
| |
| f.name = UserFuncName::testcase("foo"); |
| assert_eq!(f.to_string(), "function %foo() fast {\n}\n"); |
| |
| f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 0)); |
| assert_eq!( |
| f.to_string(), |
| "function %foo() fast {\n ss0 = explicit_slot 4\n}\n" |
| ); |
| |
| let block = f.dfg.make_block(); |
| f.layout.append_block(block); |
| assert_eq!( |
| f.to_string(), |
| "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0:\n}\n" |
| ); |
| |
| f.dfg.append_block_param(block, types::I8); |
| assert_eq!( |
| f.to_string(), |
| "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8):\n}\n" |
| ); |
| |
| f.dfg.append_block_param(block, types::F32.by(4).unwrap()); |
| assert_eq!( |
| f.to_string(), |
| "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n}\n" |
| ); |
| |
| { |
| let mut cursor = FuncCursor::new(&mut f); |
| cursor.set_position(CursorPosition::After(block)); |
| cursor.ins().return_(&[]) |
| }; |
| assert_eq!( |
| f.to_string(), |
| "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n return\n}\n" |
| ); |
| |
| let mut f = Function::new(); |
| f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 2)); |
| assert_eq!( |
| f.to_string(), |
| "function u0:0() fast {\n ss0 = explicit_slot 4, align = 4\n}\n" |
| ); |
| } |
| |
| #[test] |
| fn aliases() { |
| use crate::ir::InstBuilder; |
| |
| let mut func = Function::new(); |
| { |
| let block0 = func.dfg.make_block(); |
| let mut pos = FuncCursor::new(&mut func); |
| pos.insert_block(block0); |
| |
| // make some detached values for change_to_alias |
| let v0 = pos.func.dfg.append_block_param(block0, types::I32); |
| let v1 = pos.func.dfg.append_block_param(block0, types::I32); |
| let v2 = pos.func.dfg.append_block_param(block0, types::I32); |
| pos.func.dfg.detach_block_params(block0); |
| |
| // alias to a param--will be printed at beginning of block defining param |
| let v3 = pos.func.dfg.append_block_param(block0, types::I32); |
| pos.func.dfg.change_to_alias(v0, v3); |
| |
| // alias to an alias--should print attached to alias, not ultimate target |
| pos.func.dfg.make_value_alias_for_serialization(v0, v2); // v0 <- v2 |
| |
| // alias to a result--will be printed after instruction producing result |
| let _dummy0 = pos.ins().iconst(types::I32, 42); |
| let v4 = pos.ins().iadd(v0, v0); |
| pos.func.dfg.change_to_alias(v1, v4); |
| let _dummy1 = pos.ins().iconst(types::I32, 23); |
| let _v7 = pos.ins().iadd(v1, v1); |
| } |
| assert_eq!( |
| func.to_string(), |
| "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" |
| ); |
| } |
| |
| #[test] |
| fn cold_blocks() { |
| let mut func = Function::new(); |
| { |
| let mut pos = FuncCursor::new(&mut func); |
| |
| let block0 = pos.func.dfg.make_block(); |
| pos.insert_block(block0); |
| pos.func.layout.set_cold(block0); |
| |
| let block1 = pos.func.dfg.make_block(); |
| pos.insert_block(block1); |
| pos.func.dfg.append_block_param(block1, types::I32); |
| pos.func.layout.set_cold(block1); |
| } |
| |
| assert_eq!( |
| func.to_string(), |
| "function u0:0() fast {\nblock0 cold:\n\nblock1(v0: i32) cold:\n}\n" |
| ); |
| } |
| } |