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, Store};
13 
14 /// Configuration for `wasmtime::Config` and generated modules for a session of
15 /// fuzzing.
16 ///
17 /// This configuration guides what modules are generated, how wasmtime
18 /// configuration is generated, and is typically itself generated through a call
19 /// to `Arbitrary` which allows for a form of "swarm testing".
20 #[derive(Debug, Clone)]
21 pub struct Config {
22     /// Configuration related to the `wasmtime::Config`.
23     pub wasmtime: WasmtimeConfig,
24     /// Configuration related to generated modules.
25     pub module_config: ModuleConfig,
26 }
27 
28 impl Config {
29     /// Indicates that this configuration is being used for differential
30     /// execution.
31     ///
32     /// The purpose of this function is to update the configuration which was
33     /// generated to be compatible with execution in multiple engines. The goal
34     /// is to produce the exact same result in all engines so we need to paper
35     /// over things like nan differences and memory/table behavior differences.
36     pub fn set_differential_config(&mut self) {
37         let config = &mut self.module_config.config;
38 
39         // Make it more likely that there are types available to generate a
40         // function with.
41         config.min_types = config.min_types.max(1);
42         config.max_types = config.max_types.max(1);
43 
44         // Generate at least one function
45         config.min_funcs = config.min_funcs.max(1);
46         config.max_funcs = config.max_funcs.max(1);
47 
48         // Allow a memory to be generated, but don't let it get too large.
49         // Additionally require the maximum size to guarantee that the growth
50         // behavior is consistent across engines.
51         config.max_memory32_bytes = 10 << 16;
52         config.max_memory64_bytes = 10 << 16;
53         config.memory_max_size_required = true;
54 
55         // If tables are generated make sure they don't get too large to avoid
56         // hitting any engine-specific limit. Additionally ensure that the
57         // maximum size is required to guarantee consistent growth across
58         // engines.
59         //
60         // Note that while reference types are disabled below, only allow one
61         // table.
62         config.max_table_elements = 1_000;
63         config.table_max_size_required = true;
64 
65         // Don't allow any imports
66         config.max_imports = 0;
67 
68         // Try to get the function and the memory exported
69         config.export_everything = true;
70 
71         // NaN is canonicalized at the wasm level for differential fuzzing so we
72         // can paper over NaN differences between engines.
73         config.canonicalize_nans = true;
74 
75         // If using the pooling allocator, update the instance limits too
76         if let InstanceAllocationStrategy::Pooling(pooling) = &mut self.wasmtime.strategy {
77             // One single-page memory
78             pooling.total_memories = config.max_memories as u32;
79             pooling.max_memory_size = 10 << 16;
80             pooling.max_memories_per_module = config.max_memories as u32;
81 
82             pooling.total_tables = config.max_tables as u32;
83             pooling.table_elements = 1_000;
84             pooling.max_tables_per_module = config.max_tables as u32;
85 
86             pooling.core_instance_size = 1_000_000;
87 
88             if let MemoryConfig::Normal(cfg) = &mut self.wasmtime.memory_config {
89                 match &mut cfg.static_memory_maximum_size {
90                     Some(size) => *size = (*size).max(pooling.max_memory_size as u64),
91                     other @ None => *other = Some(pooling.max_memory_size as u64),
92                 }
93             }
94         }
95     }
96 
97     /// Uses this configuration and the supplied source of data to generate
98     /// a wasm module.
99     ///
100     /// If a `default_fuel` is provided, the resulting module will be configured
101     /// to ensure termination; as doing so will add an additional global to the module,
102     /// the pooling allocator, if configured, will also have its globals limit updated.
103     pub fn generate(
104         &self,
105         input: &mut Unstructured<'_>,
106         default_fuel: Option<u32>,
107     ) -> arbitrary::Result<wasm_smith::Module> {
108         self.module_config.generate(input, default_fuel)
109     }
110 
111     /// Tests whether this configuration is capable of running all wast tests.
112     pub fn is_wast_test_compliant(&self) -> bool {
113         let config = &self.module_config.config;
114 
115         // Check for wasm features that must be disabled to run spec tests
116         if config.memory64_enabled {
117             return false;
118         }
119 
120         // Check for wasm features that must be enabled to run spec tests
121         if !config.bulk_memory_enabled
122             || !config.reference_types_enabled
123             || !config.multi_value_enabled
124             || !config.simd_enabled
125             || !config.threads_enabled
126             || config.max_memories <= 1
127         {
128             return false;
129         }
130 
131         // Make sure the runtime limits allow for the instantiation of all spec
132         // tests. Note that the max memories must be precisely one since 0 won't
133         // instantiate spec tests and more than one is multi-memory which is
134         // disabled for spec tests.
135         if config.max_memories != 1 || config.max_tables < 5 {
136             return false;
137         }
138 
139         if let InstanceAllocationStrategy::Pooling(pooling) = &self.wasmtime.strategy {
140             // Check to see if any item limit is less than the required
141             // threshold to execute the spec tests.
142             if pooling.total_memories < 1
143                 || pooling.total_tables < 5
144                 || pooling.table_elements < 1_000
145                 || pooling.max_memory_size < (900 << 16)
146                 || pooling.total_core_instances < 500
147                 || pooling.core_instance_size < 64 * 1024
148             {
149                 return false;
150             }
151         }
152 
153         true
154     }
155 
156     /// Converts this to a `wasmtime::Config` object
157     pub fn to_wasmtime(&self) -> wasmtime::Config {
158         crate::init_fuzzing();
159         log::debug!("creating wasmtime config with {:#?}", self.wasmtime);
160 
161         let mut cfg = wasmtime::Config::new();
162         cfg.wasm_bulk_memory(true)
163             .wasm_reference_types(true)
164             .wasm_multi_value(self.module_config.config.multi_value_enabled)
165             .wasm_multi_memory(self.module_config.config.max_memories > 1)
166             .wasm_simd(self.module_config.config.simd_enabled)
167             .wasm_memory64(self.module_config.config.memory64_enabled)
168             .wasm_tail_call(self.module_config.config.tail_call_enabled)
169             .wasm_custom_page_sizes(self.module_config.config.custom_page_sizes_enabled)
170             .wasm_threads(self.module_config.config.threads_enabled)
171             .wasm_function_references(self.module_config.config.gc_enabled)
172             .wasm_gc(self.module_config.config.gc_enabled)
173             .native_unwind_info(cfg!(target_os = "windows") || self.wasmtime.native_unwind_info)
174             .cranelift_nan_canonicalization(self.wasmtime.canonicalize_nans)
175             .cranelift_opt_level(self.wasmtime.opt_level.to_wasmtime())
176             .consume_fuel(self.wasmtime.consume_fuel)
177             .epoch_interruption(self.wasmtime.epoch_interruption)
178             .memory_guaranteed_dense_image_size(std::cmp::min(
179                 // Clamp this at 16MiB so we don't get huge in-memory
180                 // images during fuzzing.
181                 16 << 20,
182                 self.wasmtime.memory_guaranteed_dense_image_size,
183             ))
184             .allocation_strategy(self.wasmtime.strategy.to_wasmtime())
185             .generate_address_map(self.wasmtime.generate_address_map)
186             .signals_based_traps(self.wasmtime.signals_based_traps);
187 
188         if !self.module_config.config.simd_enabled {
189             cfg.wasm_relaxed_simd(false);
190         }
191 
192         let compiler_strategy = &self.wasmtime.compiler_strategy;
193         let cranelift_strategy = *compiler_strategy == CompilerStrategy::Cranelift;
194         cfg.strategy(self.wasmtime.compiler_strategy.to_wasmtime());
195 
196         self.wasmtime.codegen.configure(&mut cfg);
197 
198         // Determine whether we will actually enable PCC -- this is
199         // disabled if the module requires memory64, which is not yet
200         // compatible (due to the need for dynamic checks).
201         let pcc = cfg!(feature = "fuzz-pcc")
202             && self.wasmtime.pcc
203             && !self.module_config.config.memory64_enabled;
204 
205         // Only set cranelift specific flags when the Cranelift strategy is
206         // chosen.
207         if cranelift_strategy {
208             // If the wasm-smith-generated module use nan canonicalization then we
209             // don't need to enable it, but if it doesn't enable it already then we
210             // enable this codegen option.
211             cfg.cranelift_nan_canonicalization(!self.module_config.config.canonicalize_nans);
212 
213             // Enabling the verifier will at-least-double compilation time, which
214             // with a 20-30x slowdown in fuzzing can cause issues related to
215             // timeouts. If generated modules can have more than a small handful of
216             // functions then disable the verifier when fuzzing to try to lessen the
217             // impact of timeouts.
218             if self.module_config.config.max_funcs > 10 {
219                 cfg.cranelift_debug_verifier(false);
220             }
221 
222             if self.wasmtime.force_jump_veneers {
223                 unsafe {
224                     cfg.cranelift_flag_set("wasmtime_linkopt_force_jump_veneer", "true");
225                 }
226             }
227 
228             if let Some(pad) = self.wasmtime.padding_between_functions {
229                 unsafe {
230                     cfg.cranelift_flag_set(
231                         "wasmtime_linkopt_padding_between_functions",
232                         &pad.to_string(),
233                     );
234                 }
235             }
236 
237             cfg.cranelift_pcc(pcc);
238 
239             // Eager init is currently only supported on Cranelift, not Winch.
240             cfg.table_lazy_init(self.wasmtime.table_lazy_init);
241         }
242 
243         self.wasmtime.async_config.configure(&mut cfg);
244 
245         // Vary the memory configuration, but only if threads are not enabled.
246         // When the threads proposal is enabled we might generate shared memory,
247         // which is less amenable to different memory configurations:
248         // - shared memories are required to be "static" so fuzzing the various
249         //   memory configurations will mostly result in uninteresting errors.
250         //   The interesting part about shared memories is the runtime so we
251         //   don't fuzz non-default settings.
252         // - shared memories are required to be aligned which means that the
253         //   `CustomUnaligned` variant isn't actually safe to use with a shared
254         //   memory.
255         if !self.module_config.config.threads_enabled {
256             // If PCC is enabled, force other options to be compatible: PCC is currently only
257             // supported when bounds checks are elided.
258             let memory_config = if pcc {
259                 MemoryConfig::Normal(NormalMemoryConfig {
260                     static_memory_maximum_size: Some(4 << 30), // 4 GiB
261                     static_memory_guard_size: Some(2 << 30),   // 2 GiB
262                     dynamic_memory_guard_size: Some(0),
263                     dynamic_memory_reserved_for_growth: Some(0),
264                     guard_before_linear_memory: false,
265                     memory_init_cow: true,
266                     // Doesn't matter, only using virtual memory.
267                     cranelift_enable_heap_access_spectre_mitigations: None,
268                 })
269             } else {
270                 self.wasmtime.memory_config.clone()
271             };
272 
273             match &memory_config {
274                 MemoryConfig::Normal(memory_config) => {
275                     memory_config.apply_to(&mut cfg);
276                 }
277                 MemoryConfig::CustomUnaligned => {
278                     cfg.with_host_memory(Arc::new(UnalignedMemoryCreator))
279                         .static_memory_maximum_size(0)
280                         .dynamic_memory_guard_size(0)
281                         .dynamic_memory_reserved_for_growth(0)
282                         .static_memory_guard_size(0)
283                         .guard_before_linear_memory(false)
284                         .memory_init_cow(false);
285                 }
286             }
287         }
288 
289         return cfg;
290     }
291 
292     /// Convenience function for generating a `Store<T>` using this
293     /// configuration.
294     pub fn to_store(&self) -> Store<StoreLimits> {
295         let engine = Engine::new(&self.to_wasmtime()).unwrap();
296         let mut store = Store::new(&engine, StoreLimits::new());
297         self.configure_store(&mut store);
298         store
299     }
300 
301     /// Configures a store based on this configuration.
302     pub fn configure_store(&self, store: &mut Store<StoreLimits>) {
303         store.limiter(|s| s as &mut dyn wasmtime::ResourceLimiter);
304         match self.wasmtime.async_config {
305             AsyncConfig::Disabled => {
306                 if self.wasmtime.consume_fuel {
307                     store.set_fuel(u64::MAX).unwrap();
308                 }
309                 if self.wasmtime.epoch_interruption {
310                     store.epoch_deadline_trap();
311                     store.set_epoch_deadline(1);
312                 }
313             }
314             AsyncConfig::YieldWithFuel(amt) => {
315                 assert!(self.wasmtime.consume_fuel);
316                 store.fuel_async_yield_interval(Some(amt)).unwrap();
317                 store.set_fuel(amt).unwrap();
318             }
319             AsyncConfig::YieldWithEpochs { ticks, .. } => {
320                 assert!(self.wasmtime.epoch_interruption);
321                 store.set_epoch_deadline(ticks);
322                 store.epoch_deadline_async_yield_and_update(ticks);
323             }
324         }
325     }
326 
327     /// Generates an arbitrary method of timing out an instance, ensuring that
328     /// this configuration supports the returned timeout.
329     pub fn generate_timeout(&mut self, u: &mut Unstructured<'_>) -> arbitrary::Result<Timeout> {
330         let time_duration = Duration::from_millis(100);
331         let timeout = u
332             .choose(&[Timeout::Fuel(100_000), Timeout::Epoch(time_duration)])?
333             .clone();
334         match &timeout {
335             Timeout::Fuel(..) => {
336                 self.wasmtime.consume_fuel = true;
337             }
338             Timeout::Epoch(..) => {
339                 self.wasmtime.epoch_interruption = true;
340             }
341             Timeout::None => unreachable!("Not an option given to choose()"),
342         }
343         Ok(timeout)
344     }
345 
346     /// Compiles the `wasm` within the `engine` provided.
347     ///
348     /// This notably will use `Module::{serialize,deserialize_file}` to
349     /// round-trip if configured in the fuzzer.
350     pub fn compile(&self, engine: &Engine, wasm: &[u8]) -> Result<Module> {
351         // Propagate this error in case the caller wants to handle
352         // valid-vs-invalid wasm.
353         let module = Module::new(engine, wasm)?;
354         if !self.wasmtime.use_precompiled_cwasm {
355             return Ok(module);
356         }
357 
358         // Don't propagate these errors to prevent them from accidentally being
359         // interpreted as invalid wasm, these should never fail on a
360         // well-behaved host system.
361         let dir = tempfile::TempDir::new().unwrap();
362         let file = dir.path().join("module.wasm");
363         std::fs::write(&file, module.serialize().unwrap()).unwrap();
364         unsafe { Ok(Module::deserialize_file(engine, &file).unwrap()) }
365     }
366 
367     /// Updates this configuration to forcibly enable async support. Only useful
368     /// in fuzzers which do async calls.
369     pub fn enable_async(&mut self, u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
370         if self.wasmtime.consume_fuel || u.arbitrary()? {
371             self.wasmtime.async_config =
372                 AsyncConfig::YieldWithFuel(u.int_in_range(1000..=100_000)?);
373             self.wasmtime.consume_fuel = true;
374         } else {
375             self.wasmtime.async_config = AsyncConfig::YieldWithEpochs {
376                 dur: Duration::from_millis(u.int_in_range(1..=10)?),
377                 ticks: u.int_in_range(1..=10)?,
378             };
379             self.wasmtime.epoch_interruption = true;
380         }
381         Ok(())
382     }
383 }
384 
385 impl<'a> Arbitrary<'a> for Config {
386     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
387         let mut config = Self {
388             wasmtime: u.arbitrary()?,
389             module_config: u.arbitrary()?,
390         };
391 
392         config
393             .wasmtime
394             .update_module_config(&mut config.module_config.config, u)?;
395 
396         Ok(config)
397     }
398 }
399 
400 /// Configuration related to `wasmtime::Config` and the various settings which
401 /// can be tweaked from within.
402 #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)]
403 pub struct WasmtimeConfig {
404     opt_level: OptLevel,
405     debug_info: bool,
406     canonicalize_nans: bool,
407     interruptable: bool,
408     pub(crate) consume_fuel: bool,
409     pub(crate) epoch_interruption: bool,
410     /// The Wasmtime memory configuration to use.
411     pub memory_config: MemoryConfig,
412     force_jump_veneers: bool,
413     memory_init_cow: bool,
414     memory_guaranteed_dense_image_size: u64,
415     use_precompiled_cwasm: bool,
416     /// Configuration for the instance allocation strategy to use.
417     pub strategy: InstanceAllocationStrategy,
418     codegen: CodegenSettings,
419     padding_between_functions: Option<u16>,
420     generate_address_map: bool,
421     native_unwind_info: bool,
422     /// Configuration for the compiler to use.
423     pub compiler_strategy: CompilerStrategy,
424     table_lazy_init: bool,
425 
426     /// Whether or not fuzzing should enable PCC.
427     pcc: bool,
428 
429     /// Configuration for whether wasm is invoked in an async fashion and how
430     /// it's cooperatively time-sliced.
431     pub async_config: AsyncConfig,
432 
433     /// Whether or not host signal handlers are enabled for this configuration,
434     /// aka whether signal handlers are supported.
435     signals_based_traps: bool,
436 }
437 
438 impl WasmtimeConfig {
439     /// Force `self` to be a configuration compatible with `other`. This is
440     /// useful for differential execution to avoid unhelpful fuzz crashes when
441     /// one engine has a feature enabled and the other does not.
442     pub fn make_compatible_with(&mut self, other: &Self) {
443         // Use the same allocation strategy between the two configs.
444         //
445         // Ideally this wouldn't be necessary, but, during differential
446         // evaluation, if the `lhs` is using ondemand and the `rhs` is using the
447         // pooling allocator (or vice versa), then the module may have been
448         // generated in such a way that is incompatible with the other
449         // allocation strategy.
450         //
451         // We can remove this in the future when it's possible to access the
452         // fields of `wasm_smith::Module` to constrain the pooling allocator
453         // based on what was actually generated.
454         self.strategy = other.strategy.clone();
455         if let InstanceAllocationStrategy::Pooling { .. } = &other.strategy {
456             // Also use the same memory configuration when using the pooling
457             // allocator.
458             self.memory_config = other.memory_config.clone();
459         }
460 
461         self.make_internally_consistent();
462     }
463 
464     /// Updates `config` to be compatible with `self` and the other way around
465     /// too.
466     pub fn update_module_config(
467         &mut self,
468         config: &mut wasm_smith::Config,
469         u: &mut Unstructured<'_>,
470     ) -> arbitrary::Result<()> {
471         // Not implemented in Wasmtime
472         config.exceptions_enabled = false;
473 
474         // Not fully implemented in Wasmtime and fuzzing.
475         config.gc_enabled = false;
476 
477         // Winch doesn't support the same set of wasm proposal as Cranelift at
478         // this time, so if winch is selected be sure to disable wasm proposals
479         // in `Config` to ensure that Winch can compile the module that
480         // wasm-smith generates.
481         if let CompilerStrategy::Winch = self.compiler_strategy {
482             config.simd_enabled = false;
483             config.relaxed_simd_enabled = false;
484             config.gc_enabled = false;
485             config.threads_enabled = false;
486             config.tail_call_enabled = false;
487             config.reference_types_enabled = false;
488 
489             // Winch requires host trap handlers to be enabled at this time.
490             self.signals_based_traps = true;
491         }
492 
493         // If using the pooling allocator, constrain the memory and module configurations
494         // to the module limits.
495         if let InstanceAllocationStrategy::Pooling(pooling) = &mut self.strategy {
496             // Forcibly don't use the `CustomUnaligned` memory configuration
497             // with the pooling allocator active.
498             if let MemoryConfig::CustomUnaligned = self.memory_config {
499                 self.memory_config = MemoryConfig::Normal(u.arbitrary()?);
500             }
501 
502             // If the pooling allocator is used, do not allow shared memory to
503             // be created. FIXME: see
504             // https://github.com/bytecodealliance/wasmtime/issues/4244.
505             config.threads_enabled = false;
506 
507             // Ensure the pooling allocator can support the maximal size of
508             // memory, picking the smaller of the two to win.
509             let min_bytes = config
510                 .max_memory32_bytes
511                 // memory64_bytes is a u128, but since we are taking the min
512                 // we can truncate it down to a u64.
513                 .min(config.max_memory64_bytes.try_into().unwrap_or(u64::MAX));
514             let mut min = min_bytes.min(pooling.max_memory_size as u64);
515             if let MemoryConfig::Normal(cfg) = &self.memory_config {
516                 min = min.min(cfg.static_memory_maximum_size.unwrap_or(0));
517             }
518             pooling.max_memory_size = min as usize;
519             config.max_memory32_bytes = min;
520             config.max_memory64_bytes = min as u128;
521 
522             // If traps are disallowed then memories must have at least one page
523             // of memory so if we still are only allowing 0 pages of memory then
524             // increase that to one here.
525             if config.disallow_traps {
526                 if pooling.max_memory_size < (1 << 16) {
527                     pooling.max_memory_size = 1 << 16;
528                     config.max_memory32_bytes = 1 << 16;
529                     config.max_memory64_bytes = 1 << 16;
530                     if let MemoryConfig::Normal(cfg) = &mut self.memory_config {
531                         match &mut cfg.static_memory_maximum_size {
532                             Some(size) => *size = (*size).max(pooling.max_memory_size as u64),
533                             size @ None => *size = Some(pooling.max_memory_size as u64),
534                         }
535                     }
536                 }
537                 // .. additionally update tables
538                 if pooling.table_elements == 0 {
539                     pooling.table_elements = 1;
540                 }
541             }
542 
543             // Don't allow too many linear memories per instance since massive
544             // virtual mappings can fail to get allocated.
545             config.min_memories = config.min_memories.min(10);
546             config.max_memories = config.max_memories.min(10);
547 
548             // Force this pooling allocator to always be able to accommodate the
549             // module that may be generated.
550             pooling.total_memories = config.max_memories as u32;
551             pooling.total_tables = config.max_tables as u32;
552         }
553 
554         if !self.signals_based_traps {
555             // At this time shared memories require a "static" memory
556             // configuration but when signals-based traps are disabled all
557             // memories are forced to the "dynamic" configuration. This is
558             // fixable with some more work on the bounds-checks side of things
559             // to do a full bounds check even on static memories, but that's
560             // left for a future PR.
561             config.threads_enabled = false;
562 
563             // Spectre-based heap mitigations require signal handlers so this
564             // must always be disabled if signals-based traps are disabled.
565             if let MemoryConfig::Normal(cfg) = &mut self.memory_config {
566                 cfg.cranelift_enable_heap_access_spectre_mitigations = None;
567             }
568         }
569 
570         self.make_internally_consistent();
571 
572         Ok(())
573     }
574 
575     /// Helper method to handle some dependencies between various configuration
576     /// options. This is intended to be called whenever a `Config` is created or
577     /// modified to ensure that the final result is an instantiable `Config`.
578     ///
579     /// Note that in general this probably shouldn't exist and anything here can
580     /// be considered a "TODO" to go implement more stuff in Wasmtime to accept
581     /// these sorts of configurations. For now though it's intended to reflect
582     /// the current state of the engine's development.
583     fn make_internally_consistent(&mut self) {
584         if !self.signals_based_traps {
585             // Spectre-based heap mitigations require signal handlers so this
586             // must always be disabled if signals-based traps are disabled.
587             if let MemoryConfig::Normal(cfg) = &mut self.memory_config {
588                 cfg.cranelift_enable_heap_access_spectre_mitigations = None;
589             }
590         }
591     }
592 }
593 
594 #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)]
595 enum OptLevel {
596     None,
597     Speed,
598     SpeedAndSize,
599 }
600 
601 impl OptLevel {
602     fn to_wasmtime(&self) -> wasmtime::OptLevel {
603         match self {
604             OptLevel::None => wasmtime::OptLevel::None,
605             OptLevel::Speed => wasmtime::OptLevel::Speed,
606             OptLevel::SpeedAndSize => wasmtime::OptLevel::SpeedAndSize,
607         }
608     }
609 }
610 
611 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
612 /// Compiler to use.
613 pub enum CompilerStrategy {
614     /// Cranelift compiler.
615     Cranelift,
616     /// Winch compiler.
617     Winch,
618 }
619 
620 impl CompilerStrategy {
621     fn to_wasmtime(&self) -> wasmtime::Strategy {
622         match self {
623             CompilerStrategy::Cranelift => wasmtime::Strategy::Cranelift,
624             CompilerStrategy::Winch => wasmtime::Strategy::Winch,
625         }
626     }
627 }
628 
629 impl Arbitrary<'_> for CompilerStrategy {
630     fn arbitrary(_: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
631         // NB: Winch isn't selected here yet as it doesn't yet implement all the
632         // compiler features for things such as trampolines, so it's only used
633         // on fuzz targets that don't need those trampolines.
634         Ok(Self::Cranelift)
635     }
636 }
637