Files
wasmtime/fuzz/fuzz_targets/cranelift-icache.rs
Afonso Bordado e96214968c fuzzgen: Move Arbitrary structs into the fuzzers (#5820)
* fuzzgen: Move `FunctionWithIsa` to icache fuzzer

* fuzzgen: Move `Testcase` to fuzzgen fuzzer

* fuzzgen: Move allowed libcalls to fuzzers

* fuzzgen: Centralize printing of testcases
2023-03-04 19:17:28 +00:00

218 lines
7.5 KiB
Rust

#![no_main]
use cranelift_codegen::{
cursor::{Cursor, FuncCursor},
incremental_cache as icache,
ir::{
self, immediates::Imm64, ExternalName, Function, LibCall, Signature, UserExternalName,
UserFuncName,
},
isa, Context,
};
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<Self> {
// 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::<anyhow::Result<Vec<(UserExternalName, Signature)>>>()
.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> {
Self::generate(u).map_err(|_| arbitrary::Error::IncorrectFormat)
}
}
fuzz_target!(|func: FunctionWithIsa| {
let FunctionWithIsa { mut func, isa } = func;
let cache_key_hash = icache::compute_cache_key(&*isa, &mut func);
let mut context = Context::for_function(func.clone());
let prev_stencil = match context.compile_stencil(&*isa) {
Ok(stencil) => stencil,
Err(_) => return,
};
let (prev_stencil, serialized) = icache::serialize_compiled(prev_stencil);
let serialized = serialized.expect("serialization should work");
let prev_result = prev_stencil.apply_params(&func.params);
let new_result = icache::try_finish_recompile(&func, &serialized)
.expect("recompilation should always work for identity");
assert_eq!(new_result, prev_result, "MachCompileResult:s don't match");
let new_info = new_result.code_info();
assert_eq!(new_info, prev_result.code_info(), "CodeInfo:s don't match");
// If the func has at least one user-defined func ref, change it to match a
// different external function.
let expect_cache_hit = if let Some(user_ext_ref) =
func.stencil.dfg.ext_funcs.values().find_map(|data| {
if let ExternalName::User(user_ext_ref) = &data.name {
Some(user_ext_ref)
} else {
None
}
}) {
let mut prev = func.params.user_named_funcs()[*user_ext_ref].clone();
prev.index = prev.index.checked_add(1).unwrap_or_else(|| prev.index - 1);
func.params.reset_user_func_name(*user_ext_ref, prev);
true
} else {
// otherwise just randomly change one instruction in the middle and see what happens.
let mut changed = false;
let mut cursor = FuncCursor::new(&mut func);
'out: while let Some(_block) = cursor.next_block() {
while let Some(inst) = cursor.next_inst() {
// It's impractical to do any replacement at this point. Try to find any
// instruction that returns one int value, and replace it with an iconst.
if cursor.func.dfg.inst_results(inst).len() != 1 {
continue;
}
let out_ty = cursor
.func
.dfg
.value_type(cursor.func.dfg.first_result(inst));
match out_ty {
ir::types::I32 | ir::types::I64 => {}
_ => continue,
}
if let ir::InstructionData::UnaryImm {
opcode: ir::Opcode::Iconst,
imm,
} = cursor.func.dfg.insts[inst]
{
let imm = imm.bits();
cursor.func.dfg.insts[inst] = ir::InstructionData::UnaryImm {
opcode: ir::Opcode::Iconst,
imm: Imm64::new(imm.checked_add(1).unwrap_or_else(|| imm - 1)),
};
} else {
cursor.func.dfg.insts[inst] = ir::InstructionData::UnaryImm {
opcode: ir::Opcode::Iconst,
imm: Imm64::new(42),
};
}
changed = true;
break 'out;
}
}
if !changed {
return;
}
// We made it so that there shouldn't be a cache hit.
false
};
let new_cache_key_hash = icache::compute_cache_key(&*isa, &mut func);
if expect_cache_hit {
assert!(cache_key_hash == new_cache_key_hash);
} else {
assert!(cache_key_hash != new_cache_key_hash);
}
context = Context::for_function(func.clone());
let after_mutation_result = match context.compile(&*isa) {
Ok(info) => info,
Err(_) => return,
};
if expect_cache_hit {
let after_mutation_result_from_cache = icache::try_finish_recompile(&func, &serialized)
.expect("recompilation should always work for identity");
assert_eq!(*after_mutation_result, after_mutation_result_from_cache);
let new_info = after_mutation_result_from_cache.code_info();
assert_eq!(
new_info,
after_mutation_result.code_info(),
"CodeInfo:s don't match"
);
}
});