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 #[allow(unused_variables)] 114 fn super_entity_definition( 115 &mut self, 116 w: &mut dyn Write, 117 func: &Function, 118 entity: AnyEntity, 119 value: &dyn fmt::Display, 120 maybe_fact: Option<&Fact>, 121 ) -> fmt::Result { 122 if let Some(fact) = maybe_fact { 123 writeln!(w, " {entity} ! {fact} = {value}") 124 } else { 125 writeln!(w, " {entity} = {value}") 126 } 127 } 128 } 129 130 /// A `PlainWriter` that doesn't decorate the function. 131 pub struct PlainWriter; 132 133 impl 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. 158 pub 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 163 fn 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. 177 pub 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_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 fn write_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 are present. 263 let indent = if func.rel_srclocs().is_empty() { 4 } else { 36 }; 264 265 func_w.write_block_header(w, func, block, indent)?; 266 for a in func.dfg.block_params(block).iter().cloned() { 267 write_value_aliases(w, aliases, a, indent)?; 268 } 269 270 for inst in func.layout.block_insts(block) { 271 func_w.write_instruction(w, func, aliases, inst, indent)?; 272 } 273 274 Ok(()) 275 } 276 277 //---------------------------------------------------------------------- 278 // 279 // Instructions 280 281 // Should `inst` be printed with a type suffix? 282 // 283 // Polymorphic instructions may need a suffix indicating the value of the controlling type variable 284 // if it can't be trivially inferred. 285 // 286 fn type_suffix(func: &Function, inst: Inst) -> Option<Type> { 287 let inst_data = &func.dfg.insts[inst]; 288 let constraints = inst_data.opcode().constraints(); 289 290 if !constraints.is_polymorphic() { 291 return None; 292 } 293 294 // If the controlling type variable can be inferred from the type of the designated value input 295 // operand, we don't need the type suffix. 296 if constraints.use_typevar_operand() { 297 let ctrl_var = inst_data.typevar_operand(&func.dfg.value_lists).unwrap(); 298 let def_block = match func.dfg.value_def(ctrl_var) { 299 ValueDef::Result(instr, _) => func.layout.inst_block(instr), 300 ValueDef::Param(block, _) => Some(block), 301 ValueDef::Union(..) => None, 302 }; 303 if def_block.is_some() && def_block == func.layout.inst_block(inst) { 304 return None; 305 } 306 } 307 308 let rtype = func.dfg.ctrl_typevar(inst); 309 assert!( 310 !rtype.is_invalid(), 311 "Polymorphic instruction must produce a result" 312 ); 313 Some(rtype) 314 } 315 316 /// Write out any aliases to the given target, including indirect aliases 317 fn write_value_aliases( 318 w: &mut dyn Write, 319 aliases: &SecondaryMap<Value, Vec<Value>>, 320 target: Value, 321 indent: usize, 322 ) -> fmt::Result { 323 let mut todo_stack = vec![target]; 324 while let Some(target) = todo_stack.pop() { 325 for &a in &aliases[target] { 326 writeln!(w, "{1:0$}{2} -> {3}", indent, "", a, target)?; 327 todo_stack.push(a); 328 } 329 } 330 331 Ok(()) 332 } 333 334 fn write_instruction( 335 w: &mut dyn Write, 336 func: &Function, 337 aliases: &SecondaryMap<Value, Vec<Value>>, 338 inst: Inst, 339 indent: usize, 340 ) -> fmt::Result { 341 // Prefix containing source location, encoding, and value locations. 342 let mut s = String::with_capacity(16); 343 344 // Source location goes first. 345 let srcloc = func.srcloc(inst); 346 if !srcloc.is_default() { 347 write!(s, "{srcloc} ")?; 348 } 349 350 // Write out prefix and indent the instruction. 351 write!(w, "{s:indent$}")?; 352 353 // Write out the result values, if any. 354 let mut has_results = false; 355 for r in func.dfg.inst_results(inst) { 356 if !has_results { 357 has_results = true; 358 write!(w, "{r}")?; 359 } else { 360 write!(w, ", {r}")?; 361 } 362 if let Some(f) = &func.dfg.facts[*r] { 363 write!(w, " ! {f}")?; 364 } 365 } 366 if has_results { 367 write!(w, " = ")?; 368 } 369 370 // Then the opcode, possibly with a '.type' suffix. 371 let opcode = func.dfg.insts[inst].opcode(); 372 373 match type_suffix(func, inst) { 374 Some(suf) => write!(w, "{opcode}.{suf}")?, 375 None => write!(w, "{opcode}")?, 376 } 377 378 write_operands(w, &func.dfg, inst)?; 379 writeln!(w)?; 380 381 // Value aliases come out on lines after the instruction defining the referent. 382 for r in func.dfg.inst_results(inst) { 383 write_value_aliases(w, aliases, *r, indent)?; 384 } 385 Ok(()) 386 } 387 388 /// Write the operands of `inst` to `w` with a prepended space. 389 pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result { 390 let pool = &dfg.value_lists; 391 let jump_tables = &dfg.jump_tables; 392 use crate::ir::instructions::InstructionData::*; 393 let ctrl_ty = dfg.ctrl_typevar(inst); 394 match dfg.insts[inst] { 395 AtomicRmw { op, args, .. } => write!(w, " {} {}, {}", op, args[0], args[1]), 396 AtomicCas { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), 397 LoadNoOffset { flags, arg, .. } => write!(w, "{flags} {arg}"), 398 StoreNoOffset { flags, args, .. } => write!(w, "{} {}, {}", flags, args[0], args[1]), 399 Unary { arg, .. } => write!(w, " {arg}"), 400 UnaryImm { imm, .. } => write!(w, " {}", { 401 let mut imm = imm; 402 if ctrl_ty.bits() != 0 { 403 imm = imm.sign_extend_from_width(ctrl_ty.bits()); 404 } 405 imm 406 }), 407 UnaryIeee16 { imm, .. } => write!(w, " {imm}"), 408 UnaryIeee32 { imm, .. } => write!(w, " {imm}"), 409 UnaryIeee64 { imm, .. } => write!(w, " {imm}"), 410 UnaryGlobalValue { global_value, .. } => write!(w, " {global_value}"), 411 UnaryConst { 412 constant_handle, .. 413 } => write!(w, " {constant_handle}"), 414 Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]), 415 BinaryImm8 { arg, imm, .. } => write!(w, " {arg}, {imm}"), 416 BinaryImm64 { arg, imm, .. } => write!(w, " {}, {}", arg, { 417 let mut imm = imm; 418 if ctrl_ty.bits() != 0 { 419 imm = imm.sign_extend_from_width(ctrl_ty.bits()); 420 } 421 imm 422 }), 423 Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), 424 MultiAry { ref args, .. } => { 425 if args.is_empty() { 426 write!(w, "") 427 } else { 428 write!(w, " {}", DisplayValues(args.as_slice(pool))) 429 } 430 } 431 NullAry { .. } => write!(w, " "), 432 TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm), 433 Shuffle { imm, args, .. } => { 434 let data = dfg.immediates.get(imm).expect( 435 "Expected the shuffle mask to already be inserted into the immediates table", 436 ); 437 write!(w, " {}, {}, {}", args[0], args[1], data) 438 } 439 IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), 440 IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, { 441 let mut imm = imm; 442 if ctrl_ty.bits() != 0 { 443 imm = imm.sign_extend_from_width(ctrl_ty.bits()); 444 } 445 imm 446 }), 447 IntAddTrap { args, code, .. } => write!(w, " {}, {}, {}", args[0], args[1], code), 448 FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), 449 Jump { destination, .. } => { 450 write!(w, " {}", destination.display(pool)) 451 } 452 Brif { 453 arg, 454 blocks: [block_then, block_else], 455 .. 456 } => { 457 write!(w, " {}, {}", arg, block_then.display(pool))?; 458 write!(w, ", {}", block_else.display(pool)) 459 } 460 BranchTable { arg, table, .. } => { 461 write!(w, " {}, {}", arg, jump_tables[table].display(pool)) 462 } 463 Call { 464 func_ref, ref args, .. 465 } => { 466 write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool)))?; 467 write_user_stack_map_entries(w, dfg, inst) 468 } 469 CallIndirect { 470 sig_ref, ref args, .. 471 } => { 472 let args = args.as_slice(pool); 473 write!( 474 w, 475 " {}, {}({})", 476 sig_ref, 477 args[0], 478 DisplayValues(&args[1..]) 479 )?; 480 write_user_stack_map_entries(w, dfg, inst) 481 } 482 FuncAddr { func_ref, .. } => write!(w, " {func_ref}"), 483 StackLoad { 484 stack_slot, offset, .. 485 } => write!(w, " {stack_slot}{offset}"), 486 StackStore { 487 arg, 488 stack_slot, 489 offset, 490 .. 491 } => write!(w, " {arg}, {stack_slot}{offset}"), 492 DynamicStackLoad { 493 dynamic_stack_slot, .. 494 } => write!(w, " {dynamic_stack_slot}"), 495 DynamicStackStore { 496 arg, 497 dynamic_stack_slot, 498 .. 499 } => write!(w, " {arg}, {dynamic_stack_slot}"), 500 Load { 501 flags, arg, offset, .. 502 } => write!(w, "{flags} {arg}{offset}"), 503 Store { 504 flags, 505 args, 506 offset, 507 .. 508 } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset), 509 Trap { code, .. } => write!(w, " {code}"), 510 CondTrap { arg, code, .. } => write!(w, " {arg}, {code}"), 511 }?; 512 513 let mut sep = " ; "; 514 for arg in dfg.inst_values(inst) { 515 if let ValueDef::Result(src, _) = dfg.value_def(arg) { 516 let imm = match dfg.insts[src] { 517 UnaryImm { imm, .. } => { 518 let mut imm = imm; 519 if dfg.ctrl_typevar(src).bits() != 0 { 520 imm = imm.sign_extend_from_width(dfg.ctrl_typevar(src).bits()); 521 } 522 imm.to_string() 523 } 524 UnaryIeee16 { imm, .. } => imm.to_string(), 525 UnaryIeee32 { imm, .. } => imm.to_string(), 526 UnaryIeee64 { imm, .. } => imm.to_string(), 527 UnaryConst { 528 constant_handle, 529 opcode: Opcode::F128const, 530 } => Ieee128::try_from(dfg.constants.get(constant_handle)) 531 .expect("16-byte f128 constant") 532 .to_string(), 533 UnaryConst { 534 constant_handle, .. 535 } => constant_handle.to_string(), 536 _ => continue, 537 }; 538 write!(w, "{sep}{arg} = {imm}")?; 539 sep = ", "; 540 } 541 } 542 Ok(()) 543 } 544 545 fn write_user_stack_map_entries(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result { 546 let entries = match dfg.user_stack_map_entries(inst) { 547 None => return Ok(()), 548 Some(es) => es, 549 }; 550 write!(w, ", stack_map=[")?; 551 let mut need_comma = false; 552 for entry in entries { 553 if need_comma { 554 write!(w, ", ")?; 555 } 556 write!(w, "{} @ {}+{}", entry.ty, entry.slot, entry.offset)?; 557 need_comma = true; 558 } 559 write!(w, "]")?; 560 Ok(()) 561 } 562 563 /// Displayable slice of values. 564 struct DisplayValues<'a>(&'a [Value]); 565 566 impl<'a> fmt::Display for DisplayValues<'a> { 567 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 568 for (i, val) in self.0.iter().enumerate() { 569 if i == 0 { 570 write!(f, "{val}")?; 571 } else { 572 write!(f, ", {val}")?; 573 } 574 } 575 Ok(()) 576 } 577 } 578 579 #[cfg(test)] 580 mod tests { 581 use crate::cursor::{Cursor, CursorPosition, FuncCursor}; 582 use crate::ir::types; 583 use crate::ir::{Function, InstBuilder, StackSlotData, StackSlotKind, UserFuncName}; 584 use alloc::string::ToString; 585 586 #[test] 587 fn basic() { 588 let mut f = Function::new(); 589 assert_eq!(f.to_string(), "function u0:0() fast {\n}\n"); 590 591 f.name = UserFuncName::testcase("foo"); 592 assert_eq!(f.to_string(), "function %foo() fast {\n}\n"); 593 594 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 0)); 595 assert_eq!( 596 f.to_string(), 597 "function %foo() fast {\n ss0 = explicit_slot 4\n}\n" 598 ); 599 600 let block = f.dfg.make_block(); 601 f.layout.append_block(block); 602 assert_eq!( 603 f.to_string(), 604 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0:\n}\n" 605 ); 606 607 f.dfg.append_block_param(block, types::I8); 608 assert_eq!( 609 f.to_string(), 610 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8):\n}\n" 611 ); 612 613 f.dfg.append_block_param(block, types::F32.by(4).unwrap()); 614 assert_eq!( 615 f.to_string(), 616 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n}\n" 617 ); 618 619 { 620 let mut cursor = FuncCursor::new(&mut f); 621 cursor.set_position(CursorPosition::After(block)); 622 cursor.ins().return_(&[]) 623 }; 624 assert_eq!( 625 f.to_string(), 626 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n return\n}\n" 627 ); 628 629 let mut f = Function::new(); 630 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 2)); 631 assert_eq!( 632 f.to_string(), 633 "function u0:0() fast {\n ss0 = explicit_slot 4, align = 4\n}\n" 634 ); 635 } 636 637 #[test] 638 fn aliases() { 639 use crate::ir::InstBuilder; 640 641 let mut func = Function::new(); 642 { 643 let block0 = func.dfg.make_block(); 644 let mut pos = FuncCursor::new(&mut func); 645 pos.insert_block(block0); 646 647 // make some detached values for change_to_alias 648 let v0 = pos.func.dfg.append_block_param(block0, types::I32); 649 let v1 = pos.func.dfg.append_block_param(block0, types::I32); 650 let v2 = pos.func.dfg.append_block_param(block0, types::I32); 651 pos.func.dfg.detach_block_params(block0); 652 653 // alias to a param--will be printed at beginning of block defining param 654 let v3 = pos.func.dfg.append_block_param(block0, types::I32); 655 pos.func.dfg.change_to_alias(v0, v3); 656 657 // alias to an alias--should print attached to alias, not ultimate target 658 pos.func.dfg.make_value_alias_for_serialization(v0, v2); // v0 <- v2 659 660 // alias to a result--will be printed after instruction producing result 661 let _dummy0 = pos.ins().iconst(types::I32, 42); 662 let v4 = pos.ins().iadd(v0, v0); 663 pos.func.dfg.change_to_alias(v1, v4); 664 let _dummy1 = pos.ins().iconst(types::I32, 23); 665 let _v7 = pos.ins().iadd(v1, v1); 666 } 667 assert_eq!( 668 func.to_string(), 669 "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" 670 ); 671 } 672 673 #[test] 674 fn cold_blocks() { 675 let mut func = Function::new(); 676 { 677 let mut pos = FuncCursor::new(&mut func); 678 679 let block0 = pos.func.dfg.make_block(); 680 pos.insert_block(block0); 681 pos.func.layout.set_cold(block0); 682 683 let block1 = pos.func.dfg.make_block(); 684 pos.insert_block(block1); 685 pos.func.dfg.append_block_param(block1, types::I32); 686 pos.func.layout.set_cold(block1); 687 } 688 689 assert_eq!( 690 func.to_string(), 691 "function u0:0() fast {\nblock0 cold:\n\nblock1(v0: i32) cold:\n}\n" 692 ); 693 } 694 } 695