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