We have some operations defined on DataFlowGraph purely to work around borrow-checker issues with InstructionData and other data on DataFlowGraph. Part of the problem is that indexing the DFG directly hides the fact that we're only indexing the insts field of the DFG. This PR makes the insts field of the DFG public, but wraps it in a newtype that only allows indexing. This means that the borrow checker is better able to tell when operations on memory held by the DFG won't conflict, which comes up frequently when mutating ValueLists held by InstructionData.
79 lines
3.0 KiB
Rust
79 lines
3.0 KiB
Rust
use crate::FuzzGen;
|
|
use anyhow::Result;
|
|
use cranelift::codegen::cursor::{Cursor, FuncCursor};
|
|
use cranelift::codegen::ir::{Function, Inst, Opcode};
|
|
use cranelift::prelude::{InstBuilder, IntCC};
|
|
|
|
pub fn do_int_divz_pass(fuzz: &mut FuzzGen, func: &mut Function) -> Result<()> {
|
|
// Insert this per function, otherwise the actual rate of int_divz doesn't go down that much
|
|
// Experimentally if we decide this per instruction with a 0.1% allow rate, we get 4.4% of runs
|
|
// trapping. Doing this per function decreases the number of runs that trap. It also consumes
|
|
// fewer fuzzer input bytes which is nice.
|
|
let ratio = fuzz.config.allowed_int_divz_ratio;
|
|
let insert_seq = !fuzz.u.ratio(ratio.0, ratio.1)?;
|
|
if !insert_seq {
|
|
return Ok(());
|
|
}
|
|
|
|
let mut pos = FuncCursor::new(func);
|
|
while let Some(_block) = pos.next_block() {
|
|
while let Some(inst) = pos.next_inst() {
|
|
if can_int_divz(&pos, inst) {
|
|
insert_int_divz_sequence(&mut pos, inst);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns true/false if this instruction can cause a `int_divz` trap
|
|
fn can_int_divz(pos: &FuncCursor, inst: Inst) -> bool {
|
|
let opcode = pos.func.dfg.insts[inst].opcode();
|
|
|
|
matches!(
|
|
opcode,
|
|
Opcode::Sdiv | Opcode::Udiv | Opcode::Srem | Opcode::Urem
|
|
)
|
|
}
|
|
|
|
/// Prepend instructions to inst to avoid `int_divz` traps
|
|
fn insert_int_divz_sequence(pos: &mut FuncCursor, inst: Inst) {
|
|
let opcode = pos.func.dfg.insts[inst].opcode();
|
|
let inst_args = pos.func.dfg.inst_args(inst);
|
|
let (lhs, rhs) = (inst_args[0], inst_args[1]);
|
|
assert_eq!(pos.func.dfg.value_type(lhs), pos.func.dfg.value_type(rhs));
|
|
let ty = pos.func.dfg.value_type(lhs);
|
|
|
|
// All of these instructions can trap if the denominator is zero
|
|
let zero = pos.ins().iconst(ty, 0);
|
|
let one = pos.ins().iconst(ty, 1);
|
|
let denominator_is_zero = pos.ins().icmp(IntCC::Equal, rhs, zero);
|
|
|
|
let replace_denominator = if matches!(opcode, Opcode::Srem | Opcode::Sdiv) {
|
|
// Srem and Sdiv can also trap on INT_MIN / -1. So we need to check for the second one
|
|
|
|
// 1 << (ty bits - 1) to get INT_MIN
|
|
let int_min = pos.ins().ishl_imm(one, ty.lane_bits() as i64 - 1);
|
|
|
|
// Get a -1 const
|
|
// TODO: A iconst -1 would be clearer, but #2906 makes this impossible for i128
|
|
let neg_one = pos.ins().isub(zero, one);
|
|
|
|
let lhs_check = pos.ins().icmp(IntCC::Equal, lhs, int_min);
|
|
let rhs_check = pos.ins().icmp(IntCC::Equal, rhs, neg_one);
|
|
let is_invalid = pos.ins().band(lhs_check, rhs_check);
|
|
|
|
// These also crash if the denominator is zero, so we still need to check for that.
|
|
pos.ins().bor(denominator_is_zero, is_invalid)
|
|
} else {
|
|
denominator_is_zero
|
|
};
|
|
|
|
// If we have a trap we replace the denominator with a 1
|
|
let new_rhs = pos.ins().select(replace_denominator, one, rhs);
|
|
|
|
// Replace the previous rhs with the new one
|
|
let args = pos.func.dfg.inst_args_mut(inst);
|
|
args[1] = new_rhs;
|
|
}
|