/src/wasmtime/fuzz/fuzz_targets/cranelift-icache.rs
Line | Count | Source |
1 | | #![no_main] |
2 | | |
3 | | use cranelift_codegen::{ |
4 | | Context, |
5 | | cursor::{Cursor, FuncCursor}, |
6 | | incremental_cache as icache, |
7 | | ir::{ |
8 | | self, ExternalName, Function, LibCall, Signature, UserExternalName, UserFuncName, |
9 | | immediates::Imm64, |
10 | | }, |
11 | | isa, |
12 | | }; |
13 | | use libfuzzer_sys::{ |
14 | | arbitrary::{self, Arbitrary, Unstructured}, |
15 | | fuzz_target, |
16 | | }; |
17 | | use std::fmt; |
18 | | |
19 | | use cranelift_fuzzgen::*; |
20 | | |
21 | | /// TODO: This *almost* could be replaced with `LibCall::all()`, but |
22 | | /// `LibCall::signature` panics for some libcalls, so we need to avoid that. |
23 | | const ALLOWED_LIBCALLS: &'static [LibCall] = &[ |
24 | | LibCall::CeilF32, |
25 | | LibCall::CeilF64, |
26 | | LibCall::FloorF32, |
27 | | LibCall::FloorF64, |
28 | | LibCall::TruncF32, |
29 | | LibCall::TruncF64, |
30 | | LibCall::NearestF32, |
31 | | LibCall::NearestF64, |
32 | | LibCall::FmaF32, |
33 | | LibCall::FmaF64, |
34 | | ]; |
35 | | |
36 | | /// A generated function with an ISA that targets one of cranelift's backends. |
37 | | pub struct FunctionWithIsa { |
38 | | /// TargetIsa to use when compiling this test case |
39 | | pub isa: isa::OwnedTargetIsa, |
40 | | |
41 | | /// Function under test |
42 | | pub func: Function, |
43 | | } |
44 | | |
45 | | impl FunctionWithIsa { |
46 | 8.55k | pub fn generate(u: &mut Unstructured) -> anyhow::Result<Self> { |
47 | 8.55k | let _ = env_logger::try_init(); |
48 | | |
49 | | // We filter out targets that aren't supported in the current build |
50 | | // configuration after randomly choosing one, instead of randomly choosing |
51 | | // a supported one, so that the same fuzz input works across different build |
52 | | // configurations. |
53 | 8.55k | let target = u.choose(isa::ALL_ARCHITECTURES)?; |
54 | 8.55k | let mut builder = |
55 | 8.55k | isa::lookup_by_name(target).map_err(|_| arbitrary::Error::IncorrectFormat)?; |
56 | 8.55k | let architecture = builder.triple().architecture; |
57 | | |
58 | 8.55k | let mut generator = FuzzGen::new(u); |
59 | 8.55k | let flags = generator |
60 | 8.55k | .generate_flags(architecture) |
61 | 8.55k | .map_err(|_| arbitrary::Error::IncorrectFormat)?; |
62 | 8.55k | generator.set_isa_flags(&mut builder, IsaFlagGen::All)?; |
63 | 8.55k | let isa = builder |
64 | 8.55k | .finish(flags) |
65 | 8.55k | .map_err(|_| arbitrary::Error::IncorrectFormat)?; |
66 | | |
67 | | // Function name must be in a different namespace than TESTFILE_NAMESPACE (0) |
68 | 8.55k | let fname = UserFuncName::user(1, 0); |
69 | | |
70 | | // We don't actually generate these functions, we just simulate their signatures and names |
71 | 8.55k | let func_count = generator |
72 | 8.55k | .u |
73 | 8.55k | .int_in_range(generator.config.testcase_funcs.clone())?; |
74 | 8.55k | let usercalls = (0..func_count) |
75 | 17.2k | .map(|i| { |
76 | 17.2k | let name = UserExternalName::new(2, i as u32); |
77 | 17.2k | let sig = generator.generate_signature(&*isa)?; |
78 | 17.2k | Ok((name, sig)) |
79 | 17.2k | }) |
80 | 8.55k | .collect::<anyhow::Result<Vec<(UserExternalName, Signature)>>>() |
81 | 8.55k | .map_err(|_| arbitrary::Error::IncorrectFormat)?; |
82 | | |
83 | 8.55k | let func = generator |
84 | 8.55k | .generate_func(fname, isa.clone(), usercalls, ALLOWED_LIBCALLS.to_vec()) |
85 | 8.55k | .map_err(|_| arbitrary::Error::IncorrectFormat)?; |
86 | | |
87 | 7.73k | Ok(FunctionWithIsa { isa, func }) |
88 | 8.55k | } |
89 | | } |
90 | | |
91 | | impl fmt::Debug for FunctionWithIsa { |
92 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
93 | | // TODO: We could avoid the clone here. |
94 | 0 | let funcs = &[self.func.clone()]; |
95 | 0 | PrintableTestCase::compile(&self.isa, funcs).fmt(f) |
96 | 0 | } |
97 | | } |
98 | | |
99 | | impl<'a> Arbitrary<'a> for FunctionWithIsa { |
100 | 8.55k | fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { |
101 | 8.55k | Self::generate(u).map_err(|_| arbitrary::Error::IncorrectFormat) |
102 | 8.55k | } |
103 | | } |
104 | | |
105 | | fuzz_target!(|func: FunctionWithIsa| { |
106 | | let FunctionWithIsa { mut func, isa } = func; |
107 | | |
108 | | let cache_key_hash = icache::compute_cache_key(&*isa, &func); |
109 | | |
110 | | let mut context = Context::for_function(func.clone()); |
111 | | let prev_stencil = match context.compile_stencil(&*isa, &mut Default::default()) { |
112 | | Ok(stencil) => stencil, |
113 | | Err(_) => return, |
114 | | }; |
115 | | |
116 | | let (prev_stencil, serialized) = icache::serialize_compiled(prev_stencil); |
117 | | let serialized = serialized.expect("serialization should work"); |
118 | | let prev_result = prev_stencil.apply_params(&func.params); |
119 | | |
120 | | let new_result = icache::try_finish_recompile(&func, &serialized) |
121 | | .expect("recompilation should always work for identity"); |
122 | | |
123 | | assert_eq!(new_result, prev_result, "MachCompileResult:s don't match"); |
124 | | |
125 | | let new_info = new_result.code_info(); |
126 | | assert_eq!(new_info, prev_result.code_info(), "CodeInfo:s don't match"); |
127 | | |
128 | | // If the func has at least one user-defined func ref, change it to match a |
129 | | // different external function. |
130 | | let expect_cache_hit = if let Some(user_ext_ref) = |
131 | 7.73k | func.stencil.dfg.ext_funcs.values().find_map(|data| { |
132 | 7.73k | if let ExternalName::User(user_ext_ref) = &data.name { |
133 | 7.73k | Some(user_ext_ref) |
134 | | } else { |
135 | 0 | None |
136 | | } |
137 | 7.73k | }) { |
138 | | let mut prev = func.params.user_named_funcs()[*user_ext_ref].clone(); |
139 | 0 | prev.index = prev.index.checked_add(1).unwrap_or_else(|| prev.index - 1); |
140 | | func.params.reset_user_func_name(*user_ext_ref, prev); |
141 | | true |
142 | | } else { |
143 | | // otherwise just randomly change one instruction in the middle and see what happens. |
144 | | let mut changed = false; |
145 | | let mut cursor = FuncCursor::new(&mut func); |
146 | | 'out: while let Some(_block) = cursor.next_block() { |
147 | | while let Some(inst) = cursor.next_inst() { |
148 | | // It's impractical to do any replacement at this point. Try to find any |
149 | | // instruction that returns one int value, and replace it with an iconst. |
150 | | if cursor.func.dfg.inst_results(inst).len() != 1 { |
151 | | continue; |
152 | | } |
153 | | let out_ty = cursor |
154 | | .func |
155 | | .dfg |
156 | | .value_type(cursor.func.dfg.first_result(inst)); |
157 | | match out_ty { |
158 | | ir::types::I32 | ir::types::I64 => {} |
159 | | _ => continue, |
160 | | } |
161 | | |
162 | | if let ir::InstructionData::UnaryImm { |
163 | | opcode: ir::Opcode::Iconst, |
164 | | imm, |
165 | | } = cursor.func.dfg.insts[inst] |
166 | | { |
167 | | let imm = imm.bits(); |
168 | | cursor.func.dfg.insts[inst] = ir::InstructionData::UnaryImm { |
169 | | opcode: ir::Opcode::Iconst, |
170 | 0 | imm: Imm64::new(imm.checked_add(1).unwrap_or_else(|| imm - 1)), |
171 | | }; |
172 | | } else { |
173 | | cursor.func.dfg.insts[inst] = ir::InstructionData::UnaryImm { |
174 | | opcode: ir::Opcode::Iconst, |
175 | | imm: Imm64::new(42), |
176 | | }; |
177 | | } |
178 | | |
179 | | changed = true; |
180 | | break 'out; |
181 | | } |
182 | | } |
183 | | |
184 | | if !changed { |
185 | | return; |
186 | | } |
187 | | |
188 | | // We made it so that there shouldn't be a cache hit. |
189 | | false |
190 | | }; |
191 | | |
192 | | let new_cache_key_hash = icache::compute_cache_key(&*isa, &func); |
193 | | |
194 | | if expect_cache_hit { |
195 | | assert!(cache_key_hash == new_cache_key_hash); |
196 | | } else { |
197 | | assert!(cache_key_hash != new_cache_key_hash); |
198 | | } |
199 | | |
200 | | context = Context::for_function(func.clone()); |
201 | | |
202 | | let after_mutation_result = match context.compile(&*isa, &mut Default::default()) { |
203 | | Ok(info) => info, |
204 | | Err(_) => return, |
205 | | }; |
206 | | |
207 | | if expect_cache_hit { |
208 | | let after_mutation_result_from_cache = icache::try_finish_recompile(&func, &serialized) |
209 | | .expect("recompilation should always work for identity"); |
210 | | assert_eq!(*after_mutation_result, after_mutation_result_from_cache); |
211 | | |
212 | | let new_info = after_mutation_result_from_cache.code_info(); |
213 | | assert_eq!( |
214 | | new_info, |
215 | | after_mutation_result.code_info(), |
216 | | "CodeInfo:s don't match" |
217 | | ); |
218 | | } |
219 | | }); |