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