/src/wasmtime/fuzz/fuzz_targets/cranelift-fuzzgen.rs
Line | Count | Source |
1 | | #![no_main] |
2 | | |
3 | | use cranelift_codegen::Context; |
4 | | use cranelift_codegen::ir::Function; |
5 | | use cranelift_codegen::ir::Signature; |
6 | | use cranelift_codegen::ir::UserExternalName; |
7 | | use cranelift_codegen::ir::UserFuncName; |
8 | | use cranelift_control::ControlPlane; |
9 | | use libfuzzer_sys::arbitrary; |
10 | | use libfuzzer_sys::arbitrary::Arbitrary; |
11 | | use libfuzzer_sys::arbitrary::Unstructured; |
12 | | use libfuzzer_sys::fuzz_target; |
13 | | use std::collections::HashMap; |
14 | | use std::fmt; |
15 | | use std::sync::LazyLock; |
16 | | use std::sync::atomic::AtomicU64; |
17 | | use std::sync::atomic::Ordering; |
18 | | |
19 | | use cranelift_codegen::data_value::DataValue; |
20 | | use cranelift_codegen::ir::{LibCall, TrapCode}; |
21 | | use cranelift_codegen::isa; |
22 | | use cranelift_filetests::function_runner::{CompiledTestFile, TestFileCompiler, Trampoline}; |
23 | | use cranelift_fuzzgen::*; |
24 | | use cranelift_interpreter::environment::FuncIndex; |
25 | | use cranelift_interpreter::environment::FunctionStore; |
26 | | use cranelift_interpreter::interpreter::{ |
27 | | Interpreter, InterpreterError, InterpreterState, LibCallValues, |
28 | | }; |
29 | | use cranelift_interpreter::step::ControlFlow; |
30 | | use cranelift_interpreter::step::CraneliftTrap; |
31 | | use cranelift_native::builder_with_options; |
32 | | use smallvec::smallvec; |
33 | | |
34 | | const INTERPRETER_FUEL: u64 = 4096; |
35 | | |
36 | | /// Gather statistics about the fuzzer executions |
37 | | struct Statistics { |
38 | | /// Inputs that fuzzgen can build a function with |
39 | | /// This is also how many compiles we executed |
40 | | pub valid_inputs: AtomicU64, |
41 | | /// How many times did we generate an invalid format? |
42 | | pub invalid_inputs: AtomicU64, |
43 | | |
44 | | /// Total amount of runs that we tried in the interpreter |
45 | | /// One fuzzer input can have many runs |
46 | | pub total_runs: AtomicU64, |
47 | | /// How many runs were successful? |
48 | | /// This is also how many runs were run in the backend |
49 | | pub run_result_success: AtomicU64, |
50 | | /// How many runs resulted in a timeout? |
51 | | pub run_result_timeout: AtomicU64, |
52 | | /// How many runs ended with a trap? |
53 | | pub run_result_trap: HashMap<CraneliftTrap, AtomicU64>, |
54 | | } |
55 | | |
56 | | impl Statistics { |
57 | 0 | pub fn print(&self, valid_inputs: u64) { |
58 | | // We get valid_inputs as a param since we already loaded it previously. |
59 | 0 | let total_runs = self.total_runs.load(Ordering::SeqCst); |
60 | 0 | let invalid_inputs = self.invalid_inputs.load(Ordering::SeqCst); |
61 | 0 | let run_result_success = self.run_result_success.load(Ordering::SeqCst); |
62 | 0 | let run_result_timeout = self.run_result_timeout.load(Ordering::SeqCst); |
63 | | |
64 | 0 | println!("== FuzzGen Statistics ===================="); |
65 | 0 | println!("Valid Inputs: {valid_inputs}"); |
66 | 0 | println!( |
67 | 0 | "Invalid Inputs: {} ({:.1}% of Total Inputs)", |
68 | | invalid_inputs, |
69 | 0 | (invalid_inputs as f64 / (valid_inputs + invalid_inputs) as f64) * 100.0 |
70 | | ); |
71 | 0 | println!("Total Runs: {total_runs}"); |
72 | 0 | println!( |
73 | 0 | "Successful Runs: {} ({:.1}% of Total Runs)", |
74 | | run_result_success, |
75 | 0 | (run_result_success as f64 / total_runs as f64) * 100.0 |
76 | | ); |
77 | 0 | println!( |
78 | 0 | "Timed out Runs: {} ({:.1}% of Total Runs)", |
79 | | run_result_timeout, |
80 | 0 | (run_result_timeout as f64 / total_runs as f64) * 100.0 |
81 | | ); |
82 | 0 | println!("Traps:"); |
83 | | // Load and filter out empty trap codes. |
84 | 0 | let mut traps = self |
85 | 0 | .run_result_trap |
86 | 0 | .iter() |
87 | 0 | .map(|(trap, count)| (trap, count.load(Ordering::SeqCst))) |
88 | 0 | .filter(|(_, count)| *count != 0) |
89 | 0 | .collect::<Vec<_>>(); |
90 | | |
91 | | // Sort traps by count in a descending order |
92 | 0 | traps.sort_by_key(|(_, count)| -(*count as i64)); |
93 | | |
94 | 0 | for (trap, count) in traps.into_iter() { |
95 | 0 | println!( |
96 | 0 | "\t{}: {} ({:.1}% of Total Runs)", |
97 | 0 | trap, |
98 | 0 | count, |
99 | 0 | (count as f64 / total_runs as f64) * 100.0 |
100 | 0 | ); |
101 | 0 | } |
102 | 0 | } |
103 | | } |
104 | | |
105 | | impl Default for Statistics { |
106 | 1 | fn default() -> Self { |
107 | | // Pre-Register all trap codes since we can't modify this hashmap atomically. |
108 | 1 | let mut run_result_trap = HashMap::new(); |
109 | 1 | run_result_trap.insert(CraneliftTrap::Debug, AtomicU64::new(0)); |
110 | 1 | run_result_trap.insert(CraneliftTrap::BadSignature, AtomicU64::new(0)); |
111 | 1 | run_result_trap.insert(CraneliftTrap::UnreachableCodeReached, AtomicU64::new(0)); |
112 | 1 | run_result_trap.insert(CraneliftTrap::HeapMisaligned, AtomicU64::new(0)); |
113 | 5 | for trapcode in TrapCode::non_user_traps() { |
114 | 5 | run_result_trap.insert(CraneliftTrap::User(*trapcode), AtomicU64::new(0)); |
115 | 5 | } |
116 | | |
117 | 1 | Self { |
118 | 1 | valid_inputs: AtomicU64::new(0), |
119 | 1 | invalid_inputs: AtomicU64::new(0), |
120 | 1 | total_runs: AtomicU64::new(0), |
121 | 1 | run_result_success: AtomicU64::new(0), |
122 | 1 | run_result_timeout: AtomicU64::new(0), |
123 | 1 | run_result_trap, |
124 | 1 | } |
125 | 1 | } |
126 | | } |
127 | | |
128 | | #[derive(Debug)] |
129 | | enum RunResult { |
130 | | Success(Vec<DataValue>), |
131 | | Trap(CraneliftTrap), |
132 | | Timeout, |
133 | | Error(Box<dyn std::error::Error>), |
134 | | } |
135 | | |
136 | | impl PartialEq for RunResult { |
137 | 26.9k | fn eq(&self, other: &Self) -> bool { |
138 | 26.9k | match (self, other) { |
139 | 13.4k | (RunResult::Success(l), RunResult::Success(r)) => { |
140 | 117k | l.len() == r.len() && l.iter().zip(r).all(|(l, r)| l.bitwise_eq(r)) |
141 | | } |
142 | 0 | (RunResult::Trap(l), RunResult::Trap(r)) => l == r, |
143 | 0 | (RunResult::Timeout, RunResult::Timeout) => true, |
144 | 0 | (RunResult::Error(_), RunResult::Error(_)) => unimplemented!(), |
145 | 13.4k | _ => false, |
146 | | } |
147 | 26.9k | } |
148 | | } |
149 | | |
150 | | pub struct TestCase { |
151 | | /// TargetIsa to use when compiling this test case |
152 | | pub isa: isa::OwnedTargetIsa, |
153 | | /// Functions under test |
154 | | /// By convention the first function is the main function. |
155 | | pub functions: Vec<Function>, |
156 | | /// Control planes for function compilation. |
157 | | /// There should be an equal amount as functions to compile. |
158 | | pub ctrl_planes: Vec<ControlPlane>, |
159 | | /// Generate multiple test inputs for each test case. |
160 | | /// This allows us to get more coverage per compilation, which may be somewhat expensive. |
161 | | pub inputs: Vec<TestCaseInput>, |
162 | | /// Should this `TestCase` be tested after optimizations. |
163 | | pub compare_against_host: bool, |
164 | | } |
165 | | |
166 | | impl fmt::Debug for TestCase { |
167 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
168 | 0 | if !self.compare_against_host { |
169 | 0 | writeln!(f, ";; Testing against optimized version")?; |
170 | 0 | } |
171 | 0 | PrintableTestCase::run(&self.isa, &self.functions, &self.inputs).fmt(f) |
172 | 0 | } |
173 | | } |
174 | | |
175 | | impl<'a> Arbitrary<'a> for TestCase { |
176 | 7.27k | fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { |
177 | 7.27k | let _ = env_logger::try_init(); |
178 | 7.27k | Self::generate(u).map_err(|_| { |
179 | 606 | STATISTICS.invalid_inputs.fetch_add(1, Ordering::SeqCst); |
180 | 606 | arbitrary::Error::IncorrectFormat |
181 | 606 | }) |
182 | 7.27k | } |
183 | | } |
184 | | |
185 | | impl TestCase { |
186 | 7.27k | pub fn generate(u: &mut Unstructured) -> anyhow::Result<Self> { |
187 | 7.27k | let mut generator = FuzzGen::new(u); |
188 | | |
189 | 7.27k | let compare_against_host = generator.u.arbitrary()?; |
190 | | |
191 | | // TestCase is meant to be consumed by a runner, so we make the assumption here that we're |
192 | | // generating a TargetIsa for the host. |
193 | 7.27k | let mut builder = |
194 | 7.27k | builder_with_options(true).expect("Unable to build a TargetIsa for the current host"); |
195 | 7.27k | let flags = generator.generate_flags(builder.triple().architecture)?; |
196 | 7.27k | generator.set_isa_flags(&mut builder, IsaFlagGen::Host)?; |
197 | 7.27k | let isa = builder.finish(flags)?; |
198 | | |
199 | | // When generating functions, we allow each function to call any function that has |
200 | | // already been generated. This guarantees that we never have loops in the call graph. |
201 | | // We generate these backwards, and then reverse them so that the main function is at |
202 | | // the start. |
203 | 7.27k | let func_count = generator |
204 | 7.27k | .u |
205 | 7.27k | .int_in_range(generator.config.testcase_funcs.clone())?; |
206 | 7.27k | let mut functions: Vec<Function> = Vec::with_capacity(func_count); |
207 | 7.27k | let mut ctrl_planes: Vec<ControlPlane> = Vec::with_capacity(func_count); |
208 | 17.4k | for i in (0..func_count).rev() { |
209 | | // Function name must be in a different namespace than TESTFILE_NAMESPACE (0) |
210 | 17.4k | let fname = UserFuncName::user(1, i as u32); |
211 | | |
212 | 17.4k | let usercalls: Vec<(UserExternalName, Signature)> = functions |
213 | 17.4k | .iter() |
214 | 29.7k | .map(|f| { |
215 | 29.7k | ( |
216 | 29.7k | f.name.get_user().unwrap().clone(), |
217 | 29.7k | f.stencil.signature.clone(), |
218 | 29.7k | ) |
219 | 29.7k | }) |
220 | 17.4k | .collect(); |
221 | | |
222 | 17.4k | let func = generator.generate_func( |
223 | 17.4k | fname, |
224 | 17.4k | isa.clone(), |
225 | 17.4k | usercalls, |
226 | 17.4k | ALLOWED_LIBCALLS.to_vec(), |
227 | 606 | )?; |
228 | 16.8k | functions.push(func); |
229 | | |
230 | 16.8k | ctrl_planes.push(ControlPlane::arbitrary(generator.u)?); |
231 | | } |
232 | | // Now reverse the functions so that the main function is at the start. |
233 | 6.67k | functions.reverse(); |
234 | | |
235 | 6.67k | let main = &functions[0]; |
236 | 6.67k | let inputs = generator.generate_test_inputs(&main.signature)?; |
237 | | |
238 | 6.67k | Ok(TestCase { |
239 | 6.67k | isa, |
240 | 6.67k | functions, |
241 | 6.67k | ctrl_planes, |
242 | 6.67k | inputs, |
243 | 6.67k | compare_against_host, |
244 | 6.67k | }) |
245 | 7.27k | } |
246 | | |
247 | 327 | fn to_optimized(&self) -> Self { |
248 | 327 | let mut ctrl_planes = self.ctrl_planes.clone(); |
249 | 327 | let optimized_functions: Vec<Function> = self |
250 | 327 | .functions |
251 | 327 | .iter() |
252 | 327 | .zip(ctrl_planes.iter_mut()) |
253 | 1.03k | .map(|(func, ctrl_plane)| { |
254 | 1.03k | let mut ctx = Context::for_function(func.clone()); |
255 | 1.03k | ctx.optimize(self.isa.as_ref(), ctrl_plane).unwrap(); |
256 | 1.03k | ctx.func |
257 | 1.03k | }) |
258 | 327 | .collect(); |
259 | | |
260 | 327 | TestCase { |
261 | 327 | isa: self.isa.clone(), |
262 | 327 | functions: optimized_functions, |
263 | 327 | ctrl_planes, |
264 | 327 | inputs: self.inputs.clone(), |
265 | 327 | compare_against_host: false, |
266 | 327 | } |
267 | 327 | } |
268 | | |
269 | | /// Returns the main function of this test case. |
270 | 6.34k | pub fn main(&self) -> &Function { |
271 | 6.34k | &self.functions[0] |
272 | 6.34k | } |
273 | | } |
274 | | |
275 | 15.5k | fn run_in_interpreter(interpreter: &mut Interpreter, args: &[DataValue]) -> RunResult { |
276 | | // The entrypoint function is always 0 |
277 | 15.5k | let index = FuncIndex::from_u32(0); |
278 | 15.5k | let res = interpreter.call_by_index(index, args); |
279 | | |
280 | 15.0k | match res { |
281 | 14.8k | Ok(ControlFlow::Return(results)) => RunResult::Success(results.to_vec()), |
282 | 181 | Ok(ControlFlow::Trap(trap)) => RunResult::Trap(trap), |
283 | 0 | Ok(cf) => RunResult::Error(format!("Unrecognized exit ControlFlow: {cf:?}").into()), |
284 | 480 | Err(InterpreterError::FuelExhausted) => RunResult::Timeout, |
285 | 0 | Err(e) => RunResult::Error(e.into()), |
286 | | } |
287 | 15.5k | } |
288 | | |
289 | 12.0k | fn run_in_host( |
290 | 12.0k | compiled: &CompiledTestFile, |
291 | 12.0k | trampoline: &Trampoline, |
292 | 12.0k | args: &[DataValue], |
293 | 12.0k | ) -> RunResult { |
294 | 12.0k | let res = trampoline.call(compiled, args); |
295 | 12.0k | RunResult::Success(res) |
296 | 12.0k | } |
297 | | |
298 | | /// These libcalls need a interpreter implementation in `build_interpreter` |
299 | | const ALLOWED_LIBCALLS: &'static [LibCall] = &[ |
300 | | LibCall::CeilF32, |
301 | | LibCall::CeilF64, |
302 | | LibCall::FloorF32, |
303 | | LibCall::FloorF64, |
304 | | LibCall::TruncF32, |
305 | | LibCall::TruncF64, |
306 | | ]; |
307 | | |
308 | 15.5k | fn build_interpreter(testcase: &TestCase) -> Interpreter<'_> { |
309 | 15.5k | let mut env = FunctionStore::default(); |
310 | 33.2k | for func in testcase.functions.iter() { |
311 | 33.2k | env.add(func.name.to_string(), &func); |
312 | 33.2k | } |
313 | | |
314 | 15.5k | let state = InterpreterState::default() |
315 | 15.5k | .with_function_store(env) |
316 | 243k | .with_libcall_handler(|libcall: LibCall, args: LibCallValues| { |
317 | | use LibCall::*; |
318 | 243k | Ok(smallvec![match (libcall, &args[..]) { |
319 | 0 | (CeilF32, [DataValue::F32(a)]) => DataValue::F32(a.ceil()), |
320 | 0 | (CeilF64, [DataValue::F64(a)]) => DataValue::F64(a.ceil()), |
321 | 0 | (FloorF32, [DataValue::F32(a)]) => DataValue::F32(a.floor()), |
322 | 0 | (FloorF64, [DataValue::F64(a)]) => DataValue::F64(a.floor()), |
323 | 0 | (TruncF32, [DataValue::F32(a)]) => DataValue::F32(a.trunc()), |
324 | 0 | (TruncF64, [DataValue::F64(a)]) => DataValue::F64(a.trunc()), |
325 | 0 | _ => unreachable!(), |
326 | | }]) |
327 | 243k | }); |
328 | | |
329 | 15.5k | let interpreter = Interpreter::new(state).with_fuel(Some(INTERPRETER_FUEL)); |
330 | 15.5k | interpreter |
331 | 15.5k | } |
332 | | |
333 | | static STATISTICS: LazyLock<Statistics> = LazyLock::new(Statistics::default); |
334 | | |
335 | 6.67k | fn run_test_inputs(testcase: &TestCase, run: impl Fn(&[DataValue]) -> RunResult) { |
336 | 20.3k | for args in &testcase.inputs { |
337 | 14.1k | STATISTICS.total_runs.fetch_add(1, Ordering::SeqCst); |
338 | | |
339 | | // We rebuild the interpreter every run so that we don't accidentally carry over any state |
340 | | // between runs, such as fuel remaining. |
341 | 14.1k | let mut interpreter = build_interpreter(&testcase); |
342 | 14.1k | let int_res = run_in_interpreter(&mut interpreter, args); |
343 | 14.1k | match int_res { |
344 | 13.4k | RunResult::Success(_) => { |
345 | 13.4k | STATISTICS.run_result_success.fetch_add(1, Ordering::SeqCst); |
346 | 13.4k | } |
347 | 181 | RunResult::Trap(trap) => { |
348 | 181 | STATISTICS.run_result_trap[&trap].fetch_add(1, Ordering::SeqCst); |
349 | | // If this input traps, skip it and continue trying other inputs |
350 | | // for this function. We've already compiled it anyway. |
351 | | // |
352 | | // We could catch traps in the host run and compare them to the |
353 | | // interpreter traps, but since we already test trap cases with |
354 | | // wasm tests and wasm-level fuzzing, the amount of effort does |
355 | | // not justify implementing it again here. |
356 | 181 | continue; |
357 | | } |
358 | | RunResult::Timeout => { |
359 | | // We probably generated an infinite loop, we should drop this entire input. |
360 | | // We could `continue` like we do on traps, but timeouts are *really* expensive. |
361 | 480 | STATISTICS.run_result_timeout.fetch_add(1, Ordering::SeqCst); |
362 | 480 | return; |
363 | | } |
364 | 0 | RunResult::Error(e) => panic!("interpreter failed: {e:?}"), |
365 | | } |
366 | | |
367 | 13.4k | let res = run(args); |
368 | | |
369 | | // This situation can happen when we are comparing the interpreter against the interpreter, and |
370 | | // one of the optimization passes has increased the number of instructions in the function. |
371 | | // This can cause the interpreter to run out of fuel in the second run, but not the first. |
372 | | // We should ignore these cases. |
373 | | // Running in the host should never return a timeout, so that should be ok. |
374 | 13.4k | if res == RunResult::Timeout { |
375 | 0 | return; |
376 | 13.4k | } |
377 | | |
378 | 13.4k | assert_eq!(int_res, res); |
379 | | } |
380 | 6.67k | } cranelift_fuzzgen::run_test_inputs::<cranelift_fuzzgen::_::__libfuzzer_sys_run::{closure#2}> Line | Count | Source | 335 | 327 | fn run_test_inputs(testcase: &TestCase, run: impl Fn(&[DataValue]) -> RunResult) { | 336 | 1.74k | for args in &testcase.inputs { | 337 | 1.43k | STATISTICS.total_runs.fetch_add(1, Ordering::SeqCst); | 338 | | | 339 | | // We rebuild the interpreter every run so that we don't accidentally carry over any state | 340 | | // between runs, such as fuel remaining. | 341 | 1.43k | let mut interpreter = build_interpreter(&testcase); | 342 | 1.43k | let int_res = run_in_interpreter(&mut interpreter, args); | 343 | 1.43k | match int_res { | 344 | 1.41k | RunResult::Success(_) => { | 345 | 1.41k | STATISTICS.run_result_success.fetch_add(1, Ordering::SeqCst); | 346 | 1.41k | } | 347 | 0 | RunResult::Trap(trap) => { | 348 | 0 | STATISTICS.run_result_trap[&trap].fetch_add(1, Ordering::SeqCst); | 349 | | // If this input traps, skip it and continue trying other inputs | 350 | | // for this function. We've already compiled it anyway. | 351 | | // | 352 | | // We could catch traps in the host run and compare them to the | 353 | | // interpreter traps, but since we already test trap cases with | 354 | | // wasm tests and wasm-level fuzzing, the amount of effort does | 355 | | // not justify implementing it again here. | 356 | 0 | continue; | 357 | | } | 358 | | RunResult::Timeout => { | 359 | | // We probably generated an infinite loop, we should drop this entire input. | 360 | | // We could `continue` like we do on traps, but timeouts are *really* expensive. | 361 | 15 | STATISTICS.run_result_timeout.fetch_add(1, Ordering::SeqCst); | 362 | 15 | return; | 363 | | } | 364 | 0 | RunResult::Error(e) => panic!("interpreter failed: {e:?}"), | 365 | | } | 366 | | | 367 | 1.41k | let res = run(args); | 368 | | | 369 | | // This situation can happen when we are comparing the interpreter against the interpreter, and | 370 | | // one of the optimization passes has increased the number of instructions in the function. | 371 | | // This can cause the interpreter to run out of fuel in the second run, but not the first. | 372 | | // We should ignore these cases. | 373 | | // Running in the host should never return a timeout, so that should be ok. | 374 | 1.41k | if res == RunResult::Timeout { | 375 | 0 | return; | 376 | 1.41k | } | 377 | | | 378 | 1.41k | assert_eq!(int_res, res); | 379 | | } | 380 | 327 | } |
cranelift_fuzzgen::run_test_inputs::<cranelift_fuzzgen::_::__libfuzzer_sys_run::{closure#3}> Line | Count | Source | 335 | 6.34k | fn run_test_inputs(testcase: &TestCase, run: impl Fn(&[DataValue]) -> RunResult) { | 336 | 18.5k | for args in &testcase.inputs { | 337 | 12.7k | STATISTICS.total_runs.fetch_add(1, Ordering::SeqCst); | 338 | | | 339 | | // We rebuild the interpreter every run so that we don't accidentally carry over any state | 340 | | // between runs, such as fuel remaining. | 341 | 12.7k | let mut interpreter = build_interpreter(&testcase); | 342 | 12.7k | let int_res = run_in_interpreter(&mut interpreter, args); | 343 | 12.7k | match int_res { | 344 | 12.0k | RunResult::Success(_) => { | 345 | 12.0k | STATISTICS.run_result_success.fetch_add(1, Ordering::SeqCst); | 346 | 12.0k | } | 347 | 181 | RunResult::Trap(trap) => { | 348 | 181 | STATISTICS.run_result_trap[&trap].fetch_add(1, Ordering::SeqCst); | 349 | | // If this input traps, skip it and continue trying other inputs | 350 | | // for this function. We've already compiled it anyway. | 351 | | // | 352 | | // We could catch traps in the host run and compare them to the | 353 | | // interpreter traps, but since we already test trap cases with | 354 | | // wasm tests and wasm-level fuzzing, the amount of effort does | 355 | | // not justify implementing it again here. | 356 | 181 | continue; | 357 | | } | 358 | | RunResult::Timeout => { | 359 | | // We probably generated an infinite loop, we should drop this entire input. | 360 | | // We could `continue` like we do on traps, but timeouts are *really* expensive. | 361 | 465 | STATISTICS.run_result_timeout.fetch_add(1, Ordering::SeqCst); | 362 | 465 | return; | 363 | | } | 364 | 0 | RunResult::Error(e) => panic!("interpreter failed: {e:?}"), | 365 | | } | 366 | | | 367 | 12.0k | let res = run(args); | 368 | | | 369 | | // This situation can happen when we are comparing the interpreter against the interpreter, and | 370 | | // one of the optimization passes has increased the number of instructions in the function. | 371 | | // This can cause the interpreter to run out of fuel in the second run, but not the first. | 372 | | // We should ignore these cases. | 373 | | // Running in the host should never return a timeout, so that should be ok. | 374 | 12.0k | if res == RunResult::Timeout { | 375 | 0 | return; | 376 | 12.0k | } | 377 | | | 378 | 12.0k | assert_eq!(int_res, res); | 379 | | } | 380 | 6.34k | } |
|
381 | | |
382 | | fuzz_target!(|testcase: TestCase| { |
383 | | let mut testcase = testcase; |
384 | | let fuel: u8 = std::env::args() |
385 | 40.0k | .find_map(|arg| arg.strip_prefix("--fuel=").map(|s| s.to_owned())) |
386 | 0 | .map(|fuel| fuel.parse().expect("fuel should be a valid integer")) |
387 | | .unwrap_or_default(); |
388 | | for i in 0..testcase.ctrl_planes.len() { |
389 | | testcase.ctrl_planes[i].set_fuel(fuel) |
390 | | } |
391 | | let testcase = testcase; |
392 | | |
393 | | // This is the default, but we should ensure that it wasn't accidentally turned off anywhere. |
394 | | assert!(testcase.isa.flags().enable_verifier()); |
395 | | |
396 | | // Periodically print statistics |
397 | | let valid_inputs = STATISTICS.valid_inputs.fetch_add(1, Ordering::SeqCst); |
398 | | if valid_inputs != 0 && valid_inputs % 10000 == 0 { |
399 | | STATISTICS.print(valid_inputs); |
400 | | } |
401 | | |
402 | | if !testcase.compare_against_host { |
403 | | let opt_testcase = testcase.to_optimized(); |
404 | | |
405 | 1.41k | run_test_inputs(&testcase, |args| { |
406 | | // We rebuild the interpreter every run so that we don't accidentally carry over any state |
407 | | // between runs, such as fuel remaining. |
408 | 1.41k | let mut interpreter = build_interpreter(&opt_testcase); |
409 | | |
410 | 1.41k | run_in_interpreter(&mut interpreter, args) |
411 | 1.41k | }); |
412 | | } else { |
413 | | let mut compiler = TestFileCompiler::new(testcase.isa.clone()); |
414 | | compiler |
415 | | .add_functions(&testcase.functions[..], testcase.ctrl_planes.clone()) |
416 | | .unwrap(); |
417 | | let compiled = compiler.compile().unwrap(); |
418 | | let trampoline = compiled.get_trampoline(testcase.main()).unwrap(); |
419 | | |
420 | 12.0k | run_test_inputs(&testcase, |args| run_in_host(&compiled, &trampoline, args)); |
421 | | } |
422 | | }); |