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