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