Skip to content

Commit 51d8734

Browse files
authored
fuzzgen: Generate compiler flags (#5020)
* fuzzgen: Test compiler flags * cranelift: Generate `all()` function for all enum flags This allows a user to iterate all flags that exist. * fuzzgen: Minimize regalloc_checker compiles * fuzzgen: Limit the amount of test case inputs * fuzzgen: Add egraphs flag It's finally here! 🥳 * cranelift: Add fuzzing comment to settings * fuzzgen: Add riscv64 * fuzzgen: Unconditionally enable some flags
1 parent 0959f90 commit 51d8734

File tree

5 files changed

+138
-29
lines changed

5 files changed

+138
-29
lines changed

cranelift/codegen/meta/src/gen_settings.rs

+26
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,26 @@ fn gen_iterator(group: &SettingGroup, fmt: &mut Formatter) {
9898
fmtln!(fmt, "}");
9999
}
100100

101+
/// Generates a `all()` function with all options for this enum
102+
fn gen_enum_all(name: &str, values: &[&'static str], fmt: &mut Formatter) {
103+
fmtln!(
104+
fmt,
105+
"/// Returns a slice with all possible [{}] values.",
106+
name
107+
);
108+
fmtln!(fmt, "pub fn all() -> &'static [{}] {{", name);
109+
fmt.indent(|fmt| {
110+
fmtln!(fmt, "&[");
111+
fmt.indent(|fmt| {
112+
for v in values.iter() {
113+
fmtln!(fmt, "Self::{},", camel_case(v));
114+
}
115+
});
116+
fmtln!(fmt, "]");
117+
});
118+
fmtln!(fmt, "}");
119+
}
120+
101121
/// Emit Display and FromStr implementations for enum settings.
102122
fn gen_to_and_from_str(name: &str, values: &[&'static str], fmt: &mut Formatter) {
103123
fmtln!(fmt, "impl fmt::Display for {} {{", name);
@@ -158,6 +178,12 @@ fn gen_enum_types(group: &SettingGroup, fmt: &mut Formatter) {
158178
});
159179
fmtln!(fmt, "}");
160180

181+
fmtln!(fmt, "impl {} {{", name);
182+
fmt.indent(|fmt| {
183+
gen_enum_all(&name, values, fmt);
184+
});
185+
fmtln!(fmt, "}");
186+
161187
gen_to_and_from_str(&name, values, fmt);
162188
}
163189
}

cranelift/codegen/meta/src/shared/settings.rs

+2
Original file line numberDiff line numberDiff line change
@@ -364,5 +364,7 @@ pub(crate) fn define() -> SettingGroup {
364364
false,
365365
);
366366

367+
// When adding new settings please check if they can also be added
368+
// in cranelift/fuzzgen/src/lib.rs for fuzzing.
367369
settings.build()
368370
}

cranelift/fuzzgen/src/config.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
use std::collections::HashMap;
12
use std::ops::RangeInclusive;
23

34
/// Holds the range of acceptable values to use during the generation of testcases
45
pub struct Config {
5-
pub test_case_inputs: RangeInclusive<usize>,
6+
/// Maximum allowed test case inputs.
7+
/// We build test case inputs from the rest of the bytes that the fuzzer provides us
8+
/// so we allow the fuzzer to control this by feeding us more or less bytes.
9+
/// The upper bound here is to prevent too many inputs that cause long test times
10+
pub max_test_case_inputs: usize,
611
pub signature_params: RangeInclusive<usize>,
712
pub signature_rets: RangeInclusive<usize>,
813
pub instructions_per_block: RangeInclusive<usize>,
@@ -51,12 +56,17 @@ pub struct Config {
5156
/// We insert a checking sequence to guarantee that those inputs never make
5257
/// it to the instruction, but sometimes we want to allow them.
5358
pub allowed_fcvt_traps_ratio: (usize, usize),
59+
60+
/// Some flags really impact compile performance, we still want to test
61+
/// them, but probably at a lower rate, so that overall execution time isn't
62+
/// impacted as much
63+
pub compile_flag_ratio: HashMap<&'static str, (usize, usize)>,
5464
}
5565

5666
impl Default for Config {
5767
fn default() -> Self {
5868
Config {
59-
test_case_inputs: 1..=10,
69+
max_test_case_inputs: 100,
6070
signature_params: 0..=16,
6171
signature_rets: 0..=16,
6272
instructions_per_block: 0..=64,
@@ -75,6 +85,7 @@ impl Default for Config {
7585
backwards_branch_ratio: (1, 1000),
7686
allowed_int_divz_ratio: (1, 1_000_000),
7787
allowed_fcvt_traps_ratio: (1, 1_000_000),
88+
compile_flag_ratio: [("regalloc_checker", (1usize, 1000))].into_iter().collect(),
7889
}
7990
}
8091
}

cranelift/fuzzgen/src/lib.rs

+93-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::config::Config;
22
use crate::function_generator::FunctionGenerator;
3+
use crate::settings::{Flags, OptLevel};
34
use anyhow::Result;
45
use arbitrary::{Arbitrary, Unstructured};
56
use cranelift::codegen::data_value::DataValue;
@@ -30,6 +31,9 @@ impl<'a> Arbitrary<'a> for SingleFunction {
3031
}
3132

3233
pub struct TestCase {
34+
/// [Flags] to use when compiling this test case
35+
pub flags: Flags,
36+
/// Function under test
3337
pub func: Function,
3438
/// Generate multiple test inputs for each test case.
3539
/// This allows us to get more coverage per compilation, which may be somewhat expensive.
@@ -38,19 +42,24 @@ pub struct TestCase {
3842

3943
impl fmt::Debug for TestCase {
4044
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41-
write!(
42-
f,
43-
r#";; Fuzzgen test case
45+
writeln!(f, ";; Fuzzgen test case\n")?;
46+
writeln!(f, "test interpret")?;
47+
writeln!(f, "test run")?;
4448

45-
test interpret
46-
test run
47-
set enable_llvm_abi_extensions
48-
target aarch64
49-
target s390x
50-
target x86_64
49+
// Print only non default flags
50+
let default_flags = Flags::new(settings::builder());
51+
for (default, flag) in default_flags.iter().zip(self.flags.iter()) {
52+
assert_eq!(default.name, flag.name);
5153

52-
"#
53-
)?;
54+
if default.value_string() != flag.value_string() {
55+
writeln!(f, "set {}={}", flag.name, flag.value_string())?;
56+
}
57+
}
58+
59+
writeln!(f, "target aarch64")?;
60+
writeln!(f, "target s390x")?;
61+
writeln!(f, "target riscv64")?;
62+
writeln!(f, "target x86_64\n")?;
5463

5564
writeln!(f, "{}", self.func)?;
5665

@@ -140,7 +149,10 @@ where
140149
fn generate_test_inputs(mut self, signature: &Signature) -> Result<Vec<TestCaseInput>> {
141150
let mut inputs = Vec::new();
142151

143-
loop {
152+
// Generate up to "max_test_case_inputs" inputs, we need an upper bound here since
153+
// the fuzzer at some point starts trying to feed us way too many inputs. (I found one
154+
// test case with 130k inputs!)
155+
for _ in 0..self.config.max_test_case_inputs {
144156
let last_len = self.u.len();
145157

146158
let test_args = signature
@@ -217,14 +229,82 @@ where
217229
self.run_func_passes(func)
218230
}
219231

232+
/// Generate a random set of cranelift flags.
233+
/// Only semantics preserving flags are considered
234+
fn generate_flags(&mut self) -> Result<Flags> {
235+
let mut builder = settings::builder();
236+
237+
let opt = self.u.choose(OptLevel::all())?;
238+
builder.set("opt_level", &format!("{}", opt)[..])?;
239+
240+
// Boolean flags
241+
// TODO: probestack is semantics preserving, but only works inline and on x64
242+
// TODO: enable_pinned_reg does not work with our current trampolines. See: #4376
243+
// TODO: is_pic has issues:
244+
// x86: https://github.com/bytecodealliance/wasmtime/issues/5005
245+
// aarch64: https://github.com/bytecodealliance/wasmtime/issues/2735
246+
let bool_settings = [
247+
"enable_alias_analysis",
248+
"enable_safepoints",
249+
"unwind_info",
250+
"preserve_frame_pointers",
251+
"enable_jump_tables",
252+
"enable_heap_access_spectre_mitigation",
253+
"enable_table_access_spectre_mitigation",
254+
"enable_incremental_compilation_cache_checks",
255+
"regalloc_checker",
256+
"enable_llvm_abi_extensions",
257+
"use_egraphs",
258+
];
259+
for flag_name in bool_settings {
260+
let enabled = self
261+
.config
262+
.compile_flag_ratio
263+
.get(&flag_name)
264+
.map(|&(num, denum)| self.u.ratio(num, denum))
265+
.unwrap_or_else(|| bool::arbitrary(self.u))?;
266+
267+
let value = format!("{}", enabled);
268+
builder.set(flag_name, value.as_str())?;
269+
}
270+
271+
// Fixed settings
272+
273+
// We need llvm ABI extensions for i128 values on x86, so enable it regardless of
274+
// what we picked above.
275+
if cfg!(target_arch = "x86_64") {
276+
builder.enable("enable_llvm_abi_extensions")?;
277+
}
278+
279+
// This is the default, but we should ensure that it wasn't accidentally turned off anywhere.
280+
builder.enable("enable_verifier")?;
281+
282+
// These settings just panic when they're not enabled and we try to use their respective functionality
283+
// so they aren't very interesting to be automatically generated.
284+
builder.enable("enable_atomics")?;
285+
builder.enable("enable_float")?;
286+
builder.enable("enable_simd")?;
287+
288+
// `machine_code_cfg_info` generates additional metadata for the embedder but this doesn't feed back
289+
// into compilation anywhere, we leave it on unconditionally to make sure the generation doesn't panic.
290+
builder.enable("machine_code_cfg_info")?;
291+
292+
Ok(Flags::new(builder))
293+
}
294+
220295
pub fn generate_test(mut self) -> Result<TestCase> {
221296
// If we're generating test inputs as well as a function, then we're planning to execute
222297
// this function. That means that any function references in it need to exist. We don't yet
223298
// have infrastructure for generating multiple functions, so just don't generate funcrefs.
224299
self.config.funcrefs_per_function = 0..=0;
225300

301+
let flags = self.generate_flags()?;
226302
let func = self.generate_func()?;
227303
let inputs = self.generate_test_inputs(&func.signature)?;
228-
Ok(TestCase { func, inputs })
304+
Ok(TestCase {
305+
flags,
306+
func,
307+
inputs,
308+
})
229309
}
230310
}

fuzz/fuzz_targets/cranelift-fuzzgen.rs

+4-14
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ use std::sync::atomic::Ordering;
88

99
use cranelift_codegen::data_value::DataValue;
1010
use cranelift_codegen::ir::{LibCall, TrapCode};
11-
use cranelift_codegen::settings;
12-
use cranelift_codegen::settings::Configurable;
1311
use cranelift_filetests::function_runner::{TestFileCompiler, Trampoline};
1412
use cranelift_fuzzgen::*;
1513
use cranelift_interpreter::environment::FuncIndex;
@@ -167,24 +165,16 @@ fn build_interpreter(testcase: &TestCase) -> Interpreter {
167165
static STATISTICS: Lazy<Statistics> = Lazy::new(Statistics::default);
168166

169167
fuzz_target!(|testcase: TestCase| {
168+
// This is the default, but we should ensure that it wasn't accidentally turned off anywhere.
169+
assert!(testcase.flags.enable_verifier());
170+
170171
// Periodically print statistics
171172
let valid_inputs = STATISTICS.valid_inputs.fetch_add(1, Ordering::SeqCst);
172173
if valid_inputs != 0 && valid_inputs % 10000 == 0 {
173174
STATISTICS.print(valid_inputs);
174175
}
175176

176-
// Native fn
177-
let flags = {
178-
let mut builder = settings::builder();
179-
// We need llvm ABI extensions for i128 values on x86
180-
builder.set("enable_llvm_abi_extensions", "true").unwrap();
181-
182-
// This is the default, but we should ensure that it wasn't accidentally turned off anywhere.
183-
builder.set("enable_verifier", "true").unwrap();
184-
185-
settings::Flags::new(builder)
186-
};
187-
let mut compiler = TestFileCompiler::with_host_isa(flags).unwrap();
177+
let mut compiler = TestFileCompiler::with_host_isa(testcase.flags.clone()).unwrap();
188178
compiler.declare_function(&testcase.func).unwrap();
189179
compiler.define_function(testcase.func.clone()).unwrap();
190180
compiler

0 commit comments

Comments
 (0)