Path: blob/main/fuzz/fuzz_targets/cranelift-icache.rs
1690 views
#![no_main]12use cranelift_codegen::{3Context,4cursor::{Cursor, FuncCursor},5incremental_cache as icache,6ir::{7self, ExternalName, Function, LibCall, Signature, UserExternalName, UserFuncName,8immediates::Imm64,9},10isa,11};12use libfuzzer_sys::{13arbitrary::{self, Arbitrary, Unstructured},14fuzz_target,15};16use std::fmt;1718use cranelift_fuzzgen::*;1920/// TODO: This *almost* could be replaced with `LibCall::all()`, but21/// `LibCall::signature` panics for some libcalls, so we need to avoid that.22const ALLOWED_LIBCALLS: &'static [LibCall] = &[23LibCall::CeilF32,24LibCall::CeilF64,25LibCall::FloorF32,26LibCall::FloorF64,27LibCall::TruncF32,28LibCall::TruncF64,29LibCall::NearestF32,30LibCall::NearestF64,31LibCall::FmaF32,32LibCall::FmaF64,33];3435/// A generated function with an ISA that targets one of cranelift's backends.36pub struct FunctionWithIsa {37/// TargetIsa to use when compiling this test case38pub isa: isa::OwnedTargetIsa,3940/// Function under test41pub func: Function,42}4344impl FunctionWithIsa {45pub fn generate(u: &mut Unstructured) -> anyhow::Result<Self> {46let _ = env_logger::try_init();4748// We filter out targets that aren't supported in the current build49// configuration after randomly choosing one, instead of randomly choosing50// a supported one, so that the same fuzz input works across different build51// configurations.52let target = u.choose(isa::ALL_ARCHITECTURES)?;53let mut builder =54isa::lookup_by_name(target).map_err(|_| arbitrary::Error::IncorrectFormat)?;55let architecture = builder.triple().architecture;5657let mut generator = FuzzGen::new(u);58let flags = generator59.generate_flags(architecture)60.map_err(|_| arbitrary::Error::IncorrectFormat)?;61generator.set_isa_flags(&mut builder, IsaFlagGen::All)?;62let isa = builder63.finish(flags)64.map_err(|_| arbitrary::Error::IncorrectFormat)?;6566// Function name must be in a different namespace than TESTFILE_NAMESPACE (0)67let fname = UserFuncName::user(1, 0);6869// We don't actually generate these functions, we just simulate their signatures and names70let func_count = generator71.u72.int_in_range(generator.config.testcase_funcs.clone())?;73let usercalls = (0..func_count)74.map(|i| {75let name = UserExternalName::new(2, i as u32);76let sig = generator.generate_signature(&*isa)?;77Ok((name, sig))78})79.collect::<anyhow::Result<Vec<(UserExternalName, Signature)>>>()80.map_err(|_| arbitrary::Error::IncorrectFormat)?;8182let func = generator83.generate_func(fname, isa.clone(), usercalls, ALLOWED_LIBCALLS.to_vec())84.map_err(|_| arbitrary::Error::IncorrectFormat)?;8586Ok(FunctionWithIsa { isa, func })87}88}8990impl fmt::Debug for FunctionWithIsa {91fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {92// TODO: We could avoid the clone here.93let funcs = &[self.func.clone()];94PrintableTestCase::compile(&self.isa, funcs).fmt(f)95}96}9798impl<'a> Arbitrary<'a> for FunctionWithIsa {99fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {100Self::generate(u).map_err(|_| arbitrary::Error::IncorrectFormat)101}102}103104fuzz_target!(|func: FunctionWithIsa| {105let FunctionWithIsa { mut func, isa } = func;106107let cache_key_hash = icache::compute_cache_key(&*isa, &func);108109let mut context = Context::for_function(func.clone());110let prev_stencil = match context.compile_stencil(&*isa, &mut Default::default()) {111Ok(stencil) => stencil,112Err(_) => return,113};114115let (prev_stencil, serialized) = icache::serialize_compiled(prev_stencil);116let serialized = serialized.expect("serialization should work");117let prev_result = prev_stencil.apply_params(&func.params);118119let new_result = icache::try_finish_recompile(&func, &serialized)120.expect("recompilation should always work for identity");121122assert_eq!(new_result, prev_result, "MachCompileResult:s don't match");123124let new_info = new_result.code_info();125assert_eq!(new_info, prev_result.code_info(), "CodeInfo:s don't match");126127// If the func has at least one user-defined func ref, change it to match a128// different external function.129let expect_cache_hit = if let Some(user_ext_ref) =130func.stencil.dfg.ext_funcs.values().find_map(|data| {131if let ExternalName::User(user_ext_ref) = &data.name {132Some(user_ext_ref)133} else {134None135}136}) {137let mut prev = func.params.user_named_funcs()[*user_ext_ref].clone();138prev.index = prev.index.checked_add(1).unwrap_or_else(|| prev.index - 1);139func.params.reset_user_func_name(*user_ext_ref, prev);140true141} else {142// otherwise just randomly change one instruction in the middle and see what happens.143let mut changed = false;144let mut cursor = FuncCursor::new(&mut func);145'out: while let Some(_block) = cursor.next_block() {146while let Some(inst) = cursor.next_inst() {147// It's impractical to do any replacement at this point. Try to find any148// instruction that returns one int value, and replace it with an iconst.149if cursor.func.dfg.inst_results(inst).len() != 1 {150continue;151}152let out_ty = cursor153.func154.dfg155.value_type(cursor.func.dfg.first_result(inst));156match out_ty {157ir::types::I32 | ir::types::I64 => {}158_ => continue,159}160161if let ir::InstructionData::UnaryImm {162opcode: ir::Opcode::Iconst,163imm,164} = cursor.func.dfg.insts[inst]165{166let imm = imm.bits();167cursor.func.dfg.insts[inst] = ir::InstructionData::UnaryImm {168opcode: ir::Opcode::Iconst,169imm: Imm64::new(imm.checked_add(1).unwrap_or_else(|| imm - 1)),170};171} else {172cursor.func.dfg.insts[inst] = ir::InstructionData::UnaryImm {173opcode: ir::Opcode::Iconst,174imm: Imm64::new(42),175};176}177178changed = true;179break 'out;180}181}182183if !changed {184return;185}186187// We made it so that there shouldn't be a cache hit.188false189};190191let new_cache_key_hash = icache::compute_cache_key(&*isa, &func);192193if expect_cache_hit {194assert!(cache_key_hash == new_cache_key_hash);195} else {196assert!(cache_key_hash != new_cache_key_hash);197}198199context = Context::for_function(func.clone());200201let after_mutation_result = match context.compile(&*isa, &mut Default::default()) {202Ok(info) => info,203Err(_) => return,204};205206if expect_cache_hit {207let after_mutation_result_from_cache = icache::try_finish_recompile(&func, &serialized)208.expect("recompilation should always work for identity");209assert_eq!(*after_mutation_result, after_mutation_result_from_cache);210211let new_info = after_mutation_result_from_cache.code_info();212assert_eq!(213new_info,214after_mutation_result.code_info(),215"CodeInfo:s don't match"216);217}218});219220221