fuzzgen: Add fcvt_* ops (#4958)
This commit is contained in:
98
cranelift/fuzzgen/src/passes/fcvt.rs
Normal file
98
cranelift/fuzzgen/src/passes/fcvt.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use crate::{FuzzGen, Type};
|
||||
use anyhow::Result;
|
||||
use cranelift::codegen::cursor::{Cursor, FuncCursor};
|
||||
use cranelift::codegen::ir::{Function, Inst, Opcode};
|
||||
use cranelift::prelude::{types::*, *};
|
||||
|
||||
pub fn do_fcvt_trap_pass(fuzz: &mut FuzzGen, func: &mut Function) -> Result<()> {
|
||||
let ratio = fuzz.config.allowed_fcvt_traps_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_fcvt_trap(&pos, inst) {
|
||||
insert_fcvt_sequence(&mut pos, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns true/false if this instruction can trap
|
||||
fn can_fcvt_trap(pos: &FuncCursor, inst: Inst) -> bool {
|
||||
let opcode = pos.func.dfg[inst].opcode();
|
||||
|
||||
matches!(opcode, Opcode::FcvtToUint | Opcode::FcvtToSint)
|
||||
}
|
||||
|
||||
/// Gets the max and min float values for this integer type
|
||||
/// Inserts fconst instructions with these values.
|
||||
//
|
||||
// When converting to integers, floats are truncated. This means that the maximum float value
|
||||
// that can be converted into an i8 is 127.99999. And surprisingly the minimum float for an
|
||||
// u8 is -0.99999! So get the limits of this type as a float value by adding or subtracting
|
||||
// 1.0 from its min and max integer values.
|
||||
fn float_limits(
|
||||
pos: &mut FuncCursor,
|
||||
float_ty: Type,
|
||||
int_ty: Type,
|
||||
is_signed: bool,
|
||||
) -> (Value, Value) {
|
||||
let (min_int, max_int) = int_ty.bounds(is_signed);
|
||||
|
||||
if float_ty == F32 {
|
||||
let (min, max) = if is_signed {
|
||||
((min_int as i128) as f32, (max_int as i128) as f32)
|
||||
} else {
|
||||
(min_int as f32, max_int as f32)
|
||||
};
|
||||
|
||||
(pos.ins().f32const(min - 1.0), pos.ins().f32const(max + 1.0))
|
||||
} else {
|
||||
let (min, max) = if is_signed {
|
||||
((min_int as i128) as f64, (max_int as i128) as f64)
|
||||
} else {
|
||||
(min_int as f64, max_int as f64)
|
||||
};
|
||||
|
||||
(pos.ins().f64const(min - 1.0), pos.ins().f64const(max + 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepend instructions to inst to avoid traps
|
||||
fn insert_fcvt_sequence(pos: &mut FuncCursor, inst: Inst) {
|
||||
let dfg = &pos.func.dfg;
|
||||
let opcode = dfg[inst].opcode();
|
||||
let arg = dfg.inst_args(inst)[0];
|
||||
let float_ty = dfg.value_type(arg);
|
||||
let int_ty = dfg.value_type(dfg.first_result(inst));
|
||||
|
||||
// These instructions trap on NaN
|
||||
let is_nan = pos.ins().fcmp(FloatCC::NotEqual, arg, arg);
|
||||
|
||||
// They also trap if the value is larger or smaller than what the integer type can represent. So
|
||||
// we generate the maximum and minimum float value that would make this trap, and compare against
|
||||
// those limits.
|
||||
let is_signed = opcode == Opcode::FcvtToSint;
|
||||
let (min, max) = float_limits(pos, float_ty, int_ty, is_signed);
|
||||
let underflows = pos.ins().fcmp(FloatCC::LessThanOrEqual, arg, min);
|
||||
let overflows = pos.ins().fcmp(FloatCC::GreaterThanOrEqual, arg, max);
|
||||
|
||||
// Check the previous conditions and replace with a 1.0 if this instruction would trap
|
||||
let overflows_int = pos.ins().bor(underflows, overflows);
|
||||
let is_invalid = pos.ins().bor(is_nan, overflows_int);
|
||||
|
||||
let one = if float_ty == F32 {
|
||||
pos.ins().f32const(1.0)
|
||||
} else {
|
||||
pos.ins().f64const(1.0)
|
||||
};
|
||||
let new_arg = pos.ins().select(is_invalid, one, arg);
|
||||
|
||||
// Replace the previous arg with the new one
|
||||
pos.func.dfg.inst_args_mut(inst)[0] = new_arg;
|
||||
}
|
||||
78
cranelift/fuzzgen/src/passes/int_divz.rs
Normal file
78
cranelift/fuzzgen/src/passes/int_divz.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
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[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[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;
|
||||
}
|
||||
5
cranelift/fuzzgen/src/passes/mod.rs
Normal file
5
cranelift/fuzzgen/src/passes/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod fcvt;
|
||||
mod int_divz;
|
||||
|
||||
pub use fcvt::do_fcvt_trap_pass;
|
||||
pub use int_divz::do_int_divz_pass;
|
||||
Reference in New Issue
Block a user