diff --git a/Cargo.lock b/Cargo.lock index a819a215db..8c6c4ef5dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3674,6 +3674,7 @@ dependencies = [ "cranelift-filetests", "cranelift-fuzzgen", "cranelift-interpreter", + "cranelift-native", "cranelift-reader", "cranelift-wasm", "libfuzzer-sys", diff --git a/cranelift/fuzzgen/src/lib.rs b/cranelift/fuzzgen/src/lib.rs index 1a4c912ec2..26cca8363d 100644 --- a/cranelift/fuzzgen/src/lib.rs +++ b/cranelift/fuzzgen/src/lib.rs @@ -7,190 +7,27 @@ use cranelift::codegen::data_value::DataValue; use cranelift::codegen::ir::{types::*, UserExternalName, UserFuncName}; use cranelift::codegen::ir::{Function, LibCall}; use cranelift::codegen::Context; -use cranelift::prelude::isa; use cranelift::prelude::*; use cranelift_arbitrary::CraneliftArbitrary; use cranelift_native::builder_with_options; -use std::fmt; use target_lexicon::{Architecture, Triple}; mod config; mod cranelift_arbitrary; mod function_generator; mod passes; +mod print; -/// These libcalls need a interpreter implementation in `cranelift-fuzzgen.rs` -const ALLOWED_LIBCALLS: &'static [LibCall] = &[ - LibCall::CeilF32, - LibCall::CeilF64, - LibCall::FloorF32, - LibCall::FloorF64, - LibCall::TruncF32, - LibCall::TruncF64, -]; +pub use print::PrintableTestCase; pub type TestCaseInput = Vec; -/// Print only non default flags. -fn write_non_default_flags(f: &mut fmt::Formatter<'_>, flags: &settings::Flags) -> fmt::Result { - let default_flags = settings::Flags::new(settings::builder()); - for (default, flag) in default_flags.iter().zip(flags.iter()) { - assert_eq!(default.name, flag.name); - - if default.value_string() != flag.value_string() { - writeln!(f, "set {}={}", flag.name, flag.value_string())?; - } - } - - Ok(()) -} - -/// A generated function with an ISA that targets one of cranelift's backends. -pub struct FunctionWithIsa { - /// TargetIsa to use when compiling this test case - pub isa: isa::OwnedTargetIsa, - - /// Function under test - pub func: Function, -} - -impl fmt::Debug for FunctionWithIsa { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, ";; Compile test case\n")?; - - write_non_default_flags(f, self.isa.flags())?; - - writeln!(f, "test compile")?; - writeln!(f, "target {}", self.isa.triple().architecture)?; - writeln!(f, "{}", self.func)?; - - Ok(()) - } -} - -impl<'a> Arbitrary<'a> for FunctionWithIsa { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - // We filter out targets that aren't supported in the current build - // configuration after randomly choosing one, instead of randomly choosing - // a supported one, so that the same fuzz input works across different build - // configurations. - let target = u.choose(isa::ALL_ARCHITECTURES)?; - let builder = isa::lookup_by_name(target).map_err(|_| arbitrary::Error::IncorrectFormat)?; - let architecture = builder.triple().architecture; - - let mut gen = FuzzGen::new(u); - let flags = gen - .generate_flags(architecture) - .map_err(|_| arbitrary::Error::IncorrectFormat)?; - let isa = builder - .finish(flags) - .map_err(|_| arbitrary::Error::IncorrectFormat)?; - - // Function name must be in a different namespace than TESTFILE_NAMESPACE (0) - let fname = UserFuncName::user(1, 0); - - // We don't actually generate these functions, we just simulate their signatures and names - let func_count = gen.u.int_in_range(gen.config.testcase_funcs.clone())?; - let usercalls = (0..func_count) - .map(|i| { - let name = UserExternalName::new(2, i as u32); - let sig = gen.generate_signature(architecture)?; - Ok((name, sig)) - }) - .collect::>>() - .map_err(|_| arbitrary::Error::IncorrectFormat)?; - - let func = gen - .generate_func(fname, isa.triple().clone(), usercalls) - .map_err(|_| arbitrary::Error::IncorrectFormat)?; - - Ok(FunctionWithIsa { isa, func }) - } -} - -pub struct TestCase { - /// TargetIsa to use when compiling this test case - pub isa: isa::OwnedTargetIsa, - /// Functions under test - /// By convention the first function is the main function. - pub functions: Vec, - /// Generate multiple test inputs for each test case. - /// This allows us to get more coverage per compilation, which may be somewhat expensive. - pub inputs: Vec, -} - -impl fmt::Debug for TestCase { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, ";; Fuzzgen test case\n")?; - writeln!(f, "test interpret")?; - writeln!(f, "test run")?; - - write_non_default_flags(f, self.isa.flags())?; - - writeln!(f, "target {}\n", self.isa.triple().architecture)?; - - // Print the functions backwards, so that the main function is printed last - // and near the test inputs. - for func in self.functions.iter().rev() { - writeln!(f, "{}\n", func)?; - } - - writeln!(f, "; Note: the results in the below test cases are simply a placeholder and probably will be wrong\n")?; - - for input in self.inputs.iter() { - // TODO: We don't know the expected outputs, maybe we can run the interpreter - // here to figure them out? Should work, however we need to be careful to catch - // panics in case its the interpreter that is failing. - // For now create a placeholder output consisting of the zero value for the type - let returns = &self.main().signature.returns; - let placeholder_output = returns - .iter() - .map(|param| DataValue::read_from_slice_ne(&[0; 16][..], param.value_type)) - .map(|val| format!("{}", val)) - .collect::>() - .join(", "); - - // If we have no output, we don't need the == condition - let test_condition = match returns.len() { - 0 => String::new(), - 1 => format!(" == {}", placeholder_output), - _ => format!(" == [{}]", placeholder_output), - }; - - let args = input - .iter() - .map(|val| format!("{}", val)) - .collect::>() - .join(", "); - - writeln!(f, "; run: {}({}){}", self.main().name, args, test_condition)?; - } - - Ok(()) - } -} - -impl<'a> Arbitrary<'a> for TestCase { - fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - FuzzGen::new(u) - .generate_host_test() - .map_err(|_| arbitrary::Error::IncorrectFormat) - } -} - -impl TestCase { - /// Returns the main function of this test case. - pub fn main(&self) -> &Function { - &self.functions[0] - } -} - pub struct FuzzGen<'r, 'data> where 'data: 'r, { - u: &'r mut Unstructured<'data>, - config: Config, + pub u: &'r mut Unstructured<'data>, + pub config: Config, } impl<'r, 'data> FuzzGen<'r, 'data> @@ -204,13 +41,13 @@ where } } - fn generate_signature(&mut self, architecture: Architecture) -> Result { + pub fn generate_signature(&mut self, architecture: Architecture) -> Result { let max_params = self.u.int_in_range(self.config.signature_params.clone())?; let max_rets = self.u.int_in_range(self.config.signature_rets.clone())?; Ok(self.u.signature(architecture, max_params, max_rets)?) } - fn generate_test_inputs(mut self, signature: &Signature) -> Result> { + pub fn generate_test_inputs(mut self, signature: &Signature) -> Result> { let mut inputs = Vec::new(); // Generate up to "max_test_case_inputs" inputs, we need an upper bound here since @@ -288,11 +125,12 @@ where Ok(ctx.func) } - fn generate_func( + pub fn generate_func( &mut self, name: UserFuncName, target_triple: Triple, usercalls: Vec<(UserExternalName, Signature)>, + libcalls: Vec, ) -> Result { let sig = self.generate_signature(target_triple.architecture)?; @@ -303,7 +141,7 @@ where name, sig, usercalls, - ALLOWED_LIBCALLS.to_vec(), + libcalls, ) .generate()?; @@ -312,7 +150,7 @@ where /// Generate a random set of cranelift flags. /// Only semantics preserving flags are considered - fn generate_flags(&mut self, target_arch: Architecture) -> Result { + pub fn generate_flags(&mut self, target_arch: Architecture) -> Result { let mut builder = settings::builder(); let opt = self.u.choose(OptLevel::all())?; @@ -390,48 +228,4 @@ where Ok(Flags::new(builder)) } - - pub fn generate_host_test(mut self) -> Result { - // TestCase is meant to be consumed by a runner, so we make the assumption here that we're - // generating a TargetIsa for the host. - let builder = - builder_with_options(true).expect("Unable to build a TargetIsa for the current host"); - let flags = self.generate_flags(builder.triple().architecture)?; - let isa = builder.finish(flags)?; - - // When generating functions, we allow each function to call any function that has - // already been generated. This guarantees that we never have loops in the call graph. - // We generate these backwards, and then reverse them so that the main function is at - // the start. - let func_count = self.u.int_in_range(self.config.testcase_funcs.clone())?; - let mut functions: Vec = Vec::with_capacity(func_count); - for i in (0..func_count).rev() { - // Function name must be in a different namespace than TESTFILE_NAMESPACE (0) - let fname = UserFuncName::user(1, i as u32); - - let usercalls: Vec<(UserExternalName, Signature)> = functions - .iter() - .map(|f| { - ( - f.name.get_user().unwrap().clone(), - f.stencil.signature.clone(), - ) - }) - .collect(); - - let func = self.generate_func(fname, isa.triple().clone(), usercalls)?; - functions.push(func); - } - // Now reverse the functions so that the main function is at the start. - functions.reverse(); - - let main = &functions[0]; - let inputs = self.generate_test_inputs(&main.signature)?; - - Ok(TestCase { - isa, - functions, - inputs, - }) - } } diff --git a/cranelift/fuzzgen/src/print.rs b/cranelift/fuzzgen/src/print.rs new file mode 100644 index 0000000000..e74f0f3a84 --- /dev/null +++ b/cranelift/fuzzgen/src/print.rs @@ -0,0 +1,130 @@ +use cranelift::codegen::data_value::DataValue; +use cranelift::codegen::ir::Function; +use cranelift::prelude::settings; +use cranelift::prelude::*; +use std::fmt; + +use crate::TestCaseInput; + +#[derive(Debug)] +enum TestCaseKind { + Compile, + Run, +} + +/// Provides a way to format a `TestCase` in the .clif format. +pub struct PrintableTestCase<'a> { + kind: TestCaseKind, + isa: &'a isa::OwnedTargetIsa, + functions: &'a [Function], + // Only applicable for run test cases + inputs: &'a [TestCaseInput], +} + +impl<'a> PrintableTestCase<'a> { + /// Emits a `test compile` test case. + pub fn compile(isa: &'a isa::OwnedTargetIsa, functions: &'a [Function]) -> Self { + Self { + kind: TestCaseKind::Compile, + isa, + functions, + inputs: &[], + } + } + + /// Emits a `test run` test case. These also include a `test interpret`. + /// + /// By convention the first function in `functions` will be considered the main function. + pub fn run( + isa: &'a isa::OwnedTargetIsa, + functions: &'a [Function], + inputs: &'a [TestCaseInput], + ) -> Self { + Self { + kind: TestCaseKind::Run, + isa, + functions, + inputs, + } + } + + /// Returns the main function of this test case. + pub fn main(&self) -> &Function { + &self.functions[0] + } +} + +impl<'a> fmt::Debug for PrintableTestCase<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.kind { + TestCaseKind::Compile => { + writeln!(f, ";; Compile test case\n")?; + writeln!(f, "test compile")?; + } + TestCaseKind::Run => { + writeln!(f, ";; Run test case\n")?; + writeln!(f, "test interpret")?; + writeln!(f, "test run")?; + } + }; + + write_non_default_flags(f, self.isa.flags())?; + + writeln!(f, "target {}\n", self.isa.triple().architecture)?; + + // Print the functions backwards, so that the main function is printed last + // and near the test inputs for run test cases. + for func in self.functions.iter().rev() { + writeln!(f, "{}\n", func)?; + } + + if !self.inputs.is_empty() { + writeln!(f, "; Note: the results in the below test cases are simply a placeholder and probably will be wrong\n")?; + } + + for input in self.inputs.iter() { + // TODO: We don't know the expected outputs, maybe we can run the interpreter + // here to figure them out? Should work, however we need to be careful to catch + // panics in case its the interpreter that is failing. + // For now create a placeholder output consisting of the zero value for the type + let returns = &self.main().signature.returns; + let placeholder_output = returns + .iter() + .map(|param| DataValue::read_from_slice_ne(&[0; 16][..], param.value_type)) + .map(|val| format!("{}", val)) + .collect::>() + .join(", "); + + // If we have no output, we don't need the == condition + let test_condition = match returns.len() { + 0 => String::new(), + 1 => format!(" == {}", placeholder_output), + _ => format!(" == [{}]", placeholder_output), + }; + + let args = input + .iter() + .map(|val| format!("{}", val)) + .collect::>() + .join(", "); + + writeln!(f, "; run: {}({}){}", self.main().name, args, test_condition)?; + } + + Ok(()) + } +} + +/// Print only non default flags. +fn write_non_default_flags(f: &mut fmt::Formatter<'_>, flags: &settings::Flags) -> fmt::Result { + let default_flags = settings::Flags::new(settings::builder()); + for (default, flag) in default_flags.iter().zip(flags.iter()) { + assert_eq!(default.name, flag.name); + + if default.value_string() != flag.value_string() { + writeln!(f, "set {}={}", flag.name, flag.value_string())?; + } + } + + Ok(()) +} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 11b6ff3d02..f9774cac38 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -16,6 +16,7 @@ cranelift-wasm = { workspace = true } cranelift-filetests = { workspace = true } cranelift-interpreter = { workspace = true } cranelift-fuzzgen = { workspace = true } +cranelift-native = { workspace = true } libfuzzer-sys = { version = "0.4.0", features = ["arbitrary-derive"] } target-lexicon = { workspace = true } smallvec = { workspace = true } diff --git a/fuzz/fuzz_targets/cranelift-fuzzgen.rs b/fuzz/fuzz_targets/cranelift-fuzzgen.rs index 171a8248ef..94f3212f34 100644 --- a/fuzz/fuzz_targets/cranelift-fuzzgen.rs +++ b/fuzz/fuzz_targets/cranelift-fuzzgen.rs @@ -1,13 +1,22 @@ #![no_main] +use cranelift_codegen::ir::Function; +use cranelift_codegen::ir::Signature; +use cranelift_codegen::ir::UserExternalName; +use cranelift_codegen::ir::UserFuncName; +use libfuzzer_sys::arbitrary; +use libfuzzer_sys::arbitrary::Arbitrary; +use libfuzzer_sys::arbitrary::Unstructured; use libfuzzer_sys::fuzz_target; use once_cell::sync::Lazy; use std::collections::HashMap; +use std::fmt; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; use cranelift_codegen::data_value::DataValue; use cranelift_codegen::ir::{LibCall, TrapCode}; +use cranelift_codegen::isa; use cranelift_filetests::function_runner::{TestFileCompiler, Trampoline}; use cranelift_fuzzgen::*; use cranelift_interpreter::environment::FuncIndex; @@ -17,6 +26,7 @@ use cranelift_interpreter::interpreter::{ }; use cranelift_interpreter::step::ControlFlow; use cranelift_interpreter::step::CraneliftTrap; +use cranelift_native::builder_with_options; use smallvec::smallvec; const INTERPRETER_FUEL: u64 = 4096; @@ -120,6 +130,87 @@ impl PartialEq for RunResult { } } +pub struct TestCase { + /// TargetIsa to use when compiling this test case + pub isa: isa::OwnedTargetIsa, + /// Functions under test + /// By convention the first function is the main function. + pub functions: Vec, + /// Generate multiple test inputs for each test case. + /// This allows us to get more coverage per compilation, which may be somewhat expensive. + pub inputs: Vec, +} + +impl fmt::Debug for TestCase { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + PrintableTestCase::run(&self.isa, &self.functions, &self.inputs).fmt(f) + } +} + +impl<'a> Arbitrary<'a> for TestCase { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + Self::generate(u).map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + +impl TestCase { + pub fn generate(u: &mut Unstructured) -> anyhow::Result { + let mut gen = FuzzGen::new(u); + + // TestCase is meant to be consumed by a runner, so we make the assumption here that we're + // generating a TargetIsa for the host. + let builder = + builder_with_options(true).expect("Unable to build a TargetIsa for the current host"); + let flags = gen.generate_flags(builder.triple().architecture)?; + let isa = builder.finish(flags)?; + + // When generating functions, we allow each function to call any function that has + // already been generated. This guarantees that we never have loops in the call graph. + // We generate these backwards, and then reverse them so that the main function is at + // the start. + let func_count = gen.u.int_in_range(gen.config.testcase_funcs.clone())?; + let mut functions: Vec = Vec::with_capacity(func_count); + for i in (0..func_count).rev() { + // Function name must be in a different namespace than TESTFILE_NAMESPACE (0) + let fname = UserFuncName::user(1, i as u32); + + let usercalls: Vec<(UserExternalName, Signature)> = functions + .iter() + .map(|f| { + ( + f.name.get_user().unwrap().clone(), + f.stencil.signature.clone(), + ) + }) + .collect(); + + let func = gen.generate_func( + fname, + isa.triple().clone(), + usercalls, + ALLOWED_LIBCALLS.to_vec(), + )?; + functions.push(func); + } + // Now reverse the functions so that the main function is at the start. + functions.reverse(); + + let main = &functions[0]; + let inputs = gen.generate_test_inputs(&main.signature)?; + + Ok(TestCase { + isa, + functions, + inputs, + }) + } + + /// Returns the main function of this test case. + pub fn main(&self) -> &Function { + &self.functions[0] + } +} + fn run_in_interpreter(interpreter: &mut Interpreter, args: &[DataValue]) -> RunResult { // The entrypoint function is always 0 let index = FuncIndex::from_u32(0); @@ -139,6 +230,16 @@ fn run_in_host(trampoline: &Trampoline, args: &[DataValue]) -> RunResult { RunResult::Success(res) } +/// These libcalls need a interpreter implementation in `build_interpreter` +const ALLOWED_LIBCALLS: &'static [LibCall] = &[ + LibCall::CeilF32, + LibCall::CeilF64, + LibCall::FloorF32, + LibCall::FloorF64, + LibCall::TruncF32, + LibCall::TruncF64, +]; + fn build_interpreter(testcase: &TestCase) -> Interpreter { let mut env = FunctionStore::default(); for func in testcase.functions.iter() { diff --git a/fuzz/fuzz_targets/cranelift-icache.rs b/fuzz/fuzz_targets/cranelift-icache.rs index 6023d00411..4c0ca54553 100644 --- a/fuzz/fuzz_targets/cranelift-icache.rs +++ b/fuzz/fuzz_targets/cranelift-icache.rs @@ -3,13 +3,103 @@ use cranelift_codegen::{ cursor::{Cursor, FuncCursor}, incremental_cache as icache, - ir::{self, immediates::Imm64, ExternalName}, - Context, + ir::{ + self, immediates::Imm64, ExternalName, Function, LibCall, Signature, UserExternalName, + UserFuncName, + }, + isa, Context, }; -use libfuzzer_sys::fuzz_target; +use libfuzzer_sys::{ + arbitrary::{self, Arbitrary, Unstructured}, + fuzz_target, +}; +use std::fmt; use cranelift_fuzzgen::*; +/// TODO: This *almost* could be replaced with `LibCall::all()`, but +/// `LibCall::signature` panics for some libcalls, so we need to avoid that. +const ALLOWED_LIBCALLS: &'static [LibCall] = &[ + LibCall::CeilF32, + LibCall::CeilF64, + LibCall::FloorF32, + LibCall::FloorF64, + LibCall::TruncF32, + LibCall::TruncF64, + LibCall::NearestF32, + LibCall::NearestF64, + LibCall::FmaF32, + LibCall::FmaF64, +]; + +/// A generated function with an ISA that targets one of cranelift's backends. +pub struct FunctionWithIsa { + /// TargetIsa to use when compiling this test case + pub isa: isa::OwnedTargetIsa, + + /// Function under test + pub func: Function, +} + +impl FunctionWithIsa { + pub fn generate(u: &mut Unstructured) -> anyhow::Result { + // We filter out targets that aren't supported in the current build + // configuration after randomly choosing one, instead of randomly choosing + // a supported one, so that the same fuzz input works across different build + // configurations. + let target = u.choose(isa::ALL_ARCHITECTURES)?; + let builder = isa::lookup_by_name(target).map_err(|_| arbitrary::Error::IncorrectFormat)?; + let architecture = builder.triple().architecture; + + let mut gen = FuzzGen::new(u); + let flags = gen + .generate_flags(architecture) + .map_err(|_| arbitrary::Error::IncorrectFormat)?; + let isa = builder + .finish(flags) + .map_err(|_| arbitrary::Error::IncorrectFormat)?; + + // Function name must be in a different namespace than TESTFILE_NAMESPACE (0) + let fname = UserFuncName::user(1, 0); + + // We don't actually generate these functions, we just simulate their signatures and names + let func_count = gen.u.int_in_range(gen.config.testcase_funcs.clone())?; + let usercalls = (0..func_count) + .map(|i| { + let name = UserExternalName::new(2, i as u32); + let sig = gen.generate_signature(architecture)?; + Ok((name, sig)) + }) + .collect::>>() + .map_err(|_| arbitrary::Error::IncorrectFormat)?; + + let func = gen + .generate_func( + fname, + isa.triple().clone(), + usercalls, + ALLOWED_LIBCALLS.to_vec(), + ) + .map_err(|_| arbitrary::Error::IncorrectFormat)?; + + Ok(FunctionWithIsa { isa, func }) + } +} + +impl fmt::Debug for FunctionWithIsa { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: We could avoid the clone here. + let funcs = &[self.func.clone()]; + PrintableTestCase::compile(&self.isa, funcs).fmt(f) + } +} + +impl<'a> Arbitrary<'a> for FunctionWithIsa { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + Self::generate(u).map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + fuzz_target!(|func: FunctionWithIsa| { let FunctionWithIsa { mut func, isa } = func;