/src/wasmtime/crates/fuzzing/src/generators/config.rs
Line | Count | Source |
1 | | //! Generate a configuration for both Wasmtime and the Wasm module to execute. |
2 | | |
3 | | use super::{AsyncConfig, CodegenSettings, InstanceAllocationStrategy, MemoryConfig, ModuleConfig}; |
4 | | use crate::oracles::{StoreLimits, Timeout}; |
5 | | use arbitrary::{Arbitrary, Unstructured}; |
6 | | use std::time::Duration; |
7 | | use wasmtime::Result; |
8 | | use wasmtime::{Enabled, Engine, Module, Store}; |
9 | | use wasmtime_test_util::wast::{WastConfig, WastTest, limits}; |
10 | | |
11 | | /// Configuration for `wasmtime::Config` and generated modules for a session of |
12 | | /// fuzzing. |
13 | | /// |
14 | | /// This configuration guides what modules are generated, how wasmtime |
15 | | /// configuration is generated, and is typically itself generated through a call |
16 | | /// to `Arbitrary` which allows for a form of "swarm testing". |
17 | | #[derive(Debug, Clone)] |
18 | | pub struct Config { |
19 | | /// Configuration related to the `wasmtime::Config`. |
20 | | pub wasmtime: WasmtimeConfig, |
21 | | /// Configuration related to generated modules. |
22 | | pub module_config: ModuleConfig, |
23 | | } |
24 | | |
25 | | impl Config { |
26 | | /// Indicates that this configuration is being used for differential |
27 | | /// execution. |
28 | | /// |
29 | | /// The purpose of this function is to update the configuration which was |
30 | | /// generated to be compatible with execution in multiple engines. The goal |
31 | | /// is to produce the exact same result in all engines so we need to paper |
32 | | /// over things like nan differences and memory/table behavior differences. |
33 | 16.5k | pub fn set_differential_config(&mut self) { |
34 | 16.5k | let config = &mut self.module_config.config; |
35 | | |
36 | | // Make it more likely that there are types available to generate a |
37 | | // function with. |
38 | 16.5k | config.min_types = config.min_types.max(1); |
39 | 16.5k | config.max_types = config.max_types.max(1); |
40 | | |
41 | | // Generate at least one function |
42 | 16.5k | config.min_funcs = config.min_funcs.max(1); |
43 | 16.5k | config.max_funcs = config.max_funcs.max(1); |
44 | | |
45 | | // Allow a memory to be generated, but don't let it get too large. |
46 | | // Additionally require the maximum size to guarantee that the growth |
47 | | // behavior is consistent across engines. |
48 | 16.5k | config.max_memory32_bytes = 10 << 16; |
49 | 16.5k | config.max_memory64_bytes = 10 << 16; |
50 | 16.5k | config.memory_max_size_required = true; |
51 | | |
52 | | // If tables are generated make sure they don't get too large to avoid |
53 | | // hitting any engine-specific limit. Additionally ensure that the |
54 | | // maximum size is required to guarantee consistent growth across |
55 | | // engines. |
56 | | // |
57 | | // Note that while reference types are disabled below, only allow one |
58 | | // table. |
59 | 16.5k | config.max_table_elements = 1_000; |
60 | 16.5k | config.table_max_size_required = true; |
61 | | |
62 | | // Don't allow any imports |
63 | 16.5k | config.max_imports = 0; |
64 | | |
65 | | // Try to get the function and the memory exported |
66 | 16.5k | config.export_everything = true; |
67 | | |
68 | | // NaN is canonicalized at the wasm level for differential fuzzing so we |
69 | | // can paper over NaN differences between engines. |
70 | 16.5k | config.canonicalize_nans = true; |
71 | | |
72 | | // If using the pooling allocator, update the instance limits too |
73 | 16.5k | if let InstanceAllocationStrategy::Pooling(pooling) = &mut self.wasmtime.strategy { |
74 | | // One single-page memory |
75 | 2.67k | pooling.total_memories = config.max_memories as u32; |
76 | 2.67k | pooling.max_memory_size = 10 << 16; |
77 | 2.67k | pooling.max_memories_per_module = config.max_memories as u32; |
78 | 2.67k | if pooling.memory_protection_keys == Enabled::Auto |
79 | 1.31k | && pooling.max_memory_protection_keys > 1 |
80 | 1.10k | { |
81 | 1.10k | pooling.total_memories = |
82 | 1.10k | pooling.total_memories * (pooling.max_memory_protection_keys as u32); |
83 | 1.57k | } |
84 | | |
85 | 2.67k | pooling.total_tables = config.max_tables as u32; |
86 | 2.67k | pooling.table_elements = 1_000; |
87 | 2.67k | pooling.max_tables_per_module = config.max_tables as u32; |
88 | | |
89 | 2.67k | pooling.core_instance_size = 1_000_000; |
90 | | |
91 | 2.67k | let cfg = &mut self.wasmtime.memory_config; |
92 | 2.67k | match &mut cfg.memory_reservation { |
93 | 2.42k | Some(size) => *size = (*size).max(pooling.max_memory_size as u64), |
94 | 253 | other @ None => *other = Some(pooling.max_memory_size as u64), |
95 | | } |
96 | 13.8k | } |
97 | | |
98 | | // These instructions are explicitly not expected to be exactly the same |
99 | | // across engines. Don't fuzz them. |
100 | 16.5k | config.relaxed_simd_enabled = false; |
101 | 16.5k | } |
102 | | |
103 | | /// Uses this configuration and the supplied source of data to generate |
104 | | /// a wasm module. |
105 | | /// |
106 | | /// If a `default_fuel` is provided, the resulting module will be configured |
107 | | /// to ensure termination; as doing so will add an additional global to the module, |
108 | | /// the pooling allocator, if configured, will also have its globals limit updated. |
109 | 68.1k | pub fn generate( |
110 | 68.1k | &self, |
111 | 68.1k | input: &mut Unstructured<'_>, |
112 | 68.1k | default_fuel: Option<u32>, |
113 | 68.1k | ) -> arbitrary::Result<wasm_smith::Module> { |
114 | 68.1k | self.module_config.generate(input, default_fuel) |
115 | 68.1k | } |
116 | | |
117 | | /// Updates this configuration to be able to run the `test` specified. |
118 | | /// |
119 | | /// This primarily updates `self.module_config` to ensure that it enables |
120 | | /// all features and proposals necessary to execute the `test` specified. |
121 | | /// This will additionally update limits in the pooling allocator to be able |
122 | | /// to execute all tests. |
123 | 183 | pub fn make_wast_test_compliant(&mut self, test: &WastTest) -> WastConfig { |
124 | | let wasmtime_test_util::wast::TestConfig { |
125 | 183 | memory64, |
126 | 183 | custom_page_sizes, |
127 | 183 | multi_memory, |
128 | 183 | threads, |
129 | 183 | shared_everything_threads, |
130 | 183 | gc, |
131 | 183 | function_references, |
132 | 183 | relaxed_simd, |
133 | 183 | reference_types, |
134 | 183 | tail_call, |
135 | 183 | extended_const, |
136 | 183 | wide_arithmetic, |
137 | 183 | component_model_async, |
138 | 183 | component_model_async_builtins, |
139 | 183 | component_model_async_stackful, |
140 | 183 | component_model_threading, |
141 | 183 | component_model_error_context, |
142 | 183 | component_model_gc, |
143 | 183 | component_model_fixed_length_lists, |
144 | 183 | simd, |
145 | 183 | exceptions, |
146 | | legacy_exceptions: _, |
147 | | custom_descriptors: _, |
148 | | |
149 | | hogs_memory: _, |
150 | | nan_canonicalization: _, |
151 | | gc_types: _, |
152 | | stack_switching: _, |
153 | | spec_test: _, |
154 | 183 | } = test.config; |
155 | | |
156 | | // Enable/disable some proposals that aren't configurable in wasm-smith |
157 | | // but are configurable in Wasmtime. |
158 | 183 | self.module_config.function_references_enabled = |
159 | 183 | function_references.or(gc).unwrap_or(false); |
160 | 183 | self.module_config.component_model_async = component_model_async.unwrap_or(false); |
161 | 183 | self.module_config.component_model_async_builtins = |
162 | 183 | component_model_async_builtins.unwrap_or(false); |
163 | 183 | self.module_config.component_model_async_stackful = |
164 | 183 | component_model_async_stackful.unwrap_or(false); |
165 | 183 | self.module_config.component_model_threading = component_model_threading.unwrap_or(false); |
166 | 183 | self.module_config.component_model_error_context = |
167 | 183 | component_model_error_context.unwrap_or(false); |
168 | 183 | self.module_config.component_model_gc = component_model_gc.unwrap_or(false); |
169 | 183 | self.module_config.component_model_fixed_length_lists = |
170 | 183 | component_model_fixed_length_lists.unwrap_or(false); |
171 | | |
172 | | // Enable/disable proposals that wasm-smith has knobs for which will be |
173 | | // read when creating `wasmtime::Config`. |
174 | 183 | let config = &mut self.module_config.config; |
175 | 183 | config.bulk_memory_enabled = true; |
176 | 183 | config.multi_value_enabled = true; |
177 | 183 | config.wide_arithmetic_enabled = wide_arithmetic.unwrap_or(false); |
178 | 183 | config.memory64_enabled = memory64.unwrap_or(false); |
179 | 183 | config.relaxed_simd_enabled = relaxed_simd.unwrap_or(false); |
180 | 183 | config.simd_enabled = config.relaxed_simd_enabled || simd.unwrap_or(false); |
181 | 183 | config.tail_call_enabled = tail_call.unwrap_or(false); |
182 | 183 | config.custom_page_sizes_enabled = custom_page_sizes.unwrap_or(false); |
183 | 183 | config.threads_enabled = threads.unwrap_or(false); |
184 | 183 | config.shared_everything_threads_enabled = shared_everything_threads.unwrap_or(false); |
185 | 183 | config.gc_enabled = gc.unwrap_or(false); |
186 | 183 | config.reference_types_enabled = config.gc_enabled |
187 | 88 | || self.module_config.function_references_enabled |
188 | 88 | || reference_types.unwrap_or(false); |
189 | 183 | config.extended_const_enabled = extended_const.unwrap_or(false); |
190 | 183 | config.exceptions_enabled = exceptions.unwrap_or(false); |
191 | 183 | if multi_memory.unwrap_or(false) { |
192 | 111 | config.max_memories = limits::MEMORIES_PER_MODULE as usize; |
193 | 111 | } else { |
194 | 72 | config.max_memories = 1; |
195 | 72 | } |
196 | | |
197 | 183 | if let Some(n) = &mut self.wasmtime.memory_config.memory_reservation { |
198 | 83 | *n = (*n).max(limits::MEMORY_SIZE as u64); |
199 | 100 | } |
200 | | |
201 | | // FIXME: it might be more ideal to avoid the need for this entirely |
202 | | // and to just let the test fail. If a test fails due to a pooling |
203 | | // allocator resource limit being met we could ideally detect that and |
204 | | // let the fuzz test case pass. That would avoid the need to hardcode |
205 | | // so much here and in theory wouldn't reduce the usefulness of fuzzers |
206 | | // all that much. At this time though we can't easily test this configuration. |
207 | 183 | if let InstanceAllocationStrategy::Pooling(pooling) = &mut self.wasmtime.strategy { |
208 | 60 | // Clamp protection keys between 1 & 2 to reduce the number of |
209 | 60 | // slots and then multiply the total memories by the number of keys |
210 | 60 | // we have since a single store has access to only one key. |
211 | 60 | pooling.max_memory_protection_keys = pooling.max_memory_protection_keys.max(1).min(2); |
212 | 60 | pooling.total_memories = pooling |
213 | 60 | .total_memories |
214 | 60 | .max(limits::MEMORIES * (pooling.max_memory_protection_keys as u32)); |
215 | 60 | |
216 | 60 | // For other limits make sure they meet the minimum threshold |
217 | 60 | // required for our wast tests. |
218 | 60 | pooling.total_component_instances = pooling |
219 | 60 | .total_component_instances |
220 | 60 | .max(limits::COMPONENT_INSTANCES); |
221 | 60 | pooling.total_tables = pooling.total_tables.max(limits::TABLES); |
222 | 60 | pooling.max_tables_per_module = |
223 | 60 | pooling.max_tables_per_module.max(limits::TABLES_PER_MODULE); |
224 | 60 | pooling.max_memories_per_module = pooling |
225 | 60 | .max_memories_per_module |
226 | 60 | .max(limits::MEMORIES_PER_MODULE); |
227 | 60 | pooling.max_memories_per_component = pooling |
228 | 60 | .max_memories_per_component |
229 | 60 | .max(limits::MEMORIES_PER_MODULE); |
230 | 60 | pooling.total_core_instances = pooling.total_core_instances.max(limits::CORE_INSTANCES); |
231 | 60 | pooling.max_memory_size = pooling.max_memory_size.max(limits::MEMORY_SIZE); |
232 | 60 | pooling.table_elements = pooling.table_elements.max(limits::TABLE_ELEMENTS); |
233 | 60 | pooling.core_instance_size = pooling.core_instance_size.max(limits::CORE_INSTANCE_SIZE); |
234 | 60 | pooling.component_instance_size = pooling |
235 | 60 | .component_instance_size |
236 | 60 | .max(limits::CORE_INSTANCE_SIZE); |
237 | 60 | pooling.total_stacks = pooling.total_stacks.max(limits::TOTAL_STACKS); |
238 | 123 | } |
239 | | |
240 | | // Return the test configuration that this fuzz configuration represents |
241 | | // which is used afterwards to test if the `test` here is expected to |
242 | | // fail or not. |
243 | | WastConfig { |
244 | 183 | collector: match self.wasmtime.collector { |
245 | 50 | Collector::Null => wasmtime_test_util::wast::Collector::Null, |
246 | | Collector::DeferredReferenceCounting => { |
247 | 133 | wasmtime_test_util::wast::Collector::DeferredReferenceCounting |
248 | | } |
249 | | }, |
250 | 123 | pooling: matches!( |
251 | 183 | self.wasmtime.strategy, |
252 | | InstanceAllocationStrategy::Pooling(_) |
253 | | ), |
254 | 183 | compiler: match self.wasmtime.compiler_strategy { |
255 | | CompilerStrategy::CraneliftNative => { |
256 | 117 | wasmtime_test_util::wast::Compiler::CraneliftNative |
257 | | } |
258 | | CompilerStrategy::CraneliftPulley => { |
259 | 53 | wasmtime_test_util::wast::Compiler::CraneliftPulley |
260 | | } |
261 | 13 | CompilerStrategy::Winch => wasmtime_test_util::wast::Compiler::Winch, |
262 | | }, |
263 | | } |
264 | 183 | } |
265 | | |
266 | | /// Converts this to a `wasmtime::Config` object |
267 | 91.5k | pub fn to_wasmtime(&self) -> wasmtime::Config { |
268 | 91.5k | crate::init_fuzzing(); |
269 | | |
270 | 91.5k | let mut cfg = wasmtime_cli_flags::CommonOptions::default(); |
271 | | cfg.codegen.native_unwind_info = |
272 | 91.5k | Some(cfg!(target_os = "windows") || self.wasmtime.native_unwind_info); |
273 | 91.5k | cfg.codegen.parallel_compilation = Some(false); |
274 | | |
275 | 91.5k | cfg.debug.address_map = Some(self.wasmtime.generate_address_map); |
276 | 91.5k | cfg.opts.opt_level = Some(self.wasmtime.opt_level.to_wasmtime()); |
277 | 91.5k | cfg.opts.regalloc_algorithm = Some(self.wasmtime.regalloc_algorithm.to_wasmtime()); |
278 | 91.5k | cfg.opts.signals_based_traps = Some(self.wasmtime.signals_based_traps); |
279 | 91.5k | cfg.opts.memory_guaranteed_dense_image_size = Some(std::cmp::min( |
280 | 91.5k | // Clamp this at 16MiB so we don't get huge in-memory |
281 | 91.5k | // images during fuzzing. |
282 | 91.5k | 16 << 20, |
283 | 91.5k | self.wasmtime.memory_guaranteed_dense_image_size, |
284 | 91.5k | )); |
285 | 91.5k | cfg.wasm.async_stack_zeroing = Some(self.wasmtime.async_stack_zeroing); |
286 | 91.5k | cfg.wasm.bulk_memory = Some(true); |
287 | 91.5k | cfg.wasm.component_model_async = Some(self.module_config.component_model_async); |
288 | 91.5k | cfg.wasm.component_model_async_builtins = |
289 | 91.5k | Some(self.module_config.component_model_async_builtins); |
290 | 91.5k | cfg.wasm.component_model_async_stackful = |
291 | 91.5k | Some(self.module_config.component_model_async_stackful); |
292 | 91.5k | cfg.wasm.component_model_threading = Some(self.module_config.component_model_threading); |
293 | 91.5k | cfg.wasm.component_model_error_context = |
294 | 91.5k | Some(self.module_config.component_model_error_context); |
295 | 91.5k | cfg.wasm.component_model_gc = Some(self.module_config.component_model_gc); |
296 | 91.5k | cfg.wasm.component_model_fixed_length_lists = |
297 | 91.5k | Some(self.module_config.component_model_fixed_length_lists); |
298 | 91.5k | cfg.wasm.custom_page_sizes = Some(self.module_config.config.custom_page_sizes_enabled); |
299 | 91.5k | cfg.wasm.epoch_interruption = Some(self.wasmtime.epoch_interruption); |
300 | 91.5k | cfg.wasm.extended_const = Some(self.module_config.config.extended_const_enabled); |
301 | 91.5k | cfg.wasm.fuel = self.wasmtime.consume_fuel.then(|| u64::MAX); |
302 | 91.5k | cfg.wasm.function_references = Some(self.module_config.function_references_enabled); |
303 | 91.5k | cfg.wasm.gc = Some(self.module_config.config.gc_enabled); |
304 | 91.5k | cfg.wasm.memory64 = Some(self.module_config.config.memory64_enabled); |
305 | 91.5k | cfg.wasm.multi_memory = Some(self.module_config.config.max_memories > 1); |
306 | 91.5k | cfg.wasm.multi_value = Some(self.module_config.config.multi_value_enabled); |
307 | 91.5k | cfg.wasm.nan_canonicalization = Some(self.wasmtime.canonicalize_nans); |
308 | 91.5k | cfg.wasm.reference_types = Some(self.module_config.config.reference_types_enabled); |
309 | 91.5k | cfg.wasm.simd = Some(self.module_config.config.simd_enabled); |
310 | 91.5k | cfg.wasm.tail_call = Some(self.module_config.config.tail_call_enabled); |
311 | 91.5k | cfg.wasm.threads = Some(self.module_config.config.threads_enabled); |
312 | 91.5k | cfg.wasm.shared_everything_threads = |
313 | 91.5k | Some(self.module_config.config.shared_everything_threads_enabled); |
314 | 91.5k | cfg.wasm.wide_arithmetic = Some(self.module_config.config.wide_arithmetic_enabled); |
315 | 91.5k | cfg.wasm.exceptions = Some(self.module_config.config.exceptions_enabled); |
316 | 91.5k | cfg.wasm.shared_memory = Some(self.module_config.shared_memory); |
317 | 91.5k | if !self.module_config.config.simd_enabled { |
318 | 39.9k | cfg.wasm.relaxed_simd = Some(false); |
319 | 51.6k | } |
320 | 91.5k | cfg.codegen.collector = Some(self.wasmtime.collector.to_wasmtime()); |
321 | | |
322 | 91.5k | let compiler_strategy = &self.wasmtime.compiler_strategy; |
323 | 91.5k | let cranelift_strategy = match compiler_strategy { |
324 | 84.1k | CompilerStrategy::CraneliftNative | CompilerStrategy::CraneliftPulley => true, |
325 | 7.37k | CompilerStrategy::Winch => false, |
326 | | }; |
327 | 91.5k | self.wasmtime.compiler_strategy.configure(&mut cfg); |
328 | | |
329 | 91.5k | self.wasmtime.codegen.configure(&mut cfg); |
330 | | |
331 | | // Determine whether we will actually enable PCC -- this is |
332 | | // disabled if the module requires memory64, which is not yet |
333 | | // compatible (due to the need for dynamic checks). |
334 | 91.5k | let pcc = cfg!(feature = "fuzz-pcc") |
335 | 0 | && self.wasmtime.pcc |
336 | 0 | && !self.module_config.config.memory64_enabled; |
337 | | |
338 | 91.5k | cfg.codegen.inlining = self.wasmtime.inlining; |
339 | | |
340 | | // Only set cranelift specific flags when the Cranelift strategy is |
341 | | // chosen. |
342 | 91.5k | if cranelift_strategy { |
343 | 84.1k | if let Some(option) = self.wasmtime.inlining_intra_module { |
344 | 44.1k | cfg.codegen.cranelift.push(( |
345 | 44.1k | "wasmtime_inlining_intra_module".to_string(), |
346 | 44.1k | Some(option.to_string()), |
347 | 44.1k | )); |
348 | 44.1k | } |
349 | 84.1k | if let Some(size) = self.wasmtime.inlining_small_callee_size { |
350 | 40.5k | cfg.codegen.cranelift.push(( |
351 | 40.5k | "wasmtime_inlining_small_callee_size".to_string(), |
352 | 40.5k | // Clamp to avoid extreme code size blow up. |
353 | 40.5k | Some(std::cmp::min(1000, size).to_string()), |
354 | 40.5k | )); |
355 | 43.6k | } |
356 | 84.1k | if let Some(size) = self.wasmtime.inlining_sum_size_threshold { |
357 | 42.1k | cfg.codegen.cranelift.push(( |
358 | 42.1k | "wasmtime_inlining_sum_size_threshold".to_string(), |
359 | 42.1k | // Clamp to avoid extreme code size blow up. |
360 | 42.1k | Some(std::cmp::min(1000, size).to_string()), |
361 | 42.1k | )); |
362 | 42.1k | } |
363 | | |
364 | | // If the wasm-smith-generated module use nan canonicalization then we |
365 | | // don't need to enable it, but if it doesn't enable it already then we |
366 | | // enable this codegen option. |
367 | 84.1k | cfg.wasm.nan_canonicalization = Some(!self.module_config.config.canonicalize_nans); |
368 | | |
369 | | // Enabling the verifier will at-least-double compilation time, which |
370 | | // with a 20-30x slowdown in fuzzing can cause issues related to |
371 | | // timeouts. If generated modules can have more than a small handful of |
372 | | // functions then disable the verifier when fuzzing to try to lessen the |
373 | | // impact of timeouts. |
374 | 84.1k | if self.module_config.config.max_funcs > 10 { |
375 | 80.0k | cfg.codegen.cranelift_debug_verifier = Some(false); |
376 | 80.0k | } |
377 | | |
378 | 84.1k | if self.wasmtime.force_jump_veneers { |
379 | 41.2k | cfg.codegen.cranelift.push(( |
380 | 41.2k | "wasmtime_linkopt_force_jump_veneer".to_string(), |
381 | 41.2k | Some("true".to_string()), |
382 | 41.2k | )); |
383 | 42.9k | } |
384 | | |
385 | 84.1k | if let Some(pad) = self.wasmtime.padding_between_functions { |
386 | 46.6k | cfg.codegen.cranelift.push(( |
387 | 46.6k | "wasmtime_linkopt_padding_between_functions".to_string(), |
388 | 46.6k | Some(pad.to_string()), |
389 | 46.6k | )); |
390 | 46.6k | } |
391 | | |
392 | 84.1k | cfg.codegen.pcc = Some(pcc); |
393 | | |
394 | | // Eager init is currently only supported on Cranelift, not Winch. |
395 | 84.1k | cfg.opts.table_lazy_init = Some(self.wasmtime.table_lazy_init); |
396 | 7.37k | } |
397 | | |
398 | 91.5k | self.wasmtime.strategy.configure(&mut cfg); |
399 | | |
400 | | // Vary the memory configuration, but only if threads are not enabled. |
401 | | // When the threads proposal is enabled we might generate shared memory, |
402 | | // which is less amenable to different memory configurations: |
403 | | // - shared memories are required to be "static" so fuzzing the various |
404 | | // memory configurations will mostly result in uninteresting errors. |
405 | | // The interesting part about shared memories is the runtime so we |
406 | | // don't fuzz non-default settings. |
407 | | // - shared memories are required to be aligned which means that the |
408 | | // `CustomUnaligned` variant isn't actually safe to use with a shared |
409 | | // memory. |
410 | 91.5k | if !self.module_config.config.threads_enabled { |
411 | | // If PCC is enabled, force other options to be compatible: PCC is currently only |
412 | | // supported when bounds checks are elided. |
413 | 91.5k | let memory_config = if pcc { |
414 | 0 | MemoryConfig { |
415 | 0 | memory_reservation: Some(4 << 30), // 4 GiB |
416 | 0 | memory_guard_size: Some(2 << 30), // 2 GiB |
417 | 0 | memory_reservation_for_growth: Some(0), |
418 | 0 | guard_before_linear_memory: false, |
419 | 0 | memory_init_cow: true, |
420 | 0 | // Doesn't matter, only using virtual memory. |
421 | 0 | cranelift_enable_heap_access_spectre_mitigations: None, |
422 | 0 | } |
423 | | } else { |
424 | 91.5k | self.wasmtime.memory_config.clone() |
425 | | }; |
426 | | |
427 | 91.5k | memory_config.configure(&mut cfg); |
428 | 10 | }; |
429 | | |
430 | | // If malloc-based memory is going to be used, which requires these four |
431 | | // options set to specific values (and Pulley auto-sets two of them) |
432 | | // then be sure to cap `memory_reservation_for_growth` at a smaller |
433 | | // value than the default. For malloc-based memory reservation beyond |
434 | | // the end of memory isn't captured by `StoreLimiter` so we need to be |
435 | | // sure it's small enough to not blow OOM limits while fuzzing. |
436 | 91.5k | if ((cfg.opts.signals_based_traps == Some(true) && cfg.opts.memory_guard_size == Some(0)) |
437 | 86.3k | || self.wasmtime.compiler_strategy == CompilerStrategy::CraneliftPulley) |
438 | 20.5k | && cfg.opts.memory_reservation == Some(0) |
439 | 1.90k | && cfg.opts.memory_init_cow == Some(false) |
440 | | { |
441 | 865 | let growth = &mut cfg.opts.memory_reservation_for_growth; |
442 | 865 | let max = 1 << 20; |
443 | 865 | *growth = match *growth { |
444 | 245 | Some(n) => Some(n.min(max)), |
445 | 620 | None => Some(max), |
446 | | }; |
447 | 90.6k | } |
448 | | |
449 | 91.5k | log::debug!("creating wasmtime config with CLI options:\n{cfg}"); |
450 | 91.5k | let mut cfg = cfg.config(None).expect("failed to create wasmtime::Config"); |
451 | | |
452 | 91.5k | if self.wasmtime.async_config != AsyncConfig::Disabled { |
453 | 27.7k | log::debug!("async config in use {:?}", self.wasmtime.async_config); |
454 | 27.7k | self.wasmtime.async_config.configure(&mut cfg); |
455 | 63.7k | } |
456 | | |
457 | | // Fuzzing on macOS with mach ports seems to sometimes bypass the mach |
458 | | // port handling thread entirely and go straight to asan's or fuzzing's |
459 | | // signal handler. No idea why and for me at least it's just easier to |
460 | | // disable mach ports when fuzzing because there's no need to use that |
461 | | // over signal handlers. |
462 | 91.5k | if cfg!(target_vendor = "apple") { |
463 | 0 | cfg.macos_use_mach_ports(false); |
464 | 91.5k | } |
465 | | |
466 | 91.5k | return cfg; |
467 | 91.5k | } |
468 | | |
469 | | /// Convenience function for generating a `Store<T>` using this |
470 | | /// configuration. |
471 | 68.8k | pub fn to_store(&self) -> Store<StoreLimits> { |
472 | 68.8k | let engine = Engine::new(&self.to_wasmtime()).unwrap(); |
473 | 68.8k | let mut store = Store::new(&engine, StoreLimits::new()); |
474 | 68.8k | self.configure_store(&mut store); |
475 | 68.8k | store |
476 | 68.8k | } |
477 | | |
478 | | /// Configures a store based on this configuration. |
479 | 326k | pub fn configure_store(&self, store: &mut Store<StoreLimits>) { |
480 | 38.3M | store.limiter(|s| s as &mut dyn wasmtime::ResourceLimiter); |
481 | 326k | self.configure_store_epoch_and_fuel(store); |
482 | 326k | } |
483 | | |
484 | | /// Configures everything unrelated to `T` in a store, such as epochs and |
485 | | /// fuel. |
486 | 336k | pub fn configure_store_epoch_and_fuel<T>(&self, store: &mut Store<T>) { |
487 | | // Configure the store to never abort by default, that is it'll have |
488 | | // max fuel or otherwise trap on an epoch change but the epoch won't |
489 | | // ever change. |
490 | | // |
491 | | // Afterwards though see what `AsyncConfig` is being used an further |
492 | | // refine the store's configuration based on that. |
493 | 336k | if self.wasmtime.consume_fuel { |
494 | 178k | store.set_fuel(u64::MAX).unwrap(); |
495 | 178k | } |
496 | 336k | if self.wasmtime.epoch_interruption { |
497 | 171k | store.epoch_deadline_trap(); |
498 | 171k | store.set_epoch_deadline(1); |
499 | 171k | } |
500 | 336k | match self.wasmtime.async_config { |
501 | 308k | AsyncConfig::Disabled => {} |
502 | 21.0k | AsyncConfig::YieldWithFuel(amt) => { |
503 | 21.0k | assert!(self.wasmtime.consume_fuel); |
504 | 21.0k | store.fuel_async_yield_interval(Some(amt)).unwrap(); |
505 | | } |
506 | 6.97k | AsyncConfig::YieldWithEpochs { ticks, .. } => { |
507 | 6.97k | assert!(self.wasmtime.epoch_interruption); |
508 | 6.97k | store.set_epoch_deadline(ticks); |
509 | 6.97k | store.epoch_deadline_async_yield_and_update(ticks); |
510 | | } |
511 | | } |
512 | 336k | } <wasmtime_fuzzing::generators::config::Config>::configure_store_epoch_and_fuel::<wasmtime_fuzzing::oracles::StoreLimits> Line | Count | Source | 486 | 326k | pub fn configure_store_epoch_and_fuel<T>(&self, store: &mut Store<T>) { | 487 | | // Configure the store to never abort by default, that is it'll have | 488 | | // max fuel or otherwise trap on an epoch change but the epoch won't | 489 | | // ever change. | 490 | | // | 491 | | // Afterwards though see what `AsyncConfig` is being used an further | 492 | | // refine the store's configuration based on that. | 493 | 326k | if self.wasmtime.consume_fuel { | 494 | 171k | store.set_fuel(u64::MAX).unwrap(); | 495 | 171k | } | 496 | 326k | if self.wasmtime.epoch_interruption { | 497 | 164k | store.epoch_deadline_trap(); | 498 | 164k | store.set_epoch_deadline(1); | 499 | 164k | } | 500 | 326k | match self.wasmtime.async_config { | 501 | 308k | AsyncConfig::Disabled => {} | 502 | 13.9k | AsyncConfig::YieldWithFuel(amt) => { | 503 | 13.9k | assert!(self.wasmtime.consume_fuel); | 504 | 13.9k | store.fuel_async_yield_interval(Some(amt)).unwrap(); | 505 | | } | 506 | 4.61k | AsyncConfig::YieldWithEpochs { ticks, .. } => { | 507 | 4.61k | assert!(self.wasmtime.epoch_interruption); | 508 | 4.61k | store.set_epoch_deadline(ticks); | 509 | 4.61k | store.epoch_deadline_async_yield_and_update(ticks); | 510 | | } | 511 | | } | 512 | 326k | } |
<wasmtime_fuzzing::generators::config::Config>::configure_store_epoch_and_fuel::<(alloc::vec::Vec<wasmtime::runtime::component::values::Val>, core::option::Option<alloc::vec::Vec<wasmtime::runtime::component::values::Val>>)> Line | Count | Source | 486 | 5.30k | pub fn configure_store_epoch_and_fuel<T>(&self, store: &mut Store<T>) { | 487 | | // Configure the store to never abort by default, that is it'll have | 488 | | // max fuel or otherwise trap on an epoch change but the epoch won't | 489 | | // ever change. | 490 | | // | 491 | | // Afterwards though see what `AsyncConfig` is being used an further | 492 | | // refine the store's configuration based on that. | 493 | 5.30k | if self.wasmtime.consume_fuel { | 494 | 3.84k | store.set_fuel(u64::MAX).unwrap(); | 495 | 3.84k | } | 496 | 5.30k | if self.wasmtime.epoch_interruption { | 497 | 3.80k | store.epoch_deadline_trap(); | 498 | 3.80k | store.set_epoch_deadline(1); | 499 | 3.80k | } | 500 | 5.30k | match self.wasmtime.async_config { | 501 | 0 | AsyncConfig::Disabled => {} | 502 | 3.84k | AsyncConfig::YieldWithFuel(amt) => { | 503 | 3.84k | assert!(self.wasmtime.consume_fuel); | 504 | 3.84k | store.fuel_async_yield_interval(Some(amt)).unwrap(); | 505 | | } | 506 | 1.45k | AsyncConfig::YieldWithEpochs { ticks, .. } => { | 507 | 1.45k | assert!(self.wasmtime.epoch_interruption); | 508 | 1.45k | store.set_epoch_deadline(ticks); | 509 | 1.45k | store.epoch_deadline_async_yield_and_update(ticks); | 510 | | } | 511 | | } | 512 | 5.30k | } |
<wasmtime_fuzzing::generators::config::Config>::configure_store_epoch_and_fuel::<()> Line | Count | Source | 486 | 462 | pub fn configure_store_epoch_and_fuel<T>(&self, store: &mut Store<T>) { | 487 | | // Configure the store to never abort by default, that is it'll have | 488 | | // max fuel or otherwise trap on an epoch change but the epoch won't | 489 | | // ever change. | 490 | | // | 491 | | // Afterwards though see what `AsyncConfig` is being used an further | 492 | | // refine the store's configuration based on that. | 493 | 462 | if self.wasmtime.consume_fuel { | 494 | 338 | store.set_fuel(u64::MAX).unwrap(); | 495 | 338 | } | 496 | 462 | if self.wasmtime.epoch_interruption { | 497 | 202 | store.epoch_deadline_trap(); | 498 | 202 | store.set_epoch_deadline(1); | 499 | 260 | } | 500 | 462 | match self.wasmtime.async_config { | 501 | 123 | AsyncConfig::Disabled => {} | 502 | 312 | AsyncConfig::YieldWithFuel(amt) => { | 503 | 312 | assert!(self.wasmtime.consume_fuel); | 504 | 312 | store.fuel_async_yield_interval(Some(amt)).unwrap(); | 505 | | } | 506 | 27 | AsyncConfig::YieldWithEpochs { ticks, .. } => { | 507 | 27 | assert!(self.wasmtime.epoch_interruption); | 508 | 27 | store.set_epoch_deadline(ticks); | 509 | 27 | store.epoch_deadline_async_yield_and_update(ticks); | 510 | | } | 511 | | } | 512 | 462 | } |
<wasmtime_fuzzing::generators::config::Config>::configure_store_epoch_and_fuel::<alloc::boxed::Box<dyn core::any::Any + core::marker::Send>> Line | Count | Source | 486 | 3.79k | pub fn configure_store_epoch_and_fuel<T>(&self, store: &mut Store<T>) { | 487 | | // Configure the store to never abort by default, that is it'll have | 488 | | // max fuel or otherwise trap on an epoch change but the epoch won't | 489 | | // ever change. | 490 | | // | 491 | | // Afterwards though see what `AsyncConfig` is being used an further | 492 | | // refine the store's configuration based on that. | 493 | 3.79k | if self.wasmtime.consume_fuel { | 494 | 2.92k | store.set_fuel(u64::MAX).unwrap(); | 495 | 2.92k | } | 496 | 3.79k | if self.wasmtime.epoch_interruption { | 497 | 2.57k | store.epoch_deadline_trap(); | 498 | 2.57k | store.set_epoch_deadline(1); | 499 | 2.57k | } | 500 | 3.79k | match self.wasmtime.async_config { | 501 | 0 | AsyncConfig::Disabled => {} | 502 | 2.92k | AsyncConfig::YieldWithFuel(amt) => { | 503 | 2.92k | assert!(self.wasmtime.consume_fuel); | 504 | 2.92k | store.fuel_async_yield_interval(Some(amt)).unwrap(); | 505 | | } | 506 | 872 | AsyncConfig::YieldWithEpochs { ticks, .. } => { | 507 | 872 | assert!(self.wasmtime.epoch_interruption); | 508 | 872 | store.set_epoch_deadline(ticks); | 509 | 872 | store.epoch_deadline_async_yield_and_update(ticks); | 510 | | } | 511 | | } | 512 | 3.79k | } |
|
513 | | |
514 | | /// Generates an arbitrary method of timing out an instance, ensuring that |
515 | | /// this configuration supports the returned timeout. |
516 | 12.1k | pub fn generate_timeout(&mut self, u: &mut Unstructured<'_>) -> arbitrary::Result<Timeout> { |
517 | 12.1k | let time_duration = Duration::from_millis(100); |
518 | 12.1k | let timeout = u |
519 | 12.1k | .choose(&[Timeout::Fuel(100_000), Timeout::Epoch(time_duration)])? |
520 | 12.1k | .clone(); |
521 | 12.1k | match &timeout { |
522 | 4.77k | Timeout::Fuel(..) => { |
523 | 4.77k | self.wasmtime.consume_fuel = true; |
524 | 4.77k | } |
525 | 7.35k | Timeout::Epoch(..) => { |
526 | 7.35k | self.wasmtime.epoch_interruption = true; |
527 | 7.35k | } |
528 | 0 | Timeout::None => unreachable!("Not an option given to choose()"), |
529 | | } |
530 | 12.1k | Ok(timeout) |
531 | 12.1k | } |
532 | | |
533 | | /// Compiles the `wasm` within the `engine` provided. |
534 | | /// |
535 | | /// This notably will use `Module::{serialize,deserialize_file}` to |
536 | | /// round-trip if configured in the fuzzer. |
537 | 92.6k | pub fn compile(&self, engine: &Engine, wasm: &[u8]) -> Result<Module> { |
538 | | // Propagate this error in case the caller wants to handle |
539 | | // valid-vs-invalid wasm. |
540 | 92.6k | let module = Module::new(engine, wasm)?; |
541 | 88.9k | if !self.wasmtime.use_precompiled_cwasm { |
542 | 45.7k | return Ok(module); |
543 | 43.1k | } |
544 | | |
545 | | // Don't propagate these errors to prevent them from accidentally being |
546 | | // interpreted as invalid wasm, these should never fail on a |
547 | | // well-behaved host system. |
548 | 43.1k | let dir = tempfile::TempDir::new().unwrap(); |
549 | 43.1k | let file = dir.path().join("module.wasm"); |
550 | 43.1k | std::fs::write(&file, module.serialize().unwrap()).unwrap(); |
551 | 43.1k | unsafe { Ok(Module::deserialize_file(engine, &file).unwrap()) } |
552 | 92.6k | } |
553 | | |
554 | | /// Updates this configuration to forcibly enable async support. Only useful |
555 | | /// in fuzzers which do async calls. |
556 | 28.0k | pub fn enable_async(&mut self, u: &mut Unstructured<'_>) -> arbitrary::Result<()> { |
557 | 28.0k | if self.wasmtime.consume_fuel || u.arbitrary()? { |
558 | | self.wasmtime.async_config = |
559 | 20.9k | AsyncConfig::YieldWithFuel(u.int_in_range(1000..=100_000)?); |
560 | 20.9k | self.wasmtime.consume_fuel = true; |
561 | | } else { |
562 | | self.wasmtime.async_config = AsyncConfig::YieldWithEpochs { |
563 | 7.02k | dur: Duration::from_millis(u.int_in_range(1..=10)?), |
564 | 7.02k | ticks: u.int_in_range(1..=10)?, |
565 | | }; |
566 | 7.02k | self.wasmtime.epoch_interruption = true; |
567 | | } |
568 | 28.0k | Ok(()) |
569 | 28.0k | } |
570 | | } |
571 | | |
572 | | impl<'a> Arbitrary<'a> for Config { |
573 | 84.7k | fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { |
574 | 83.2k | let mut config = Self { |
575 | 84.7k | wasmtime: u.arbitrary()?, |
576 | 83.2k | module_config: u.arbitrary()?, |
577 | | }; |
578 | | |
579 | 83.2k | config |
580 | 83.2k | .wasmtime |
581 | 83.2k | .update_module_config(&mut config.module_config, u)?; |
582 | | |
583 | 83.2k | Ok(config) |
584 | 84.7k | } |
585 | | } |
586 | | |
587 | | /// Configuration related to `wasmtime::Config` and the various settings which |
588 | | /// can be tweaked from within. |
589 | 0 | #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)] Unexecuted instantiation: <wasmtime_fuzzing::generators::config::WasmtimeConfig as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#1}Unexecuted instantiation: <wasmtime_fuzzing::generators::config::WasmtimeConfig as arbitrary::Arbitrary>::arbitrary::{closure#1}Unexecuted instantiation: <wasmtime_fuzzing::generators::config::WasmtimeConfig as arbitrary::Arbitrary>::try_size_hint::{closure#0} |
590 | | pub struct WasmtimeConfig { |
591 | | opt_level: OptLevel, |
592 | | regalloc_algorithm: RegallocAlgorithm, |
593 | | debug_info: bool, |
594 | | canonicalize_nans: bool, |
595 | | interruptible: bool, |
596 | | pub(crate) consume_fuel: bool, |
597 | | pub(crate) epoch_interruption: bool, |
598 | | /// The Wasmtime memory configuration to use. |
599 | | pub memory_config: MemoryConfig, |
600 | | force_jump_veneers: bool, |
601 | | memory_init_cow: bool, |
602 | | memory_guaranteed_dense_image_size: u64, |
603 | | inlining: Option<bool>, |
604 | | inlining_intra_module: Option<IntraModuleInlining>, |
605 | | inlining_small_callee_size: Option<u32>, |
606 | | inlining_sum_size_threshold: Option<u32>, |
607 | | use_precompiled_cwasm: bool, |
608 | | async_stack_zeroing: bool, |
609 | | /// Configuration for the instance allocation strategy to use. |
610 | | pub strategy: InstanceAllocationStrategy, |
611 | | codegen: CodegenSettings, |
612 | | padding_between_functions: Option<u16>, |
613 | | generate_address_map: bool, |
614 | | native_unwind_info: bool, |
615 | | /// Configuration for the compiler to use. |
616 | | pub compiler_strategy: CompilerStrategy, |
617 | | collector: Collector, |
618 | | table_lazy_init: bool, |
619 | | |
620 | | /// Whether or not fuzzing should enable PCC. |
621 | | pcc: bool, |
622 | | |
623 | | /// Configuration for whether wasm is invoked in an async fashion and how |
624 | | /// it's cooperatively time-sliced. |
625 | | pub async_config: AsyncConfig, |
626 | | |
627 | | /// Whether or not host signal handlers are enabled for this configuration, |
628 | | /// aka whether signal handlers are supported. |
629 | | signals_based_traps: bool, |
630 | | } |
631 | | |
632 | | impl WasmtimeConfig { |
633 | | /// Force `self` to be a configuration compatible with `other`. This is |
634 | | /// useful for differential execution to avoid unhelpful fuzz crashes when |
635 | | /// one engine has a feature enabled and the other does not. |
636 | 9.01k | pub fn make_compatible_with(&mut self, other: &Self) { |
637 | | // Use the same allocation strategy between the two configs. |
638 | | // |
639 | | // Ideally this wouldn't be necessary, but, during differential |
640 | | // evaluation, if the `lhs` is using ondemand and the `rhs` is using the |
641 | | // pooling allocator (or vice versa), then the module may have been |
642 | | // generated in such a way that is incompatible with the other |
643 | | // allocation strategy. |
644 | | // |
645 | | // We can remove this in the future when it's possible to access the |
646 | | // fields of `wasm_smith::Module` to constrain the pooling allocator |
647 | | // based on what was actually generated. |
648 | 9.01k | self.strategy = other.strategy.clone(); |
649 | 9.01k | if let InstanceAllocationStrategy::Pooling { .. } = &other.strategy { |
650 | 1.51k | // Also use the same memory configuration when using the pooling |
651 | 1.51k | // allocator. |
652 | 1.51k | self.memory_config = other.memory_config.clone(); |
653 | 7.50k | } |
654 | | |
655 | 9.01k | self.make_internally_consistent(); |
656 | 9.01k | } |
657 | | |
658 | | /// Updates `config` to be compatible with `self` and the other way around |
659 | | /// too. |
660 | 92.2k | pub fn update_module_config( |
661 | 92.2k | &mut self, |
662 | 92.2k | config: &mut ModuleConfig, |
663 | 92.2k | _u: &mut Unstructured<'_>, |
664 | 92.2k | ) -> arbitrary::Result<()> { |
665 | 92.2k | match self.compiler_strategy { |
666 | 68.3k | CompilerStrategy::CraneliftNative => {} |
667 | | |
668 | | CompilerStrategy::Winch => { |
669 | | // Winch is not complete on non-x64 targets, so just abandon this test |
670 | | // case. We don't want to force Cranelift because we change what module |
671 | | // config features are enabled based on the compiler strategy, and we |
672 | | // don't want to make the same fuzz input DNA generate different test |
673 | | // cases on different targets. |
674 | 7.73k | if cfg!(not(target_arch = "x86_64")) { |
675 | 0 | log::warn!( |
676 | 0 | "want to compile with Winch but host architecture does not support it" |
677 | | ); |
678 | 0 | return Err(arbitrary::Error::IncorrectFormat); |
679 | 7.73k | } |
680 | | |
681 | | // Winch doesn't support the same set of wasm proposal as Cranelift |
682 | | // at this time, so if winch is selected be sure to disable wasm |
683 | | // proposals in `Config` to ensure that Winch can compile the |
684 | | // module that wasm-smith generates. |
685 | 7.73k | config.config.relaxed_simd_enabled = false; |
686 | 7.73k | config.config.gc_enabled = false; |
687 | 7.73k | config.config.tail_call_enabled = false; |
688 | 7.73k | config.config.reference_types_enabled = false; |
689 | 7.73k | config.config.exceptions_enabled = false; |
690 | 7.73k | config.function_references_enabled = false; |
691 | | |
692 | | // Winch's SIMD implementations require AVX and AVX2. |
693 | 7.73k | if self |
694 | 7.73k | .codegen_flag("has_avx") |
695 | 7.73k | .is_some_and(|value| value == "false") |
696 | 7.27k | || self |
697 | 7.27k | .codegen_flag("has_avx2") |
698 | 7.27k | .is_some_and(|value| value == "false") |
699 | 651 | { |
700 | 651 | config.config.simd_enabled = false; |
701 | 7.07k | } |
702 | | |
703 | | // Tuning the following engine options is currently not supported |
704 | | // by Winch. |
705 | 7.73k | self.signals_based_traps = true; |
706 | 7.73k | self.table_lazy_init = true; |
707 | 7.73k | self.debug_info = false; |
708 | | } |
709 | | |
710 | 16.1k | CompilerStrategy::CraneliftPulley => { |
711 | 16.1k | config.config.threads_enabled = false; |
712 | 16.1k | } |
713 | | } |
714 | | |
715 | | // If using the pooling allocator, constrain the memory and module configurations |
716 | | // to the module limits. |
717 | 92.2k | if let InstanceAllocationStrategy::Pooling(pooling) = &mut self.strategy { |
718 | | // If the pooling allocator is used, do not allow shared memory to |
719 | | // be created. FIXME: see |
720 | | // https://github.com/bytecodealliance/wasmtime/issues/4244. |
721 | 19.7k | config.config.threads_enabled = false; |
722 | | |
723 | | // Ensure the pooling allocator can support the maximal size of |
724 | | // memory, picking the smaller of the two to win. |
725 | 19.7k | let min_bytes = config |
726 | 19.7k | .config |
727 | 19.7k | .max_memory32_bytes |
728 | | // memory64_bytes is a u128, but since we are taking the min |
729 | | // we can truncate it down to a u64. |
730 | 19.7k | .min( |
731 | 19.7k | config |
732 | 19.7k | .config |
733 | 19.7k | .max_memory64_bytes |
734 | 19.7k | .try_into() |
735 | 19.7k | .unwrap_or(u64::MAX), |
736 | | ); |
737 | 19.7k | let min = min_bytes |
738 | 19.7k | .min(pooling.max_memory_size as u64) |
739 | 19.7k | .min(self.memory_config.memory_reservation.unwrap_or(0)); |
740 | 19.7k | pooling.max_memory_size = min as usize; |
741 | 19.7k | config.config.max_memory32_bytes = min; |
742 | 19.7k | config.config.max_memory64_bytes = min as u128; |
743 | | |
744 | | // If traps are disallowed then memories must have at least one page |
745 | | // of memory so if we still are only allowing 0 pages of memory then |
746 | | // increase that to one here. |
747 | 19.7k | if config.config.disallow_traps { |
748 | 17.4k | if pooling.max_memory_size < (1 << 16) { |
749 | 14.9k | pooling.max_memory_size = 1 << 16; |
750 | 14.9k | config.config.max_memory32_bytes = 1 << 16; |
751 | 14.9k | config.config.max_memory64_bytes = 1 << 16; |
752 | 14.9k | let cfg = &mut self.memory_config; |
753 | 14.9k | match &mut cfg.memory_reservation { |
754 | 2.37k | Some(size) => *size = (*size).max(pooling.max_memory_size as u64), |
755 | 12.5k | size @ None => *size = Some(pooling.max_memory_size as u64), |
756 | | } |
757 | 2.53k | } |
758 | | // .. additionally update tables |
759 | 17.4k | if pooling.table_elements == 0 { |
760 | 536 | pooling.table_elements = 1; |
761 | 16.9k | } |
762 | 2.29k | } |
763 | | |
764 | | // Don't allow too many linear memories per instance since massive |
765 | | // virtual mappings can fail to get allocated. |
766 | 19.7k | config.config.min_memories = config.config.min_memories.min(10); |
767 | 19.7k | config.config.max_memories = config.config.max_memories.min(10); |
768 | | |
769 | | // Force this pooling allocator to always be able to accommodate the |
770 | | // module that may be generated. |
771 | 19.7k | pooling.total_memories = config.config.max_memories as u32; |
772 | 19.7k | pooling.total_tables = config.config.max_tables as u32; |
773 | 72.4k | } |
774 | | |
775 | 92.2k | if !self.signals_based_traps { |
776 | 40.5k | // At this time shared memories require a "static" memory |
777 | 40.5k | // configuration but when signals-based traps are disabled all |
778 | 40.5k | // memories are forced to the "dynamic" configuration. This is |
779 | 40.5k | // fixable with some more work on the bounds-checks side of things |
780 | 40.5k | // to do a full bounds check even on static memories, but that's |
781 | 40.5k | // left for a future PR. |
782 | 40.5k | config.config.threads_enabled = false; |
783 | 40.5k | |
784 | 40.5k | // Spectre-based heap mitigations require signal handlers so this |
785 | 40.5k | // must always be disabled if signals-based traps are disabled. |
786 | 40.5k | self.memory_config |
787 | 40.5k | .cranelift_enable_heap_access_spectre_mitigations = None; |
788 | 51.6k | } |
789 | | |
790 | 92.2k | self.make_internally_consistent(); |
791 | | |
792 | 92.2k | Ok(()) |
793 | 92.2k | } |
794 | | |
795 | | /// Returns the codegen flag value, if any, for `name`. |
796 | 15.0k | pub(crate) fn codegen_flag(&self, name: &str) -> Option<&str> { |
797 | 15.0k | self.codegen.flags().iter().find_map(|(n, value)| { |
798 | 13.4k | if n == name { |
799 | 1.81k | Some(value.as_str()) |
800 | | } else { |
801 | 11.5k | None |
802 | | } |
803 | 13.4k | }) |
804 | 15.0k | } |
805 | | |
806 | | /// Helper method to handle some dependencies between various configuration |
807 | | /// options. This is intended to be called whenever a `Config` is created or |
808 | | /// modified to ensure that the final result is an instantiable `Config`. |
809 | | /// |
810 | | /// Note that in general this probably shouldn't exist and anything here can |
811 | | /// be considered a "TODO" to go implement more stuff in Wasmtime to accept |
812 | | /// these sorts of configurations. For now though it's intended to reflect |
813 | | /// the current state of the engine's development. |
814 | 101k | fn make_internally_consistent(&mut self) { |
815 | 101k | if !self.signals_based_traps { |
816 | 44.1k | let cfg = &mut self.memory_config; |
817 | | // Spectre-based heap mitigations require signal handlers so |
818 | | // this must always be disabled if signals-based traps are |
819 | | // disabled. |
820 | 44.1k | cfg.cranelift_enable_heap_access_spectre_mitigations = None; |
821 | | |
822 | | // With configuration settings that match the use of malloc for |
823 | | // linear memories cap the `memory_reservation_for_growth` value |
824 | | // to something reasonable to avoid OOM in fuzzing. |
825 | 44.1k | if !cfg.memory_init_cow |
826 | 25.4k | && cfg.memory_guard_size == Some(0) |
827 | 1.90k | && cfg.memory_reservation == Some(0) |
828 | | { |
829 | 298 | let min = 10 << 20; // 10 MiB |
830 | 298 | if let Some(val) = &mut cfg.memory_reservation_for_growth { |
831 | 93 | *val = (*val).min(min); |
832 | 205 | } else { |
833 | 205 | cfg.memory_reservation_for_growth = Some(min); |
834 | 205 | } |
835 | 43.8k | } |
836 | 57.0k | } |
837 | 101k | } |
838 | | } |
839 | | |
840 | 0 | #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] Unexecuted instantiation: <wasmtime_fuzzing::generators::config::OptLevel as arbitrary::Arbitrary>::try_size_hint::{closure#0}Unexecuted instantiation: <wasmtime_fuzzing::generators::config::OptLevel as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#1}Unexecuted instantiation: <wasmtime_fuzzing::generators::config::OptLevel as arbitrary::Arbitrary>::arbitrary::{closure#1} |
841 | | enum OptLevel { |
842 | | None, |
843 | | Speed, |
844 | | SpeedAndSize, |
845 | | } |
846 | | |
847 | | impl OptLevel { |
848 | 91.5k | fn to_wasmtime(&self) -> wasmtime::OptLevel { |
849 | 91.5k | match self { |
850 | 45.9k | OptLevel::None => wasmtime::OptLevel::None, |
851 | 29.8k | OptLevel::Speed => wasmtime::OptLevel::Speed, |
852 | 15.6k | OptLevel::SpeedAndSize => wasmtime::OptLevel::SpeedAndSize, |
853 | | } |
854 | 91.5k | } |
855 | | } |
856 | | |
857 | 0 | #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] Unexecuted instantiation: <wasmtime_fuzzing::generators::config::RegallocAlgorithm as arbitrary::Arbitrary>::try_size_hint::{closure#0}Unexecuted instantiation: <wasmtime_fuzzing::generators::config::RegallocAlgorithm as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#1}Unexecuted instantiation: <wasmtime_fuzzing::generators::config::RegallocAlgorithm as arbitrary::Arbitrary>::arbitrary::{closure#1} |
858 | | enum RegallocAlgorithm { |
859 | | Backtracking, |
860 | | SinglePass, |
861 | | } |
862 | | |
863 | | impl RegallocAlgorithm { |
864 | 91.5k | fn to_wasmtime(&self) -> wasmtime::RegallocAlgorithm { |
865 | 91.5k | match self { |
866 | 71.9k | RegallocAlgorithm::Backtracking => wasmtime::RegallocAlgorithm::Backtracking, |
867 | | RegallocAlgorithm::SinglePass => { |
868 | | // FIXME(#11850) |
869 | | const SINGLE_PASS_KNOWN_BUGGY_AT_THIS_TIME: bool = true; |
870 | 19.6k | if SINGLE_PASS_KNOWN_BUGGY_AT_THIS_TIME { |
871 | 19.6k | wasmtime::RegallocAlgorithm::Backtracking |
872 | | } else { |
873 | 0 | wasmtime::RegallocAlgorithm::SinglePass |
874 | | } |
875 | | } |
876 | | } |
877 | 91.5k | } |
878 | | } |
879 | | |
880 | 0 | #[derive(Arbitrary, Clone, Copy, Debug, PartialEq, Eq, Hash)] Unexecuted instantiation: <wasmtime_fuzzing::generators::config::IntraModuleInlining as arbitrary::Arbitrary>::try_size_hint::{closure#0}Unexecuted instantiation: <wasmtime_fuzzing::generators::config::IntraModuleInlining as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#1}Unexecuted instantiation: <wasmtime_fuzzing::generators::config::IntraModuleInlining as arbitrary::Arbitrary>::arbitrary::{closure#1} |
881 | | enum IntraModuleInlining { |
882 | | Yes, |
883 | | No, |
884 | | WhenUsingGc, |
885 | | } |
886 | | |
887 | | impl std::fmt::Display for IntraModuleInlining { |
888 | 44.1k | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
889 | 44.1k | match self { |
890 | 22.6k | IntraModuleInlining::Yes => write!(f, "yes"), |
891 | 12.5k | IntraModuleInlining::No => write!(f, "no"), |
892 | 9.00k | IntraModuleInlining::WhenUsingGc => write!(f, "gc"), |
893 | | } |
894 | 44.1k | } |
895 | | } |
896 | | |
897 | | #[derive(Clone, Debug, PartialEq, Eq, Hash)] |
898 | | /// Compiler to use. |
899 | | pub enum CompilerStrategy { |
900 | | /// Cranelift compiler for the native architecture. |
901 | | CraneliftNative, |
902 | | /// Winch compiler. |
903 | | Winch, |
904 | | /// Cranelift compiler for the native architecture. |
905 | | CraneliftPulley, |
906 | | } |
907 | | |
908 | | impl CompilerStrategy { |
909 | | /// Configures `config` to use this compilation strategy |
910 | 91.5k | pub fn configure(&self, config: &mut wasmtime_cli_flags::CommonOptions) { |
911 | 91.5k | match self { |
912 | 68.0k | CompilerStrategy::CraneliftNative => { |
913 | 68.0k | config.codegen.compiler = Some(wasmtime::Strategy::Cranelift); |
914 | 68.0k | } |
915 | 7.37k | CompilerStrategy::Winch => { |
916 | 7.37k | config.codegen.compiler = Some(wasmtime::Strategy::Winch); |
917 | 7.37k | } |
918 | 16.1k | CompilerStrategy::CraneliftPulley => { |
919 | 16.1k | config.codegen.compiler = Some(wasmtime::Strategy::Cranelift); |
920 | 16.1k | config.target = Some("pulley64".to_string()); |
921 | 16.1k | } |
922 | | } |
923 | 91.5k | } |
924 | | } |
925 | | |
926 | | impl Arbitrary<'_> for CompilerStrategy { |
927 | 92.2k | fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> { |
928 | | // Favor fuzzing native cranelift, but if allowed also enable |
929 | | // winch/pulley. |
930 | 92.2k | match u.int_in_range(0..=19)? { |
931 | 13.3k | 1 => Ok(Self::CraneliftPulley), |
932 | 5.81k | 2 => Ok(Self::Winch), |
933 | 73.0k | _ => Ok(Self::CraneliftNative), |
934 | | } |
935 | 92.2k | } |
936 | | } |
937 | | |
938 | 0 | #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] Unexecuted instantiation: <wasmtime_fuzzing::generators::config::Collector as arbitrary::Arbitrary>::try_size_hint::{closure#0}Unexecuted instantiation: <wasmtime_fuzzing::generators::config::Collector as arbitrary::Arbitrary>::arbitrary_take_rest::{closure#1}Unexecuted instantiation: <wasmtime_fuzzing::generators::config::Collector as arbitrary::Arbitrary>::arbitrary::{closure#1} |
939 | | pub enum Collector { |
940 | | DeferredReferenceCounting, |
941 | | Null, |
942 | | } |
943 | | |
944 | | impl Collector { |
945 | 91.5k | fn to_wasmtime(&self) -> wasmtime::Collector { |
946 | 91.5k | match self { |
947 | 72.4k | Collector::DeferredReferenceCounting => wasmtime::Collector::DeferredReferenceCounting, |
948 | 19.1k | Collector::Null => wasmtime::Collector::Null, |
949 | | } |
950 | 91.5k | } |
951 | | } |