diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 3705e031e644..eb6f929dccf4 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -9,7 +9,7 @@ publish = false cargo-fuzz = true [dependencies] -cranelift-codegen = { path = "../cranelift/codegen" } +cranelift-codegen = { path = "../cranelift/codegen", features = ["incremental-cache"] } cranelift-reader = { path = "../cranelift/reader" } cranelift-wasm = { path = "../cranelift/wasm" } cranelift-filetests = { path = "../cranelift/filetests" } diff --git a/fuzz/fuzz_targets/cranelift-icache.rs b/fuzz/fuzz_targets/cranelift-icache.rs index 95d6b80003a8..3debeff58199 100644 --- a/fuzz/fuzz_targets/cranelift-icache.rs +++ b/fuzz/fuzz_targets/cranelift-icache.rs @@ -1,13 +1,18 @@ #![no_main] -use cranelift_codegen::{incremental_cache as icache, isa, settings, Context}; +use cranelift_codegen::{ + cursor::{Cursor, FuncCursor}, + incremental_cache as icache, + ir::{self, immediates::Imm64, ExternalName}, + isa, settings, Context, +}; use libfuzzer_sys::fuzz_target; use cranelift_fuzzgen::*; use target_lexicon::Triple; fuzz_target!(|func: SingleFunction| { - let func = func.0; + let mut func = func.0; let flags = settings::Flags::new(settings::builder()); @@ -29,30 +34,107 @@ fuzz_target!(|func: SingleFunction| { Ok(info) => info, Err(_) => return, }; + let prev_result = context.mach_compile_result.unwrap(); - let serialized = icache::serialize_compiled( - cache_key.clone(), - &func, - context.mach_compile_result.as_ref().unwrap(), - ) - .expect("serialization failure"); - - let mut prev_assembly = vec![0; prev_info.total_size as usize]; - unsafe { - context.emit_to_memory(prev_assembly.as_mut_ptr()); - } + let serialized = icache::serialize_compiled(cache_key.clone(), &func, &prev_result) + .expect("serialization failure"); let new_result = icache::try_finish_recompile(&cache_key, &func, &serialized) .expect("recompilation should always work for identity"); - let new_info = new_result.code_info(); + assert_eq!(new_result, prev_result, "MachCompileResult:s don't match"); + + let new_info = new_result.code_info(); assert_eq!(new_info, prev_info, "CodeInfo:s don't match"); - context.mach_compile_result = Some(new_result); - let mut new_assembly = vec![0; new_info.total_size as usize]; - unsafe { - context.emit_to_memory(new_assembly.as_mut_ptr()); + // 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((func_ref, namespace, index)) = + func.dfg.ext_funcs.iter().find_map(|(func_ref, data)| { + if let ExternalName::User { namespace, index } = &data.name { + Some((func_ref, *namespace, *index)) + } else { + None + } + }) { + func.dfg.ext_funcs[func_ref].name = ExternalName::User { + namespace: namespace.checked_add(1).unwrap_or(namespace - 1), + index: index.checked_add(1).unwrap_or(index - 1), + }; + 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[inst] + { + let imm = imm.bits(); + cursor.func.dfg[inst] = ir::InstructionData::UnaryImm { + opcode: ir::Opcode::Iconst, + imm: Imm64::new(imm.checked_add(1).unwrap_or(imm - 1)), + }; + } else { + cursor.func.dfg[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, _cache_key_hash) = icache::compute_cache_key(&*isa, &func); + + if expect_cache_hit { + assert!(cache_key == new_cache_key); + } else { + assert!(cache_key != new_cache_key); } - assert_eq!(prev_assembly, new_assembly, "assembly buffers don't match"); + context = Context::for_function(func.clone()); + + let after_mutation_info = match context.compile(&*isa) { + Ok(info) => info, + Err(_) => return, + }; + let after_mutation_result = context.mach_compile_result.unwrap(); + + if expect_cache_hit { + let after_mutation_result_from_cache = + icache::try_finish_recompile(&new_cache_key, &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_info, "CodeInfo:s don't match"); + } });