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 fn super_entity_definition( 114 &mut self, 115 w: &mut dyn Write, 116 _func: &Function, 117 entity: AnyEntity, 118 value: &dyn fmt::Display, 119 maybe_fact: Option<&Fact>, 120 ) -> fmt::Result { 121 if let Some(fact) = maybe_fact { 122 writeln!(w, " {entity} ! {fact} = {value}") 123 } else { 124 writeln!(w, " {entity} = {value}") 125 } 126 } 127 } 128 129 /// A `PlainWriter` that doesn't decorate the function. 130 pub struct PlainWriter; 131 132 impl FuncWriter for PlainWriter { 133 fn write_instruction( 134 &mut self, 135 w: &mut dyn Write, 136 func: &Function, 137 aliases: &SecondaryMap<Value, Vec<Value>>, 138 inst: Inst, 139 indent: usize, 140 ) -> fmt::Result { 141 write_instruction(w, func, aliases, inst, indent) 142 } 143 144 fn write_block_header( 145 &mut self, 146 w: &mut dyn Write, 147 func: &Function, 148 block: Block, 149 indent: usize, 150 ) -> fmt::Result { 151 write_block_header(w, func, block, indent) 152 } 153 } 154 155 /// Write `func` to `w` as equivalent text. 156 /// Use `isa` to emit ISA-dependent annotations. 157 pub fn write_function(w: &mut dyn Write, func: &Function) -> fmt::Result { 158 decorate_function(&mut PlainWriter, w, func) 159 } 160 161 /// Create a reverse-alias map from a value to all aliases having that value as a direct target 162 fn alias_map(func: &Function) -> SecondaryMap<Value, Vec<Value>> { 163 let mut aliases = SecondaryMap::<_, Vec<_>>::new(); 164 for v in func.dfg.values() { 165 // VADFS returns the immediate target of an alias 166 if let Some(k) = func.dfg.value_alias_dest_for_serialization(v) { 167 aliases[k].push(v); 168 } 169 } 170 aliases 171 } 172 173 /// Writes `func` to `w` as text. 174 /// write_function_plain is passed as 'closure' to print instructions as text. 175 /// pretty_function_error is passed as 'closure' to add error decoration. 176 pub fn decorate_function<FW: FuncWriter>( 177 func_w: &mut FW, 178 w: &mut dyn Write, 179 func: &Function, 180 ) -> fmt::Result { 181 write!(w, "function ")?; 182 write_function_spec(w, func)?; 183 writeln!(w, " {{")?; 184 let aliases = alias_map(func); 185 let mut any = func_w.write_preamble(w, func)?; 186 for block in &func.layout { 187 if any { 188 writeln!(w)?; 189 } 190 decorate_block(func_w, w, func, &aliases, block)?; 191 any = true; 192 } 193 writeln!(w, "}}") 194 } 195 196 //---------------------------------------------------------------------- 197 // 198 // Function spec. 199 200 /// Writes the spec (name and signature) of 'func' to 'w' as text. 201 pub fn write_function_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 let exception_tables = &dfg.exception_tables; 393 use crate::ir::instructions::InstructionData::*; 394 let ctrl_ty = dfg.ctrl_typevar(inst); 395 match dfg.insts[inst] { 396 AtomicRmw { op, args, .. } => write!(w, " {} {}, {}", op, args[0], args[1]), 397 AtomicCas { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), 398 LoadNoOffset { flags, arg, .. } => write!(w, "{flags} {arg}"), 399 StoreNoOffset { flags, args, .. } => write!(w, "{} {}, {}", flags, args[0], args[1]), 400 Unary { arg, .. } => write!(w, " {arg}"), 401 UnaryImm { imm, .. } => write!(w, " {}", { 402 let mut imm = imm; 403 if ctrl_ty.bits() != 0 { 404 imm = imm.sign_extend_from_width(ctrl_ty.bits()); 405 } 406 imm 407 }), 408 UnaryIeee16 { imm, .. } => write!(w, " {imm}"), 409 UnaryIeee32 { imm, .. } => write!(w, " {imm}"), 410 UnaryIeee64 { imm, .. } => write!(w, " {imm}"), 411 UnaryGlobalValue { global_value, .. } => write!(w, " {global_value}"), 412 UnaryConst { 413 constant_handle, .. 414 } => write!(w, " {constant_handle}"), 415 Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]), 416 BinaryImm8 { arg, imm, .. } => write!(w, " {arg}, {imm}"), 417 BinaryImm64 { arg, imm, .. } => write!(w, " {}, {}", arg, { 418 let mut imm = imm; 419 if ctrl_ty.bits() != 0 { 420 imm = imm.sign_extend_from_width(ctrl_ty.bits()); 421 } 422 imm 423 }), 424 Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), 425 MultiAry { ref args, .. } => { 426 if args.is_empty() { 427 write!(w, "") 428 } else { 429 write!(w, " {}", DisplayValues(args.as_slice(pool))) 430 } 431 } 432 NullAry { .. } => write!(w, " "), 433 TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm), 434 Shuffle { imm, args, .. } => { 435 let data = dfg.immediates.get(imm).expect( 436 "Expected the shuffle mask to already be inserted into the immediates table", 437 ); 438 write!(w, " {}, {}, {}", args[0], args[1], data) 439 } 440 IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), 441 IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, { 442 let mut imm = imm; 443 if ctrl_ty.bits() != 0 { 444 imm = imm.sign_extend_from_width(ctrl_ty.bits()); 445 } 446 imm 447 }), 448 IntAddTrap { args, code, .. } => write!(w, " {}, {}, {}", args[0], args[1], code), 449 FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), 450 Jump { destination, .. } => { 451 write!(w, " {}", destination.display(pool)) 452 } 453 Brif { 454 arg, 455 blocks: [block_then, block_else], 456 .. 457 } => { 458 write!(w, " {}, {}", arg, block_then.display(pool))?; 459 write!(w, ", {}", block_else.display(pool)) 460 } 461 BranchTable { arg, table, .. } => { 462 write!(w, " {}, {}", arg, jump_tables[table].display(pool)) 463 } 464 Call { 465 func_ref, ref args, .. 466 } => { 467 write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool)))?; 468 write_user_stack_map_entries(w, dfg, inst) 469 } 470 CallIndirect { 471 sig_ref, ref args, .. 472 } => { 473 let args = args.as_slice(pool); 474 write!( 475 w, 476 " {}, {}({})", 477 sig_ref, 478 args[0], 479 DisplayValues(&args[1..]) 480 )?; 481 write_user_stack_map_entries(w, dfg, inst) 482 } 483 TryCall { 484 func_ref, 485 ref args, 486 exception, 487 .. 488 } => { 489 write!( 490 w, 491 " {}({}), {}", 492 func_ref, 493 DisplayValues(args.as_slice(pool)), 494 exception_tables[exception].display(pool), 495 ) 496 } 497 TryCallIndirect { 498 ref args, 499 exception, 500 .. 501 } => { 502 let args = args.as_slice(pool); 503 write!( 504 w, 505 " {}({}), {}", 506 args[0], 507 DisplayValues(&args[1..]), 508 exception_tables[exception].display(pool), 509 ) 510 } 511 FuncAddr { func_ref, .. } => write!(w, " {func_ref}"), 512 StackLoad { 513 stack_slot, offset, .. 514 } => write!(w, " {stack_slot}{offset}"), 515 StackStore { 516 arg, 517 stack_slot, 518 offset, 519 .. 520 } => write!(w, " {arg}, {stack_slot}{offset}"), 521 DynamicStackLoad { 522 dynamic_stack_slot, .. 523 } => write!(w, " {dynamic_stack_slot}"), 524 DynamicStackStore { 525 arg, 526 dynamic_stack_slot, 527 .. 528 } => write!(w, " {arg}, {dynamic_stack_slot}"), 529 Load { 530 flags, arg, offset, .. 531 } => write!(w, "{flags} {arg}{offset}"), 532 Store { 533 flags, 534 args, 535 offset, 536 .. 537 } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset), 538 Trap { code, .. } => write!(w, " {code}"), 539 CondTrap { arg, code, .. } => write!(w, " {arg}, {code}"), 540 ExceptionHandlerAddress { block, imm, .. } => write!(w, " {block}, {imm}"), 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