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