Skip to content

Commit 10deb9b

Browse files
authored
fuzzgen: Add fcvt_* ops (#4958)
1 parent 65a3af7 commit 10deb9b

6 files changed

Lines changed: 232 additions & 2 deletions

File tree

cranelift/fuzzgen/src/config.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ pub struct Config {
4747
/// that avoids these issues. However we can allow some `int_divz` traps
4848
/// by controlling this config.
4949
pub allowed_int_divz_ratio: (usize, usize),
50+
51+
/// How often should we allow fcvt related traps.
52+
///
53+
/// `Fcvt*` instructions fail under some inputs, most commonly NaN's.
54+
/// We insert a checking sequence to guarantee that those inputs never make
55+
/// it to the instruction, but sometimes we want to allow them.
56+
pub allowed_fcvt_traps_ratio: (usize, usize),
5057
}
5158

5259
impl Default for Config {
@@ -71,6 +78,7 @@ impl Default for Config {
7178
// impact execs/s
7279
backwards_branch_ratio: (1, 1000),
7380
allowed_int_divz_ratio: (1, 1_000_000),
81+
allowed_fcvt_traps_ratio: (1, 1_000_000),
7482
}
7583
}
7684
}

cranelift/fuzzgen/src/function_generator.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,122 @@ const OPCODE_SIGNATURES: &'static [(
486486
// Nearest
487487
(Opcode::Nearest, &[F32], &[F32], insert_opcode),
488488
(Opcode::Nearest, &[F64], &[F64], insert_opcode),
489+
// FcvtToUint
490+
// TODO: Some ops disabled:
491+
// x64: https://github.com/bytecodealliance/wasmtime/issues/4897
492+
// x64: https://github.com/bytecodealliance/wasmtime/issues/4899
493+
// aarch64: https://github.com/bytecodealliance/wasmtime/issues/4934
494+
#[cfg(not(target_arch = "x86_64"))]
495+
(Opcode::FcvtToUint, &[F32], &[I8], insert_opcode),
496+
#[cfg(not(target_arch = "x86_64"))]
497+
(Opcode::FcvtToUint, &[F32], &[I16], insert_opcode),
498+
(Opcode::FcvtToUint, &[F32], &[I32], insert_opcode),
499+
(Opcode::FcvtToUint, &[F32], &[I64], insert_opcode),
500+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
501+
(Opcode::FcvtToUint, &[F32], &[I128], insert_opcode),
502+
#[cfg(not(target_arch = "x86_64"))]
503+
(Opcode::FcvtToUint, &[F64], &[I8], insert_opcode),
504+
#[cfg(not(target_arch = "x86_64"))]
505+
(Opcode::FcvtToUint, &[F64], &[I16], insert_opcode),
506+
(Opcode::FcvtToUint, &[F64], &[I32], insert_opcode),
507+
(Opcode::FcvtToUint, &[F64], &[I64], insert_opcode),
508+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
509+
(Opcode::FcvtToUint, &[F64], &[I128], insert_opcode),
510+
// FcvtToUintSat
511+
// TODO: Some ops disabled:
512+
// x64: https://github.com/bytecodealliance/wasmtime/issues/4897
513+
// x64: https://github.com/bytecodealliance/wasmtime/issues/4899
514+
// aarch64: https://github.com/bytecodealliance/wasmtime/issues/4934
515+
#[cfg(not(target_arch = "x86_64"))]
516+
(Opcode::FcvtToUintSat, &[F32], &[I8], insert_opcode),
517+
#[cfg(not(target_arch = "x86_64"))]
518+
(Opcode::FcvtToUintSat, &[F32], &[I16], insert_opcode),
519+
(Opcode::FcvtToUintSat, &[F32], &[I32], insert_opcode),
520+
(Opcode::FcvtToUintSat, &[F32], &[I64], insert_opcode),
521+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
522+
(Opcode::FcvtToUintSat, &[F32], &[I128], insert_opcode),
523+
#[cfg(not(target_arch = "x86_64"))]
524+
(Opcode::FcvtToUintSat, &[F64], &[I8], insert_opcode),
525+
#[cfg(not(target_arch = "x86_64"))]
526+
(Opcode::FcvtToUintSat, &[F64], &[I16], insert_opcode),
527+
(Opcode::FcvtToUintSat, &[F64], &[I32], insert_opcode),
528+
(Opcode::FcvtToUintSat, &[F64], &[I64], insert_opcode),
529+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
530+
(Opcode::FcvtToUintSat, &[F64], &[I128], insert_opcode),
531+
// FcvtToSint
532+
// TODO: Some ops disabled:
533+
// x64: https://github.com/bytecodealliance/wasmtime/issues/4897
534+
// x64: https://github.com/bytecodealliance/wasmtime/issues/4899
535+
// aarch64: https://github.com/bytecodealliance/wasmtime/issues/4934
536+
#[cfg(not(target_arch = "x86_64"))]
537+
(Opcode::FcvtToSint, &[F32], &[I8], insert_opcode),
538+
#[cfg(not(target_arch = "x86_64"))]
539+
(Opcode::FcvtToSint, &[F32], &[I16], insert_opcode),
540+
(Opcode::FcvtToSint, &[F32], &[I32], insert_opcode),
541+
(Opcode::FcvtToSint, &[F32], &[I64], insert_opcode),
542+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
543+
(Opcode::FcvtToSint, &[F32], &[I128], insert_opcode),
544+
#[cfg(not(target_arch = "x86_64"))]
545+
(Opcode::FcvtToSint, &[F64], &[I8], insert_opcode),
546+
#[cfg(not(target_arch = "x86_64"))]
547+
(Opcode::FcvtToSint, &[F64], &[I16], insert_opcode),
548+
(Opcode::FcvtToSint, &[F64], &[I32], insert_opcode),
549+
(Opcode::FcvtToSint, &[F64], &[I64], insert_opcode),
550+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
551+
(Opcode::FcvtToSint, &[F64], &[I128], insert_opcode),
552+
// FcvtToSintSat
553+
// TODO: Some ops disabled:
554+
// x64: https://github.com/bytecodealliance/wasmtime/issues/4897
555+
// x64: https://github.com/bytecodealliance/wasmtime/issues/4899
556+
// aarch64: https://github.com/bytecodealliance/wasmtime/issues/4934
557+
#[cfg(not(target_arch = "x86_64"))]
558+
(Opcode::FcvtToSintSat, &[F32], &[I8], insert_opcode),
559+
#[cfg(not(target_arch = "x86_64"))]
560+
(Opcode::FcvtToSintSat, &[F32], &[I16], insert_opcode),
561+
(Opcode::FcvtToSintSat, &[F32], &[I32], insert_opcode),
562+
(Opcode::FcvtToSintSat, &[F32], &[I64], insert_opcode),
563+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
564+
(Opcode::FcvtToSintSat, &[F32], &[I128], insert_opcode),
565+
#[cfg(not(target_arch = "x86_64"))]
566+
(Opcode::FcvtToSintSat, &[F64], &[I8], insert_opcode),
567+
#[cfg(not(target_arch = "x86_64"))]
568+
(Opcode::FcvtToSintSat, &[F64], &[I16], insert_opcode),
569+
(Opcode::FcvtToSintSat, &[F64], &[I32], insert_opcode),
570+
(Opcode::FcvtToSintSat, &[F64], &[I64], insert_opcode),
571+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
572+
(Opcode::FcvtToSintSat, &[F64], &[I128], insert_opcode),
573+
// FcvtFromUint
574+
// TODO: Some ops disabled:
575+
// x64: https://github.com/bytecodealliance/wasmtime/issues/4900
576+
// aarch64: https://github.com/bytecodealliance/wasmtime/issues/4933
577+
(Opcode::FcvtFromUint, &[I8], &[F32], insert_opcode),
578+
(Opcode::FcvtFromUint, &[I16], &[F32], insert_opcode),
579+
(Opcode::FcvtFromUint, &[I32], &[F32], insert_opcode),
580+
(Opcode::FcvtFromUint, &[I64], &[F32], insert_opcode),
581+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
582+
(Opcode::FcvtFromUint, &[I128], &[F32], insert_opcode),
583+
(Opcode::FcvtFromUint, &[I8], &[F64], insert_opcode),
584+
(Opcode::FcvtFromUint, &[I16], &[F64], insert_opcode),
585+
(Opcode::FcvtFromUint, &[I32], &[F64], insert_opcode),
586+
(Opcode::FcvtFromUint, &[I64], &[F64], insert_opcode),
587+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
588+
(Opcode::FcvtFromUint, &[I128], &[F64], insert_opcode),
589+
// FcvtFromSint
590+
// TODO: Some ops disabled:
591+
// x64: https://github.com/bytecodealliance/wasmtime/issues/4900
592+
// aarch64: https://github.com/bytecodealliance/wasmtime/issues/4933
593+
(Opcode::FcvtFromSint, &[I8], &[F32], insert_opcode),
594+
(Opcode::FcvtFromSint, &[I16], &[F32], insert_opcode),
595+
(Opcode::FcvtFromSint, &[I32], &[F32], insert_opcode),
596+
(Opcode::FcvtFromSint, &[I64], &[F32], insert_opcode),
597+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
598+
(Opcode::FcvtFromSint, &[I128], &[F32], insert_opcode),
599+
(Opcode::FcvtFromSint, &[I8], &[F64], insert_opcode),
600+
(Opcode::FcvtFromSint, &[I16], &[F64], insert_opcode),
601+
(Opcode::FcvtFromSint, &[I32], &[F64], insert_opcode),
602+
(Opcode::FcvtFromSint, &[I64], &[F64], insert_opcode),
603+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
604+
(Opcode::FcvtFromSint, &[I128], &[F64], insert_opcode),
489605
// Fcmp
490606
(Opcode::Fcmp, &[F32, F32], &[B1], insert_cmp),
491607
(Opcode::Fcmp, &[F64, F64], &[B1], insert_cmp),

cranelift/fuzzgen/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::fmt;
1212

1313
mod config;
1414
mod function_generator;
15-
mod pass;
15+
mod passes;
1616

1717
pub type TestCaseInput = Vec<DataValue>;
1818

@@ -205,7 +205,10 @@ where
205205
// Run the int_divz pass
206206
//
207207
// This pass replaces divs and rems with sequences that do not trap
208-
pass::do_int_divz_pass(self, &mut ctx.func)?;
208+
passes::do_int_divz_pass(self, &mut ctx.func)?;
209+
210+
// This pass replaces fcvt* instructions with sequences that do not trap
211+
passes::do_fcvt_trap_pass(self, &mut ctx.func)?;
209212

210213
Ok(ctx.func)
211214
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use crate::{FuzzGen, Type};
2+
use anyhow::Result;
3+
use cranelift::codegen::cursor::{Cursor, FuncCursor};
4+
use cranelift::codegen::ir::{Function, Inst, Opcode};
5+
use cranelift::prelude::{types::*, *};
6+
7+
pub fn do_fcvt_trap_pass(fuzz: &mut FuzzGen, func: &mut Function) -> Result<()> {
8+
let ratio = fuzz.config.allowed_fcvt_traps_ratio;
9+
let insert_seq = !fuzz.u.ratio(ratio.0, ratio.1)?;
10+
if !insert_seq {
11+
return Ok(());
12+
}
13+
14+
let mut pos = FuncCursor::new(func);
15+
while let Some(_block) = pos.next_block() {
16+
while let Some(inst) = pos.next_inst() {
17+
if can_fcvt_trap(&pos, inst) {
18+
insert_fcvt_sequence(&mut pos, inst);
19+
}
20+
}
21+
}
22+
Ok(())
23+
}
24+
25+
/// Returns true/false if this instruction can trap
26+
fn can_fcvt_trap(pos: &FuncCursor, inst: Inst) -> bool {
27+
let opcode = pos.func.dfg[inst].opcode();
28+
29+
matches!(opcode, Opcode::FcvtToUint | Opcode::FcvtToSint)
30+
}
31+
32+
/// Gets the max and min float values for this integer type
33+
/// Inserts fconst instructions with these values.
34+
//
35+
// When converting to integers, floats are truncated. This means that the maximum float value
36+
// that can be converted into an i8 is 127.99999. And surprisingly the minimum float for an
37+
// u8 is -0.99999! So get the limits of this type as a float value by adding or subtracting
38+
// 1.0 from its min and max integer values.
39+
fn float_limits(
40+
pos: &mut FuncCursor,
41+
float_ty: Type,
42+
int_ty: Type,
43+
is_signed: bool,
44+
) -> (Value, Value) {
45+
let (min_int, max_int) = int_ty.bounds(is_signed);
46+
47+
if float_ty == F32 {
48+
let (min, max) = if is_signed {
49+
((min_int as i128) as f32, (max_int as i128) as f32)
50+
} else {
51+
(min_int as f32, max_int as f32)
52+
};
53+
54+
(pos.ins().f32const(min - 1.0), pos.ins().f32const(max + 1.0))
55+
} else {
56+
let (min, max) = if is_signed {
57+
((min_int as i128) as f64, (max_int as i128) as f64)
58+
} else {
59+
(min_int as f64, max_int as f64)
60+
};
61+
62+
(pos.ins().f64const(min - 1.0), pos.ins().f64const(max + 1.0))
63+
}
64+
}
65+
66+
/// Prepend instructions to inst to avoid traps
67+
fn insert_fcvt_sequence(pos: &mut FuncCursor, inst: Inst) {
68+
let dfg = &pos.func.dfg;
69+
let opcode = dfg[inst].opcode();
70+
let arg = dfg.inst_args(inst)[0];
71+
let float_ty = dfg.value_type(arg);
72+
let int_ty = dfg.value_type(dfg.first_result(inst));
73+
74+
// These instructions trap on NaN
75+
let is_nan = pos.ins().fcmp(FloatCC::NotEqual, arg, arg);
76+
77+
// They also trap if the value is larger or smaller than what the integer type can represent. So
78+
// we generate the maximum and minimum float value that would make this trap, and compare against
79+
// those limits.
80+
let is_signed = opcode == Opcode::FcvtToSint;
81+
let (min, max) = float_limits(pos, float_ty, int_ty, is_signed);
82+
let underflows = pos.ins().fcmp(FloatCC::LessThanOrEqual, arg, min);
83+
let overflows = pos.ins().fcmp(FloatCC::GreaterThanOrEqual, arg, max);
84+
85+
// Check the previous conditions and replace with a 1.0 if this instruction would trap
86+
let overflows_int = pos.ins().bor(underflows, overflows);
87+
let is_invalid = pos.ins().bor(is_nan, overflows_int);
88+
89+
let one = if float_ty == F32 {
90+
pos.ins().f32const(1.0)
91+
} else {
92+
pos.ins().f64const(1.0)
93+
};
94+
let new_arg = pos.ins().select(is_invalid, one, arg);
95+
96+
// Replace the previous arg with the new one
97+
pos.func.dfg.inst_args_mut(inst)[0] = new_arg;
98+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod fcvt;
2+
mod int_divz;
3+
4+
pub use fcvt::do_fcvt_trap_pass;
5+
pub use int_divz::do_int_divz_pass;

0 commit comments

Comments
 (0)