1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
#![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 mut 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)?;
gen.set_isa_flags(&mut builder, IsaFlagGen::All)?;
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(&*isa)?;
Ok((name, sig))
})
.collect::<anyhow::Result<Vec<(UserExternalName, Signature)>>>()
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
let func = gen
.generate_func(fname, isa.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, &func);
let mut context = Context::for_function(func.clone());
let prev_stencil = match context.compile_stencil(&*isa, &mut Default::default()) {
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, &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, &mut Default::default()) {
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"
);
}
});
|