fuzzgen: Fuzz ISA flags (#6001)
This commit is contained in:
@@ -6,7 +6,9 @@ use arbitrary::{Arbitrary, Unstructured};
|
|||||||
use cranelift::codegen::data_value::DataValue;
|
use cranelift::codegen::data_value::DataValue;
|
||||||
use cranelift::codegen::ir::{types::*, UserExternalName, UserFuncName};
|
use cranelift::codegen::ir::{types::*, UserExternalName, UserFuncName};
|
||||||
use cranelift::codegen::ir::{Function, LibCall};
|
use cranelift::codegen::ir::{Function, LibCall};
|
||||||
|
use cranelift::codegen::isa::{self, Builder};
|
||||||
use cranelift::codegen::Context;
|
use cranelift::codegen::Context;
|
||||||
|
use cranelift::prelude::settings::SettingKind;
|
||||||
use cranelift::prelude::*;
|
use cranelift::prelude::*;
|
||||||
use cranelift_arbitrary::CraneliftArbitrary;
|
use cranelift_arbitrary::CraneliftArbitrary;
|
||||||
use cranelift_native::builder_with_options;
|
use cranelift_native::builder_with_options;
|
||||||
@@ -22,6 +24,15 @@ pub use print::PrintableTestCase;
|
|||||||
|
|
||||||
pub type TestCaseInput = Vec<DataValue>;
|
pub type TestCaseInput = Vec<DataValue>;
|
||||||
|
|
||||||
|
pub enum IsaFlagGen {
|
||||||
|
/// When generating ISA flags, ensure that they are all supported by
|
||||||
|
/// the current host.
|
||||||
|
Host,
|
||||||
|
/// All flags available in cranelift are allowed to be generated.
|
||||||
|
/// We also allow generating all possible values for each enum flag.
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FuzzGen<'r, 'data>
|
pub struct FuzzGen<'r, 'data>
|
||||||
where
|
where
|
||||||
'data: 'r,
|
'data: 'r,
|
||||||
@@ -228,4 +239,56 @@ where
|
|||||||
|
|
||||||
Ok(Flags::new(builder))
|
Ok(Flags::new(builder))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a random set of ISA flags and apply them to a Builder.
|
||||||
|
///
|
||||||
|
/// Based on `mode` we can either allow all flags, or just the subset that is
|
||||||
|
/// supported by the current host.
|
||||||
|
///
|
||||||
|
/// In all cases only a subset of the allowed flags is applied to the builder.
|
||||||
|
pub fn set_isa_flags(&mut self, builder: &mut Builder, mode: IsaFlagGen) -> Result<()> {
|
||||||
|
// `max_isa` is the maximal set of flags that we can use.
|
||||||
|
let max_builder = match mode {
|
||||||
|
IsaFlagGen::All => {
|
||||||
|
let mut max_builder = isa::lookup(builder.triple().clone())?;
|
||||||
|
|
||||||
|
for flag in max_builder.iter() {
|
||||||
|
match flag.kind {
|
||||||
|
SettingKind::Bool => {
|
||||||
|
max_builder.enable(flag.name)?;
|
||||||
|
}
|
||||||
|
SettingKind::Enum => {
|
||||||
|
// Since these are enums there isn't a "max" value per se, pick one at random.
|
||||||
|
let value = self.u.choose(flag.values.unwrap())?;
|
||||||
|
max_builder.set(flag.name, value)?;
|
||||||
|
}
|
||||||
|
SettingKind::Preset => {
|
||||||
|
// Presets are just special flags that combine other flags, we don't
|
||||||
|
// want to enable them directly, just the underlying flags.
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
max_builder
|
||||||
|
}
|
||||||
|
// Use `cranelift-native` to do feature detection for us.
|
||||||
|
IsaFlagGen::Host => builder_with_options(true)
|
||||||
|
.expect("Unable to build a TargetIsa for the current host"),
|
||||||
|
};
|
||||||
|
// Cranelift has a somwhat weird API for this, but we need to build the final `TargetIsa` to be able
|
||||||
|
// to extract the values for the ISA flags. We need that to use the `string_value()` that formats
|
||||||
|
// the values so that we can pass it into the builder again.
|
||||||
|
let max_isa = max_builder.finish(Flags::new(settings::builder()))?;
|
||||||
|
|
||||||
|
// We give each of the flags a chance of being copied over. Otherwise we keep the default.
|
||||||
|
for value in max_isa.isa_flags().iter() {
|
||||||
|
let should_copy = bool::arbitrary(self.u)?;
|
||||||
|
if !should_copy {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
builder.set(value.name, &value.value_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use cranelift::codegen::data_value::DataValue;
|
use cranelift::codegen::data_value::DataValue;
|
||||||
use cranelift::codegen::ir::Function;
|
use cranelift::codegen::ir::Function;
|
||||||
use cranelift::prelude::settings;
|
use cranelift::prelude::settings::{self, SettingKind};
|
||||||
use cranelift::prelude::*;
|
use cranelift::prelude::*;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
@@ -70,7 +70,9 @@ impl<'a> fmt::Debug for PrintableTestCase<'a> {
|
|||||||
|
|
||||||
write_non_default_flags(f, self.isa.flags())?;
|
write_non_default_flags(f, self.isa.flags())?;
|
||||||
|
|
||||||
writeln!(f, "target {}\n", self.isa.triple().architecture)?;
|
write!(f, "target {} ", self.isa.triple().architecture)?;
|
||||||
|
write_non_default_isa_flags(f, &self.isa)?;
|
||||||
|
write!(f, "\n\n")?;
|
||||||
|
|
||||||
// Print the functions backwards, so that the main function is printed last
|
// Print the functions backwards, so that the main function is printed last
|
||||||
// and near the test inputs for run test cases.
|
// and near the test inputs for run test cases.
|
||||||
@@ -128,3 +130,33 @@ fn write_non_default_flags(f: &mut fmt::Formatter<'_>, flags: &settings::Flags)
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Print non default ISA flags in a single line, as used in `target` declarations.
|
||||||
|
fn write_non_default_isa_flags(
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
isa: &isa::OwnedTargetIsa,
|
||||||
|
) -> fmt::Result {
|
||||||
|
let default_isa = isa::lookup(isa.triple().clone())
|
||||||
|
.unwrap()
|
||||||
|
.finish(isa.flags().clone())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for (default, flag) in default_isa.isa_flags().iter().zip(isa.isa_flags()) {
|
||||||
|
assert_eq!(default.name, flag.name);
|
||||||
|
|
||||||
|
// Skip default flags, putting them all out there is too verbose.
|
||||||
|
if default.value_string() == flag.value_string() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On boolean flags we can use the shorthand syntax instead of just specifying the flag name.
|
||||||
|
// This is slightly neater than the full syntax.
|
||||||
|
if flag.kind() == SettingKind::Bool && flag.value_string() == "true" {
|
||||||
|
write!(f, "{} ", flag.name)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{}={} ", flag.name, flag.value_string())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -179,9 +179,10 @@ impl TestCase {
|
|||||||
|
|
||||||
// TestCase is meant to be consumed by a runner, so we make the assumption here that we're
|
// TestCase is meant to be consumed by a runner, so we make the assumption here that we're
|
||||||
// generating a TargetIsa for the host.
|
// generating a TargetIsa for the host.
|
||||||
let builder =
|
let mut builder =
|
||||||
builder_with_options(true).expect("Unable to build a TargetIsa for the current host");
|
builder_with_options(true).expect("Unable to build a TargetIsa for the current host");
|
||||||
let flags = gen.generate_flags(builder.triple().architecture)?;
|
let flags = gen.generate_flags(builder.triple().architecture)?;
|
||||||
|
gen.set_isa_flags(&mut builder, IsaFlagGen::Host)?;
|
||||||
let isa = builder.finish(flags)?;
|
let isa = builder.finish(flags)?;
|
||||||
|
|
||||||
// When generating functions, we allow each function to call any function that has
|
// When generating functions, we allow each function to call any function that has
|
||||||
|
|||||||
@@ -48,13 +48,15 @@ impl FunctionWithIsa {
|
|||||||
// a supported one, so that the same fuzz input works across different build
|
// a supported one, so that the same fuzz input works across different build
|
||||||
// configurations.
|
// configurations.
|
||||||
let target = u.choose(isa::ALL_ARCHITECTURES)?;
|
let target = u.choose(isa::ALL_ARCHITECTURES)?;
|
||||||
let builder = isa::lookup_by_name(target).map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
let mut builder =
|
||||||
|
isa::lookup_by_name(target).map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
||||||
let architecture = builder.triple().architecture;
|
let architecture = builder.triple().architecture;
|
||||||
|
|
||||||
let mut gen = FuzzGen::new(u);
|
let mut gen = FuzzGen::new(u);
|
||||||
let flags = gen
|
let flags = gen
|
||||||
.generate_flags(architecture)
|
.generate_flags(architecture)
|
||||||
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
||||||
|
gen.set_isa_flags(&mut builder, IsaFlagGen::All)?;
|
||||||
let isa = builder
|
let isa = builder
|
||||||
.finish(flags)
|
.finish(flags)
|
||||||
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user