diff --git a/cranelift/fuzzgen/src/config.rs b/cranelift/fuzzgen/src/config.rs index 7ac7412ed592..1213a89dbfd3 100644 --- a/cranelift/fuzzgen/src/config.rs +++ b/cranelift/fuzzgen/src/config.rs @@ -47,6 +47,13 @@ pub struct Config { /// that avoids these issues. However we can allow some `int_divz` traps /// by controlling this config. pub allowed_int_divz_ratio: (usize, usize), + + /// How often should we allow fcvt related traps. + /// + /// `Fcvt*` instructions fail under some inputs, most commonly NaN's. + /// We insert a checking sequence to guarantee that those inputs never make + /// it to the instruction, but sometimes we want to allow them. + pub allowed_fcvt_traps_ratio: (usize, usize), } impl Default for Config { @@ -71,6 +78,7 @@ impl Default for Config { // impact execs/s backwards_branch_ratio: (1, 1000), allowed_int_divz_ratio: (1, 1_000_000), + allowed_fcvt_traps_ratio: (1, 1_000_000), } } } diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs index ee16cb68cb7b..c99d6a9595a7 100644 --- a/cranelift/fuzzgen/src/function_generator.rs +++ b/cranelift/fuzzgen/src/function_generator.rs @@ -486,6 +486,122 @@ const OPCODE_SIGNATURES: &'static [( // Nearest (Opcode::Nearest, &[F32], &[F32], insert_opcode), (Opcode::Nearest, &[F64], &[F64], insert_opcode), + // FcvtToUint + // TODO: Some ops disabled: + // x64: https://github.com/bytecodealliance/wasmtime/issues/4897 + // x64: https://github.com/bytecodealliance/wasmtime/issues/4899 + // aarch64: https://github.com/bytecodealliance/wasmtime/issues/4934 + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToUint, &[F32], &[I8], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToUint, &[F32], &[I16], insert_opcode), + (Opcode::FcvtToUint, &[F32], &[I32], insert_opcode), + (Opcode::FcvtToUint, &[F32], &[I64], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtToUint, &[F32], &[I128], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToUint, &[F64], &[I8], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToUint, &[F64], &[I16], insert_opcode), + (Opcode::FcvtToUint, &[F64], &[I32], insert_opcode), + (Opcode::FcvtToUint, &[F64], &[I64], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtToUint, &[F64], &[I128], insert_opcode), + // FcvtToUintSat + // TODO: Some ops disabled: + // x64: https://github.com/bytecodealliance/wasmtime/issues/4897 + // x64: https://github.com/bytecodealliance/wasmtime/issues/4899 + // aarch64: https://github.com/bytecodealliance/wasmtime/issues/4934 + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToUintSat, &[F32], &[I8], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToUintSat, &[F32], &[I16], insert_opcode), + (Opcode::FcvtToUintSat, &[F32], &[I32], insert_opcode), + (Opcode::FcvtToUintSat, &[F32], &[I64], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtToUintSat, &[F32], &[I128], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToUintSat, &[F64], &[I8], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToUintSat, &[F64], &[I16], insert_opcode), + (Opcode::FcvtToUintSat, &[F64], &[I32], insert_opcode), + (Opcode::FcvtToUintSat, &[F64], &[I64], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtToUintSat, &[F64], &[I128], insert_opcode), + // FcvtToSint + // TODO: Some ops disabled: + // x64: https://github.com/bytecodealliance/wasmtime/issues/4897 + // x64: https://github.com/bytecodealliance/wasmtime/issues/4899 + // aarch64: https://github.com/bytecodealliance/wasmtime/issues/4934 + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToSint, &[F32], &[I8], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToSint, &[F32], &[I16], insert_opcode), + (Opcode::FcvtToSint, &[F32], &[I32], insert_opcode), + (Opcode::FcvtToSint, &[F32], &[I64], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtToSint, &[F32], &[I128], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToSint, &[F64], &[I8], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToSint, &[F64], &[I16], insert_opcode), + (Opcode::FcvtToSint, &[F64], &[I32], insert_opcode), + (Opcode::FcvtToSint, &[F64], &[I64], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtToSint, &[F64], &[I128], insert_opcode), + // FcvtToSintSat + // TODO: Some ops disabled: + // x64: https://github.com/bytecodealliance/wasmtime/issues/4897 + // x64: https://github.com/bytecodealliance/wasmtime/issues/4899 + // aarch64: https://github.com/bytecodealliance/wasmtime/issues/4934 + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToSintSat, &[F32], &[I8], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToSintSat, &[F32], &[I16], insert_opcode), + (Opcode::FcvtToSintSat, &[F32], &[I32], insert_opcode), + (Opcode::FcvtToSintSat, &[F32], &[I64], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtToSintSat, &[F32], &[I128], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToSintSat, &[F64], &[I8], insert_opcode), + #[cfg(not(target_arch = "x86_64"))] + (Opcode::FcvtToSintSat, &[F64], &[I16], insert_opcode), + (Opcode::FcvtToSintSat, &[F64], &[I32], insert_opcode), + (Opcode::FcvtToSintSat, &[F64], &[I64], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtToSintSat, &[F64], &[I128], insert_opcode), + // FcvtFromUint + // TODO: Some ops disabled: + // x64: https://github.com/bytecodealliance/wasmtime/issues/4900 + // aarch64: https://github.com/bytecodealliance/wasmtime/issues/4933 + (Opcode::FcvtFromUint, &[I8], &[F32], insert_opcode), + (Opcode::FcvtFromUint, &[I16], &[F32], insert_opcode), + (Opcode::FcvtFromUint, &[I32], &[F32], insert_opcode), + (Opcode::FcvtFromUint, &[I64], &[F32], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtFromUint, &[I128], &[F32], insert_opcode), + (Opcode::FcvtFromUint, &[I8], &[F64], insert_opcode), + (Opcode::FcvtFromUint, &[I16], &[F64], insert_opcode), + (Opcode::FcvtFromUint, &[I32], &[F64], insert_opcode), + (Opcode::FcvtFromUint, &[I64], &[F64], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtFromUint, &[I128], &[F64], insert_opcode), + // FcvtFromSint + // TODO: Some ops disabled: + // x64: https://github.com/bytecodealliance/wasmtime/issues/4900 + // aarch64: https://github.com/bytecodealliance/wasmtime/issues/4933 + (Opcode::FcvtFromSint, &[I8], &[F32], insert_opcode), + (Opcode::FcvtFromSint, &[I16], &[F32], insert_opcode), + (Opcode::FcvtFromSint, &[I32], &[F32], insert_opcode), + (Opcode::FcvtFromSint, &[I64], &[F32], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtFromSint, &[I128], &[F32], insert_opcode), + (Opcode::FcvtFromSint, &[I8], &[F64], insert_opcode), + (Opcode::FcvtFromSint, &[I16], &[F64], insert_opcode), + (Opcode::FcvtFromSint, &[I32], &[F64], insert_opcode), + (Opcode::FcvtFromSint, &[I64], &[F64], insert_opcode), + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + (Opcode::FcvtFromSint, &[I128], &[F64], insert_opcode), // Fcmp (Opcode::Fcmp, &[F32, F32], &[B1], insert_cmp), (Opcode::Fcmp, &[F64, F64], &[B1], insert_cmp), diff --git a/cranelift/fuzzgen/src/lib.rs b/cranelift/fuzzgen/src/lib.rs index 4ffac255d03c..69355dc05e04 100644 --- a/cranelift/fuzzgen/src/lib.rs +++ b/cranelift/fuzzgen/src/lib.rs @@ -12,7 +12,7 @@ use std::fmt; mod config; mod function_generator; -mod pass; +mod passes; pub type TestCaseInput = Vec; @@ -205,7 +205,10 @@ where // Run the int_divz pass // // This pass replaces divs and rems with sequences that do not trap - pass::do_int_divz_pass(self, &mut ctx.func)?; + passes::do_int_divz_pass(self, &mut ctx.func)?; + + // This pass replaces fcvt* instructions with sequences that do not trap + passes::do_fcvt_trap_pass(self, &mut ctx.func)?; Ok(ctx.func) } diff --git a/cranelift/fuzzgen/src/passes/fcvt.rs b/cranelift/fuzzgen/src/passes/fcvt.rs new file mode 100644 index 000000000000..8d6a92c435b8 --- /dev/null +++ b/cranelift/fuzzgen/src/passes/fcvt.rs @@ -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; +} diff --git a/cranelift/fuzzgen/src/pass.rs b/cranelift/fuzzgen/src/passes/int_divz.rs similarity index 100% rename from cranelift/fuzzgen/src/pass.rs rename to cranelift/fuzzgen/src/passes/int_divz.rs diff --git a/cranelift/fuzzgen/src/passes/mod.rs b/cranelift/fuzzgen/src/passes/mod.rs new file mode 100644 index 000000000000..f3572d3f611d --- /dev/null +++ b/cranelift/fuzzgen/src/passes/mod.rs @@ -0,0 +1,5 @@ +mod fcvt; +mod int_divz; + +pub use fcvt::do_fcvt_trap_pass; +pub use int_divz::do_int_divz_pass;