1 //! CLI tool to reduce Cranelift IR files crashing during compilation. 2 3 use crate::utils::{parse_sets_and_triple, read_to_string}; 4 use anyhow::{Context as _, Result}; 5 use clap::Parser; 6 use cranelift_codegen::cursor::{Cursor, FuncCursor}; 7 use cranelift_codegen::flowgraph::ControlFlowGraph; 8 use cranelift_codegen::ir::types::{F32, F64}; 9 use cranelift_codegen::ir::{ 10 self, Block, FuncRef, Function, GlobalValueData, Inst, InstBuilder, InstructionData, 11 StackSlots, TrapCode, 12 }; 13 use cranelift_codegen::isa::TargetIsa; 14 use cranelift_codegen::Context; 15 use cranelift_entity::PrimaryMap; 16 use cranelift_reader::{parse_test, ParseOptions}; 17 use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; 18 use std::collections::HashMap; 19 use std::path::PathBuf; 20 21 /// Reduce size of clif file causing panic during compilation. 22 #[derive(Parser)] 23 pub struct Options { 24 /// Specify an input file to be used. Use '-' for stdin. 25 file: PathBuf, 26 27 /// Configure Cranelift settings 28 #[clap(long = "set")] 29 settings: Vec<String>, 30 31 /// Specify the target architecture. 32 target: String, 33 34 /// Be more verbose 35 #[clap(short, long)] 36 verbose: bool, 37 } 38 39 pub fn run(options: &Options) -> Result<()> { 40 let parsed = parse_sets_and_triple(&options.settings, &options.target)?; 41 let fisa = parsed.as_fisa(); 42 43 let buffer = read_to_string(&options.file)?; 44 let test_file = parse_test(&buffer, ParseOptions::default()) 45 .with_context(|| format!("failed to parse {}", options.file.display()))?; 46 47 // If we have an isa from the command-line, use that. Otherwise if the 48 // file contains a unique isa, use that. 49 let isa = if let Some(isa) = fisa.isa { 50 isa 51 } else if let Some(isa) = test_file.isa_spec.unique_isa() { 52 isa 53 } else { 54 anyhow::bail!("compilation requires a target isa"); 55 }; 56 57 std::env::set_var("RUST_BACKTRACE", "0"); // Disable backtraces to reduce verbosity 58 59 for (func, _) in test_file.functions { 60 let (orig_block_count, orig_inst_count) = (block_count(&func), inst_count(&func)); 61 62 match reduce(isa, func, options.verbose) { 63 Ok((func, crash_msg)) => { 64 println!("Crash message: {}", crash_msg); 65 println!("\n{}", func); 66 println!( 67 "{} blocks {} insts -> {} blocks {} insts", 68 orig_block_count, 69 orig_inst_count, 70 block_count(&func), 71 inst_count(&func) 72 ); 73 } 74 Err(err) => println!("Warning: {}", err), 75 } 76 } 77 78 Ok(()) 79 } 80 81 enum ProgressStatus { 82 /// The mutation raised or reduced the amount of instructions or blocks. 83 ExpandedOrShrinked, 84 85 /// The mutation only changed an instruction. Performing another round of mutations may only 86 /// reduce the test case if another mutation shrank the test case. 87 Changed, 88 89 /// No need to re-test if the program crashes, because the mutation had no effect, but we want 90 /// to keep on iterating. 91 Skip, 92 } 93 94 trait Mutator { 95 fn name(&self) -> &'static str; 96 fn mutation_count(&self, func: &Function) -> usize; 97 fn mutate(&mut self, func: Function) -> Option<(Function, String, ProgressStatus)>; 98 99 /// Gets called when the returned mutated function kept on causing the crash. This can be used 100 /// to update position of the next item to look at. Does nothing by default. 101 fn did_crash(&mut self) {} 102 } 103 104 /// Try to remove instructions. 105 struct RemoveInst { 106 block: Block, 107 inst: Inst, 108 } 109 110 impl RemoveInst { 111 fn new(func: &Function) -> Self { 112 let first_block = func.layout.entry_block().unwrap(); 113 let first_inst = func.layout.first_inst(first_block).unwrap(); 114 Self { 115 block: first_block, 116 inst: first_inst, 117 } 118 } 119 } 120 121 impl Mutator for RemoveInst { 122 fn name(&self) -> &'static str { 123 "remove inst" 124 } 125 126 fn mutation_count(&self, func: &Function) -> usize { 127 inst_count(func) 128 } 129 130 fn mutate(&mut self, mut func: Function) -> Option<(Function, String, ProgressStatus)> { 131 next_inst_ret_prev(&func, &mut self.block, &mut self.inst).map(|(prev_block, prev_inst)| { 132 func.layout.remove_inst(prev_inst); 133 let msg = if func.layout.block_insts(prev_block).next().is_none() { 134 // Make sure empty blocks are removed, as `next_inst_ret_prev` depends on non empty blocks 135 func.layout.remove_block(prev_block); 136 format!("Remove inst {} and empty block {}", prev_inst, prev_block) 137 } else { 138 format!("Remove inst {}", prev_inst) 139 }; 140 (func, msg, ProgressStatus::ExpandedOrShrinked) 141 }) 142 } 143 } 144 145 /// Try to replace instructions with `iconst` or `fconst`. 146 struct ReplaceInstWithConst { 147 block: Block, 148 inst: Inst, 149 } 150 151 impl ReplaceInstWithConst { 152 fn new(func: &Function) -> Self { 153 let first_block = func.layout.entry_block().unwrap(); 154 let first_inst = func.layout.first_inst(first_block).unwrap(); 155 Self { 156 block: first_block, 157 inst: first_inst, 158 } 159 } 160 } 161 162 impl Mutator for ReplaceInstWithConst { 163 fn name(&self) -> &'static str { 164 "replace inst with const" 165 } 166 167 fn mutation_count(&self, func: &Function) -> usize { 168 inst_count(func) 169 } 170 171 fn mutate(&mut self, mut func: Function) -> Option<(Function, String, ProgressStatus)> { 172 next_inst_ret_prev(&func, &mut self.block, &mut self.inst).map( 173 |(_prev_block, prev_inst)| { 174 let num_results = func.dfg.inst_results(prev_inst).len(); 175 176 let opcode = func.dfg[prev_inst].opcode(); 177 if num_results == 0 178 || opcode == ir::Opcode::Iconst 179 || opcode == ir::Opcode::F32const 180 || opcode == ir::Opcode::F64const 181 { 182 return (func, format!(""), ProgressStatus::Skip); 183 } 184 185 if num_results == 1 { 186 let ty = func.dfg.value_type(func.dfg.first_result(prev_inst)); 187 let new_inst_name = const_for_type(func.dfg.replace(prev_inst), ty); 188 return ( 189 func, 190 format!("Replace inst {} with {}.", prev_inst, new_inst_name), 191 ProgressStatus::Changed, 192 ); 193 } 194 195 // At least 2 results. Replace each instruction with as many const instructions as 196 // there are results. 197 let mut pos = FuncCursor::new(&mut func).at_inst(prev_inst); 198 199 // Copy result SSA names into our own vector; otherwise we couldn't mutably borrow pos 200 // in the loop below. 201 let results = pos.func.dfg.inst_results(prev_inst).to_vec(); 202 203 // Detach results from the previous instruction, since we're going to reuse them. 204 pos.func.dfg.clear_results(prev_inst); 205 206 let mut inst_names = Vec::new(); 207 for r in results { 208 let ty = pos.func.dfg.value_type(r); 209 let builder = pos.ins().with_results([Some(r)]); 210 let new_inst_name = const_for_type(builder, ty); 211 inst_names.push(new_inst_name); 212 } 213 214 // Remove the instruction. 215 assert_eq!(pos.remove_inst(), prev_inst); 216 217 ( 218 func, 219 format!("Replace inst {} with {}", prev_inst, inst_names.join(" / ")), 220 ProgressStatus::ExpandedOrShrinked, 221 ) 222 }, 223 ) 224 } 225 } 226 227 /// Try to replace instructions with `trap`. 228 struct ReplaceInstWithTrap { 229 block: Block, 230 inst: Inst, 231 } 232 233 impl ReplaceInstWithTrap { 234 fn new(func: &Function) -> Self { 235 let first_block = func.layout.entry_block().unwrap(); 236 let first_inst = func.layout.first_inst(first_block).unwrap(); 237 Self { 238 block: first_block, 239 inst: first_inst, 240 } 241 } 242 } 243 244 impl Mutator for ReplaceInstWithTrap { 245 fn name(&self) -> &'static str { 246 "replace inst with trap" 247 } 248 249 fn mutation_count(&self, func: &Function) -> usize { 250 inst_count(func) 251 } 252 253 fn mutate(&mut self, mut func: Function) -> Option<(Function, String, ProgressStatus)> { 254 next_inst_ret_prev(&func, &mut self.block, &mut self.inst).map( 255 |(_prev_block, prev_inst)| { 256 let status = if func.dfg[prev_inst].opcode() == ir::Opcode::Trap { 257 ProgressStatus::Skip 258 } else { 259 func.dfg.replace(prev_inst).trap(TrapCode::User(0)); 260 ProgressStatus::Changed 261 }; 262 ( 263 func, 264 format!("Replace inst {} with trap", prev_inst), 265 status, 266 ) 267 }, 268 ) 269 } 270 } 271 272 /// Try to move instructions to entry block. 273 struct MoveInstToEntryBlock { 274 block: Block, 275 inst: Inst, 276 } 277 278 impl MoveInstToEntryBlock { 279 fn new(func: &Function) -> Self { 280 let first_block = func.layout.entry_block().unwrap(); 281 let first_inst = func.layout.first_inst(first_block).unwrap(); 282 Self { 283 block: first_block, 284 inst: first_inst, 285 } 286 } 287 } 288 289 impl Mutator for MoveInstToEntryBlock { 290 fn name(&self) -> &'static str { 291 "move inst to entry block" 292 } 293 294 fn mutation_count(&self, func: &Function) -> usize { 295 inst_count(func) 296 } 297 298 fn mutate(&mut self, mut func: Function) -> Option<(Function, String, ProgressStatus)> { 299 next_inst_ret_prev(&func, &mut self.block, &mut self.inst).map(|(prev_block, prev_inst)| { 300 // Don't move instructions that are already in entry block 301 // and instructions that end blocks. 302 let first_block = func.layout.entry_block().unwrap(); 303 if first_block == prev_block || self.block != prev_block { 304 return ( 305 func, 306 format!("did nothing for {}", prev_inst), 307 ProgressStatus::Skip, 308 ); 309 } 310 311 let last_inst_of_first_block = func.layout.last_inst(first_block).unwrap(); 312 func.layout.remove_inst(prev_inst); 313 func.layout.insert_inst(prev_inst, last_inst_of_first_block); 314 315 ( 316 func, 317 format!("Move inst {} to entry block", prev_inst), 318 ProgressStatus::ExpandedOrShrinked, 319 ) 320 }) 321 } 322 } 323 324 /// Try to remove a block. 325 struct RemoveBlock { 326 block: Block, 327 } 328 329 impl RemoveBlock { 330 fn new(func: &Function) -> Self { 331 Self { 332 block: func.layout.entry_block().unwrap(), 333 } 334 } 335 } 336 337 impl Mutator for RemoveBlock { 338 fn name(&self) -> &'static str { 339 "remove block" 340 } 341 342 fn mutation_count(&self, func: &Function) -> usize { 343 block_count(func) 344 } 345 346 fn mutate(&mut self, mut func: Function) -> Option<(Function, String, ProgressStatus)> { 347 func.layout.next_block(self.block).map(|next_block| { 348 self.block = next_block; 349 while let Some(inst) = func.layout.last_inst(self.block) { 350 func.layout.remove_inst(inst); 351 } 352 func.layout.remove_block(self.block); 353 ( 354 func, 355 format!("Remove block {}", next_block), 356 ProgressStatus::ExpandedOrShrinked, 357 ) 358 }) 359 } 360 } 361 362 /// Try to replace the block params with constants. 363 struct ReplaceBlockParamWithConst { 364 block: Block, 365 params_remaining: usize, 366 } 367 368 impl ReplaceBlockParamWithConst { 369 fn new(func: &Function) -> Self { 370 let first_block = func.layout.entry_block().unwrap(); 371 Self { 372 block: first_block, 373 params_remaining: func.dfg.num_block_params(first_block), 374 } 375 } 376 } 377 378 impl Mutator for ReplaceBlockParamWithConst { 379 fn name(&self) -> &'static str { 380 "replace block parameter with const" 381 } 382 383 fn mutation_count(&self, func: &Function) -> usize { 384 func.layout 385 .blocks() 386 .map(|block| func.dfg.num_block_params(block)) 387 .sum() 388 } 389 390 fn mutate(&mut self, mut func: Function) -> Option<(Function, String, ProgressStatus)> { 391 while self.params_remaining == 0 { 392 self.block = func.layout.next_block(self.block)?; 393 self.params_remaining = func.dfg.num_block_params(self.block); 394 } 395 396 self.params_remaining -= 1; 397 let param_index = self.params_remaining; 398 399 let param = func.dfg.block_params(self.block)[param_index]; 400 let param_type = func.dfg.value_type(param); 401 func.dfg.remove_block_param(param); 402 403 let first_inst = func.layout.first_inst(self.block).unwrap(); 404 let mut pos = FuncCursor::new(&mut func).at_inst(first_inst); 405 let builder = pos.ins().with_results([Some(param)]); 406 let new_inst_name = const_for_type(builder, param_type); 407 408 let mut cfg = ControlFlowGraph::new(); 409 cfg.compute(&func); 410 411 // Remove parameters in branching instructions that point to this block 412 for pred in cfg.pred_iter(self.block) { 413 let inst = &mut func.dfg[pred.inst]; 414 let num_fixed_args = inst.opcode().constraints().num_fixed_value_arguments(); 415 let mut values = inst.take_value_list().unwrap(); 416 values.remove(num_fixed_args + param_index, &mut func.dfg.value_lists); 417 func.dfg[pred.inst].put_value_list(values); 418 } 419 420 if Some(self.block) == func.layout.entry_block() { 421 // Entry block params must match function params 422 func.signature.params.remove(param_index); 423 } 424 425 Some(( 426 func, 427 format!( 428 "Replaced param {} of {} by {}", 429 param, self.block, new_inst_name 430 ), 431 ProgressStatus::ExpandedOrShrinked, 432 )) 433 } 434 } 435 436 /// Try to remove unused entities. 437 struct RemoveUnusedEntities { 438 kind: u32, 439 } 440 441 impl RemoveUnusedEntities { 442 fn new() -> Self { 443 Self { kind: 0 } 444 } 445 } 446 447 impl Mutator for RemoveUnusedEntities { 448 fn name(&self) -> &'static str { 449 "remove unused entities" 450 } 451 452 fn mutation_count(&self, _func: &Function) -> usize { 453 4 454 } 455 456 #[allow(clippy::cognitive_complexity)] 457 fn mutate(&mut self, mut func: Function) -> Option<(Function, String, ProgressStatus)> { 458 let name = match self.kind { 459 0 => { 460 let mut ext_func_usage_map = HashMap::new(); 461 for block in func.layout.blocks() { 462 for inst in func.layout.block_insts(block) { 463 match func.dfg[inst] { 464 // Add new cases when there are new instruction formats taking a `FuncRef`. 465 InstructionData::Call { func_ref, .. } 466 | InstructionData::FuncAddr { func_ref, .. } => { 467 ext_func_usage_map 468 .entry(func_ref) 469 .or_insert_with(Vec::new) 470 .push(inst); 471 } 472 _ => {} 473 } 474 } 475 } 476 477 let mut ext_funcs = PrimaryMap::new(); 478 479 for (func_ref, ext_func_data) in func.dfg.ext_funcs.clone().into_iter() { 480 if let Some(func_ref_usage) = ext_func_usage_map.get(&func_ref) { 481 let new_func_ref = ext_funcs.push(ext_func_data.clone()); 482 for &inst in func_ref_usage { 483 match func.dfg[inst] { 484 // Keep in sync with the above match. 485 InstructionData::Call { 486 ref mut func_ref, .. 487 } 488 | InstructionData::FuncAddr { 489 ref mut func_ref, .. 490 } => { 491 *func_ref = new_func_ref; 492 } 493 _ => unreachable!(), 494 } 495 } 496 } 497 } 498 499 func.dfg.ext_funcs = ext_funcs; 500 501 "Remove unused ext funcs" 502 } 503 1 => { 504 #[derive(Copy, Clone)] 505 enum SigRefUser { 506 Instruction(Inst), 507 ExtFunc(FuncRef), 508 } 509 510 let mut signatures_usage_map = HashMap::new(); 511 for block in func.layout.blocks() { 512 for inst in func.layout.block_insts(block) { 513 // Add new cases when there are new instruction formats taking a `SigRef`. 514 if let InstructionData::CallIndirect { sig_ref, .. } = func.dfg[inst] { 515 signatures_usage_map 516 .entry(sig_ref) 517 .or_insert_with(Vec::new) 518 .push(SigRefUser::Instruction(inst)); 519 } 520 } 521 } 522 for (func_ref, ext_func_data) in func.dfg.ext_funcs.iter() { 523 signatures_usage_map 524 .entry(ext_func_data.signature) 525 .or_insert_with(Vec::new) 526 .push(SigRefUser::ExtFunc(func_ref)); 527 } 528 529 let mut signatures = PrimaryMap::new(); 530 531 for (sig_ref, sig_data) in func.dfg.signatures.clone().into_iter() { 532 if let Some(sig_ref_usage) = signatures_usage_map.get(&sig_ref) { 533 let new_sig_ref = signatures.push(sig_data.clone()); 534 for &sig_ref_user in sig_ref_usage { 535 match sig_ref_user { 536 SigRefUser::Instruction(inst) => match func.dfg[inst] { 537 // Keep in sync with the above match. 538 InstructionData::CallIndirect { 539 ref mut sig_ref, .. 540 } => { 541 *sig_ref = new_sig_ref; 542 } 543 _ => unreachable!(), 544 }, 545 SigRefUser::ExtFunc(func_ref) => { 546 func.dfg.ext_funcs[func_ref].signature = new_sig_ref; 547 } 548 } 549 } 550 } 551 } 552 553 func.dfg.signatures = signatures; 554 555 "Remove unused signatures" 556 } 557 2 => { 558 let mut stack_slot_usage_map = HashMap::new(); 559 for block in func.layout.blocks() { 560 for inst in func.layout.block_insts(block) { 561 match func.dfg[inst] { 562 // Add new cases when there are new instruction formats taking a `StackSlot`. 563 InstructionData::StackLoad { stack_slot, .. } 564 | InstructionData::StackStore { stack_slot, .. } => { 565 stack_slot_usage_map 566 .entry(stack_slot) 567 .or_insert_with(Vec::new) 568 .push(inst); 569 } 570 571 _ => {} 572 } 573 } 574 } 575 576 let mut stack_slots = StackSlots::new(); 577 578 for (stack_slot, stack_slot_data) in func.stack_slots.clone().iter() { 579 if let Some(stack_slot_usage) = stack_slot_usage_map.get(&stack_slot) { 580 let new_stack_slot = stack_slots.push(stack_slot_data.clone()); 581 for &inst in stack_slot_usage { 582 match &mut func.dfg[inst] { 583 // Keep in sync with the above match. 584 InstructionData::StackLoad { stack_slot, .. } 585 | InstructionData::StackStore { stack_slot, .. } => { 586 *stack_slot = new_stack_slot; 587 } 588 _ => unreachable!(), 589 } 590 } 591 } 592 } 593 594 func.stack_slots = stack_slots; 595 596 "Remove unused stack slots" 597 } 598 3 => { 599 let mut global_value_usage_map = HashMap::new(); 600 for block in func.layout.blocks() { 601 for inst in func.layout.block_insts(block) { 602 // Add new cases when there are new instruction formats taking a `GlobalValue`. 603 if let InstructionData::UnaryGlobalValue { global_value, .. } = 604 func.dfg[inst] 605 { 606 global_value_usage_map 607 .entry(global_value) 608 .or_insert_with(Vec::new) 609 .push(inst); 610 } 611 } 612 } 613 614 for (_global_value, global_value_data) in func.global_values.iter() { 615 match *global_value_data { 616 GlobalValueData::VMContext | GlobalValueData::Symbol { .. } => {} 617 // These can create cyclic references, which cause complications. Just skip 618 // the global value removal for now. 619 // FIXME Handle them in a better way. 620 GlobalValueData::Load { .. } | GlobalValueData::IAddImm { .. } => { 621 return None 622 } 623 } 624 } 625 626 let mut global_values = PrimaryMap::new(); 627 628 for (global_value, global_value_data) in func.global_values.clone().into_iter() { 629 if let Some(global_value_usage) = global_value_usage_map.get(&global_value) { 630 let new_global_value = global_values.push(global_value_data.clone()); 631 for &inst in global_value_usage { 632 match &mut func.dfg[inst] { 633 // Keep in sync with the above match. 634 InstructionData::UnaryGlobalValue { global_value, .. } => { 635 *global_value = new_global_value; 636 } 637 _ => unreachable!(), 638 } 639 } 640 } 641 } 642 643 func.global_values = global_values; 644 645 "Remove unused global values" 646 } 647 _ => return None, 648 }; 649 self.kind += 1; 650 Some((func, name.to_owned(), ProgressStatus::Changed)) 651 } 652 } 653 654 struct MergeBlocks { 655 block: Block, 656 prev_block: Option<Block>, 657 } 658 659 impl MergeBlocks { 660 fn new(func: &Function) -> Self { 661 Self { 662 block: func.layout.entry_block().unwrap(), 663 prev_block: None, 664 } 665 } 666 } 667 668 impl Mutator for MergeBlocks { 669 fn name(&self) -> &'static str { 670 "merge blocks" 671 } 672 673 fn mutation_count(&self, func: &Function) -> usize { 674 // N blocks may result in at most N-1 merges. 675 block_count(func) - 1 676 } 677 678 fn mutate(&mut self, mut func: Function) -> Option<(Function, String, ProgressStatus)> { 679 let block = match func.layout.next_block(self.block) { 680 Some(block) => block, 681 None => return None, 682 }; 683 684 self.block = block; 685 686 let mut cfg = ControlFlowGraph::new(); 687 cfg.compute(&func); 688 689 if cfg.pred_iter(block).count() != 1 { 690 return Some(( 691 func, 692 format!("did nothing for {}", block), 693 ProgressStatus::Skip, 694 )); 695 } 696 697 let pred = cfg.pred_iter(block).next().unwrap(); 698 699 // If the branch instruction that lead us to this block is preceded by another branch 700 // instruction, then we have a conditional jump sequence that we should not break by 701 // replacing the second instruction by more of them. 702 if let Some(pred_pred_inst) = func.layout.prev_inst(pred.inst) { 703 if func.dfg[pred_pred_inst].opcode().is_branch() { 704 return Some(( 705 func, 706 format!("did nothing for {}", block), 707 ProgressStatus::Skip, 708 )); 709 } 710 } 711 712 assert!(func.dfg.block_params(block).len() == func.dfg.inst_variable_args(pred.inst).len()); 713 714 // If there were any block parameters in block, then the last instruction in pred will 715 // fill these parameters. Make the block params aliases of the terminator arguments. 716 for (block_param, arg) in func 717 .dfg 718 .detach_block_params(block) 719 .as_slice(&func.dfg.value_lists) 720 .iter() 721 .cloned() 722 .zip(func.dfg.inst_variable_args(pred.inst).iter().cloned()) 723 .collect::<Vec<_>>() 724 { 725 if block_param != arg { 726 func.dfg.change_to_alias(block_param, arg); 727 } 728 } 729 730 // Remove the terminator branch to the current block. 731 func.layout.remove_inst(pred.inst); 732 733 // Move all the instructions to the predecessor. 734 while let Some(inst) = func.layout.first_inst(block) { 735 func.layout.remove_inst(inst); 736 func.layout.append_inst(inst, pred.block); 737 } 738 739 // Remove the predecessor block. 740 func.layout.remove_block(block); 741 742 // Record the previous block: if we caused a crash (as signaled by a call to did_crash), then 743 // we'll start back to this block. 744 self.prev_block = Some(pred.block); 745 746 Some(( 747 func, 748 format!("merged {} and {}", pred.block, block), 749 ProgressStatus::ExpandedOrShrinked, 750 )) 751 } 752 753 fn did_crash(&mut self) { 754 self.block = self.prev_block.unwrap(); 755 } 756 } 757 758 fn const_for_type<'f, T: InstBuilder<'f>>(mut builder: T, ty: ir::Type) -> &'static str { 759 if ty == F32 { 760 builder.f32const(0.0); 761 "f32const" 762 } else if ty == F64 { 763 builder.f64const(0.0); 764 "f64const" 765 } else if ty.is_bool() { 766 builder.bconst(ty, false); 767 "bconst" 768 } else if ty.is_ref() { 769 builder.null(ty); 770 "null" 771 } else if ty.is_vector() { 772 let zero_data = vec![0; ty.bytes() as usize].into(); 773 let zero_handle = builder.data_flow_graph_mut().constants.insert(zero_data); 774 builder.vconst(ty, zero_handle); 775 "vconst" 776 } else { 777 // Default to an integer type and possibly create verifier error 778 builder.iconst(ty, 0); 779 "iconst" 780 } 781 } 782 783 fn next_inst_ret_prev( 784 func: &Function, 785 block: &mut Block, 786 inst: &mut Inst, 787 ) -> Option<(Block, Inst)> { 788 let prev = (*block, *inst); 789 if let Some(next_inst) = func.layout.next_inst(*inst) { 790 *inst = next_inst; 791 return Some(prev); 792 } 793 if let Some(next_block) = func.layout.next_block(*block) { 794 *block = next_block; 795 *inst = func.layout.first_inst(*block).expect("no inst"); 796 return Some(prev); 797 } 798 None 799 } 800 801 fn block_count(func: &Function) -> usize { 802 func.layout.blocks().count() 803 } 804 805 fn inst_count(func: &Function) -> usize { 806 func.layout 807 .blocks() 808 .map(|block| func.layout.block_insts(block).count()) 809 .sum() 810 } 811 812 fn resolve_aliases(func: &mut Function) { 813 for block in func.layout.blocks() { 814 for inst in func.layout.block_insts(block) { 815 func.dfg.resolve_aliases_in_arguments(inst); 816 } 817 } 818 } 819 820 /// Resolve aliases only if function still crashes after this. 821 fn try_resolve_aliases(context: &mut CrashCheckContext, func: &mut Function) { 822 let mut func_with_resolved_aliases = func.clone(); 823 resolve_aliases(&mut func_with_resolved_aliases); 824 if let CheckResult::Crash(_) = context.check_for_crash(&func_with_resolved_aliases) { 825 *func = func_with_resolved_aliases; 826 } 827 } 828 829 fn reduce(isa: &dyn TargetIsa, mut func: Function, verbose: bool) -> Result<(Function, String)> { 830 let mut context = CrashCheckContext::new(isa); 831 832 if let CheckResult::Succeed = context.check_for_crash(&func) { 833 anyhow::bail!("Given function compiled successfully or gave a verifier error."); 834 } 835 836 try_resolve_aliases(&mut context, &mut func); 837 838 let progress_bar = ProgressBar::with_draw_target(0, ProgressDrawTarget::stdout()); 839 progress_bar.set_style( 840 ProgressStyle::default_bar().template("{bar:60} {prefix:40} {pos:>4}/{len:>4} {msg}"), 841 ); 842 843 for pass_idx in 0..100 { 844 let mut should_keep_reducing = false; 845 let mut phase = 0; 846 847 loop { 848 let mut mutator: Box<dyn Mutator> = match phase { 849 0 => Box::new(RemoveInst::new(&func)), 850 1 => Box::new(ReplaceInstWithConst::new(&func)), 851 2 => Box::new(ReplaceInstWithTrap::new(&func)), 852 3 => Box::new(MoveInstToEntryBlock::new(&func)), 853 4 => Box::new(RemoveBlock::new(&func)), 854 5 => Box::new(ReplaceBlockParamWithConst::new(&func)), 855 6 => Box::new(RemoveUnusedEntities::new()), 856 7 => Box::new(MergeBlocks::new(&func)), 857 _ => break, 858 }; 859 860 progress_bar.set_prefix(&format!("pass {} phase {}", pass_idx, mutator.name())); 861 progress_bar.set_length(mutator.mutation_count(&func) as u64); 862 863 // Reset progress bar. 864 progress_bar.set_position(0); 865 progress_bar.set_draw_delta(0); 866 867 for _ in 0..10000 { 868 progress_bar.inc(1); 869 870 let (mutated_func, msg, mutation_kind) = match mutator.mutate(func.clone()) { 871 Some(res) => res, 872 None => { 873 break; 874 } 875 }; 876 877 if let ProgressStatus::Skip = mutation_kind { 878 // The mutator didn't change anything, but we want to try more mutator 879 // iterations. 880 continue; 881 } 882 883 progress_bar.set_message(&msg); 884 885 match context.check_for_crash(&mutated_func) { 886 CheckResult::Succeed => { 887 // Mutating didn't hit the problem anymore, discard changes. 888 continue; 889 } 890 CheckResult::Crash(_) => { 891 // Panic remained while mutating, make changes definitive. 892 func = mutated_func; 893 894 // Notify the mutator that the mutation was successful. 895 mutator.did_crash(); 896 897 let verb = match mutation_kind { 898 ProgressStatus::ExpandedOrShrinked => { 899 should_keep_reducing = true; 900 "shrink" 901 } 902 ProgressStatus::Changed => "changed", 903 ProgressStatus::Skip => unreachable!(), 904 }; 905 if verbose { 906 progress_bar.println(format!("{}: {}", msg, verb)); 907 } 908 } 909 } 910 } 911 912 phase += 1; 913 } 914 915 progress_bar.println(format!( 916 "After pass {}, remaining insts/blocks: {}/{} ({})", 917 pass_idx, 918 inst_count(&func), 919 block_count(&func), 920 if should_keep_reducing { 921 "will keep reducing" 922 } else { 923 "stop reducing" 924 } 925 )); 926 927 if !should_keep_reducing { 928 // No new shrinking opportunities have been found this pass. This means none will ever 929 // be found. Skip the rest of the passes over the function. 930 break; 931 } 932 } 933 934 try_resolve_aliases(&mut context, &mut func); 935 progress_bar.finish(); 936 937 let crash_msg = match context.check_for_crash(&func) { 938 CheckResult::Succeed => unreachable!("Used to crash, but doesn't anymore???"), 939 CheckResult::Crash(crash_msg) => crash_msg, 940 }; 941 942 Ok((func, crash_msg)) 943 } 944 945 struct CrashCheckContext<'a> { 946 /// Cached `Context`, to prevent repeated allocation. 947 context: Context, 948 949 /// Cached code memory, to prevent repeated allocation. 950 code_memory: Vec<u8>, 951 952 /// The target isa to compile for. 953 isa: &'a dyn TargetIsa, 954 } 955 956 fn get_panic_string(panic: Box<dyn std::any::Any>) -> String { 957 let panic = match panic.downcast::<&'static str>() { 958 Ok(panic_msg) => { 959 return panic_msg.to_string(); 960 } 961 Err(panic) => panic, 962 }; 963 match panic.downcast::<String>() { 964 Ok(panic_msg) => *panic_msg, 965 Err(_) => "Box<Any>".to_string(), 966 } 967 } 968 969 enum CheckResult { 970 /// The function compiled fine, or the verifier noticed an error. 971 Succeed, 972 973 /// The compilation of the function panicked. 974 Crash(String), 975 } 976 977 impl<'a> CrashCheckContext<'a> { 978 fn new(isa: &'a dyn TargetIsa) -> Self { 979 CrashCheckContext { 980 context: Context::new(), 981 code_memory: Vec::new(), 982 isa, 983 } 984 } 985 986 #[cfg_attr(test, allow(unreachable_code))] 987 fn check_for_crash(&mut self, func: &Function) -> CheckResult { 988 self.context.clear(); 989 self.code_memory.clear(); 990 991 self.context.func = func.clone(); 992 993 use std::io::Write; 994 std::io::stdout().flush().unwrap(); // Flush stdout to sync with panic messages on stderr 995 996 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { 997 cranelift_codegen::verifier::verify_function(&func, self.isa).err() 998 })) { 999 Ok(Some(_)) => return CheckResult::Succeed, 1000 Ok(None) => {} 1001 // The verifier panicked. Compiling it will probably give the same panic. 1002 // We treat it as succeeding to make it possible to reduce for the actual error. 1003 // FIXME prevent verifier panic on removing block0. 1004 Err(_) => return CheckResult::Succeed, 1005 } 1006 1007 #[cfg(test)] 1008 { 1009 // For testing purposes we emulate a panic caused by the existence of 1010 // a `call` instruction. 1011 let contains_call = func.layout.blocks().any(|block| { 1012 func.layout 1013 .block_insts(block) 1014 .any(|inst| match func.dfg[inst] { 1015 InstructionData::Call { .. } => true, 1016 _ => false, 1017 }) 1018 }); 1019 if contains_call { 1020 return CheckResult::Crash("test crash".to_string()); 1021 } else { 1022 return CheckResult::Succeed; 1023 } 1024 } 1025 1026 let old_panic_hook = std::panic::take_hook(); 1027 std::panic::set_hook(Box::new(|_| {})); // silence panics 1028 1029 let res = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { 1030 let _ = self 1031 .context 1032 .compile_and_emit(self.isa, &mut self.code_memory); 1033 })) { 1034 Ok(()) => CheckResult::Succeed, 1035 Err(err) => CheckResult::Crash(get_panic_string(err)), 1036 }; 1037 1038 std::panic::set_hook(old_panic_hook); 1039 1040 res 1041 } 1042 } 1043 1044 #[cfg(test)] 1045 mod tests { 1046 use super::*; 1047 use cranelift_reader::ParseOptions; 1048 1049 fn run_test(test_str: &str, expected_str: &str) { 1050 let test_file = parse_test(test_str, ParseOptions::default()).unwrap(); 1051 1052 // If we have an isa from the command-line, use that. Otherwise if the 1053 // file contains a unique isa, use that. 1054 let isa = test_file.isa_spec.unique_isa().expect("Unknown isa"); 1055 1056 for (func, _) in test_file.functions { 1057 let (reduced_func, crash_msg) = 1058 reduce(isa, func, false).expect("Couldn't reduce test case"); 1059 assert_eq!(crash_msg, "test crash"); 1060 1061 let (func_reduced_twice, crash_msg) = 1062 reduce(isa, reduced_func.clone(), false).expect("Couldn't re-reduce test case"); 1063 assert_eq!(crash_msg, "test crash"); 1064 1065 assert_eq!( 1066 block_count(&func_reduced_twice), 1067 block_count(&reduced_func), 1068 "reduction wasn't maximal for blocks" 1069 ); 1070 assert_eq!( 1071 inst_count(&func_reduced_twice), 1072 inst_count(&reduced_func), 1073 "reduction wasn't maximal for insts" 1074 ); 1075 1076 assert_eq!( 1077 format!("{}", reduced_func), 1078 expected_str.replace("\r\n", "\n") 1079 ); 1080 } 1081 } 1082 1083 #[test] 1084 fn test_reduce() { 1085 const TEST: &str = include_str!("../tests/bugpoint_test.clif"); 1086 const EXPECTED: &str = include_str!("../tests/bugpoint_test_expected.clif"); 1087 run_test(TEST, EXPECTED); 1088 } 1089 1090 #[test] 1091 fn test_consts() { 1092 const TEST: &str = include_str!("../tests/bugpoint_consts.clif"); 1093 const EXPECTED: &str = include_str!("../tests/bugpoint_consts_expected.clif"); 1094 run_test(TEST, EXPECTED); 1095 } 1096 } 1097