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_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. 202 pub 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 210 fn write_arg(w: &mut dyn Write, func: &Function, arg: Value) -> fmt::Result { 211 let ty = func.dfg.value_type(arg); 212 if let Some(f) = &func.dfg.facts[arg] { 213 write!(w, "{arg} ! {f}: {ty}") 214 } else { 215 write!(w, "{arg}: {ty}") 216 } 217 } 218 219 /// Write out the basic block header, outdented: 220 /// 221 /// block1: 222 /// block1(v1: i32): 223 /// block10(v4: f64, v5: i8): 224 /// 225 pub fn write_block_header( 226 w: &mut dyn Write, 227 func: &Function, 228 block: Block, 229 indent: usize, 230 ) -> fmt::Result { 231 let cold = if func.layout.is_cold(block) { 232 " cold" 233 } else { 234 "" 235 }; 236 237 // The `indent` is the instruction indentation. block headers are 4 spaces out from that. 238 write!(w, "{1:0$}{2}", indent - 4, "", block)?; 239 240 let mut args = func.dfg.block_params(block).iter().cloned(); 241 match args.next() { 242 None => return writeln!(w, "{cold}:"), 243 Some(arg) => { 244 write!(w, "(")?; 245 write_arg(w, func, arg)?; 246 } 247 } 248 // Remaining arguments. 249 for arg in args { 250 write!(w, ", ")?; 251 write_arg(w, func, arg)?; 252 } 253 writeln!(w, "){cold}:") 254 } 255 256 fn decorate_block<FW: FuncWriter>( 257 func_w: &mut FW, 258 w: &mut dyn Write, 259 func: &Function, 260 aliases: &SecondaryMap<Value, Vec<Value>>, 261 block: Block, 262 ) -> fmt::Result { 263 // Indent all instructions if any srclocs are present. 264 let indent = if func.rel_srclocs().is_empty() { 4 } else { 36 }; 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 // 287 fn 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 318 fn 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 335 fn write_instruction( 336 w: &mut dyn Write, 337 func: &Function, 338 aliases: &SecondaryMap<Value, Vec<Value>>, 339 inst: Inst, 340 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 prefix and indent the instruction. 352 write!(w, "{s:indent$}")?; 353 354 // Write out the result values, if any. 355 let mut has_results = false; 356 for r in func.dfg.inst_results(inst) { 357 if !has_results { 358 has_results = true; 359 write!(w, "{r}")?; 360 } else { 361 write!(w, ", {r}")?; 362 } 363 if let Some(f) = &func.dfg.facts[*r] { 364 write!(w, " ! {f}")?; 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. 390 pub 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, "{flags} {arg}"), 400 StoreNoOffset { flags, args, .. } => write!(w, "{} {}, {}", flags, args[0], args[1]), 401 Unary { arg, .. } => write!(w, " {arg}"), 402 UnaryImm { imm, .. } => write!(w, " {}", { 403 let mut imm = imm; 404 if ctrl_ty.bits() != 0 { 405 imm = imm.sign_extend_from_width(ctrl_ty.bits()); 406 } 407 imm 408 }), 409 UnaryIeee16 { imm, .. } => write!(w, " {imm}"), 410 UnaryIeee32 { imm, .. } => write!(w, " {imm}"), 411 UnaryIeee64 { imm, .. } => write!(w, " {imm}"), 412 UnaryGlobalValue { global_value, .. } => write!(w, " {global_value}"), 413 UnaryConst { 414 constant_handle, .. 415 } => write!(w, " {constant_handle}"), 416 Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]), 417 BinaryImm8 { arg, imm, .. } => write!(w, " {arg}, {imm}"), 418 BinaryImm64 { arg, imm, .. } => write!(w, " {}, {}", arg, { 419 let mut imm = imm; 420 if ctrl_ty.bits() != 0 { 421 imm = imm.sign_extend_from_width(ctrl_ty.bits()); 422 } 423 imm 424 }), 425 Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), 426 MultiAry { ref args, .. } => { 427 if args.is_empty() { 428 write!(w, "") 429 } else { 430 write!(w, " {}", DisplayValues(args.as_slice(pool))) 431 } 432 } 433 NullAry { .. } => write!(w, " "), 434 TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm), 435 Shuffle { imm, args, .. } => { 436 let data = dfg.immediates.get(imm).expect( 437 "Expected the shuffle mask to already be inserted into the immediates table", 438 ); 439 write!(w, " {}, {}, {}", args[0], args[1], data) 440 } 441 IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), 442 IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, { 443 let mut imm = imm; 444 if ctrl_ty.bits() != 0 { 445 imm = imm.sign_extend_from_width(ctrl_ty.bits()); 446 } 447 imm 448 }), 449 IntAddTrap { args, code, .. } => write!(w, " {}, {}, {}", args[0], args[1], code), 450 FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), 451 Jump { destination, .. } => { 452 write!(w, " {}", destination.display(pool)) 453 } 454 Brif { 455 arg, 456 blocks: [block_then, block_else], 457 .. 458 } => { 459 write!(w, " {}, {}", arg, block_then.display(pool))?; 460 write!(w, ", {}", block_else.display(pool)) 461 } 462 BranchTable { arg, table, .. } => { 463 write!(w, " {}, {}", arg, jump_tables[table].display(pool)) 464 } 465 Call { 466 func_ref, ref args, .. 467 } => { 468 write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool)))?; 469 write_user_stack_map_entries(w, dfg, inst) 470 } 471 CallIndirect { 472 sig_ref, ref args, .. 473 } => { 474 let args = args.as_slice(pool); 475 write!( 476 w, 477 " {}, {}({})", 478 sig_ref, 479 args[0], 480 DisplayValues(&args[1..]) 481 )?; 482 write_user_stack_map_entries(w, dfg, inst) 483 } 484 TryCall { 485 func_ref, 486 ref args, 487 exception, 488 .. 489 } => { 490 write!( 491 w, 492 " {}({}), {}", 493 func_ref, 494 DisplayValues(args.as_slice(pool)), 495 exception_tables[exception].display(pool), 496 ) 497 } 498 TryCallIndirect { 499 ref args, 500 exception, 501 .. 502 } => { 503 let args = args.as_slice(pool); 504 write!( 505 w, 506 " {}({}), {}", 507 args[0], 508 DisplayValues(&args[1..]), 509 exception_tables[exception].display(pool), 510 ) 511 } 512 FuncAddr { func_ref, .. } => write!(w, " {func_ref}"), 513 StackLoad { 514 stack_slot, offset, .. 515 } => write!(w, " {stack_slot}{offset}"), 516 StackStore { 517 arg, 518 stack_slot, 519 offset, 520 .. 521 } => write!(w, " {arg}, {stack_slot}{offset}"), 522 DynamicStackLoad { 523 dynamic_stack_slot, .. 524 } => write!(w, " {dynamic_stack_slot}"), 525 DynamicStackStore { 526 arg, 527 dynamic_stack_slot, 528 .. 529 } => write!(w, " {arg}, {dynamic_stack_slot}"), 530 Load { 531 flags, arg, offset, .. 532 } => write!(w, "{flags} {arg}{offset}"), 533 Store { 534 flags, 535 args, 536 offset, 537 .. 538 } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset), 539 Trap { code, .. } => write!(w, " {code}"), 540 CondTrap { arg, code, .. } => write!(w, " {arg}, {code}"), 541 }?; 542 543 let mut sep = " ; "; 544 for arg in dfg.inst_values(inst) { 545 if let ValueDef::Result(src, _) = dfg.value_def(arg) { 546 let imm = match dfg.insts[src] { 547 UnaryImm { imm, .. } => { 548 let mut imm = imm; 549 if dfg.ctrl_typevar(src).bits() != 0 { 550 imm = imm.sign_extend_from_width(dfg.ctrl_typevar(src).bits()); 551 } 552 imm.to_string() 553 } 554 UnaryIeee16 { imm, .. } => imm.to_string(), 555 UnaryIeee32 { imm, .. } => imm.to_string(), 556 UnaryIeee64 { imm, .. } => imm.to_string(), 557 UnaryConst { 558 constant_handle, 559 opcode: Opcode::F128const, 560 } => Ieee128::try_from(dfg.constants.get(constant_handle)) 561 .expect("16-byte f128 constant") 562 .to_string(), 563 UnaryConst { 564 constant_handle, .. 565 } => constant_handle.to_string(), 566 _ => continue, 567 }; 568 write!(w, "{sep}{arg} = {imm}")?; 569 sep = ", "; 570 } 571 } 572 Ok(()) 573 } 574 575 fn write_user_stack_map_entries(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result { 576 let entries = match dfg.user_stack_map_entries(inst) { 577 None => return Ok(()), 578 Some(es) => es, 579 }; 580 write!(w, ", stack_map=[")?; 581 let mut need_comma = false; 582 for entry in entries { 583 if need_comma { 584 write!(w, ", ")?; 585 } 586 write!(w, "{} @ {}+{}", entry.ty, entry.slot, entry.offset)?; 587 need_comma = true; 588 } 589 write!(w, "]")?; 590 Ok(()) 591 } 592 593 /// Displayable slice of values. 594 struct DisplayValues<'a>(&'a [Value]); 595 596 impl<'a> fmt::Display for DisplayValues<'a> { 597 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 598 for (i, val) in self.0.iter().enumerate() { 599 if i == 0 { 600 write!(f, "{val}")?; 601 } else { 602 write!(f, ", {val}")?; 603 } 604 } 605 Ok(()) 606 } 607 } 608 609 #[cfg(test)] 610 mod tests { 611 use crate::cursor::{Cursor, CursorPosition, FuncCursor}; 612 use crate::ir::types; 613 use crate::ir::{Function, InstBuilder, StackSlotData, StackSlotKind, UserFuncName}; 614 use alloc::string::ToString; 615 616 #[test] 617 fn basic() { 618 let mut f = Function::new(); 619 assert_eq!(f.to_string(), "function u0:0() fast {\n}\n"); 620 621 f.name = UserFuncName::testcase("foo"); 622 assert_eq!(f.to_string(), "function %foo() fast {\n}\n"); 623 624 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 0)); 625 assert_eq!( 626 f.to_string(), 627 "function %foo() fast {\n ss0 = explicit_slot 4\n}\n" 628 ); 629 630 let block = f.dfg.make_block(); 631 f.layout.append_block(block); 632 assert_eq!( 633 f.to_string(), 634 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0:\n}\n" 635 ); 636 637 f.dfg.append_block_param(block, types::I8); 638 assert_eq!( 639 f.to_string(), 640 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8):\n}\n" 641 ); 642 643 f.dfg.append_block_param(block, types::F32.by(4).unwrap()); 644 assert_eq!( 645 f.to_string(), 646 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n}\n" 647 ); 648 649 { 650 let mut cursor = FuncCursor::new(&mut f); 651 cursor.set_position(CursorPosition::After(block)); 652 cursor.ins().return_(&[]) 653 }; 654 assert_eq!( 655 f.to_string(), 656 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n return\n}\n" 657 ); 658 659 let mut f = Function::new(); 660 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 2)); 661 assert_eq!( 662 f.to_string(), 663 "function u0:0() fast {\n ss0 = explicit_slot 4, align = 4\n}\n" 664 ); 665 } 666 667 #[test] 668 fn aliases() { 669 use crate::ir::InstBuilder; 670 671 let mut func = Function::new(); 672 { 673 let block0 = func.dfg.make_block(); 674 let mut pos = FuncCursor::new(&mut func); 675 pos.insert_block(block0); 676 677 // make some detached values for change_to_alias 678 let v0 = pos.func.dfg.append_block_param(block0, types::I32); 679 let v1 = pos.func.dfg.append_block_param(block0, types::I32); 680 let v2 = pos.func.dfg.append_block_param(block0, types::I32); 681 pos.func.dfg.detach_block_params(block0); 682 683 // alias to a param--will be printed at beginning of block defining param 684 let v3 = pos.func.dfg.append_block_param(block0, types::I32); 685 pos.func.dfg.change_to_alias(v0, v3); 686 687 // alias to an alias--should print attached to alias, not ultimate target 688 pos.func.dfg.make_value_alias_for_serialization(v0, v2); // v0 <- v2 689 690 // alias to a result--will be printed after instruction producing result 691 let _dummy0 = pos.ins().iconst(types::I32, 42); 692 let v4 = pos.ins().iadd(v0, v0); 693 pos.func.dfg.change_to_alias(v1, v4); 694 let _dummy1 = pos.ins().iconst(types::I32, 23); 695 let _v7 = pos.ins().iadd(v1, v1); 696 } 697 assert_eq!( 698 func.to_string(), 699 "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" 700 ); 701 } 702 703 #[test] 704 fn cold_blocks() { 705 let mut func = Function::new(); 706 { 707 let mut pos = FuncCursor::new(&mut func); 708 709 let block0 = pos.func.dfg.make_block(); 710 pos.insert_block(block0); 711 pos.func.layout.set_cold(block0); 712 713 let block1 = pos.func.dfg.make_block(); 714 pos.insert_block(block1); 715 pos.func.dfg.append_block_param(block1, types::I32); 716 pos.func.layout.set_cold(block1); 717 } 718 719 assert_eq!( 720 func.to_string(), 721 "function u0:0() fast {\nblock0 cold:\n\nblock1(v0: i32) cold:\n}\n" 722 ); 723 } 724 } 725