1 //! Generate a Wasm module and the configuration for generating it.
2 
3 use arbitrary::{Arbitrary, Unstructured};
4 
5 /// Default module-level configuration for fuzzing Wasmtime.
6 ///
7 /// Internally this uses `wasm-smith`'s own `Config` but we further refine
8 /// the defaults here as well.
9 #[derive(Debug, Clone)]
10 #[expect(missing_docs, reason = "self-describing fields")]
11 pub struct ModuleConfig {
12     pub config: wasm_smith::Config,
13 
14     // These knobs aren't exposed in `wasm-smith` at this time but are exposed
15     // in our `*.wast` testing so keep knobs here so they can be read during
16     // config-to-`wasmtime::Config` translation.
17     pub function_references_enabled: bool,
18     pub component_model_async: bool,
19     pub component_model_async_builtins: bool,
20     pub component_model_async_stackful: bool,
21 }
22 
23 impl<'a> Arbitrary<'a> for ModuleConfig {
24     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<ModuleConfig> {
25         let mut config = wasm_smith::Config::arbitrary(u)?;
26 
27         // This list is intended to be the definitive source of truth for
28         // what's at least possible to fuzz within Wasmtime. This is a
29         // combination of features in `wasm-smith` where some proposals are
30         // on-by-default (as determined by fuzz input) and others are
31         // off-by-default (as they aren't stage4+). Wasmtime will default-fuzz
32         // proposals that a pre-stage-4 to test our own implementation. Wasmtime
33         // might also unconditionally disable proposals that it doesn't
34         // implement yet which are stage4+. This is intended to be an exhaustive
35         // list of all the wasm proposals that `wasm-smith` supports and the
36         // fuzzing status within Wasmtime too.
37         let _ = config.multi_value_enabled;
38         let _ = config.saturating_float_to_int_enabled;
39         let _ = config.sign_extension_ops_enabled;
40         let _ = config.bulk_memory_enabled;
41         let _ = config.reference_types_enabled;
42         let _ = config.simd_enabled;
43         let _ = config.relaxed_simd_enabled;
44         let _ = config.tail_call_enabled;
45         let _ = config.extended_const_enabled;
46         let _ = config.gc_enabled;
47         config.exceptions_enabled = false;
48         config.custom_page_sizes_enabled = u.arbitrary()?;
49         config.wide_arithmetic_enabled = u.arbitrary()?;
50         config.memory64_enabled = u.ratio(1, 20)?;
51         config.threads_enabled = u.ratio(1, 20)?;
52         // Allow multi-memory but make it unlikely
53         if u.ratio(1, 20)? {
54             config.max_memories = config.max_memories.max(2);
55         } else {
56             config.max_memories = 1;
57         }
58         // ... NB: if you add something above this line please be sure to update
59         // `docs/stability-wasm-proposals.md`
60 
61         // We get better differential execution when we disallow traps, so we'll
62         // do that most of the time.
63         config.disallow_traps = u.ratio(9, 10)?;
64 
65         Ok(ModuleConfig {
66             component_model_async: false,
67             component_model_async_builtins: false,
68             component_model_async_stackful: false,
69             function_references_enabled: config.gc_enabled,
70             config,
71         })
72     }
73 }
74 
75 impl ModuleConfig {
76     /// Uses this configuration and the supplied source of data to generate a
77     /// Wasm module.
78     ///
79     /// If a `default_fuel` is provided, the resulting module will be configured
80     /// to ensure termination; as doing so will add an additional global to the
81     /// module, the pooling allocator, if configured, must also have its globals
82     /// limit updated.
83     pub fn generate(
84         &self,
85         input: &mut Unstructured<'_>,
86         default_fuel: Option<u32>,
87     ) -> arbitrary::Result<wasm_smith::Module> {
88         let mut module = wasm_smith::Module::new(self.config.clone(), input)?;
89 
90         if let Some(default_fuel) = default_fuel {
91             module.ensure_termination(default_fuel).unwrap();
92         }
93 
94         Ok(module)
95     }
96 }
97