/src/wasmtime/cranelift/fuzzgen/src/lib.rs
Line | Count | Source |
1 | | use crate::config::Config; |
2 | | use crate::function_generator::FunctionGenerator; |
3 | | use crate::settings::{Flags, OptLevel}; |
4 | | use anyhow::Result; |
5 | | use arbitrary::{Arbitrary, Unstructured}; |
6 | | use cranelift::codegen::Context; |
7 | | use cranelift::codegen::data_value::DataValue; |
8 | | use cranelift::codegen::ir::{Function, LibCall}; |
9 | | use cranelift::codegen::ir::{UserExternalName, UserFuncName}; |
10 | | use cranelift::codegen::isa::Builder; |
11 | | use cranelift::prelude::isa::{OwnedTargetIsa, TargetIsa}; |
12 | | use cranelift::prelude::settings::SettingKind; |
13 | | use cranelift::prelude::*; |
14 | | use cranelift_arbitrary::CraneliftArbitrary; |
15 | | use cranelift_native::builder_with_options; |
16 | | use rand::{Rng, SeedableRng, rngs::SmallRng}; |
17 | | use target_isa_extras::TargetIsaExtras; |
18 | | use target_lexicon::Architecture; |
19 | | |
20 | | mod config; |
21 | | mod cranelift_arbitrary; |
22 | | mod function_generator; |
23 | | mod passes; |
24 | | mod print; |
25 | | mod target_isa_extras; |
26 | | |
27 | | pub use print::PrintableTestCase; |
28 | | |
29 | | pub type TestCaseInput = Vec<DataValue>; |
30 | | |
31 | | pub enum IsaFlagGen { |
32 | | /// When generating ISA flags, ensure that they are all supported by |
33 | | /// the current host. |
34 | | Host, |
35 | | /// All flags available in cranelift are allowed to be generated. |
36 | | /// We also allow generating all possible values for each enum flag. |
37 | | All, |
38 | | } |
39 | | |
40 | | pub struct FuzzGen<'r, 'data> |
41 | | where |
42 | | 'data: 'r, |
43 | | { |
44 | | pub u: &'r mut Unstructured<'data>, |
45 | | pub config: Config, |
46 | | } |
47 | | |
48 | | impl<'r, 'data> FuzzGen<'r, 'data> |
49 | | where |
50 | | 'data: 'r, |
51 | | { |
52 | 15.0k | pub fn new(u: &'r mut Unstructured<'data>) -> Self { |
53 | 15.0k | Self { |
54 | 15.0k | u, |
55 | 15.0k | config: Config::default(), |
56 | 15.0k | } |
57 | 15.0k | } |
58 | | |
59 | 40.4k | pub fn generate_signature(&mut self, isa: &dyn TargetIsa) -> Result<Signature> { |
60 | 40.4k | let max_params = self.u.int_in_range(self.config.signature_params.clone())?; |
61 | 40.4k | let max_rets = self.u.int_in_range(self.config.signature_rets.clone())?; |
62 | 40.4k | Ok(self.u.signature( |
63 | 40.4k | isa.supports_simd(), |
64 | 40.4k | isa.triple().architecture, |
65 | 40.4k | max_params, |
66 | 40.4k | max_rets, |
67 | 0 | )?) |
68 | 40.4k | } |
69 | | |
70 | 7.26k | pub fn generate_test_inputs(mut self, signature: &Signature) -> Result<Vec<TestCaseInput>> { |
71 | 7.26k | let mut inputs = Vec::new(); |
72 | | |
73 | | // Generate up to "max_test_case_inputs" inputs, we need an upper bound here since |
74 | | // the fuzzer at some point starts trying to feed us way too many inputs. (I found one |
75 | | // test case with 130k inputs!) |
76 | 7.26k | for _ in 0..self.config.max_test_case_inputs { |
77 | 16.1k | let last_len = self.u.len(); |
78 | | |
79 | 16.1k | let test_args = signature |
80 | 16.1k | .params |
81 | 16.1k | .iter() |
82 | 157k | .map(|p| self.u.datavalue(p.value_type)) |
83 | 16.1k | .collect::<Result<TestCaseInput>>()?; |
84 | | |
85 | 16.1k | inputs.push(test_args); |
86 | | |
87 | | // Continue generating input as long as we just consumed some of self.u. Otherwise |
88 | | // we'll generate the same test input again and again, forever. Note that once self.u |
89 | | // becomes empty we obviously can't consume any more of it, so this check is more |
90 | | // general. Also note that we need to generate at least one input or the fuzz target |
91 | | // won't actually test anything, so checking at the end of the loop is good, even if |
92 | | // self.u is empty from the start and we end up with all zeros in test_args. |
93 | 16.1k | assert!(self.u.len() <= last_len); |
94 | 16.1k | if self.u.len() == last_len { |
95 | 7.24k | break; |
96 | 8.85k | } |
97 | | } |
98 | | |
99 | 7.26k | Ok(inputs) |
100 | 7.26k | } |
101 | | |
102 | 24.0k | fn run_func_passes(&mut self, func: Function, isa: &dyn TargetIsa) -> Result<Function> { |
103 | | // Do a NaN Canonicalization pass on the generated function. |
104 | | // |
105 | | // Both IEEE754 and the Wasm spec are somewhat loose about what is allowed |
106 | | // to be returned from NaN producing operations. And in practice this changes |
107 | | // from X86 to Aarch64 and others. Even in the same host machine, the |
108 | | // interpreter may produce a code sequence different from cranelift that |
109 | | // generates different NaN's but produces legal results according to the spec. |
110 | | // |
111 | | // These differences cause spurious failures in the fuzzer. To fix this |
112 | | // we enable the NaN Canonicalization pass that replaces any NaN's produced |
113 | | // with a single fixed canonical NaN value. |
114 | | // |
115 | | // This is something that we can enable via flags for the compiled version, however |
116 | | // the interpreter won't get that version, so call that pass manually here. |
117 | | |
118 | 24.0k | let mut ctx = Context::for_function(func); |
119 | | |
120 | | // We disable the verifier here, since if it fails it prevents a test case from |
121 | | // being generated and formatted by `cargo fuzz fmt`. |
122 | | // We run the verifier before compiling the code, so it always gets verified. |
123 | 24.0k | let flags = settings::Flags::new({ |
124 | 24.0k | let mut builder = settings::builder(); |
125 | 24.0k | builder.set("enable_verifier", "false").unwrap(); |
126 | 24.0k | builder |
127 | | }); |
128 | | |
129 | | // Create a new TargetISA from the given ISA, this ensures that we copy all ISA |
130 | | // flags, which may have an effect on the code generated by the passes below. |
131 | 24.0k | let isa = Builder::from_target_isa(isa) |
132 | 24.0k | .finish(flags) |
133 | 24.0k | .expect("Failed to build TargetISA"); |
134 | | |
135 | | // Finally run the NaN canonicalization pass |
136 | 24.0k | ctx.canonicalize_nans(isa.as_ref()) |
137 | 24.0k | .expect("Failed NaN canonicalization pass"); |
138 | | |
139 | | // Run the int_divz pass |
140 | | // |
141 | | // This pass replaces divs and rems with sequences that do not trap |
142 | 24.0k | passes::do_int_divz_pass(self, &mut ctx.func)?; |
143 | | |
144 | | // This pass replaces fcvt* instructions with sequences that do not trap |
145 | 24.0k | passes::do_fcvt_trap_pass(self, &mut ctx.func)?; |
146 | | |
147 | 24.0k | Ok(ctx.func) |
148 | 24.0k | } |
149 | | |
150 | 25.6k | pub fn generate_func( |
151 | 25.6k | &mut self, |
152 | 25.6k | name: UserFuncName, |
153 | 25.6k | isa: OwnedTargetIsa, |
154 | 25.6k | usercalls: Vec<(UserExternalName, Signature)>, |
155 | 25.6k | libcalls: Vec<LibCall>, |
156 | 25.6k | ) -> Result<Function> { |
157 | 25.6k | let sig = self.generate_signature(&*isa)?; |
158 | | |
159 | 25.6k | let func = FunctionGenerator::new( |
160 | 25.6k | &mut self.u, |
161 | 25.6k | &self.config, |
162 | 25.6k | isa.clone(), |
163 | 25.6k | name, |
164 | 25.6k | sig, |
165 | 25.6k | usercalls, |
166 | 25.6k | libcalls, |
167 | | ) |
168 | 25.6k | .generate()?; |
169 | | |
170 | 24.0k | self.run_func_passes(func, &*isa) |
171 | 25.6k | } |
172 | | |
173 | | /// Generate a random set of cranelift flags. |
174 | | /// Only semantics preserving flags are considered |
175 | 15.0k | pub fn generate_flags(&mut self, target_arch: Architecture) -> arbitrary::Result<Flags> { |
176 | 15.0k | let mut builder = settings::builder(); |
177 | | |
178 | 15.0k | let opt = self.u.choose(OptLevel::all())?; |
179 | 15.0k | builder.set("opt_level", &format!("{opt}")[..]).unwrap(); |
180 | | |
181 | | // Boolean flags |
182 | | // TODO: enable_pinned_reg does not work with our current trampolines. See: #4376 |
183 | | // TODO: is_pic has issues: |
184 | | // x86: https://github.com/bytecodealliance/wasmtime/issues/5005 |
185 | | // aarch64: https://github.com/bytecodealliance/wasmtime/issues/2735 |
186 | 15.0k | let bool_settings = [ |
187 | 15.0k | "enable_alias_analysis", |
188 | 15.0k | "unwind_info", |
189 | 15.0k | "preserve_frame_pointers", |
190 | 15.0k | "enable_heap_access_spectre_mitigation", |
191 | 15.0k | "enable_table_access_spectre_mitigation", |
192 | 15.0k | "enable_incremental_compilation_cache_checks", |
193 | 15.0k | "regalloc_checker", |
194 | 15.0k | "enable_llvm_abi_extensions", |
195 | 15.0k | ]; |
196 | 135k | for flag_name in bool_settings { |
197 | 120k | let enabled = self |
198 | 120k | .config |
199 | 120k | .compile_flag_ratio |
200 | 120k | .get(&flag_name) |
201 | 120k | .map(|&(num, denum)| self.u.ratio(num, denum)) |
202 | 120k | .unwrap_or_else(|| bool::arbitrary(self.u))?; |
203 | | |
204 | 120k | let value = format!("{enabled}"); |
205 | 120k | builder.set(flag_name, value.as_str()).unwrap(); |
206 | | } |
207 | | |
208 | 15.0k | let supports_inline_probestack = match target_arch { |
209 | 10.0k | Architecture::X86_64 => true, |
210 | 1.38k | Architecture::Aarch64(_) => true, |
211 | 1.87k | Architecture::Riscv64(_) => true, |
212 | 1.77k | _ => false, |
213 | | }; |
214 | | |
215 | | // Optionally test inline stackprobes on supported platforms |
216 | | // TODO: Test outlined stack probes. |
217 | 15.0k | if supports_inline_probestack && bool::arbitrary(self.u)? { |
218 | 4.04k | builder.enable("enable_probestack").unwrap(); |
219 | 4.04k | builder.set("probestack_strategy", "inline").unwrap(); |
220 | | |
221 | 4.04k | let size = self |
222 | 4.04k | .u |
223 | 4.04k | .int_in_range(self.config.stack_probe_size_log2.clone())?; |
224 | 4.04k | builder |
225 | 4.04k | .set("probestack_size_log2", &format!("{size}")) |
226 | 4.04k | .unwrap(); |
227 | 11.0k | } |
228 | | |
229 | | // Generate random basic block padding |
230 | 15.0k | let bb_padding = self |
231 | 15.0k | .u |
232 | 15.0k | .int_in_range(self.config.bb_padding_log2_size.clone()) |
233 | 15.0k | .unwrap(); |
234 | 15.0k | builder |
235 | 15.0k | .set("bb_padding_log2_minus_one", &format!("{bb_padding}")) |
236 | 15.0k | .unwrap(); |
237 | | |
238 | | // Fixed settings |
239 | | |
240 | | // We need llvm ABI extensions for i128 values on x86, so enable it regardless of |
241 | | // what we picked above. |
242 | 15.0k | if target_arch == Architecture::X86_64 { |
243 | 10.0k | builder.enable("enable_llvm_abi_extensions").unwrap(); |
244 | 10.0k | } |
245 | | |
246 | | // FIXME(#9510) remove once this option is permanently disabled |
247 | 15.0k | builder.enable("enable_multi_ret_implicit_sret").unwrap(); |
248 | | |
249 | | // This is the default, but we should ensure that it wasn't accidentally turned off anywhere. |
250 | 15.0k | builder.enable("enable_verifier").unwrap(); |
251 | | |
252 | | // `machine_code_cfg_info` generates additional metadata for the embedder but this doesn't feed back |
253 | | // into compilation anywhere, we leave it on unconditionally to make sure the generation doesn't panic. |
254 | 15.0k | builder.enable("machine_code_cfg_info").unwrap(); |
255 | | |
256 | | // Differential fuzzing between the interpreter and the host will only |
257 | | // really work if NaN payloads are canonicalized, so enable this. |
258 | 15.0k | builder.enable("enable_nan_canonicalization").unwrap(); |
259 | | |
260 | 15.0k | Ok(Flags::new(builder)) |
261 | 15.0k | } |
262 | | |
263 | | /// Generate a random set of ISA flags and apply them to a Builder. |
264 | | /// |
265 | | /// Based on `mode` we can either allow all flags, or just the subset that is |
266 | | /// supported by the current host. |
267 | | /// |
268 | | /// In all cases only a subset of the allowed flags is applied to the builder. |
269 | 15.0k | pub fn set_isa_flags(&mut self, builder: &mut Builder, mode: IsaFlagGen) -> Result<()> { |
270 | | // `max_isa` is the maximal set of flags that we can use. |
271 | 15.0k | let max_builder = match mode { |
272 | | IsaFlagGen::All => { |
273 | 7.04k | let mut max_builder = isa::lookup(builder.triple().clone())?; |
274 | | |
275 | 280k | for flag in max_builder.iter() { |
276 | 280k | match flag.kind { |
277 | | SettingKind::Bool => { |
278 | 110k | max_builder.enable(flag.name)?; |
279 | | } |
280 | | SettingKind::Enum => { |
281 | | // Since these are enums there isn't a "max" value per se, pick one at random. |
282 | 0 | let value = self.u.choose(flag.values.unwrap())?; |
283 | 0 | max_builder.set(flag.name, value)?; |
284 | | } |
285 | 169k | SettingKind::Preset => { |
286 | 169k | // Presets are just special flags that combine other flags, we don't |
287 | 169k | // want to enable them directly, just the underlying flags. |
288 | 169k | } |
289 | 0 | _ => todo!(), |
290 | | }; |
291 | | } |
292 | 7.04k | max_builder |
293 | | } |
294 | | // Use `cranelift-native` to do feature detection for us. |
295 | 8.02k | IsaFlagGen::Host => builder_with_options(true) |
296 | 8.02k | .expect("Unable to build a TargetIsa for the current host"), |
297 | | }; |
298 | | // Cranelift has a somewhat weird API for this, but we need to build the final `TargetIsa` to be able |
299 | | // to extract the values for the ISA flags. We need that to use the `string_value()` that formats |
300 | | // the values so that we can pass it into the builder again. |
301 | 15.0k | let max_isa = max_builder.finish(Flags::new(settings::builder()))?; |
302 | | |
303 | | // We give each of the flags a chance of being copied over. Otherwise we |
304 | | // keep the default. Note that a constant amount of data is taken from |
305 | | // `self.u` as a seed for a `SmallRng` which is then transitively used |
306 | | // to make decisions about what flags to include. This is done to ensure |
307 | | // that the same test case generates similarly across different machines |
308 | | // with different CPUs when `Host` is used above. |
309 | 15.0k | let mut rng = SmallRng::from_seed(self.u.arbitrary()?); |
310 | 247k | for value in max_isa.isa_flags().iter() { |
311 | 247k | if rng.random() { |
312 | 114k | continue; |
313 | 132k | } |
314 | 132k | builder.set(value.name, &value.value_string())?; |
315 | | } |
316 | | |
317 | 15.0k | Ok(()) |
318 | 15.0k | } |
319 | | } |