1 //! Test case generators. 2 //! 3 //! Test case generators take raw, unstructured input from a fuzzer 4 //! (e.g. libFuzzer) and translate that into a structured test case (e.g. a 5 //! valid Wasm binary). 6 //! 7 //! These are generally implementations of the `Arbitrary` trait, or some 8 //! wrapper over an external tool, such that the wrapper implements the 9 //! `Arbitrary` trait for the wrapped external tool. 10 11 pub mod api; 12 pub mod table_ops; 13 14 use crate::oracles::{StoreLimits, Timeout}; 15 use anyhow::Result; 16 use arbitrary::{Arbitrary, Unstructured}; 17 use std::sync::Arc; 18 use std::time::Duration; 19 use wasm_smith::SwarmConfig; 20 use wasmtime::{Engine, LinearMemory, MemoryCreator, MemoryType, Module, Store}; 21 22 #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] 23 enum OptLevel { 24 None, 25 Speed, 26 SpeedAndSize, 27 } 28 29 impl OptLevel { 30 fn to_wasmtime(&self) -> wasmtime::OptLevel { 31 match self { 32 OptLevel::None => wasmtime::OptLevel::None, 33 OptLevel::Speed => wasmtime::OptLevel::Speed, 34 OptLevel::SpeedAndSize => wasmtime::OptLevel::SpeedAndSize, 35 } 36 } 37 } 38 39 /// Configuration for `wasmtime::PoolingAllocationStrategy`. 40 #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] 41 pub enum PoolingAllocationStrategy { 42 /// Use next available instance slot. 43 NextAvailable, 44 /// Use random instance slot. 45 Random, 46 /// Use an affinity-based strategy. 47 ReuseAffinity, 48 } 49 50 impl PoolingAllocationStrategy { 51 fn to_wasmtime(&self) -> wasmtime::PoolingAllocationStrategy { 52 match self { 53 PoolingAllocationStrategy::NextAvailable => { 54 wasmtime::PoolingAllocationStrategy::NextAvailable 55 } 56 PoolingAllocationStrategy::Random => wasmtime::PoolingAllocationStrategy::Random, 57 PoolingAllocationStrategy::ReuseAffinity => { 58 wasmtime::PoolingAllocationStrategy::ReuseAffinity 59 } 60 } 61 } 62 } 63 64 /// Configuration for `wasmtime::ModuleLimits`. 65 #[derive(Clone, Debug, Eq, PartialEq, Hash)] 66 pub struct ModuleLimits { 67 imported_functions: u32, 68 imported_tables: u32, 69 imported_memories: u32, 70 imported_globals: u32, 71 types: u32, 72 functions: u32, 73 tables: u32, 74 memories: u32, 75 /// The maximum number of globals that can be defined in a module. 76 pub globals: u32, 77 table_elements: u32, 78 memory_pages: u64, 79 } 80 81 impl ModuleLimits { 82 fn to_wasmtime(&self) -> wasmtime::ModuleLimits { 83 wasmtime::ModuleLimits { 84 imported_functions: self.imported_functions, 85 imported_tables: self.imported_tables, 86 imported_memories: self.imported_memories, 87 imported_globals: self.imported_globals, 88 types: self.types, 89 functions: self.functions, 90 tables: self.tables, 91 memories: self.memories, 92 globals: self.globals, 93 table_elements: self.table_elements, 94 memory_pages: self.memory_pages, 95 } 96 } 97 } 98 99 impl<'a> Arbitrary<'a> for ModuleLimits { 100 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 101 const MAX_IMPORTS: u32 = 1000; 102 const MAX_TYPES: u32 = 1000; 103 const MAX_FUNCTIONS: u32 = 1000; 104 const MAX_TABLES: u32 = 10; 105 const MAX_MEMORIES: u32 = 10; 106 const MAX_GLOBALS: u32 = 1000; 107 const MAX_ELEMENTS: u32 = 1000; 108 const MAX_MEMORY_PAGES: u64 = 160; // 10 MiB 109 110 Ok(Self { 111 imported_functions: u.int_in_range(0..=MAX_IMPORTS)?, 112 imported_tables: u.int_in_range(0..=MAX_IMPORTS)?, 113 imported_memories: u.int_in_range(0..=MAX_IMPORTS)?, 114 imported_globals: u.int_in_range(0..=MAX_IMPORTS)?, 115 types: u.int_in_range(0..=MAX_TYPES)?, 116 functions: u.int_in_range(0..=MAX_FUNCTIONS)?, 117 tables: u.int_in_range(0..=MAX_TABLES)?, 118 memories: u.int_in_range(0..=MAX_MEMORIES)?, 119 globals: u.int_in_range(0..=MAX_GLOBALS)?, 120 table_elements: u.int_in_range(0..=MAX_ELEMENTS)?, 121 memory_pages: u.int_in_range(0..=MAX_MEMORY_PAGES)?, 122 }) 123 } 124 } 125 126 /// Configuration for `wasmtime::PoolingAllocationStrategy`. 127 #[derive(Debug, Clone, Eq, PartialEq, Hash)] 128 pub struct InstanceLimits { 129 /// The maximum number of instances that can be instantiated in the pool at a time. 130 pub count: u32, 131 } 132 133 impl InstanceLimits { 134 fn to_wasmtime(&self) -> wasmtime::InstanceLimits { 135 wasmtime::InstanceLimits { count: self.count } 136 } 137 } 138 139 impl<'a> Arbitrary<'a> for InstanceLimits { 140 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 141 const MAX_COUNT: u32 = 100; 142 143 Ok(Self { 144 count: u.int_in_range(1..=MAX_COUNT)?, 145 }) 146 } 147 } 148 149 /// Configuration for `wasmtime::InstanceAllocationStrategy`. 150 #[derive(Arbitrary, Clone, Debug, Eq, PartialEq, Hash)] 151 pub enum InstanceAllocationStrategy { 152 /// Use the on-demand instance allocation strategy. 153 OnDemand, 154 /// Use the pooling instance allocation strategy. 155 Pooling { 156 /// The pooling strategy to use. 157 strategy: PoolingAllocationStrategy, 158 /// The module limits. 159 module_limits: ModuleLimits, 160 /// The instance limits. 161 instance_limits: InstanceLimits, 162 }, 163 } 164 165 impl InstanceAllocationStrategy { 166 fn to_wasmtime(&self) -> wasmtime::InstanceAllocationStrategy { 167 match self { 168 InstanceAllocationStrategy::OnDemand => wasmtime::InstanceAllocationStrategy::OnDemand, 169 InstanceAllocationStrategy::Pooling { 170 strategy, 171 module_limits, 172 instance_limits, 173 } => wasmtime::InstanceAllocationStrategy::Pooling { 174 strategy: strategy.to_wasmtime(), 175 module_limits: module_limits.to_wasmtime(), 176 instance_limits: instance_limits.to_wasmtime(), 177 }, 178 } 179 } 180 } 181 182 /// Configuration for `wasmtime::Config` and generated modules for a session of 183 /// fuzzing. 184 /// 185 /// This configuration guides what modules are generated, how wasmtime 186 /// configuration is generated, and is typically itself generated through a call 187 /// to `Arbitrary` which allows for a form of "swarm testing". 188 #[derive(Debug, Clone)] 189 pub struct Config { 190 /// Configuration related to the `wasmtime::Config`. 191 pub wasmtime: WasmtimeConfig, 192 /// Configuration related to generated modules. 193 pub module_config: ModuleConfig, 194 } 195 196 impl<'a> Arbitrary<'a> for Config { 197 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 198 let mut config = Self { 199 wasmtime: u.arbitrary()?, 200 module_config: u.arbitrary()?, 201 }; 202 203 // If using the pooling allocator, constrain the memory and module configurations 204 // to the module limits. 205 if let InstanceAllocationStrategy::Pooling { 206 module_limits: limits, 207 .. 208 } = &config.wasmtime.strategy 209 { 210 // Force the use of a normal memory config when using the pooling allocator and 211 // limit the static memory maximum to be the same as the pooling allocator's memory 212 // page limit. 213 config.wasmtime.memory_config = match config.wasmtime.memory_config { 214 MemoryConfig::Normal(mut config) => { 215 config.static_memory_maximum_size = Some(limits.memory_pages * 0x10000); 216 MemoryConfig::Normal(config) 217 } 218 MemoryConfig::CustomUnaligned => { 219 let mut config: NormalMemoryConfig = u.arbitrary()?; 220 config.static_memory_maximum_size = Some(limits.memory_pages * 0x10000); 221 MemoryConfig::Normal(config) 222 } 223 }; 224 225 let cfg = &mut config.module_config.config; 226 cfg.max_imports = limits.imported_functions.min( 227 limits 228 .imported_globals 229 .min(limits.imported_memories.min(limits.imported_tables)), 230 ) as usize; 231 cfg.max_types = limits.types as usize; 232 cfg.max_funcs = limits.functions as usize; 233 cfg.max_globals = limits.globals as usize; 234 cfg.max_memories = limits.memories as usize; 235 cfg.max_tables = limits.tables as usize; 236 cfg.max_memory_pages = limits.memory_pages; 237 238 // Force no aliases in any generated modules as they might count against the 239 // import limits above. 240 cfg.max_aliases = 0; 241 } 242 243 Ok(config) 244 } 245 } 246 247 /// Configuration related to `wasmtime::Config` and the various settings which 248 /// can be tweaked from within. 249 #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)] 250 pub struct WasmtimeConfig { 251 opt_level: OptLevel, 252 debug_info: bool, 253 canonicalize_nans: bool, 254 interruptable: bool, 255 pub(crate) consume_fuel: bool, 256 /// The Wasmtime memory configuration to use. 257 pub memory_config: MemoryConfig, 258 force_jump_veneers: bool, 259 memfd: bool, 260 use_precompiled_cwasm: bool, 261 /// Configuration for the instance allocation strategy to use. 262 pub strategy: InstanceAllocationStrategy, 263 codegen: CodegenSettings, 264 } 265 266 /// Configuration for linear memories in Wasmtime. 267 #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)] 268 pub enum MemoryConfig { 269 /// Configuration for linear memories which correspond to normal 270 /// configuration settings in `wasmtime` itself. This will tweak various 271 /// parameters about static/dynamic memories. 272 Normal(NormalMemoryConfig), 273 274 /// Configuration to force use of a linear memory that's unaligned at its 275 /// base address to force all wasm addresses to be unaligned at the hardware 276 /// level, even if the wasm itself correctly aligns everything internally. 277 CustomUnaligned, 278 } 279 280 /// Represents a normal memory configuration for Wasmtime with the given 281 /// static and dynamic memory sizes. 282 #[derive(Clone, Debug, Eq, Hash, PartialEq)] 283 pub struct NormalMemoryConfig { 284 static_memory_maximum_size: Option<u64>, 285 static_memory_guard_size: Option<u64>, 286 dynamic_memory_guard_size: Option<u64>, 287 guard_before_linear_memory: bool, 288 } 289 290 impl<'a> Arbitrary<'a> for NormalMemoryConfig { 291 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 292 // This attempts to limit memory and guard sizes to 32-bit ranges so 293 // we don't exhaust a 64-bit address space easily. 294 Ok(Self { 295 static_memory_maximum_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 296 static_memory_guard_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 297 dynamic_memory_guard_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 298 guard_before_linear_memory: u.arbitrary()?, 299 }) 300 } 301 } 302 303 impl Config { 304 /// Indicates that this configuration is being used for differential 305 /// execution so only a single function should be generated since that's all 306 /// that's going to be exercised. 307 pub fn set_differential_config(&mut self) { 308 let config = &mut self.module_config.config; 309 310 config.allow_start_export = false; 311 // Make sure there's a type available for the function. 312 config.min_types = 1; 313 config.max_types = 1; 314 315 // Generate one and only one function 316 config.min_funcs = 1; 317 config.max_funcs = 1; 318 319 // Give the function a memory, but keep it small 320 config.min_memories = 1; 321 config.max_memories = 1; 322 config.max_memory_pages = 1; 323 config.memory_max_size_required = true; 324 325 // Don't allow any imports 326 config.max_imports = 0; 327 328 // Try to get the function and the memory exported 329 config.min_exports = 2; 330 config.max_exports = 4; 331 332 // NaN is canonicalized at the wasm level for differential fuzzing so we 333 // can paper over NaN differences between engines. 334 config.canonicalize_nans = true; 335 336 // When diffing against a non-wasmtime engine then disable wasm 337 // features to get selectively re-enabled against each differential 338 // engine. 339 config.bulk_memory_enabled = false; 340 config.reference_types_enabled = false; 341 config.simd_enabled = false; 342 config.memory64_enabled = false; 343 344 // If using the pooling allocator, update the module limits too 345 if let InstanceAllocationStrategy::Pooling { 346 module_limits: limits, 347 .. 348 } = &mut self.wasmtime.strategy 349 { 350 // No imports 351 limits.imported_functions = 0; 352 limits.imported_tables = 0; 353 limits.imported_memories = 0; 354 limits.imported_globals = 0; 355 356 // One type, one function, and one single-page memory 357 limits.types = 1; 358 limits.functions = 1; 359 limits.memories = 1; 360 limits.memory_pages = 1; 361 362 match &mut self.wasmtime.memory_config { 363 MemoryConfig::Normal(config) => { 364 config.static_memory_maximum_size = Some(limits.memory_pages * 0x10000); 365 } 366 MemoryConfig::CustomUnaligned => unreachable!(), // Arbitrary impl for `Config` should have prevented this 367 } 368 } 369 } 370 371 /// Uses this configuration and the supplied source of data to generate 372 /// a wasm module. 373 /// 374 /// If a `default_fuel` is provided, the resulting module will be configured 375 /// to ensure termination; as doing so will add an additional global to the module, 376 /// the pooling allocator, if configured, will also have its globals limit updated. 377 pub fn generate( 378 &mut self, 379 input: &mut Unstructured<'_>, 380 default_fuel: Option<u32>, 381 ) -> arbitrary::Result<wasm_smith::Module> { 382 let mut module = wasm_smith::Module::new(self.module_config.config.clone(), input)?; 383 384 if let Some(default_fuel) = default_fuel { 385 module.ensure_termination(default_fuel); 386 387 // Bump the allowed global count by 1 388 if let InstanceAllocationStrategy::Pooling { module_limits, .. } = 389 &mut self.wasmtime.strategy 390 { 391 module_limits.globals += 1; 392 } 393 } 394 395 Ok(module) 396 } 397 398 /// Indicates that this configuration should be spec-test-compliant, 399 /// disabling various features the spec tests assert are disabled. 400 pub fn set_spectest_compliant(&mut self) { 401 let config = &mut self.module_config.config; 402 config.memory64_enabled = false; 403 config.simd_enabled = false; 404 config.bulk_memory_enabled = true; 405 config.reference_types_enabled = true; 406 config.max_memories = 1; 407 408 if let InstanceAllocationStrategy::Pooling { module_limits, .. } = 409 &mut self.wasmtime.strategy 410 { 411 module_limits.memories = 1; 412 } 413 } 414 415 /// Converts this to a `wasmtime::Config` object 416 pub fn to_wasmtime(&self) -> wasmtime::Config { 417 crate::init_fuzzing(); 418 419 let mut cfg = wasmtime::Config::new(); 420 cfg.wasm_bulk_memory(true) 421 .wasm_reference_types(true) 422 .wasm_module_linking(self.module_config.config.module_linking_enabled) 423 .wasm_multi_memory(self.module_config.config.max_memories > 1) 424 .wasm_simd(self.module_config.config.simd_enabled) 425 .wasm_memory64(self.module_config.config.memory64_enabled) 426 .cranelift_nan_canonicalization(self.wasmtime.canonicalize_nans) 427 .cranelift_opt_level(self.wasmtime.opt_level.to_wasmtime()) 428 .interruptable(self.wasmtime.interruptable) 429 .consume_fuel(self.wasmtime.consume_fuel) 430 .memfd(self.wasmtime.memfd) 431 .allocation_strategy(self.wasmtime.strategy.to_wasmtime()); 432 433 self.wasmtime.codegen.configure(&mut cfg); 434 435 // If the wasm-smith-generated module use nan canonicalization then we 436 // don't need to enable it, but if it doesn't enable it already then we 437 // enable this codegen option. 438 cfg.cranelift_nan_canonicalization(!self.module_config.config.canonicalize_nans); 439 440 // Enabling the verifier will at-least-double compilation time, which 441 // with a 20-30x slowdown in fuzzing can cause issues related to 442 // timeouts. If generated modules can have more than a small handful of 443 // functions then disable the verifier when fuzzing to try to lessen the 444 // impact of timeouts. 445 if self.module_config.config.max_funcs > 10 { 446 cfg.cranelift_debug_verifier(false); 447 } 448 449 if self.wasmtime.force_jump_veneers { 450 unsafe { 451 cfg.cranelift_flag_set("wasmtime_linkopt_force_jump_veneer", "true") 452 .unwrap(); 453 } 454 } 455 456 match &self.wasmtime.memory_config { 457 MemoryConfig::Normal(memory_config) => { 458 cfg.static_memory_maximum_size( 459 memory_config.static_memory_maximum_size.unwrap_or(0), 460 ) 461 .static_memory_guard_size(memory_config.static_memory_guard_size.unwrap_or(0)) 462 .dynamic_memory_guard_size(memory_config.dynamic_memory_guard_size.unwrap_or(0)) 463 .guard_before_linear_memory(memory_config.guard_before_linear_memory); 464 } 465 MemoryConfig::CustomUnaligned => { 466 cfg.with_host_memory(Arc::new(UnalignedMemoryCreator)) 467 .static_memory_maximum_size(0) 468 .dynamic_memory_guard_size(0) 469 .static_memory_guard_size(0) 470 .guard_before_linear_memory(false); 471 } 472 } 473 474 return cfg; 475 } 476 477 /// Convenience function for generating a `Store<T>` using this 478 /// configuration. 479 pub fn to_store(&self) -> Store<StoreLimits> { 480 let engine = Engine::new(&self.to_wasmtime()).unwrap(); 481 let mut store = Store::new(&engine, StoreLimits::new()); 482 self.configure_store(&mut store); 483 store 484 } 485 486 /// Configures a store based on this configuration. 487 pub fn configure_store(&self, store: &mut Store<StoreLimits>) { 488 store.limiter(|s| s as &mut dyn wasmtime::ResourceLimiter); 489 if self.wasmtime.consume_fuel { 490 store.add_fuel(u64::max_value()).unwrap(); 491 } 492 } 493 494 /// Generates an arbitrary method of timing out an instance, ensuring that 495 /// this configuration supports the returned timeout. 496 pub fn generate_timeout(&mut self, u: &mut Unstructured<'_>) -> arbitrary::Result<Timeout> { 497 if u.arbitrary()? { 498 self.wasmtime.interruptable = true; 499 Ok(Timeout::Time(Duration::from_secs(20))) 500 } else { 501 self.wasmtime.consume_fuel = true; 502 Ok(Timeout::Fuel(100_000)) 503 } 504 } 505 506 /// Compiles the `wasm` within the `engine` provided. 507 /// 508 /// This notably will use `Module::{serialize,deserialize_file}` to 509 /// round-trip if configured in the fuzzer. 510 pub fn compile(&self, engine: &Engine, wasm: &[u8]) -> Result<Module> { 511 // Propagate this error in case the caller wants to handle 512 // valid-vs-invalid wasm. 513 let module = Module::new(engine, wasm)?; 514 if !self.wasmtime.use_precompiled_cwasm { 515 return Ok(module); 516 } 517 518 // Don't propagate these errors to prevent them from accidentally being 519 // interpreted as invalid wasm, these should never fail on a 520 // well-behaved host system. 521 let file = tempfile::NamedTempFile::new().unwrap(); 522 std::fs::write(file.path(), module.serialize().unwrap()).unwrap(); 523 unsafe { Ok(Module::deserialize_file(engine, file.path()).unwrap()) } 524 } 525 } 526 527 struct UnalignedMemoryCreator; 528 529 unsafe impl MemoryCreator for UnalignedMemoryCreator { 530 fn new_memory( 531 &self, 532 _ty: MemoryType, 533 minimum: usize, 534 maximum: Option<usize>, 535 reserved_size_in_bytes: Option<usize>, 536 guard_size_in_bytes: usize, 537 ) -> Result<Box<dyn LinearMemory>, String> { 538 assert_eq!(guard_size_in_bytes, 0); 539 assert!(reserved_size_in_bytes.is_none() || reserved_size_in_bytes == Some(0)); 540 Ok(Box::new(UnalignedMemory { 541 src: vec![0; minimum + 1], 542 maximum, 543 })) 544 } 545 } 546 547 /// A custom "linear memory allocator" for wasm which only works with the 548 /// "dynamic" mode of configuration where wasm always does explicit bounds 549 /// checks. 550 /// 551 /// This memory attempts to always use unaligned host addresses for the base 552 /// address of linear memory with wasm. This means that all jit loads/stores 553 /// should be unaligned, which is a "big hammer way" of testing that all our JIT 554 /// code works with unaligned addresses since alignment is not required for 555 /// correctness in wasm itself. 556 struct UnalignedMemory { 557 /// This memory is always one byte larger than the actual size of linear 558 /// memory. 559 src: Vec<u8>, 560 maximum: Option<usize>, 561 } 562 563 unsafe impl LinearMemory for UnalignedMemory { 564 fn byte_size(&self) -> usize { 565 // Chop off the extra byte reserved for the true byte size of this 566 // linear memory. 567 self.src.len() - 1 568 } 569 570 fn maximum_byte_size(&self) -> Option<usize> { 571 self.maximum 572 } 573 574 fn grow_to(&mut self, new_size: usize) -> Result<()> { 575 // Make sure to allocate an extra byte for our "unalignment" 576 self.src.resize(new_size + 1, 0); 577 Ok(()) 578 } 579 580 fn as_ptr(&self) -> *mut u8 { 581 // Return our allocated memory, offset by one, so that the base address 582 // of memory is always unaligned. 583 self.src[1..].as_ptr() as *mut _ 584 } 585 } 586 587 include!(concat!(env!("OUT_DIR"), "/spectests.rs")); 588 589 /// A spec test from the upstream wast testsuite, arbitrarily chosen from the 590 /// list of known spec tests. 591 #[derive(Debug)] 592 pub struct SpecTest { 593 /// The filename of the spec test 594 pub file: &'static str, 595 /// The `*.wast` contents of the spec test 596 pub contents: &'static str, 597 } 598 599 impl<'a> Arbitrary<'a> for SpecTest { 600 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 601 // NB: this does get a uniform value in the provided range. 602 let i = u.int_in_range(0..=FILES.len() - 1)?; 603 let (file, contents) = FILES[i]; 604 Ok(SpecTest { file, contents }) 605 } 606 607 fn size_hint(_depth: usize) -> (usize, Option<usize>) { 608 (1, Some(std::mem::size_of::<usize>())) 609 } 610 } 611 612 /// Default module-level configuration for fuzzing Wasmtime. 613 /// 614 /// Internally this uses `wasm-smith`'s own `SwarmConfig` but we further refine 615 /// the defaults here as well. 616 #[derive(Debug, Clone)] 617 pub struct ModuleConfig { 618 #[allow(missing_docs)] 619 pub config: SwarmConfig, 620 } 621 622 impl<'a> Arbitrary<'a> for ModuleConfig { 623 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<ModuleConfig> { 624 let mut config = SwarmConfig::arbitrary(u)?; 625 626 // Allow multi-memory by default. 627 config.max_memories = config.max_memories.max(2); 628 629 // Allow multi-table by default. 630 config.max_tables = config.max_tables.max(4); 631 632 // Allow enabling some various wasm proposals by default. 633 config.bulk_memory_enabled = u.arbitrary()?; 634 config.reference_types_enabled = u.arbitrary()?; 635 config.simd_enabled = u.arbitrary()?; 636 config.memory64_enabled = u.arbitrary()?; 637 638 Ok(ModuleConfig { config }) 639 } 640 } 641 642 #[derive(Clone, Debug, Eq, Hash, PartialEq)] 643 enum CodegenSettings { 644 Native, 645 #[allow(dead_code)] 646 Target { 647 target: String, 648 flags: Vec<(String, String)>, 649 }, 650 } 651 652 impl CodegenSettings { 653 fn configure(&self, config: &mut wasmtime::Config) { 654 match self { 655 CodegenSettings::Native => {} 656 CodegenSettings::Target { target, flags } => { 657 config.target(target).unwrap(); 658 for (key, value) in flags { 659 unsafe { 660 config.cranelift_flag_set(key, value).unwrap(); 661 } 662 } 663 } 664 } 665 } 666 } 667 668 impl<'a> Arbitrary<'a> for CodegenSettings { 669 #[allow(unused_macros, unused_variables)] 670 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 671 // Helper macro to enable clif features based on what the native host 672 // supports. If the input says to enable a feature and the host doesn't 673 // support it then that test case is rejected with a warning. 674 macro_rules! target_features { 675 ( 676 test:$test:ident, 677 $(std: $std:tt => clif: $clif:tt $(ratio: $a:tt in $b:tt)?,)* 678 ) => ({ 679 let mut flags = Vec::new(); 680 $( 681 let (low, hi) = (1, 2); 682 $(let (low, hi) = ($a, $b);)? 683 let enable = u.ratio(low, hi)?; 684 if enable && !std::$test!($std) { 685 log::error!("want to enable clif `{}` but host doesn't support it", 686 $clif); 687 return Err(arbitrary::Error::EmptyChoose) 688 } 689 flags.push(( 690 $clif.to_string(), 691 enable.to_string(), 692 )); 693 )* 694 flags 695 }) 696 } 697 #[cfg(target_arch = "x86_64")] 698 { 699 if u.ratio(1, 10)? { 700 let flags = target_features! { 701 test: is_x86_feature_detected, 702 std:"sse3" => clif:"has_sse3", 703 std:"ssse3" => clif:"has_ssse3", 704 std:"sse4.1" => clif:"has_sse41", 705 std:"sse4.2" => clif:"has_sse42", 706 std:"popcnt" => clif:"has_popcnt", 707 std:"avx" => clif:"has_avx", 708 std:"avx2" => clif:"has_avx2", 709 std:"bmi1" => clif:"has_bmi1", 710 std:"bmi2" => clif:"has_bmi2", 711 std:"lzcnt" => clif:"has_lzcnt", 712 713 // not a lot of of cpus support avx512 so these are weighted 714 // to get enabled much less frequently. 715 std:"avx512bitalg" => clif:"has_avx512bitalg" ratio:1 in 1000, 716 std:"avx512dq" => clif:"has_avx512dq" ratio: 1 in 1000, 717 std:"avx512f" => clif:"has_avx512f" ratio: 1 in 1000, 718 std:"avx512vl" => clif:"has_avx512vl" ratio: 1 in 1000, 719 std:"avx512vbmi" => clif:"has_avx512vbmi" ratio: 1 in 1000, 720 }; 721 return Ok(CodegenSettings::Target { 722 target: target_lexicon::Triple::host().to_string(), 723 flags, 724 }); 725 } 726 } 727 Ok(CodegenSettings::Native) 728 } 729 } 730