Repository: bytecodealliance/wasm-tools
Pull request #1351 (2024-01-03 19:53:20)

`wasm-smith`: Deduplicate and devirtualize configuration
This makes it so that we don't have three or four places where each config option needs to be enumerated anymore and also removes the `Config` trait and replaces it with a `Config` struct (what used to be the `SwarmConfig` struct). This struct implements `Arbitrary` and therefore you can still do swarm testing by generating an arbitrary config and then using that to generate a module. There is also no difference between `Module` and `ConfiguredModule` anymore.

Overall, I feel like this is a big clean up, and also should in theory make module generation a little faster since there won't be so many indirect calls.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 3538d9a0..bd68f90a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -623,6 +623,9 @@ name = "flagset"
 version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d52a7e408202050813e6f1d9addadcaafef3dca7530c7ddfb005d4081cce6779"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "flate2"
@@ -1810,7 +1813,9 @@ dependencies = [
 name = "wasm-smith"
 version = "0.13.1"
 dependencies = [
+ "anyhow",
  "arbitrary",
+ "clap 4.4.8",
  "criterion",
  "flagset",
  "indexmap 2.1.0",
@@ -1925,6 +1930,7 @@ name = "wasmparser"
 version = "0.118.1"
 dependencies = [
  "anyhow",
+ "bitflags 2.4.1",
  "criterion",
  "env_logger",
  "indexmap 2.1.0",
diff --git a/crates/c-api/src/lib.rs b/crates/c-api/src/lib.rs
index 1542dd1a..7b3c5d01 100644
--- a/crates/c-api/src/lib.rs
+++ b/crates/c-api/src/lib.rs
@@ -7,7 +7,7 @@
 #![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
 
 use arbitrary::{Error, Unstructured};
-use wasm_smith::{DefaultConfig, Module};
+use wasm_smith::{Config, Module};
 
 #[repr(C)]
 pub struct wasm_tools_byte_vec_t {
@@ -50,7 +50,7 @@ pub unsafe extern "C" fn wasm_smith_create(
 
     bytes.data = std::ptr::null_mut();
     let mut u = Unstructured::new(seed_bytes);
-    match Module::new(DefaultConfig::default(), &mut u) {
+    match Module::new(Config::default(), &mut u) {
         Ok(module) => {
             let mut wasm_buffer = module.to_bytes().into_boxed_slice();
             bytes.data = wasm_buffer.as_mut_ptr();
diff --git a/crates/fuzz-stats/src/bin/failed-instantiations.rs b/crates/fuzz-stats/src/bin/failed-instantiations.rs
index 86c8e286..0ff7f9cd 100644
--- a/crates/fuzz-stats/src/bin/failed-instantiations.rs
+++ b/crates/fuzz-stats/src/bin/failed-instantiations.rs
@@ -2,7 +2,6 @@ use arbitrary::{Arbitrary, Error, Unstructured};
 use rand::RngCore;
 use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering::SeqCst};
 use std::sync::Arc;
-use wasm_smith::SwarmConfig;
 use wasmtime::*;
 
 struct State {
@@ -95,12 +94,12 @@ impl State {
     /// Records when instantiation fails and why it fails.
     fn run_once(&self, data: &[u8]) -> Result<(), Error> {
         let mut u = Unstructured::new(data);
-        // Here `SwarmConfig` is used to get hopefully a bit more coverage of
+        // Here swarm testing is used to get hopefully a bit more coverage of
         // interesting states, and we also forcibly disable all `start`
         // functions for now. Not much work has gone into minimizing the traps
         // generated from wasm functions themselves, and this shouldn't be
         // enabled until that's been worked on.
-        let mut config = SwarmConfig::arbitrary(&mut u)?;
+        let mut config = wasm_smith::Config::arbitrary(&mut u)?;
         config.allow_start_export = false;
 
         // Wasmtime doesn't support this proposal yet.
diff --git a/crates/wasm-shrink/src/lib.rs b/crates/wasm-shrink/src/lib.rs
index 05b1bea7..cf14d7b9 100755
--- a/crates/wasm-shrink/src/lib.rs
+++ b/crates/wasm-shrink/src/lib.rs
@@ -1,7 +1,7 @@
 //! Shrink a Wasm file while maintaining a property of interest (such as
 //! triggering a compiler bug).
 //!
-//! See the [`WasmShrink`][WasmShrink] type for details.
+//! See the [`WasmShrink`] type for details.
 
 use std::collections::HashSet;
 
diff --git a/crates/wasm-smith/Cargo.toml b/crates/wasm-smith/Cargo.toml
index 216b0aeb..89e739d6 100644
--- a/crates/wasm-smith/Cargo.toml
+++ b/crates/wasm-smith/Cargo.toml
@@ -16,7 +16,9 @@ name = "corpus"
 harness = false
 
 [dependencies]
+anyhow = { workspace = true, optional = true }
 arbitrary = { workspace = true, features = ["derive"] }
+clap = { workspace = true, optional = true }
 flagset = "0.4"
 indexmap = { workspace = true }
 leb128 = { workspace = true }
@@ -24,6 +26,7 @@ serde = { workspace = true, optional = true }
 serde_derive = { workspace = true, optional = true }
 wasm-encoder = { workspace = true, features = ['wasmparser'] }
 wasmparser = { workspace = true, optional = true }
+wat = { workspace = true, optional = true }
 
 [dev-dependencies]
 criterion = { workspace = true }
@@ -36,4 +39,4 @@ wat = { path = "../wat" }
 libfuzzer-sys = { workspace = true }
 
 [features]
-_internal_cli = ["serde", "serde_derive", "wasmparser"]
+_internal_cli = ["anyhow", "clap", "flagset/serde", "serde", "serde_derive", "wasmparser", "wat"]
diff --git a/crates/wasm-smith/src/component.rs b/crates/wasm-smith/src/component.rs
index a9f8f005..5a077554 100644
--- a/crates/wasm-smith/src/component.rs
+++ b/crates/wasm-smith/src/component.rs
@@ -4,13 +4,12 @@
 // FIXME(#1000): component support in `wasm-smith` is a work in progress.
 #![allow(unused_variables, dead_code)]
 
-use crate::{arbitrary_loop, Config, DefaultConfig};
+use crate::{arbitrary_loop, Config};
 use arbitrary::{Arbitrary, Result, Unstructured};
 use std::collections::BTreeMap;
 use std::convert::TryFrom;
 use std::{
     collections::{HashMap, HashSet},
-    marker,
     rc::Rc,
 };
 use wasm_encoder::{ComponentTypeRef, ComponentValType, PrimitiveValType, TypeBounds, ValType};
@@ -26,11 +25,10 @@ mod encode;
 ///
 /// ## Configured Generated Components
 ///
-/// This uses the [`DefaultConfig`][crate::DefaultConfig] configuration. If you
-/// want to customize the shape of generated components, define your own
-/// configuration type, implement the [`Config`][crate::Config] trait for it,
-/// and use [`ConfiguredComponent<YourConfigType>`][crate::ConfiguredComponent]
-/// instead of plain `Component`.
+/// The `Arbitrary` implementation uses the [`Config::default()`][crate::Config]
+/// configuration. If you want to customize the shape of generated components,
+/// create your own [`Config`][crate::Config] instance and pass it to
+/// [`Component::new`][crate::Component::new].
 #[derive(Debug)]
 pub struct Component {
     sections: Vec<Section>,
@@ -47,7 +45,7 @@ pub struct Component {
 /// bytes.
 #[derive(Debug)]
 struct ComponentBuilder {
-    config: Rc<dyn Config>,
+    config: Config,
 
     // The set of core `valtype`s that we are configured to generate.
     core_valtypes: Vec<ValType>,
@@ -310,35 +308,7 @@ impl TypesScope {
 
 impl<'a> Arbitrary<'a> for Component {
     fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
-        Ok(ConfiguredComponent::<DefaultConfig>::arbitrary(u)?.component)
-    }
-}
-
-/// A pseudo-random generated Wasm component with custom configuration.
-///
-/// If you don't care about custom configuration, use
-/// [`Component`][crate::Component] instead.
-///
-/// For details on configuring, see the [`Config`][crate::Config] trait.
-#[derive(Debug)]
-pub struct ConfiguredComponent<C> {
-    /// The generated component, controlled by the configuration of `C` in the
-    /// `Arbitrary` implementation.
-    pub component: Component,
-    _marker: marker::PhantomData<C>,
-}
-
-impl<'a, C> Arbitrary<'a> for ConfiguredComponent<C>
-where
-    C: Config + Arbitrary<'a>,
-{
-    fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
-        let config = C::arbitrary(u)?;
-        let component = Component::new(config, u)?;
-        Ok(ConfiguredComponent {
-            component,
-            _marker: marker::PhantomData,
-        })
+        Component::new(Config::default(), u)
     }
 }
 
@@ -353,8 +323,8 @@ struct EntityCounts {
 
 impl Component {
     /// Construct a new `Component` using the given configuration.
-    pub fn new(config: impl Config, u: &mut Unstructured) -> Result<Self> {
-        let mut builder = ComponentBuilder::new(Rc::new(config));
+    pub fn new(config: Config, u: &mut Unstructured) -> Result<Self> {
+        let mut builder = ComponentBuilder::new(config);
         builder.build(u)
     }
 
@@ -381,7 +351,7 @@ impl Step {
 }
 
 impl ComponentBuilder {
-    fn new(config: Rc<dyn Config>) -> Self {
+    fn new(config: Config) -> Self {
         ComponentBuilder {
             config,
             core_valtypes: vec![],
@@ -396,7 +366,7 @@ impl ComponentBuilder {
     }
 
     fn build(&mut self, u: &mut Unstructured) -> Result<Component> {
-        self.core_valtypes = crate::core::configured_valtypes(&*self.config);
+        self.core_valtypes = crate::core::configured_valtypes(&self.config);
 
         let mut choices: Vec<fn(&mut ComponentBuilder, &mut Unstructured) -> Result<Step>> = vec![];
 
@@ -417,12 +387,12 @@ impl ComponentBuilder {
                 choices.push(Self::arbitrary_import_section);
                 choices.push(Self::arbitrary_canonical_section);
 
-                if self.total_modules < self.config.max_modules() {
+                if self.total_modules < self.config.max_modules {
                     choices.push(Self::arbitrary_core_module_section);
                 }
 
-                if self.components.len() < self.config.max_nesting_depth()
-                    && self.total_components < self.config.max_components()
+                if self.components.len() < self.config.max_nesting_depth
+                    && self.total_components < self.config.max_components
                 {
                     choices.push(Self::arbitrary_component_section);
                 }
@@ -455,13 +425,13 @@ impl ComponentBuilder {
         // Ensure we've generated all of our minimums.
         self.fill_minimums = true;
         {
-            if self.current_type_scope().types.len() < self.config.min_types() {
+            if self.current_type_scope().types.len() < self.config.min_types {
                 self.arbitrary_type_section(u)?.unwrap_still_building();
             }
-            if self.component().num_imports < self.config.min_imports() {
+            if self.component().num_imports < self.config.min_imports {
                 self.arbitrary_import_section(u)?.unwrap_still_building();
             }
-            if self.component().funcs.len() < self.config.min_funcs() {
+            if self.component().funcs.len() < self.config.min_funcs {
                 self.arbitrary_canonical_section(u)?.unwrap_still_building();
             }
         }
@@ -473,10 +443,6 @@ impl ComponentBuilder {
         Ok(Step::Finished(self.components.pop().unwrap().component))
     }
 
-    fn config(&self) -> &dyn Config {
-        &*self.config
-    }
-
     fn component(&self) -> &ComponentContext {
         self.components.last().unwrap()
     }
@@ -545,16 +511,16 @@ impl ComponentBuilder {
 
         let min = if self.fill_minimums {
             self.config
-                .min_types()
+                .min_types
                 .saturating_sub(self.current_type_scope().types.len())
         } else {
             0
         };
 
-        let max = self.config.max_types() - self.current_type_scope().types.len();
+        let max = self.config.max_types - self.current_type_scope().types.len();
 
         arbitrary_loop(u, min, max, |u| {
-            let mut type_fuel = self.config.max_type_size();
+            let mut type_fuel = self.config.max_type_size;
             let ty = self.arbitrary_core_type(u, &mut type_fuel)?;
             self.push_core_type(ty);
             Ok(true)
@@ -577,7 +543,7 @@ impl ComponentBuilder {
             0 => CoreType::Func(crate::core::arbitrary_func_type(
                 u,
                 &self.core_valtypes,
-                if self.config.multi_value_enabled() {
+                if self.config.multi_value_enabled {
                     None
                 } else {
                     Some(1)
@@ -594,16 +560,16 @@ impl ComponentBuilder {
 
         let min = if self.fill_minimums {
             self.config
-                .min_types()
+                .min_types
                 .saturating_sub(self.current_type_scope().types.len())
         } else {
             0
         };
 
-        let max = self.config.max_types() - self.current_type_scope().types.len();
+        let max = self.config.max_types - self.current_type_scope().types.len();
 
         arbitrary_loop(u, min, max, |u| {
-            let mut type_fuel = self.config.max_type_size();
+            let mut type_fuel = self.config.max_type_size;
             let ty = self.arbitrary_type(u, &mut type_fuel)?;
             self.push_type(ty);
             Ok(true)
@@ -622,7 +588,7 @@ impl ComponentBuilder {
         let scope = self.current_type_scope();
 
         if !scope.module_types.is_empty()
-            && (for_type_def || !for_import || self.total_modules < self.config.max_modules())
+            && (for_type_def || !for_import || self.total_modules < self.config.max_modules)
         {
             choices.push(|me, u| {
                 Ok(ComponentTypeRef::Module(
@@ -634,7 +600,7 @@ impl ComponentBuilder {
         // Types cannot be imported currently
         if !for_import
             && !scope.types.is_empty()
-            && (for_type_def || scope.types.len() < self.config.max_types())
+            && (for_type_def || scope.types.len() < self.config.max_types)
         {
             choices.push(|me, u| {
                 Ok(ComponentTypeRef::Type(TypeBounds::Eq(u.int_in_range(
@@ -650,9 +616,7 @@ impl ComponentBuilder {
         // }
 
         if !scope.func_types.is_empty()
-            && (for_type_def
-                || !for_import
-                || self.component().num_funcs() < self.config.max_funcs())
+            && (for_type_def || !for_import || self.component().num_funcs() < self.config.max_funcs)
         {
             choices.push(|me, u| {
                 Ok(ComponentTypeRef::Func(
@@ -662,7 +626,7 @@ impl ComponentBuilder {
         }
 
         if !scope.component_types.is_empty()
-            && (for_type_def || !for_import || self.total_components < self.config.max_components())
+            && (for_type_def || !for_import || self.total_components < self.config.max_components)
         {
             choices.push(|me, u| {
                 Ok(ComponentTypeRef::Component(
@@ -672,7 +636,7 @@ impl ComponentBuilder {
         }
 
         if !scope.instance_types.is_empty()
-            && (for_type_def || !for_import || self.total_instances < self.config.max_instances())
+            && (for_type_def || !for_import || self.total_instances < self.config.max_instances)
         {
             choices.push(|me, u| {
                 Ok(ComponentTypeRef::Instance(
@@ -726,7 +690,7 @@ impl ComponentBuilder {
         // randomly generating them is extremely unlikely.
 
         // `memory`
-        if counts.memories < self.config.max_memories() && u.ratio::<u8>(99, 100)? {
+        if counts.memories < self.config.max_memories && u.ratio::<u8>(99, 100)? {
             defs.push(ModuleTypeDef::Export(
                 "memory".into(),
                 crate::core::EntityType::Memory(self.arbitrary_core_memory_type(u)?),
@@ -737,8 +701,8 @@ impl ComponentBuilder {
         }
 
         // `canonical_abi_realloc`
-        if counts.funcs < self.config.max_funcs()
-            && types.len() < self.config.max_types()
+        if counts.funcs < self.config.max_funcs
+            && types.len() < self.config.max_types
             && u.ratio::<u8>(99, 100)?
         {
             let realloc_ty = Rc::new(crate::core::FuncType {
@@ -760,8 +724,8 @@ impl ComponentBuilder {
         }
 
         // `canonical_abi_free`
-        if counts.funcs < self.config.max_funcs()
-            && types.len() < self.config.max_types()
+        if counts.funcs < self.config.max_funcs
+            && types.len() < self.config.max_types
             && u.ratio::<u8>(99, 100)?
         {
             let free_ty = Rc::new(crate::core::FuncType {
@@ -797,7 +761,7 @@ impl ComponentBuilder {
                 return Ok(false);
             }
 
-            let max_choice = if types.len() < self.config.max_types() {
+            let max_choice = if types.len() < self.config.max_types {
                 // Check if the parent scope has core function types to alias
                 if !types.is_empty()
                     || (!self.types.is_empty()
@@ -856,7 +820,7 @@ impl ComponentBuilder {
                     let ty = crate::core::arbitrary_func_type(
                         u,
                         &self.core_valtypes,
-                        if self.config.multi_value_enabled() {
+                        if self.config.multi_value_enabled {
                             None
                         } else {
                             Some(1)
@@ -910,7 +874,7 @@ impl ComponentBuilder {
     ) -> Result<Option<crate::core::EntityType>> {
         choices.clear();
 
-        if counts.globals < self.config.max_globals() {
+        if counts.globals < self.config.max_globals {
             choices.push(|c, u, counts, _types| {
                 counts.globals += 1;
                 Ok(crate::core::EntityType::Global(
@@ -919,7 +883,7 @@ impl ComponentBuilder {
             });
         }
 
-        if counts.tables < self.config.max_tables() {
+        if counts.tables < self.config.max_tables {
             choices.push(|c, u, counts, _types| {
                 counts.tables += 1;
                 Ok(crate::core::EntityType::Table(
@@ -928,7 +892,7 @@ impl ComponentBuilder {
             });
         }
 
-        if counts.memories < self.config.max_memories() {
+        if counts.memories < self.config.max_memories {
             choices.push(|c, u, counts, _types| {
                 counts.memories += 1;
                 Ok(crate::core::EntityType::Memory(
@@ -938,8 +902,8 @@ impl ComponentBuilder {
         }
 
         if types.iter().any(|ty| ty.results.is_empty())
-            && self.config.exceptions_enabled()
-            && counts.tags < self.config.max_tags()
+            && self.config.exceptions_enabled
+            && counts.tags < self.config.max_tags
         {
             choices.push(|c, u, counts, types| {
                 counts.tags += 1;
@@ -957,7 +921,7 @@ impl ComponentBuilder {
             });
         }
 
-        if !types.is_empty() && counts.funcs < self.config.max_funcs() {
+        if !types.is_empty() && counts.funcs < self.config.max_funcs {
             choices.push(|c, u, counts, types| {
                 counts.funcs += 1;
                 let ty_idx = u.int_in_range(0..=u32::try_from(types.len() - 1).unwrap())?;
@@ -987,11 +951,11 @@ impl ComponentBuilder {
     }
 
     fn arbitrary_core_table_type(&self, u: &mut Unstructured) -> Result<crate::core::TableType> {
-        crate::core::arbitrary_table_type(u, self.config())
+        crate::core::arbitrary_table_type(u, &self.config)
     }
 
     fn arbitrary_core_memory_type(&self, u: &mut Unstructured) -> Result<crate::core::MemoryType> {
-        crate::core::arbitrary_memtype(u, self.config())
+        crate::core::arbitrary_memtype(u, &self.config)
     }
 
     fn with_types_scope<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
@@ -1159,7 +1123,7 @@ impl ComponentBuilder {
         });
 
         // Type definition.
-        if self.types.len() < self.config.max_nesting_depth() {
+        if self.types.len() < self.config.max_nesting_depth {
             choices.push(|me, _exports, _export_urls, u, type_fuel| {
                 let ty = me.arbitrary_type(u, type_fuel)?;
                 me.current_type_scope_mut().push(ty.clone());
@@ -1618,14 +1582,14 @@ impl ComponentBuilder {
 
         let min = if self.fill_minimums {
             self.config
-                .min_imports()
+                .min_imports
                 .saturating_sub(self.component().num_imports)
         } else {
             // Allow generating empty sections. We can always fill in the required
             // minimum later.
             0
         };
-        let max = self.config.max_imports() - self.component().num_imports;
+        let max = self.config.max_imports - self.component().num_imports;
 
         crate::arbitrary_loop(u, min, max, |u| {
             match self.arbitrary_type_ref(u, true, false)? {
@@ -1656,14 +1620,14 @@ impl ComponentBuilder {
 
         let min = if self.fill_minimums {
             self.config
-                .min_funcs()
+                .min_funcs
                 .saturating_sub(self.component().funcs.len())
         } else {
             // Allow generating empty sections. We can always fill in the
             // required minimum later.
             0
         };
-        let max = self.config.max_funcs() - self.component().funcs.len();
+        let max = self.config.max_funcs - self.component().funcs.len();
 
         let mut choices: Vec<fn(&mut Unstructured, &mut ComponentBuilder) -> Result<Option<Func>>> =
             Vec::with_capacity(2);
@@ -1722,7 +1686,7 @@ impl ComponentBuilder {
                         // definitions arbitrarily.
                         debug_assert!(!indices.is_empty());
                         *u.choose(indices)?
-                    } else if c.current_type_scope().types.len() < c.config.max_types() {
+                    } else if c.current_type_scope().types.len() < c.config.max_types {
                         // If we haven't already defined this component function
                         // type, and we haven't defined the configured maximum
                         // amount of types yet, then just define this type.
@@ -1758,9 +1722,8 @@ impl ComponentBuilder {
     }
 
     fn arbitrary_core_module_section(&mut self, u: &mut Unstructured) -> Result<Step> {
-        let config: Rc<dyn Config> = Rc::clone(&self.config);
         let module = crate::core::Module::new_internal(
-            config,
+            self.config.clone(),
             u,
             crate::core::DuplicateImportsBehavior::Disallowed,
         )?;
diff --git a/crates/wasm-smith/src/config.rs b/crates/wasm-smith/src/config.rs
index 3d85259a..0a4ae50d 100644
--- a/crates/wasm-smith/src/config.rs
+++ b/crates/wasm-smith/src/config.rs
@@ -2,566 +2,596 @@
 
 use crate::InstructionKinds;
 use arbitrary::{Arbitrary, Result, Unstructured};
-use std::borrow::Cow;
 
-/// Configuration for a generated module.
-///
-/// Don't care to configure your generated modules? Just use
-/// [`Module`][crate::Module], which internally uses
-/// [`DefaultConfig`][crate::DefaultConfig].
-///
-/// If you want to configure generated modules, then define a `MyConfig` type,
-/// implement this trait for it, and use
-/// [`ConfiguredModule<MyConfig>`][crate::ConfiguredModule] instead of `Module`.
-///
-/// Every trait method has a provided default implementation, so that you only
-/// need to override the methods for things you want to change away from the
-/// default.
-pub trait Config: 'static + std::fmt::Debug {
-    /// The minimum number of types to generate. Defaults to 0.
-    fn min_types(&self) -> usize {
-        0
-    }
-
-    /// The maximum number of types to generate. Defaults to 100.
-    fn max_types(&self) -> usize {
-        100
-    }
-
-    /// The minimum number of imports to generate. Defaults to 0.
-    ///
-    /// Note that if the sum of the maximum function[^1], table, global and
-    /// memory counts is less than the minimum number of imports, then it will
-    /// not be possible to satisfy all constraints (because imports count
-    /// against the limits for those element kinds). In that case, we strictly
-    /// follow the max-constraints, and can fail to satisfy this minimum number.
-    ///
-    /// [^1]: the maximum number of functions is also limited by the number of
-    ///       function types arbitrarily chosen; strictly speaking, then, the
-    ///       maximum number of imports that can be created due to
-    ///       max-constraints is `sum(min(num_func_types, max_funcs), max_tables,
-    ///       max_globals, max_memories)`.
-    fn min_imports(&self) -> usize {
-        0
-    }
-
-    /// The maximum number of imports to generate. Defaults to 100.
-    fn max_imports(&self) -> usize {
-        100
-    }
-
-    /// The minimum number of tags to generate. Defaults to 0.
-    fn min_tags(&self) -> usize {
-        0
-    }
-
-    /// The maximum number of tags to generate. Defaults to 100.
-    fn max_tags(&self) -> usize {
-        100
-    }
-
-    /// The imports that may be used when generating the module.
-    ///
-    /// Defaults to `None` which means that any arbitrary import can be generated.
-    ///
-    /// To only allow specific imports, override this method to return a WebAssembly module which
-    /// describes the imports allowed.
-    ///
-    /// Note that [`Self::min_imports`] is ignored when `available_imports` are enabled.
-    ///
-    /// # Panics
-    ///
-    /// The returned value must be a valid binary encoding of a WebAssembly module. `wasm-smith`
-    /// will panic if the module cannot be parsed.
-    ///
-    /// # Example
-    ///
-    /// An implementation of this method could use the `wat` crate to provide a human-readable and
-    /// maintainable description:
-    ///
-    /// ```rust
-    /// Some(wat::parse_str(r#"
-    ///     (module
-    ///         (import "env" "ping" (func (param i32)))
-    ///         (import "env" "pong" (func (result i32)))
-    ///         (import "env" "memory" (memory 1))
-    ///         (import "env" "table" (table 1))
-    ///         (import "env" "tag" (tag (param i32)))
-    ///     )
-    /// "#))
-    /// # ;
-    /// ```
-    fn available_imports(&self) -> Option<Cow<'_, [u8]>> {
-        None
-    }
-
-    /// The minimum number of functions to generate. Defaults to 0.  This
-    /// includes imported functions.
-    fn min_funcs(&self) -> usize {
-        0
-    }
-
-    /// The maximum number of functions to generate. Defaults to 100.  This
-    /// includes imported functions.
-    fn max_funcs(&self) -> usize {
-        100
-    }
-
-    /// The minimum number of globals to generate. Defaults to 0.  This includes
-    /// imported globals.
-    fn min_globals(&self) -> usize {
-        0
-    }
-
-    /// The maximum number of globals to generate. Defaults to 100.  This
-    /// includes imported globals.
-    fn max_globals(&self) -> usize {
-        100
-    }
-
-    /// The minimum number of exports to generate. Defaults to 0.
-    fn min_exports(&self) -> usize {
-        0
-    }
-
-    /// The maximum number of exports to generate. Defaults to 100.
-    fn max_exports(&self) -> usize {
-        100
-    }
-
-    /// Export all WebAssembly objects in the module. This overrides
-    /// [`Config::min_exports`] and [`Config::max_exports`]. Defaults to false.
-    fn export_everything(&self) -> bool {
-        false
-    }
-
-    /// The minimum number of element segments to generate. Defaults to 0.
-    fn min_element_segments(&self) -> usize {
-        0
-    }
-
-    /// The maximum number of element segments to generate. Defaults to 100.
-    fn max_element_segments(&self) -> usize {
-        100
-    }
-
-    /// The minimum number of elements within a segment to generate. Defaults to
-    /// 0.
-    fn min_elements(&self) -> usize {
-        0
-    }
-
-    /// The maximum number of elements within a segment to generate. Defaults to
-    /// 100.
-    fn max_elements(&self) -> usize {
-        100
-    }
-
-    /// The minimum number of data segments to generate. Defaults to 0.
-    fn min_data_segments(&self) -> usize {
-        0
-    }
-
-    /// The maximum number of data segments to generate. Defaults to 100.
-    fn max_data_segments(&self) -> usize {
-        100
-    }
-
-    /// The maximum number of instructions to generate in a function
-    /// body. Defaults to 100.
-    ///
-    /// Note that some additional `end`s, `else`s, and `unreachable`s may be
-    /// appended to the function body to finish block scopes.
-    fn max_instructions(&self) -> usize {
-        100
-    }
-
-    /// The minimum number of memories to use. Defaults to 0. This includes
-    /// imported memories.
-    fn min_memories(&self) -> u32 {
-        0
-    }
-
-    /// The maximum number of memories to use. Defaults to 1. This includes
-    /// imported memories.
-    ///
-    /// Note that more than one memory is in the realm of the multi-memory wasm
-    /// proposal.
-    fn max_memories(&self) -> usize {
-        1
-    }
-
-    /// The minimum number of tables to use. Defaults to 0. This includes
-    /// imported tables.
-    fn min_tables(&self) -> u32 {
-        0
-    }
-
-    /// The maximum number of tables to use. Defaults to 1. This includes
-    /// imported tables.
-    ///
-    /// Note that more than one table is in the realm of the reference types
-    /// proposal.
-    fn max_tables(&self) -> usize {
-        1
-    }
-
-    /// The maximum, in 64k Wasm pages, of any memory's initial or maximum size.
-    ///
-    /// Defaults to 2^16 = 65536 for 32-bit Wasm and 2^48 for 64-bit wasm.
-    fn max_memory_pages(&self, is_64: bool) -> u64 {
-        if is_64 {
-            1 << 48
-        } else {
-            1 << 16
+macro_rules! define_config {
+    (
+        $(#[$attr:meta])*
+        pub struct Config {
+            $(
+                $(#[$field_attr:meta])*
+                pub $field:ident : $field_ty:ty,
+            )*
+        }
+    ) => {
+        $(#[$attr])*
+        pub struct Config {
+            /// The imports that may be used when generating the module.
+            ///
+            /// Defaults to `None` which means that any arbitrary import can be
+            /// generated.
+            ///
+            /// To only allow specific imports, override this method to return a
+            /// WebAssembly module which describes the imports allowed.
+            ///
+            /// Note that [`Self::min_imports`] is ignored when
+            /// `available_imports` are enabled.
+            ///
+            /// The returned value must be a valid binary encoding of a
+            /// WebAssembly module. `wasm-smith` will panic if the module cannot
+            /// be parsed.
+            ///
+            /// # Example
+            ///
+            /// An implementation of this method could use the `wat` crate to
+            /// provide a human-readable and maintainable description:
+            ///
+            /// ```rust
+            /// Some(wat::parse_str(r#"
+            ///     (module
+            ///         (import "env" "ping" (func (param i32)))
+            ///         (import "env" "pong" (func (result i32)))
+            ///         (import "env" "memory" (memory 1))
+            ///         (import "env" "table" (table 1))
+            ///         (import "env" "tag" (tag (param i32)))
+            ///     )
+            /// "#))
+            /// # ;
+            /// ```
+            pub available_imports: Option<Vec<u8>>,
+
+            $(
+                $(#[$field_attr])*
+                pub $field: $field_ty,
+            )*
         }
-    }
-
-    /// Whether every Wasm memory must have a maximum size specified. Defaults
-    /// to `false`.
-    fn memory_max_size_required(&self) -> bool {
-        false
-    }
-
-    /// The maximum, elements, of any table's initial or maximum size.
-    ///
-    /// Defaults to 1 million.
-    fn max_table_elements(&self) -> u32 {
-        1_000_000
-    }
-
-    /// Whether every Wasm table must have a maximum size specified. Defaults
-    /// to `false`.
-    fn table_max_size_required(&self) -> bool {
-        false
-    }
-
-    /// The maximum number of instances to use. Defaults to 10. This includes
-    /// imported instances.
-    ///
-    /// Note that this is irrelevant unless module linking is enabled.
-    fn max_instances(&self) -> usize {
-        10
-    }
-
-    /// The maximum number of modules to use. Defaults to 10. This includes
-    /// imported modules.
-    ///
-    /// Note that this is irrelevant unless component model support is enabled.
-    fn max_modules(&self) -> usize {
-        10
-    }
-
-    /// The maximum number of components to use. Defaults to 10. This includes
-    /// imported components.
-    ///
-    /// Note that this is irrelevant unless component model support is enabled.
-    fn max_components(&self) -> usize {
-        10
-    }
-
-    /// The maximum number of values to use. Defaults to 10. This includes
-    /// imported values.
-    ///
-    /// Note that this is irrelevant unless value model support is enabled.
-    fn max_values(&self) -> usize {
-        10
-    }
-
-    /// Control the probability of generating memory offsets that are in bounds
-    /// vs. potentially out of bounds.
-    ///
-    /// Return a tuple `(a, b, c)` where
-    ///
-    /// * `a / (a+b+c)` is the probability of generating a memory offset within
-    ///   `0..memory.min_size`, i.e. an offset that is definitely in bounds of a
-    ///   non-empty memory. (Note that if a memory is zero-sized, however, no
-    ///   offset will ever be in bounds.)
-    ///
-    /// * `b / (a+b+c)` is the probability of generating a memory offset within
-    ///   `memory.min_size..memory.max_size`, i.e. an offset that is possibly in
-    ///   bounds if the memory has been grown.
-    ///
-    /// * `c / (a+b+c)` is the probability of generating a memory offset within
-    ///   the range `memory.max_size..`, i.e. an offset that is definitely out
-    ///   of bounds.
-    ///
-    /// At least one of `a`, `b`, and `c` must be non-zero.
-    ///
-    /// If you want to always generate memory offsets that are definitely in
-    /// bounds of a non-zero-sized memory, for example, you could return `(1, 0,
-    /// 0)`.
-    ///
-    /// By default, returns `(75, 24, 1)`.
-    fn memory_offset_choices(&self) -> (u32, u32, u32) {
-        (75, 24, 1)
-    }
-
-    /// The minimum size, in bytes, of all leb-encoded integers. Defaults to 1.
-    ///
-    /// This is useful for ensuring that all leb-encoded integers are decoded as
-    /// such rather than as simply one byte. This will forcibly extend leb
-    /// integers with an over-long encoding in some locations if the size would
-    /// otherwise be smaller than number returned here.
-    fn min_uleb_size(&self) -> u8 {
-        1
-    }
-
-    /// Determines whether the bulk memory proposal is enabled for generating
-    /// instructions.
-    ///
-    /// Defaults to `false`.
-    fn bulk_memory_enabled(&self) -> bool {
-        false
-    }
-
-    /// Determines whether the reference types proposal is enabled for
-    /// generating instructions.
-    ///
-    /// Defaults to `false`.
-    fn reference_types_enabled(&self) -> bool {
-        false
-    }
-
-    /// Determines whether the tail calls proposal is enabled for generating
-    /// instructions.
-    ///
-    /// Defaults to `false`.
-    fn tail_call_enabled(&self) -> bool {
-        false
-    }
-
-    /// Determines whether the SIMD proposal is enabled for
-    /// generating instructions.
-    ///
-    /// Defaults to `false`.
-    fn simd_enabled(&self) -> bool {
-        false
-    }
-
-    /// Determines whether the Relaxed SIMD proposal is enabled for
-    /// generating instructions.
-    ///
-    /// Defaults to `false`.
-    fn relaxed_simd_enabled(&self) -> bool {
-        false
-    }
-
-    /// Determines whether the exception-handling proposal is enabled for
-    /// generating instructions.
-    ///
-    /// Defaults to `false`.
-    fn exceptions_enabled(&self) -> bool {
-        false
-    }
-
-    /// Determines whether the multi-value results are enabled.
-    ///
-    /// Defaults to `true`.
-    fn multi_value_enabled(&self) -> bool {
-        true
-    }
-
-    /// Determines whether the nontrapping-float-to-int-conversions propsal is enabled.
-    ///
-    /// Defaults to `true`.
-    fn saturating_float_to_int_enabled(&self) -> bool {
-        true
-    }
-
-    /// Determines whether the sign-extension-ops propsal is enabled.
-    ///
-    /// Defaults to `true`.
-    fn sign_extension_ops_enabled(&self) -> bool {
-        true
-    }
-
-    /// Determines whether a `start` export may be included. Defaults to `true`.
-    fn allow_start_export(&self) -> bool {
-        true
-    }
 
-    /// Returns the maximal size of the `alias` section.
-    fn max_aliases(&self) -> usize {
-        1_000
-    }
+        #[cfg(feature = "_internal_cli")]
+        #[doc(hidden)]
+        #[derive(Clone, Debug, Default, clap::Parser, serde_derive::Deserialize)]
+        #[serde(rename_all = "kebab-case")]
+        pub struct InternalOptionalConfig {
+            /// The imports that may be used when generating the module.
+            ///
+            /// When unspecified, any arbitrary import can be generated.
+            ///
+            /// To only allow specific imports, provide a file path of a
+            /// WebAssembly module which describes the imports allowed.
+            ///
+            /// Note that [`Self::min_imports`] is ignored when
+            /// `available_imports` are enabled.
+            ///
+            /// The returned value must be a valid binary encoding of a
+            /// WebAssembly module. `wasm-smith` will panic if the module cannot
+            /// be parsed.
+            #[cfg_attr(feature = "clap", clap(long))]
+            available_imports: Option<std::path::PathBuf>,
+
+            $(
+                $(#[$field_attr])*
+                #[cfg_attr(feature = "clap", clap(long))]
+                pub $field: Option<$field_ty>,
+            )*
+        }
 
-    /// Returns the maximal nesting depth of modules with the module linking
-    /// proposal.
-    fn max_nesting_depth(&self) -> usize {
-        10
-    }
+        #[cfg(feature = "_internal_cli")]
+        impl InternalOptionalConfig {
+            pub fn or(self, other: Self) -> Self {
+                Self {
+                    available_imports: self.available_imports.or(other.available_imports),
 
-    /// Returns the maximal effective size of any type generated by wasm-smith.
-    ///
-    /// Note that this number is roughly in units of "how many types would be
-    /// needed to represent the recursive type". A function with 8 parameters
-    /// and 2 results would take 11 types (one for the type, 10 for
-    /// params/results). A module type with 2 imports and 3 exports would
-    /// take 6 (module + imports + exports) plus the size of each import/export
-    /// type. This is a somewhat rough measurement that is not intended to be
-    /// very precise.
-    fn max_type_size(&self) -> u32 {
-        1_000
-    }
-
-    /// Returns whether 64-bit memories are allowed.
-    ///
-    /// Note that this is the gate for the memory64 proposal to WebAssembly.
-    fn memory64_enabled(&self) -> bool {
-        false
-    }
+                    $(
+                        $field: self.$field.or(other.$field),
+                    )*
+                }
+            }
+        }
 
-    /// Returns whether NaN values are canonicalized after all f32/f64
-    /// operation.
-    ///
-    /// This can be useful when a generated wasm module is executed in multiple
-    /// runtimes which may produce different NaN values. This ensures that the
-    /// generated module will always use the same NaN representation for all
-    /// instructions which have visible side effects, for example writing floats
-    /// to memory or float-to-int bitcast instructions.
-    fn canonicalize_nans(&self) -> bool {
-        false
+        #[cfg(feature = "_internal_cli")]
+        impl TryFrom<InternalOptionalConfig> for Config {
+            type Error = anyhow::Error;
+            fn try_from(config: InternalOptionalConfig) -> anyhow::Result<Config> {
+                let default = Config::default();
+                Ok(Config {
+                    available_imports: if let Some(file) = config
+                        .available_imports
+                        .as_ref() {
+                            Some(wat::parse_file(file)?)
+                        } else {
+                            None
+                        },
+
+                    $(
+                        $field: config.$field.unwrap_or(default.$field),
+                    )*
+                })
+            }
+        }
     }
+}
 
-    /// Returns the kinds of instructions allowed in the generated wasm
-    /// programs.
-    ///
-    /// The categories of instructions match the categories used by the
-    /// [WebAssembly
-    /// specification](https://webassembly.github.io/spec/core/syntax/instructions.html);
-    /// e.g., numeric, vector, control, memory, etc. Note that modifying this
-    /// setting is separate from the proposal flags; that is, if `simd_enabled()
-    /// == true` but `allowed_instruction()` does not include vector
-    /// instructions, the generated programs will not include these instructions
-    /// but could contain vector types.
-    fn allowed_instructions(&self) -> InstructionKinds {
-        InstructionKinds::all()
+define_config! {
+    /// Configuration for a generated module.
+    ///
+    /// Don't care to configure your generated modules? Just use
+    /// [`Module::arbitrary`][crate::Module], which internally uses the default
+    /// configuration.
+    ///
+    /// Want control over the shape of the module that gets generated? Create a
+    /// `Config` and then pass it to [`Module::new`][crate::Module::new].
+    ///
+    /// # Swarm Testing
+    ///
+    /// You can use the `Arbitrary for Config` implementation for [swarm
+    /// testing]. This will dynamically -- but still deterministically -- choose
+    /// configuration options for you.
+    ///
+    /// [swarm testing]: https://www.cs.utah.edu/~regehr/papers/swarm12.pdf
+    ///
+    /// Note that we pick only *maximums*, not minimums, here because it is more
+    /// complex to describe the domain of valid configs when minima are involved
+    /// (`min <= max` for each variable) and minima are mostly used to ensure
+    /// certain elements are present, but do not widen the range of generated
+    /// Wasm modules.
+    #[derive(Clone, Debug)]
+    pub struct Config {
+        /// Determines whether a `start` export may be included. Defaults to `true`.
+        pub allow_start_export: bool,
+
+        /// The kinds of instructions allowed in the generated wasm
+        /// programs. Defaults to all.
+        ///
+        /// The categories of instructions match the categories used by the
+        /// [WebAssembly
+        /// specification](https://webassembly.github.io/spec/core/syntax/instructions.html);
+        /// e.g., numeric, vector, control, memory, etc.
+        ///
+        /// Note that modifying this setting is separate from the proposal
+        /// flags; that is, if `simd_enabled() == true` but
+        /// `allowed_instruction()` does not include vector instructions, the
+        /// generated programs will not include these instructions but could
+        /// contain vector types.
+        pub allowed_instructions: InstructionKinds,
+
+        /// Determines whether the bulk memory proposal is enabled for
+        /// generating instructions.
+        ///
+        /// Defaults to `false`.
+        pub bulk_memory_enabled: bool,
+
+        /// Returns whether NaN values are canonicalized after all f32/f64
+        /// operation. Defaults to false.
+        ///
+        /// This can be useful when a generated wasm module is executed in
+        /// multiple runtimes which may produce different NaN values. This
+        /// ensures that the generated module will always use the same NaN
+        /// representation for all instructions which have visible side effects,
+        /// for example writing floats to memory or float-to-int bitcast
+        /// instructions.
+        pub canonicalize_nans: bool,
+
+        /// Returns whether we should avoid generating code that will possibly
+        /// trap.
+        ///
+        /// For some trapping instructions, this will emit extra instructions to
+        /// ensure they don't trap, while some instructions will simply be
+        /// excluded.  In cases where we would run into a trap, we instead
+        /// choose some arbitrary non-trapping behavior. For example, if we
+        /// detect that a Load instruction would attempt to access out-of-bounds
+        /// memory, we instead pretend the load succeeded and push 0 onto the
+        /// stack.
+        ///
+        /// One type of trap that we can't currently avoid is
+        /// StackOverflow. Even when `disallow_traps` is set to true, wasm-smith
+        /// will eventually generate a program that infinitely recurses, causing
+        /// the call stack to be exhausted.
+        ///
+        /// Defaults to `false`.
+        pub disallow_traps: bool,
+
+        /// Determines whether the exception-handling proposal is enabled for
+        /// generating instructions.
+        ///
+        /// Defaults to `false`.
+        pub exceptions_enabled: bool,
+
+        /// Export all WebAssembly objects in the module. Defaults to false.
+        ///
+        /// This overrides [`Config::min_exports`] and [`Config::max_exports`].
+        pub export_everything: bool,
+
+        /// Returns whether we should generate custom sections or not. Defaults
+        /// to false.
+        pub generate_custom_sections: bool,
+
+        /// Returns the maximal size of the `alias` section. Defaults to 1000.
+        pub max_aliases: usize,
+
+        /// The maximum number of components to use. Defaults to 10.
+        ///
+        /// This includes imported components.
+        ///
+        /// Note that this is only relevant for components.
+        pub max_components: usize,
+
+        /// The maximum number of data segments to generate. Defaults to 100.
+        pub max_data_segments: usize,
+
+        /// The maximum number of element segments to generate. Defaults to 100.
+        pub max_element_segments: usize,
+
+        /// The maximum number of elements within a segment to
+        /// generate. Defaults to 100.
+        pub max_elements: usize,
+
+        /// The maximum number of exports to generate. Defaults to 100.
+        pub max_exports: usize,
+
+        /// The maximum number of functions to generate. Defaults to 100.  This
+        /// includes imported functions.
+        pub max_funcs: usize,
+
+        /// The maximum number of globals to generate. Defaults to 100.  This
+        /// includes imported globals.
+        pub max_globals: usize,
+
+        /// The maximum number of imports to generate. Defaults to 100.
+        pub max_imports: usize,
+
+        /// The maximum number of instances to use. Defaults to 10.
+        ///
+        /// This includes imported instances.
+        ///
+        /// Note that this is only relevant for components.
+        pub max_instances: usize,
+
+        /// The maximum number of instructions to generate in a function
+        /// body. Defaults to 100.
+        ///
+        /// Note that some additional `end`s, `else`s, and `unreachable`s may be
+        /// appended to the function body to finish block scopes.
+        pub max_instructions: usize,
+
+        /// The maximum number of memories to use. Defaults to 1.
+        ///
+        /// This includes imported memories.
+        ///
+        /// Note that more than one memory is in the realm of the multi-memory
+        /// wasm proposal.
+        pub max_memories: usize,
+
+        /// The maximum, in 64k Wasm pages, of any 32-bit memory's initial or
+        /// maximum size.
+        ///
+        /// Defaults to 2^16.
+        pub max_memory32_pages: u64,
+
+        /// The maximum, in 64k Wasm pages, of any 64-bit memory's initial or
+        /// maximum size.
+        ///
+        /// Defaults to 2^48.
+        pub max_memory64_pages: u64,
+
+        /// The maximum number of modules to use. Defaults to 10.
+        ///
+        /// This includes imported modules.
+        ///
+        /// Note that this is only relevant for components.
+        pub max_modules: usize,
+
+        /// Returns the maximal nesting depth of modules with the component
+        /// model proposal. Defaults to 10.
+        pub max_nesting_depth: usize,
+
+        /// The maximum, elements, of any table's initial or maximum
+        /// size. Defaults to 1 million.
+        pub max_table_elements: u32,
+
+        /// The maximum number of tables to use. Defaults to 1.
+        ///
+        /// This includes imported tables.
+        ///
+        /// Note that more than one table is in the realm of the reference types
+        /// proposal.
+        pub max_tables: usize,
+
+        /// The maximum number of tags to generate. Defaults to 100.
+        pub max_tags: usize,
+
+        /// Returns the maximal effective size of any type generated by
+        /// wasm-smith.
+        ///
+        /// Note that this number is roughly in units of "how many types would
+        /// be needed to represent the recursive type". A function with 8
+        /// parameters and 2 results would take 11 types (one for the type, 10
+        /// for params/results). A module type with 2 imports and 3 exports
+        /// would take 6 (module + imports + exports) plus the size of each
+        /// import/export type. This is a somewhat rough measurement that is not
+        /// intended to be very precise.
+        ///
+        /// Defaults to 1000.
+        pub max_type_size: u32,
+
+        /// The maximum number of types to generate. Defaults to 100.
+        pub max_types: usize,
+
+        /// The maximum number of values to use. Defaults to 10.
+        ///
+        /// This includes imported values.
+        ///
+        /// Note that this is irrelevant unless value model support is enabled.
+        pub max_values: usize,
+
+        /// Returns whether 64-bit memories are allowed. Defaults to false.
+        ///
+        /// Note that this is the gate for the memory64 proposal to WebAssembly.
+        pub memory64_enabled: bool,
+
+        /// Whether every Wasm memory must have a maximum size
+        /// specified. Defaults to `false`.
+        pub memory_max_size_required: bool,
+
+        /// Control the probability of generating memory offsets that are in
+        /// bounds vs. potentially out of bounds.
+        ///
+        /// See the `MemoryOffsetChoices` struct for details.
+        pub memory_offset_choices: MemoryOffsetChoices,
+
+        /// The minimum number of data segments to generate. Defaults to 0.
+        pub min_data_segments: usize,
+
+        /// The minimum number of element segments to generate. Defaults to 0.
+        pub min_element_segments: usize,
+
+        /// The minimum number of elements within a segment to
+        /// generate. Defaults to 0.
+        pub min_elements: usize,
+
+        /// The minimum number of exports to generate. Defaults to 0.
+        pub min_exports: usize,
+
+        /// The minimum number of functions to generate. Defaults to 0.
+        ///
+        /// This includes imported functions.
+        pub min_funcs: usize,
+
+        /// The minimum number of globals to generate. Defaults to 0.
+        ///
+        /// This includes imported globals.
+        pub min_globals: usize,
+
+        /// The minimum number of imports to generate. Defaults to 0.
+        ///
+        /// Note that if the sum of the maximum function[^1], table, global and
+        /// memory counts is less than the minimum number of imports, then it
+        /// will not be possible to satisfy all constraints (because imports
+        /// count against the limits for those element kinds). In that case, we
+        /// strictly follow the max-constraints, and can fail to satisfy this
+        /// minimum number.
+        ///
+        /// [^1]: the maximum number of functions is also limited by the number
+        /// of function types arbitrarily chosen; strictly speaking, then, the
+        /// maximum number of imports that can be created due to max-constraints
+        /// is `sum(min(num_func_types, max_funcs), max_tables, max_globals,
+        /// max_memories)`.
+        pub min_imports: usize,
+
+        /// The minimum number of memories to use. Defaults to 0.
+        ///
+        /// This includes imported memories.
+        pub min_memories: u32,
+
+        /// The minimum number of tables to use. Defaults to 0.
+        ///
+        /// This includes imported tables.
+        pub min_tables: u32,
+
+        /// The minimum number of tags to generate. Defaults to 0.
+        pub min_tags: usize,
+
+        /// The minimum number of types to generate. Defaults to 0.
+        pub min_types: usize,
+
+        /// The minimum size, in bytes, of all leb-encoded integers. Defaults to
+        /// 1.
+        ///
+        /// This is useful for ensuring that all leb-encoded integers are
+        /// decoded as such rather than as simply one byte. This will forcibly
+        /// extend leb integers with an over-long encoding in some locations if
+        /// the size would otherwise be smaller than number returned here.
+        pub min_uleb_size: u8,
+
+        /// Determines whether the multi-value results are enabled.
+        ///
+        /// Defaults to `true`.
+        pub multi_value_enabled: bool,
+
+        /// Determines whether the reference types proposal is enabled for
+        /// generating instructions.
+        ///
+        /// Defaults to `false`.
+        pub reference_types_enabled: bool,
+
+        /// Determines whether the Relaxed SIMD proposal is enabled for
+        /// generating instructions.
+        ///
+        /// Defaults to `false`.
+        pub relaxed_simd_enabled: bool,
+
+        /// Determines whether the nontrapping-float-to-int-conversions propsal
+        /// is enabled.
+        ///
+        /// Defaults to `true`.
+        pub saturating_float_to_int_enabled: bool,
+
+        /// Determines whether the sign-extension-ops propsal is enabled.
+        ///
+        /// Defaults to `true`.
+        pub sign_extension_ops_enabled: bool,
+
+        /// Determines whether the SIMD proposal is enabled for generating
+        /// instructions.
+        ///
+        /// Defaults to `false`.
+        pub simd_enabled: bool,
+
+        /// Determines whether the tail calls proposal is enabled for generating
+        /// instructions.
+        ///
+        /// Defaults to `false`.
+        pub tail_call_enabled: bool,
+
+        /// Whether every Wasm table must have a maximum size
+        /// specified. Defaults to `false`.
+        pub table_max_size_required: bool,
+
+        /// Determines whether the threads proposal is enabled.
+        ///
+        /// The [threads proposal] involves shared linear memory, new atomic
+        /// instructions, and new `wait` and `notify` instructions.
+        ///
+        /// [threads proposal]: https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md
+        ///
+        /// Defaults to `false`.
+        pub threads_enabled: bool,
     }
+}
 
-    /// Returns whether we should generate custom sections or not.
-    ///
-    /// This is false by default.
-    fn generate_custom_sections(&self) -> bool {
-        false
-    }
+/// This is a tuple `(a, b, c)` where
+///
+/// * `a / (a+b+c)` is the probability of generating a memory offset within
+///   `0..memory.min_size`, i.e. an offset that is definitely in bounds of a
+///   non-empty memory. (Note that if a memory is zero-sized, however, no offset
+///   will ever be in bounds.)
+///
+/// * `b / (a+b+c)` is the probability of generating a memory offset within
+///   `memory.min_size..memory.max_size`, i.e. an offset that is possibly in
+///   bounds if the memory has been grown.
+///
+/// * `c / (a+b+c)` is the probability of generating a memory offset within the
+///   range `memory.max_size..`, i.e. an offset that is definitely out of
+///   bounds.
+///
+/// At least one of `a`, `b`, and `c` must be non-zero.
+///
+/// If you want to always generate memory offsets that are definitely in bounds
+/// of a non-zero-sized memory, for example, you could return `(1, 0, 0)`.
+///
+/// The default is `(90, 9, 1)`.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "serde_derive", derive(serde_derive::Deserialize))]
+pub struct MemoryOffsetChoices(pub u32, pub u32, pub u32);
 
-    /// Determines whether the threads proposal is enabled.
-    ///
-    /// The [threads proposal] involves shared linear memory, new atomic
-    /// instructions, and new `wait` and `notify` instructions.
-    ///
-    /// [threads proposal]: https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md
-    ///
-    /// Defaults to `false`.
-    fn threads_enabled(&self) -> bool {
-        false
+impl Default for MemoryOffsetChoices {
+    fn default() -> Self {
+        MemoryOffsetChoices(90, 9, 1)
     }
+}
 
-    /// Returns whether we should avoid generating code that will possibly trap.
-    ///
-    /// For some trapping instructions, this will emit extra instructions to
-    /// ensure they don't trap, while some instructions will simply be excluded.
-    /// In cases where we would run into a trap, we instead choose some
-    /// arbitrary non-trapping behavior. For example, if we detect that a Load
-    /// instruction would attempt to access out-of-bounds memory, we instead
-    /// pretend the load succeeded and push 0 onto the stack.
-    ///
-    /// One type of trap that we can't currently avoid is StackOverflow. Even
-    /// when `disallow_traps` is set to true, wasm-smith will eventually
-    /// generate a program that infinitely recurses, causing the call stack to
-    /// be exhausted.
-    ///
-    /// Defaults to `false`.
-    fn disallow_traps(&self) -> bool {
-        false
+#[cfg(feature = "_internal_cli")]
+impl std::str::FromStr for MemoryOffsetChoices {
+    type Err = String;
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        use std::str::FromStr;
+        let mut parts = s.split(",");
+        let a = parts
+            .next()
+            .ok_or_else(|| "need 3 comma separated values".to_string())?;
+        let a = <u32 as FromStr>::from_str(a).map_err(|e| e.to_string())?;
+        let b = parts
+            .next()
+            .ok_or_else(|| "need 3 comma separated values".to_string())?;
+        let b = <u32 as FromStr>::from_str(b).map_err(|e| e.to_string())?;
+        let c = parts
+            .next()
+            .ok_or_else(|| "need 3 comma separated values".to_string())?;
+        let c = <u32 as FromStr>::from_str(c).map_err(|e| e.to_string())?;
+        if parts.next().is_some() {
+            return Err("found more than 3 comma separated values".to_string());
+        }
+        Ok(MemoryOffsetChoices(a, b, c))
     }
 }
 
-/// The default configuration.
-#[derive(Arbitrary, Debug, Default, Copy, Clone)]
-pub struct DefaultConfig;
-
-impl Config for DefaultConfig {}
-
-/// A module configuration that uses [swarm testing].
-///
-/// Dynamically -- but still deterministically, via its `Arbitrary`
-/// implementation -- chooses configuration options.
-///
-/// [swarm testing]: https://www.cs.utah.edu/~regehr/papers/swarm12.pdf
-///
-/// Note that we pick only *maximums*, not minimums, here because it is more
-/// complex to describe the domain of valid configs when minima are involved
-/// (`min <= max` for each variable) and minima are mostly used to ensure
-/// certain elements are present, but do not widen the range of generated Wasm
-/// modules.
-#[derive(Clone, Debug)]
-#[allow(missing_docs)]
-pub struct SwarmConfig {
-    pub allow_start_export: bool,
-    pub available_imports: Option<Vec<u8>>,
-    pub bulk_memory_enabled: bool,
-    pub canonicalize_nans: bool,
-    pub disallow_traps: bool,
-    pub exceptions_enabled: bool,
-    pub export_everything: bool,
-    pub max_aliases: usize,
-    pub max_components: usize,
-    pub max_data_segments: usize,
-    pub max_element_segments: usize,
-    pub max_elements: usize,
-    pub max_exports: usize,
-    pub max_funcs: usize,
-    pub max_globals: usize,
-    pub max_imports: usize,
-    pub max_instances: usize,
-    pub max_instructions: usize,
-    pub max_memories: usize,
-    pub max_memory_pages: u64,
-    pub max_modules: usize,
-    pub max_nesting_depth: usize,
-    pub max_tables: usize,
-    pub max_tags: usize,
-    pub max_type_size: u32,
-    pub max_types: usize,
-    pub max_values: usize,
-    pub memory64_enabled: bool,
-    pub memory_max_size_required: bool,
-    pub memory_offset_choices: (u32, u32, u32),
-    pub min_data_segments: usize,
-    pub min_element_segments: usize,
-    pub min_elements: usize,
-    pub min_exports: usize,
-    pub min_funcs: usize,
-    pub min_globals: usize,
-    pub min_imports: usize,
-    pub min_memories: u32,
-    pub min_tables: u32,
-    pub min_tags: usize,
-    pub min_types: usize,
-    pub min_uleb_size: u8,
-    pub multi_value_enabled: bool,
-    pub reference_types_enabled: bool,
-    pub tail_call_enabled: bool,
-    pub relaxed_simd_enabled: bool,
-    pub saturating_float_to_int_enabled: bool,
-    pub sign_extension_enabled: bool,
-    pub simd_enabled: bool,
-    pub threads_enabled: bool,
-    pub allowed_instructions: InstructionKinds,
-    pub max_table_elements: u32,
-    pub table_max_size_required: bool,
+impl Default for Config {
+    fn default() -> Self {
+        Config {
+            allow_start_export: true,
+            allowed_instructions: InstructionKinds::all(),
+            available_imports: None,
+            bulk_memory_enabled: false,
+            canonicalize_nans: false,
+            disallow_traps: false,
+            exceptions_enabled: false,
+            export_everything: false,
+            generate_custom_sections: false,
+            max_aliases: 1000,
+            max_components: 10,
+            max_data_segments: 100,
+            max_element_segments: 100,
+            max_elements: 100,
+            max_exports: 100,
+            max_funcs: 100,
+            max_globals: 100,
+            max_imports: 100,
+            max_instances: 10,
+            max_instructions: 100,
+            max_memories: 1,
+            max_memory32_pages: 1 << 16,
+            max_memory64_pages: 1 << 48,
+            max_modules: 10,
+            max_nesting_depth: 10,
+            max_tables: 1,
+            max_tags: 100,
+            max_type_size: 1000,
+            max_types: 100,
+            max_values: 10,
+            memory64_enabled: false,
+            memory_max_size_required: false,
+            memory_offset_choices: MemoryOffsetChoices::default(),
+            min_data_segments: 0,
+            min_element_segments: 0,
+            min_elements: 0,
+            min_exports: 0,
+            min_funcs: 0,
+            min_globals: 0,
+            min_imports: 0,
+            min_memories: 0,
+            max_table_elements: 1_000_000,
+            min_tables: 0,
+            min_tags: 0,
+            min_types: 0,
+            min_uleb_size: 1,
+            multi_value_enabled: true,
+            reference_types_enabled: false,
+            relaxed_simd_enabled: false,
+            saturating_float_to_int_enabled: true,
+            sign_extension_ops_enabled: true,
+            simd_enabled: false,
+            table_max_size_required: false,
+            tail_call_enabled: false,
+            threads_enabled: false,
+        }
+    }
 }
 
-impl<'a> Arbitrary<'a> for SwarmConfig {
+impl<'a> Arbitrary<'a> for Config {
     fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
         const MAX_MAXIMUM: usize = 1000;
 
         let reference_types_enabled: bool = u.arbitrary()?;
         let max_tables = if reference_types_enabled { 100 } else { 1 };
 
-        Ok(SwarmConfig {
+        Ok(Config {
             max_types: u.int_in_range(0..=MAX_MAXIMUM)?,
             max_imports: u.int_in_range(0..=MAX_MAXIMUM)?,
             max_tags: u.int_in_range(0..=MAX_MAXIMUM)?,
@@ -574,7 +604,8 @@ impl<'a> Arbitrary<'a> for SwarmConfig {
             max_instructions: u.int_in_range(0..=MAX_MAXIMUM)?,
             max_memories: u.int_in_range(0..=100)?,
             max_tables,
-            max_memory_pages: u.arbitrary()?,
+            max_memory32_pages: u.int_in_range(0..=1 << 16)?,
+            max_memory64_pages: u.int_in_range(0..=1 << 48)?,
             min_uleb_size: u.int_in_range(0..=5)?,
             bulk_memory_enabled: u.arbitrary()?,
             reference_types_enabled,
@@ -583,7 +614,7 @@ impl<'a> Arbitrary<'a> for SwarmConfig {
             max_aliases: u.int_in_range(0..=MAX_MAXIMUM)?,
             max_nesting_depth: u.int_in_range(0..=10)?,
             saturating_float_to_int_enabled: u.arbitrary()?,
-            sign_extension_enabled: u.arbitrary()?,
+            sign_extension_ops_enabled: u.arbitrary()?,
             allowed_instructions: {
                 use flagset::Flags;
                 let mut allowed = Vec::new();
@@ -616,7 +647,7 @@ impl<'a> Arbitrary<'a> for SwarmConfig {
             max_modules: 0,
             max_components: 0,
             max_values: 0,
-            memory_offset_choices: (75, 24, 1),
+            memory_offset_choices: MemoryOffsetChoices::default(),
             allow_start_export: true,
             relaxed_simd_enabled: false,
             exceptions_enabled: false,
@@ -628,210 +659,7 @@ impl<'a> Arbitrary<'a> for SwarmConfig {
             export_everything: false,
             disallow_traps: false,
             tail_call_enabled: false,
+            generate_custom_sections: false,
         })
     }
 }
-
-impl Config for SwarmConfig {
-    fn min_types(&self) -> usize {
-        self.min_types
-    }
-
-    fn max_types(&self) -> usize {
-        self.max_types
-    }
-
-    fn min_imports(&self) -> usize {
-        self.min_imports
-    }
-
-    fn max_imports(&self) -> usize {
-        self.max_imports
-    }
-
-    fn available_imports(&self) -> Option<Cow<'_, [u8]>> {
-        self.available_imports
-            .as_ref()
-            .map(|is| Cow::Borrowed(&is[..]))
-    }
-
-    fn min_funcs(&self) -> usize {
-        self.min_funcs
-    }
-
-    fn max_funcs(&self) -> usize {
-        self.max_funcs
-    }
-
-    fn min_globals(&self) -> usize {
-        self.min_globals
-    }
-
-    fn max_globals(&self) -> usize {
-        self.max_globals
-    }
-
-    fn min_exports(&self) -> usize {
-        self.min_exports
-    }
-
-    fn max_exports(&self) -> usize {
-        self.max_exports
-    }
-
-    fn export_everything(&self) -> bool {
-        self.export_everything
-    }
-
-    fn min_element_segments(&self) -> usize {
-        self.min_element_segments
-    }
-
-    fn max_element_segments(&self) -> usize {
-        self.max_element_segments
-    }
-
-    fn min_elements(&self) -> usize {
-        self.min_elements
-    }
-
-    fn max_elements(&self) -> usize {
-        self.max_elements
-    }
-
-    fn min_data_segments(&self) -> usize {
-        self.min_data_segments
-    }
-
-    fn max_data_segments(&self) -> usize {
-        self.max_data_segments
-    }
-
-    fn max_instructions(&self) -> usize {
-        self.max_instructions
-    }
-
-    fn min_memories(&self) -> u32 {
-        self.min_memories
-    }
-
-    fn max_memories(&self) -> usize {
-        self.max_memories
-    }
-
-    fn min_tables(&self) -> u32 {
-        self.min_tables
-    }
-
-    fn max_tables(&self) -> usize {
-        self.max_tables
-    }
-
-    fn max_memory_pages(&self, is_64: bool) -> u64 {
-        if is_64 {
-            self.max_memory_pages.min(1 << 48)
-        } else {
-            self.max_memory_pages.min(1 << 16)
-        }
-    }
-
-    fn memory_max_size_required(&self) -> bool {
-        self.memory_max_size_required
-    }
-
-    fn max_instances(&self) -> usize {
-        self.max_instances
-    }
-
-    fn max_modules(&self) -> usize {
-        self.max_modules
-    }
-
-    fn memory_offset_choices(&self) -> (u32, u32, u32) {
-        self.memory_offset_choices
-    }
-
-    fn min_uleb_size(&self) -> u8 {
-        self.min_uleb_size
-    }
-
-    fn bulk_memory_enabled(&self) -> bool {
-        self.bulk_memory_enabled
-    }
-
-    fn reference_types_enabled(&self) -> bool {
-        self.reference_types_enabled
-    }
-
-    fn tail_call_enabled(&self) -> bool {
-        self.tail_call_enabled
-    }
-
-    fn simd_enabled(&self) -> bool {
-        self.simd_enabled
-    }
-
-    fn relaxed_simd_enabled(&self) -> bool {
-        self.relaxed_simd_enabled
-    }
-
-    fn exceptions_enabled(&self) -> bool {
-        self.exceptions_enabled
-    }
-
-    fn multi_value_enabled(&self) -> bool {
-        self.multi_value_enabled
-    }
-
-    fn saturating_float_to_int_enabled(&self) -> bool {
-        self.saturating_float_to_int_enabled
-    }
-
-    fn sign_extension_ops_enabled(&self) -> bool {
-        self.sign_extension_enabled
-    }
-
-    fn allow_start_export(&self) -> bool {
-        self.allow_start_export
-    }
-
-    fn max_aliases(&self) -> usize {
-        self.max_aliases
-    }
-
-    fn max_nesting_depth(&self) -> usize {
-        self.max_nesting_depth
-    }
-
-    fn max_type_size(&self) -> u32 {
-        self.max_type_size
-    }
-
-    fn memory64_enabled(&self) -> bool {
-        self.memory64_enabled
-    }
-
-    fn canonicalize_nans(&self) -> bool {
-        self.canonicalize_nans
-    }
-
-    fn threads_enabled(&self) -> bool {
-        self.threads_enabled
-    }
-
-    fn allowed_instructions(&self) -> InstructionKinds {
-        self.allowed_instructions
-    }
-
-    fn max_table_elements(&self) -> u32 {
-        self.max_table_elements
-    }
-
-    fn table_max_size_required(&self) -> bool {
-        self.table_max_size_required
-    }
-
-    fn disallow_traps(&self) -> bool {
-        self.disallow_traps
-    }
-}
diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs
index 9d27aa39..659d80b0 100644
--- a/crates/wasm-smith/src/core.rs
+++ b/crates/wasm-smith/src/core.rs
@@ -4,13 +4,12 @@ mod code_builder;
 pub(crate) mod encode;
 mod terminate;
 
-use crate::{arbitrary_loop, limited_string, unique_string, Config, DefaultConfig};
+use crate::{arbitrary_loop, limited_string, unique_string, Config};
 use arbitrary::{Arbitrary, Result, Unstructured};
 use code_builder::CodeBuilderAllocations;
 use flagset::{flags, FlagSet};
 use std::collections::HashSet;
 use std::convert::TryFrom;
-use std::marker;
 use std::ops::Range;
 use std::rc::Rc;
 use std::str::{self, FromStr};
@@ -31,19 +30,18 @@ type Instruction = wasm_encoder::Instruction<'static>;
 
 /// A pseudo-random WebAssembly module.
 ///
-/// Construct instances of this type with [the `Arbitrary`
+/// Construct instances of this type (with default configuration) with [the
+/// `Arbitrary`
 /// trait](https://docs.rs/arbitrary/*/arbitrary/trait.Arbitrary.html).
 ///
 /// ## Configuring Generated Modules
 ///
-/// This uses the [`DefaultConfig`][crate::DefaultConfig] configuration. If you
-/// want to customize the shape of generated modules, define your own
-/// configuration type, implement the [`Config`][crate::Config] trait for it,
-/// and use [`ConfiguredModule<YourConfigType>`][crate::ConfiguredModule]
-/// instead of plain `Module`.
+/// To configure the shape of generated module, create a
+/// [`Config`][crate::Config] and then call [`Module::new`][crate::Module::new]
+/// with it.
 #[derive(Debug)]
 pub struct Module {
-    config: Rc<dyn Config>,
+    config: Config,
     duplicate_imports_behavior: DuplicateImportsBehavior,
     valtypes: Vec<ValType>,
 
@@ -126,24 +124,10 @@ pub struct Module {
 
 impl<'a> Arbitrary<'a> for Module {
     fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
-        Ok(ConfiguredModule::<DefaultConfig>::arbitrary(u)?.module)
+        Module::new(Config::default(), u)
     }
 }
 
-/// A pseudo-random generated WebAssembly file with custom configuration.
-///
-/// If you don't care about custom configuration, use [`Module`][crate::Module]
-/// instead.
-///
-/// For details on configuring, see the [`Config`][crate::Config] trait.
-#[derive(Debug)]
-pub struct ConfiguredModule<C> {
-    /// The generated module, controlled by the configuration of `C` in the
-    /// `Arbitrary` implementation.
-    pub module: Module,
-    _marker: marker::PhantomData<C>,
-}
-
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub(crate) enum DuplicateImportsBehavior {
     Allowed,
@@ -152,18 +136,18 @@ pub(crate) enum DuplicateImportsBehavior {
 
 impl Module {
     /// Returns a reference to the internal configuration.
-    pub fn config(&self) -> &dyn Config {
-        &*self.config
+    pub fn config(&self) -> &Config {
+        &self.config
     }
 
     /// Creates a new `Module` with the specified `config` for
     /// configuration and `Unstructured` for the DNA of this module.
-    pub fn new(config: impl Config, u: &mut Unstructured<'_>) -> Result<Self> {
-        Self::new_internal(Rc::new(config), u, DuplicateImportsBehavior::Allowed)
+    pub fn new(config: Config, u: &mut Unstructured<'_>) -> Result<Self> {
+        Self::new_internal(config, u, DuplicateImportsBehavior::Allowed)
     }
 
     pub(crate) fn new_internal(
-        config: Rc<dyn Config>,
+        config: Config,
         u: &mut Unstructured<'_>,
         duplicate_imports_behavior: DuplicateImportsBehavior,
     ) -> Result<Self> {
@@ -172,7 +156,7 @@ impl Module {
         Ok(module)
     }
 
-    fn empty(config: Rc<dyn Config>, duplicate_imports_behavior: DuplicateImportsBehavior) -> Self {
+    fn empty(config: Config, duplicate_imports_behavior: DuplicateImportsBehavior) -> Self {
         Module {
             config,
             duplicate_imports_behavior,
@@ -204,15 +188,6 @@ impl Module {
     }
 }
 
-impl<'a, C: Config + Arbitrary<'a>> Arbitrary<'a> for ConfiguredModule<C> {
-    fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
-        Ok(ConfiguredModule {
-            module: Module::new(C::arbitrary(u)?, u)?,
-            _marker: marker::PhantomData,
-        })
-    }
-}
-
 /// Same as [`Module`], but may be invalid.
 ///
 /// This module generates function bodies differnetly than `Module` to try to
@@ -231,7 +206,7 @@ impl MaybeInvalidModule {
 
 impl<'a> Arbitrary<'a> for MaybeInvalidModule {
     fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
-        let mut module = Module::empty(Rc::new(DefaultConfig), DuplicateImportsBehavior::Allowed);
+        let mut module = Module::empty(Config::default(), DuplicateImportsBehavior::Allowed);
         module.build(u, true)?;
         Ok(MaybeInvalidModule { module })
     }
@@ -348,7 +323,7 @@ pub(crate) enum GlobalInitExpr {
 
 impl Module {
     fn build(&mut self, u: &mut Unstructured, allow_invalid: bool) -> Result<()> {
-        self.valtypes = configured_valtypes(&*self.config);
+        self.valtypes = configured_valtypes(&self.config);
 
         // We attempt to figure out our available imports *before* creating the types section here,
         // because the types for the imports are already well-known (specified by the user) and we
@@ -382,8 +357,8 @@ impl Module {
         // NB: It isn't guaranteed that `self.types.is_empty()` because when
         // available imports are configured, we may add eagerly specigfic types
         // for the available imports before generating arbitrary types here.
-        let min = self.config.min_types().saturating_sub(self.types.len());
-        let max = self.config.max_types().saturating_sub(self.types.len());
+        let min = self.config.min_types.saturating_sub(self.types.len());
+        let max = self.config.max_types.saturating_sub(self.types.len());
         arbitrary_loop(u, min, max, |u| {
             let ty = self.arbitrary_type(u)?;
             self.record_type(&ty);
@@ -408,7 +383,7 @@ impl Module {
         arbitrary_func_type(
             u,
             &self.valtypes,
-            if !self.config.multi_value_enabled() {
+            if !self.config.multi_value_enabled {
                 Some(1)
             } else {
                 None
@@ -417,37 +392,37 @@ impl Module {
     }
 
     fn can_add_local_or_import_tag(&self) -> bool {
-        self.config.exceptions_enabled()
+        self.config.exceptions_enabled
             && self.has_tag_func_types()
-            && self.tags.len() < self.config.max_tags()
+            && self.tags.len() < self.config.max_tags
     }
 
     fn can_add_local_or_import_func(&self) -> bool {
-        !self.func_types.is_empty() && self.funcs.len() < self.config.max_funcs()
+        !self.func_types.is_empty() && self.funcs.len() < self.config.max_funcs
     }
 
     fn can_add_local_or_import_table(&self) -> bool {
-        self.tables.len() < self.config.max_tables()
+        self.tables.len() < self.config.max_tables
     }
 
     fn can_add_local_or_import_global(&self) -> bool {
-        self.globals.len() < self.config.max_globals()
+        self.globals.len() < self.config.max_globals
     }
 
     fn can_add_local_or_import_memory(&self) -> bool {
-        self.memories.len() < self.config.max_memories()
+        self.memories.len() < self.config.max_memories
     }
 
     fn arbitrary_imports(&mut self, u: &mut Unstructured) -> Result<()> {
-        if self.config.max_type_size() < self.type_size {
+        if self.config.max_type_size < self.type_size {
             return Ok(());
         }
 
         let mut import_strings = HashSet::new();
         let mut choices: Vec<fn(&mut Unstructured, &mut Module) -> Result<EntityType>> =
             Vec::with_capacity(5);
-        let min = self.config.min_imports().saturating_sub(self.num_imports);
-        let max = self.config.max_imports().saturating_sub(self.num_imports);
+        let min = self.config.min_imports.saturating_sub(self.num_imports);
+        let max = self.config.max_imports.saturating_sub(self.num_imports);
         arbitrary_loop(u, min, max, |u| {
             choices.clear();
             if self.can_add_local_or_import_tag() {
@@ -494,7 +469,7 @@ impl Module {
             // type size budget allows us to.
             let f = u.choose(&choices)?;
             let entity_type = f(u, self)?;
-            let budget = self.config.max_type_size() - self.type_size;
+            let budget = self.config.max_type_size - self.type_size;
             if entity_type.size() + 1 > budget {
                 return Ok(false);
             }
@@ -538,8 +513,8 @@ impl Module {
     /// Returns `true` if there was a list of available imports configured. Otherwise `false` and
     /// the caller should generate arbitrary imports.
     fn arbitrary_imports_from_available(&mut self, u: &mut Unstructured) -> Result<bool> {
-        let example_module = if let Some(wasm) = self.config.available_imports() {
-            wasm.into_owned()
+        let example_module = if let Some(wasm) = self.config.available_imports.take() {
+            wasm
         } else {
             return Ok(false);
         };
@@ -597,8 +572,8 @@ impl Module {
 
         // In this function we need to place imported function/tag types in the types section and
         // generate import entries (which refer to said types) at the same time.
-        let max_types = self.config.max_types();
-        let multi_value_enabled = self.config.multi_value_enabled();
+        let max_types = self.config.max_types;
+        let multi_value_enabled = self.config.multi_value_enabled;
         let mut new_imports = Vec::with_capacity(available_imports.len());
         let first_type_index = self.types.len();
         let mut new_types = Vec::<Type>::new();
@@ -638,10 +613,10 @@ impl Module {
         };
 
         for import in available_imports {
-            let type_size_budget = self.config.max_type_size() - self.type_size;
+            let type_size_budget = self.config.max_type_size - self.type_size;
             let entity_type = match &import.ty {
                 wasmparser::TypeRef::Func(sig_idx) => {
-                    if self.funcs.len() >= self.config.max_funcs() {
+                    if self.funcs.len() >= self.config.max_funcs {
                         continue;
                     } else if let Some((sig_idx, func_type)) = make_func_type(*sig_idx) {
                         let entity = EntityType::Func(sig_idx as u32, Rc::clone(&func_type));
@@ -656,8 +631,8 @@ impl Module {
                 }
 
                 wasmparser::TypeRef::Tag(wasmparser::TagType { func_type_idx, .. }) => {
-                    let can_add_tag = self.tags.len() < self.config.max_tags();
-                    if !self.config.exceptions_enabled() || !can_add_tag {
+                    let can_add_tag = self.tags.len() < self.config.max_tags;
+                    if !self.config.exceptions_enabled || !can_add_tag {
                         continue;
                     } else if let Some((sig_idx, func_type)) = make_func_type(*func_type_idx) {
                         let tag_type = TagType {
@@ -800,11 +775,11 @@ impl Module {
     }
 
     fn arbitrary_tags(&mut self, u: &mut Unstructured) -> Result<()> {
-        if !self.config.exceptions_enabled() || !self.has_tag_func_types() {
+        if !self.config.exceptions_enabled || !self.has_tag_func_types() {
             return Ok(());
         }
 
-        arbitrary_loop(u, self.config.min_tags(), self.config.max_tags(), |u| {
+        arbitrary_loop(u, self.config.min_tags, self.config.max_tags, |u| {
             if !self.can_add_local_or_import_tag() {
                 return Ok(false);
             }
@@ -819,7 +794,7 @@ impl Module {
             return Ok(());
         }
 
-        arbitrary_loop(u, self.config.min_funcs(), self.config.max_funcs(), |u| {
+        arbitrary_loop(u, self.config.min_funcs, self.config.max_funcs, |u| {
             if !self.can_add_local_or_import_func() {
                 return Ok(false);
             }
@@ -834,8 +809,8 @@ impl Module {
     fn arbitrary_tables(&mut self, u: &mut Unstructured) -> Result<()> {
         arbitrary_loop(
             u,
-            self.config.min_tables() as usize,
-            self.config.max_tables() as usize,
+            self.config.min_tables as usize,
+            self.config.max_tables as usize,
             |u| {
                 if !self.can_add_local_or_import_table() {
                     return Ok(false);
@@ -851,8 +826,8 @@ impl Module {
     fn arbitrary_memories(&mut self, u: &mut Unstructured) -> Result<()> {
         arbitrary_loop(
             u,
-            self.config.min_memories() as usize,
-            self.config.max_memories() as usize,
+            self.config.min_memories as usize,
+            self.config.max_memories as usize,
             |u| {
                 if !self.can_add_local_or_import_memory() {
                     return Ok(false);
@@ -869,57 +844,52 @@ impl Module {
             vec![];
         let num_imported_globals = self.globals.len();
 
-        arbitrary_loop(
-            u,
-            self.config.min_globals(),
-            self.config.max_globals(),
-            |u| {
-                if !self.can_add_local_or_import_global() {
-                    return Ok(false);
-                }
+        arbitrary_loop(u, self.config.min_globals, self.config.max_globals, |u| {
+            if !self.can_add_local_or_import_global() {
+                return Ok(false);
+            }
 
-                let ty = self.arbitrary_global_type(u)?;
-
-                choices.clear();
-                let num_funcs = self.funcs.len() as u32;
-                choices.push(Box::new(move |u, ty| {
-                    Ok(GlobalInitExpr::ConstExpr(match ty {
-                        ValType::I32 => ConstExpr::i32_const(u.arbitrary()?),
-                        ValType::I64 => ConstExpr::i64_const(u.arbitrary()?),
-                        ValType::F32 => ConstExpr::f32_const(u.arbitrary()?),
-                        ValType::F64 => ConstExpr::f64_const(u.arbitrary()?),
-                        ValType::V128 => ConstExpr::v128_const(u.arbitrary()?),
-                        ValType::Ref(ty) => {
-                            assert!(ty.nullable);
-                            if ty.heap_type == HeapType::Func && num_funcs > 0 && u.arbitrary()? {
-                                let func = u.int_in_range(0..=num_funcs - 1)?;
-                                return Ok(GlobalInitExpr::FuncRef(func));
-                            }
-                            ConstExpr::ref_null(ty.heap_type)
-                        }
-                    }))
-                }));
+            let ty = self.arbitrary_global_type(u)?;
 
-                for (i, g) in self.globals[..num_imported_globals].iter().enumerate() {
-                    if !g.mutable && g.val_type == ty.val_type {
-                        choices.push(Box::new(move |_, _| {
-                            Ok(GlobalInitExpr::ConstExpr(ConstExpr::global_get(i as u32)))
-                        }));
+            choices.clear();
+            let num_funcs = self.funcs.len() as u32;
+            choices.push(Box::new(move |u, ty| {
+                Ok(GlobalInitExpr::ConstExpr(match ty {
+                    ValType::I32 => ConstExpr::i32_const(u.arbitrary()?),
+                    ValType::I64 => ConstExpr::i64_const(u.arbitrary()?),
+                    ValType::F32 => ConstExpr::f32_const(u.arbitrary()?),
+                    ValType::F64 => ConstExpr::f64_const(u.arbitrary()?),
+                    ValType::V128 => ConstExpr::v128_const(u.arbitrary()?),
+                    ValType::Ref(ty) => {
+                        assert!(ty.nullable);
+                        if ty.heap_type == HeapType::Func && num_funcs > 0 && u.arbitrary()? {
+                            let func = u.int_in_range(0..=num_funcs - 1)?;
+                            return Ok(GlobalInitExpr::FuncRef(func));
+                        }
+                        ConstExpr::ref_null(ty.heap_type)
                     }
+                }))
+            }));
+
+            for (i, g) in self.globals[..num_imported_globals].iter().enumerate() {
+                if !g.mutable && g.val_type == ty.val_type {
+                    choices.push(Box::new(move |_, _| {
+                        Ok(GlobalInitExpr::ConstExpr(ConstExpr::global_get(i as u32)))
+                    }));
                 }
+            }
 
-                let f = u.choose(&choices)?;
-                let expr = f(u, ty.val_type)?;
-                let global_idx = self.globals.len() as u32;
-                self.globals.push(ty);
-                self.defined_globals.push((global_idx, expr));
-                Ok(true)
-            },
-        )
+            let f = u.choose(&choices)?;
+            let expr = f(u, ty.val_type)?;
+            let global_idx = self.globals.len() as u32;
+            self.globals.push(ty);
+            self.defined_globals.push((global_idx, expr));
+            Ok(true)
+        })
     }
 
     fn arbitrary_exports(&mut self, u: &mut Unstructured) -> Result<()> {
-        if self.config.max_type_size() < self.type_size && !self.config.export_everything() {
+        if self.config.max_type_size < self.type_size && !self.config.export_everything {
             return Ok(());
         }
 
@@ -948,7 +918,7 @@ impl Module {
 
         // If the configuration demands exporting everything, we do so here and
         // early-return.
-        if self.config.export_everything() {
+        if self.config.export_everything {
             for choices_by_kind in choices {
                 for (kind, idx) in choices_by_kind {
                     let name = unique_string(1_000, &mut self.export_names, u)?;
@@ -958,40 +928,35 @@ impl Module {
             return Ok(());
         }
 
-        arbitrary_loop(
-            u,
-            self.config.min_exports(),
-            self.config.max_exports(),
-            |u| {
-                // Remove all candidates for export whose type size exceeds our
-                // remaining budget for type size. Then also remove any classes
-                // of exports which no longer have any candidates.
-                //
-                // If there's nothing remaining after this, then we're done.
-                let max_size = self.config.max_type_size() - self.type_size;
-                for list in choices.iter_mut() {
-                    list.retain(|(kind, idx)| self.type_of(*kind, *idx).size() + 1 < max_size);
-                }
-                choices.retain(|list| !list.is_empty());
-                if choices.is_empty() {
-                    return Ok(false);
-                }
+        arbitrary_loop(u, self.config.min_exports, self.config.max_exports, |u| {
+            // Remove all candidates for export whose type size exceeds our
+            // remaining budget for type size. Then also remove any classes
+            // of exports which no longer have any candidates.
+            //
+            // If there's nothing remaining after this, then we're done.
+            let max_size = self.config.max_type_size - self.type_size;
+            for list in choices.iter_mut() {
+                list.retain(|(kind, idx)| self.type_of(*kind, *idx).size() + 1 < max_size);
+            }
+            choices.retain(|list| !list.is_empty());
+            if choices.is_empty() {
+                return Ok(false);
+            }
 
-                // Pick a name, then pick the export, and then we can record
-                // information about the chosen export.
-                let name = unique_string(1_000, &mut self.export_names, u)?;
-                let list = u.choose(&choices)?;
-                let (kind, idx) = *u.choose(list)?;
-                self.add_arbitrary_export(name, kind, idx)?;
-                Ok(true)
-            },
-        )
+            // Pick a name, then pick the export, and then we can record
+            // information about the chosen export.
+            let name = unique_string(1_000, &mut self.export_names, u)?;
+            let list = u.choose(&choices)?;
+            let (kind, idx) = *u.choose(list)?;
+            self.add_arbitrary_export(name, kind, idx)?;
+            Ok(true)
+        })
     }
 
     fn add_arbitrary_export(&mut self, name: String, kind: ExportKind, idx: u32) -> Result<()> {
         let ty = self.type_of(kind, idx);
         self.type_size += 1 + ty.size();
-        if self.type_size <= self.config.max_type_size() {
+        if self.type_size <= self.config.max_type_size {
             self.exports.push((name, kind, idx));
             Ok(())
         } else {
@@ -1003,7 +968,7 @@ impl Module {
     }
 
     fn arbitrary_start(&mut self, u: &mut Unstructured) -> Result<()> {
-        if !self.config.allow_start_export() {
+        if !self.config.allow_start_export {
             return Ok(());
         }
 
@@ -1028,7 +993,7 @@ impl Module {
 
         // Create a helper closure to choose an arbitrary offset.
         let mut offset_global_choices = vec![];
-        if !self.config.disallow_traps() {
+        if !self.config.disallow_traps {
             for (i, g) in self.globals[..self.globals.len() - self.defined_globals.len()]
                 .iter()
                 .enumerate()
@@ -1070,7 +1035,7 @@ impl Module {
             dyn Fn(&mut Unstructured) -> Result<(ElementKind, Option<u32>)> + 'a;
         let mut funcrefs: Vec<Box<GenElemSegment>> = Vec::new();
         let mut externrefs: Vec<Box<GenElemSegment>> = Vec::new();
-        let disallow_traps = self.config().disallow_traps();
+        let disallow_traps = self.config.disallow_traps;
         for (i, ty) in self.tables.iter().enumerate() {
             // If this table starts with no capacity then any non-empty element
             // segment placed onto it will immediately trap, which isn't too
@@ -1093,7 +1058,7 @@ impl Module {
                     arbitrary_active_elem(u, minimum, None, disallow_traps, ty)
                 }));
             }
-            if self.config.bulk_memory_enabled() {
+            if self.config.bulk_memory_enabled {
                 let idx = Some(i as u32);
                 dst.push(Box::new(move |u| {
                     arbitrary_active_elem(u, minimum, idx, disallow_traps, ty)
@@ -1103,10 +1068,10 @@ impl Module {
 
         // Bulk memory enables passive/declared segments for funcrefs, and
         // reference types additionally enables the segments for externrefs.
-        if self.config.bulk_memory_enabled() {
+        if self.config.bulk_memory_enabled {
             funcrefs.push(Box::new(|_| Ok((ElementKind::Passive, None))));
             funcrefs.push(Box::new(|_| Ok((ElementKind::Declared, None))));
-            if self.config.reference_types_enabled() {
+            if self.config.reference_types_enabled {
                 externrefs.push(Box::new(|_| Ok((ElementKind::Passive, None))));
                 externrefs.push(Box::new(|_| Ok((ElementKind::Declared, None))));
             }
@@ -1125,8 +1090,8 @@ impl Module {
         }
         arbitrary_loop(
             u,
-            self.config.min_element_segments(),
-            self.config.max_element_segments(),
+            self.config.min_element_segments,
+            self.config.max_element_segments,
             |u| {
                 // Choose whether to generate a segment whose elements are initialized via
                 // expressions, or one whose elements are initialized via function indices.
@@ -1137,16 +1102,16 @@ impl Module {
                 let (kind, max_size_hint) = u.choose(kind_candidates)?(u)?;
                 let max = max_size_hint
                     .map(|i| usize::try_from(i).unwrap())
-                    .unwrap_or_else(|| self.config.max_elements());
+                    .unwrap_or_else(|| self.config.max_elements);
 
                 // Pick whether we're going to use expression elements or
                 // indices. Note that externrefs must use expressions,
                 // and functions without reference types must use indices.
                 let items = if ty == RefType::EXTERNREF
-                    || (self.config.reference_types_enabled() && u.arbitrary()?)
+                    || (self.config.reference_types_enabled && u.arbitrary()?)
                 {
                     let mut init = vec![];
-                    arbitrary_loop(u, self.config.min_elements(), max, |u| {
+                    arbitrary_loop(u, self.config.min_elements, max, |u| {
                         init.push(
                             if ty == RefType::EXTERNREF || func_max == 0 || u.arbitrary()? {
                                 None
@@ -1160,7 +1125,7 @@ impl Module {
                 } else {
                     let mut init = vec![];
                     if func_max > 0 {
-                        arbitrary_loop(u, self.config.min_elements(), max, |u| {
+                        arbitrary_loop(u, self.config.min_elements, max, |u| {
                             let func_idx = u.int_in_range(0..=func_max - 1)?;
                             init.push(func_idx);
                             Ok(true)
@@ -1220,10 +1185,10 @@ impl Module {
         // With bulk-memory we can generate passive data, otherwise if there are
         // no memories we can't generate any data.
         let memories = self.memories.len() as u32;
-        if memories == 0 && !self.config.bulk_memory_enabled() {
+        if memories == 0 && !self.config.bulk_memory_enabled {
             return Ok(());
         }
-        let disallow_traps = self.config.disallow_traps();
+        let disallow_traps = self.config.disallow_traps;
         let mut choices32: Vec<Box<dyn Fn(&mut Unstructured, u64, usize) -> Result<Offset>>> =
             vec![];
         choices32.push(Box::new(|u, min_size, data_len| {
@@ -1244,7 +1209,7 @@ impl Module {
                 arbitrary_offset(u, min, max, data_len)? as i64
             ))
         }));
-        if !self.config().disallow_traps() {
+        if !self.config.disallow_traps {
             for (i, g) in self.globals[..self.globals.len() - self.defined_globals.len()]
                 .iter()
                 .enumerate()
@@ -1275,14 +1240,14 @@ impl Module {
         // With memories we can generate data segments, and with bulk memory we
         // can generate passive segments. Without these though we can't create
         // a valid module with data segments.
-        if memories.is_empty() && !self.config.bulk_memory_enabled() {
+        if memories.is_empty() && !self.config.bulk_memory_enabled {
             return Ok(());
         }
 
         arbitrary_loop(
             u,
-            self.config.min_data_segments(),
-            self.config.max_data_segments(),
+            self.config.min_data_segments,
+            self.config.max_data_segments,
             |u| {
                 let mut init: Vec<u8> = u.arbitrary()?;
 
@@ -1290,43 +1255,42 @@ impl Module {
                 // Otherwise if there are no memories we *only* generate passive
                 // data. Finally if all conditions are met we use an input byte to
                 // determine if it should be passive or active.
-                let kind = if self.config.bulk_memory_enabled()
-                    && (memories.is_empty() || u.arbitrary()?)
-                {
-                    DataSegmentKind::Passive
-                } else {
-                    let memory_index = *u.choose(&memories)?;
-                    let mem = &self.memories[memory_index as usize];
-                    let f = if mem.memory64 {
-                        u.choose(&choices64)?
+                let kind =
+                    if self.config.bulk_memory_enabled && (memories.is_empty() || u.arbitrary()?) {
+                        DataSegmentKind::Passive
                     } else {
-                        u.choose(&choices32)?
-                    };
-                    let mut offset = f(u, mem.minimum, init.len())?;
-
-                    // If traps are disallowed then truncate the size of the
-                    // data segment to the minimum size of memory to guarantee
-                    // it will fit. Afterwards ensure that the offset of the
-                    // data segment is in-bounds by clamping it to the
-                    if self.config.disallow_traps() {
-                        let max_size = (u64::MAX / 64 / 1024).min(mem.minimum) * 64 * 1024;
-                        init.truncate(max_size as usize);
-                        let max_offset = max_size - init.len() as u64;
-                        match &mut offset {
-                            Offset::Const32(x) => {
-                                *x = (*x as u64).min(max_offset) as i32;
-                            }
-                            Offset::Const64(x) => {
-                                *x = (*x as u64).min(max_offset) as i64;
+                        let memory_index = *u.choose(&memories)?;
+                        let mem = &self.memories[memory_index as usize];
+                        let f = if mem.memory64 {
+                            u.choose(&choices64)?
+                        } else {
+                            u.choose(&choices32)?
+                        };
+                        let mut offset = f(u, mem.minimum, init.len())?;
+
+                        // If traps are disallowed then truncate the size of the
+                        // data segment to the minimum size of memory to guarantee
+                        // it will fit. Afterwards ensure that the offset of the
+                        // data segment is in-bounds by clamping it to the
+                        if self.config.disallow_traps {
+                            let max_size = (u64::MAX / 64 / 1024).min(mem.minimum) * 64 * 1024;
+                            init.truncate(max_size as usize);
+                            let max_offset = max_size - init.len() as u64;
+                            match &mut offset {
+                                Offset::Const32(x) => {
+                                    *x = (*x as u64).min(max_offset) as i32;
+                                }
+                                Offset::Const64(x) => {
+                                    *x = (*x as u64).min(max_offset) as i64;
+                                }
+                                Offset::Global(_) => unreachable!(),
                             }
-                            Offset::Global(_) => unreachable!(),
                         }
-                    }
-                    DataSegmentKind::Active {
-                        offset,
-                        memory_index,
-                    }
-                };
+                        DataSegmentKind::Active {
+                            offset,
+                            memory_index,
+                        }
+                    };
                 self.data.push(DataSegment { kind, init });
                 Ok(true)
             },
@@ -1381,16 +1345,16 @@ pub(crate) fn arbitrary_limits64(
     Ok((min, max))
 }
 
-pub(crate) fn configured_valtypes(config: &dyn Config) -> Vec<ValType> {
+pub(crate) fn configured_valtypes(config: &Config) -> Vec<ValType> {
     let mut valtypes = Vec::with_capacity(7);
     valtypes.push(ValType::I32);
     valtypes.push(ValType::I64);
     valtypes.push(ValType::F32);
     valtypes.push(ValType::F64);
-    if config.simd_enabled() {
+    if config.simd_enabled {
         valtypes.push(ValType::V128);
     }
-    if config.reference_types_enabled() {
+    if config.reference_types_enabled {
         valtypes.push(ValType::EXTERNREF);
         valtypes.push(ValType::FUNCREF);
     }
@@ -1419,28 +1383,24 @@ fn arbitrary_valtype(u: &mut Unstructured, valtypes: &[ValType]) -> Result<ValTy
     Ok(*u.choose(valtypes)?)
 }
 
-pub(crate) fn arbitrary_table_type(u: &mut Unstructured, config: &dyn Config) -> Result<TableType> {
+pub(crate) fn arbitrary_table_type(u: &mut Unstructured, config: &Config) -> Result<TableType> {
     // We don't want to generate tables that are too large on average, so
     // keep the "inbounds" limit here a bit smaller.
     let max_inbounds = 10_000;
-    let min_elements = if config.disallow_traps() {
-        Some(1)
-    } else {
-        None
-    };
-    let max_elements = min_elements.unwrap_or(0).max(config.max_table_elements());
+    let min_elements = if config.disallow_traps { Some(1) } else { None };
+    let max_elements = min_elements.unwrap_or(0).max(config.max_table_elements);
     let (minimum, maximum) = arbitrary_limits32(
         u,
         min_elements,
         max_elements,
-        config.table_max_size_required(),
+        config.table_max_size_required,
         max_inbounds.min(max_elements),
     )?;
-    if config.disallow_traps() {
+    if config.disallow_traps {
         assert!(minimum > 0);
     }
     Ok(TableType {
-        element_type: if config.reference_types_enabled() {
+        element_type: if config.reference_types_enabled {
             *u.choose(&[RefType::FUNCREF, RefType::EXTERNREF])?
         } else {
             RefType::FUNCREF
@@ -1450,27 +1410,25 @@ pub(crate) fn arbitrary_table_type(u: &mut Unstructured, config: &dyn Config) ->
     })
 }
 
-pub(crate) fn arbitrary_memtype(u: &mut Unstructured, config: &dyn Config) -> Result<MemoryType> {
+pub(crate) fn arbitrary_memtype(u: &mut Unstructured, config: &Config) -> Result<MemoryType> {
     // When threads are enabled, we only want to generate shared memories about
     // 25% of the time.
-    let shared = config.threads_enabled() && u.ratio(1, 4)?;
+    let shared = config.threads_enabled && u.ratio(1, 4)?;
     // We want to favor memories <= 1gb in size, allocate at most 16k pages,
     // depending on the maximum number of memories.
-    let memory64 = config.memory64_enabled() && u.arbitrary()?;
-    let max_inbounds = 16 * 1024 / u64::try_from(config.max_memories()).unwrap();
-    let min_pages = if config.disallow_traps() {
-        Some(1)
+    let memory64 = config.memory64_enabled && u.arbitrary()?;
+    let max_inbounds = 16 * 1024 / u64::try_from(config.max_memories).unwrap();
+    let min_pages = if config.disallow_traps { Some(1) } else { None };
+    let max_pages = min_pages.unwrap_or(0).max(if memory64 {
+        config.max_memory64_pages
     } else {
-        None
-    };
-    let max_pages = min_pages
-        .unwrap_or(0)
-        .max(config.max_memory_pages(memory64));
+        config.max_memory32_pages
+    });
     let (minimum, maximum) = arbitrary_limits64(
         u,
         min_pages,
         max_pages,
-        config.memory_max_size_required() || shared,
+        config.memory_max_size_required || shared,
         max_inbounds.min(max_pages),
     )?;
     Ok(MemoryType {
@@ -1669,7 +1627,9 @@ struct Entities {
 /// assert!(kinds.contains(InstructionKind::Memory));
 /// ```
 #[derive(Clone, Copy, Debug, Default)]
+#[cfg_attr(feature = "serde_derive", derive(serde_derive::Deserialize))]
 pub struct InstructionKinds(pub(crate) FlagSet<InstructionKind>);
+
 impl InstructionKinds {
     /// Create a new container.
     pub fn new(kinds: &[InstructionKind]) -> Self {
@@ -1710,6 +1670,18 @@ flags! {
     }
 }
 
+impl FromStr for InstructionKinds {
+    type Err = String;
+    fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
+        let mut kinds = vec![];
+        for part in s.split(",") {
+            let kind = InstructionKind::from_str(part)?;
+            kinds.push(kind);
+        }
+        Ok(InstructionKinds::new(&kinds))
+    }
+}
+
 impl FromStr for InstructionKind {
     type Err = String;
     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs
index 28f0d49f..b31e1112 100644
--- a/crates/wasm-smith/src/core/code_builder.rs
+++ b/crates/wasm-smith/src/core/code_builder.rs
@@ -2,7 +2,7 @@ use super::{
     Elements, FuncType, GlobalInitExpr, Instruction, InstructionKind::*, InstructionKinds, Module,
     ValType,
 };
-use crate::unique_string;
+use crate::{unique_string, MemoryOffsetChoices};
 use arbitrary::{Result, Unstructured};
 use std::collections::{BTreeMap, BTreeSet};
 use std::convert::TryFrom;
@@ -866,7 +866,7 @@ impl CodeBuilder<'_> {
             Box::new(|_| Ok(BlockType::Empty)),
             Box::new(|u| Ok(BlockType::Result(module.arbitrary_valtype(u)?))),
         ];
-        if module.config.multi_value_enabled() {
+        if module.config.multi_value_enabled {
             for (i, ty) in module.func_types() {
                 if self.types_on_stack(&ty.params) {
                     options.push(Box::new(move |_| Ok(BlockType::FunctionType(i as u32))));
@@ -882,19 +882,15 @@ impl CodeBuilder<'_> {
         u: &mut Unstructured,
         module: &Module,
     ) -> Result<Vec<Instruction>> {
-        let max_instructions = module.config.max_instructions();
-        let allowed_instructions = module.config.allowed_instructions();
+        let max_instructions = module.config.max_instructions;
+        let allowed_instructions = module.config.allowed_instructions;
         let mut instructions = vec![];
 
         while !self.allocs.controls.is_empty() {
             let keep_going = instructions.len() < max_instructions
                 && u.arbitrary().map_or(false, |b: u8| b != 0);
             if !keep_going {
-                self.end_active_control_frames(
-                    u,
-                    &mut instructions,
-                    module.config.disallow_traps(),
-                )?;
+                self.end_active_control_frames(u, &mut instructions, module.config.disallow_traps)?;
                 break;
             }
 
@@ -910,7 +906,7 @@ impl CodeBuilder<'_> {
                     self.end_active_control_frames(
                         u,
                         &mut instructions,
-                        module.config.disallow_traps(),
+                        module.config.disallow_traps,
                     )?;
                     break;
                 }
@@ -922,7 +918,7 @@ impl CodeBuilder<'_> {
             // is based off Cranelift's pass for nan canonicalization for which
             // instructions to canonicalize, but the general idea is most
             // floating-point operations.
-            if module.config.canonicalize_nans() {
+            if module.config.canonicalize_nans {
                 match instructions.last().unwrap() {
                     Instruction::F32Ceil
                     | Instruction::F32Floor
@@ -1267,7 +1263,7 @@ fn arbitrary_val(ty: ValType, u: &mut Unstructured<'_>) -> Instruction {
 
 #[inline]
 fn unreachable_valid(module: &Module, _: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
+    !module.config.disallow_traps
 }
 
 fn unreachable(
@@ -1311,7 +1307,7 @@ fn block(
 
 #[inline]
 fn try_valid(module: &Module, _: &mut CodeBuilder) -> bool {
-    module.config.exceptions_enabled()
+    module.config.exceptions_enabled
 }
 
 fn r#try(
@@ -1337,7 +1333,7 @@ fn r#try(
 fn delegate_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
     let control_kind = builder.allocs.controls.last().unwrap().kind;
     // delegate is only valid if end could be used in a try control frame
-    module.config.exceptions_enabled()
+    module.config.exceptions_enabled
         && control_kind == ControlKind::Try
         && end_valid(module, builder)
 }
@@ -1367,7 +1363,7 @@ fn catch_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
     let control_kind = builder.allocs.controls.last().unwrap().kind;
     // catch is only valid if end could be used in a try or catch (not
     // catch_all) control frame. There must also be a tag that we can catch.
-    module.config.exceptions_enabled()
+    module.config.exceptions_enabled
         && (control_kind == ControlKind::Try || control_kind == ControlKind::Catch)
         && end_valid(module, builder)
         && module.tags.len() > 0
@@ -1399,7 +1395,7 @@ fn catch_all_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
     let control_kind = builder.allocs.controls.last().unwrap().kind;
     // catch_all is only valid if end could be used in a try or catch (not
     // catch_all) control frame.
-    module.config.exceptions_enabled()
+    module.config.exceptions_enabled
         && (control_kind == ControlKind::Try || control_kind == ControlKind::Catch)
         && end_valid(module, builder)
 }
@@ -1709,7 +1705,7 @@ fn call_indirect_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
     if builder.allocs.funcref_tables.is_empty() || !builder.type_on_stack(ValType::I32) {
         return false;
     }
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         // We have no way to reflect, at run time, on a `funcref` in
         // the `i`th slot in a table and dynamically avoid trapping
         // `call_indirect`s. Therefore, we can't emit *any*
@@ -1749,7 +1745,7 @@ fn call_indirect(
 
 #[inline]
 fn return_call_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    if !module.config.tail_call_enabled() {
+    if !module.config.tail_call_enabled {
         return false;
     }
 
@@ -1786,14 +1782,14 @@ fn return_call(
 
 #[inline]
 fn return_call_indirect_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    if !module.config.tail_call_enabled()
+    if !module.config.tail_call_enabled
         || builder.allocs.funcref_tables.is_empty()
         || !builder.type_on_stack(ValType::I32)
     {
         return false;
     }
 
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         // See comment in `call_indirect_valid`; same applies here.
         return false;
     }
@@ -1835,7 +1831,7 @@ fn return_call_indirect(
 
 #[inline]
 fn throw_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.exceptions_enabled()
+    module.config.exceptions_enabled
         && builder
             .allocs
             .tags
@@ -1869,7 +1865,7 @@ fn throw(
 #[inline]
 fn rethrow_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
     // There must be a catch or catch_all control on the stack
-    module.config.exceptions_enabled()
+    module.config.exceptions_enabled
         && builder
             .allocs
             .controls
@@ -2113,7 +2109,7 @@ fn i32_load(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0, 1, 2])?;
     builder.allocs.operands.push(Some(ValType::I32));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(Instruction::I32Load(memarg), module, builder, instructions);
     } else {
         instructions.push(Instruction::I32Load(memarg));
@@ -2129,7 +2125,7 @@ fn i64_load(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?;
     builder.allocs.operands.push(Some(ValType::I64));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(Instruction::I64Load(memarg), module, builder, instructions);
     } else {
         instructions.push(Instruction::I64Load(memarg));
@@ -2145,7 +2141,7 @@ fn f32_load(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0, 1, 2])?;
     builder.allocs.operands.push(Some(ValType::F32));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(Instruction::F32Load(memarg), module, builder, instructions);
     } else {
         instructions.push(Instruction::F32Load(memarg));
@@ -2161,7 +2157,7 @@ fn f64_load(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?;
     builder.allocs.operands.push(Some(ValType::F64));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(Instruction::F64Load(memarg), module, builder, instructions);
     } else {
         instructions.push(Instruction::F64Load(memarg));
@@ -2177,7 +2173,7 @@ fn i32_load_8_s(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0])?;
     builder.allocs.operands.push(Some(ValType::I32));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(
             Instruction::I32Load8S(memarg),
             module,
@@ -2198,7 +2194,7 @@ fn i32_load_8_u(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0])?;
     builder.allocs.operands.push(Some(ValType::I32));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(
             Instruction::I32Load8U(memarg),
             module,
@@ -2219,7 +2215,7 @@ fn i32_load_16_s(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0, 1])?;
     builder.allocs.operands.push(Some(ValType::I32));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(
             Instruction::I32Load16S(memarg),
             module,
@@ -2240,7 +2236,7 @@ fn i32_load_16_u(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0, 1])?;
     builder.allocs.operands.push(Some(ValType::I32));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(
             Instruction::I32Load16U(memarg),
             module,
@@ -2261,7 +2257,7 @@ fn i64_load_8_s(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0])?;
     builder.allocs.operands.push(Some(ValType::I64));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(
             Instruction::I64Load8S(memarg),
             module,
@@ -2282,7 +2278,7 @@ fn i64_load_16_s(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0, 1])?;
     builder.allocs.operands.push(Some(ValType::I64));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(
             Instruction::I64Load16S(memarg),
             module,
@@ -2303,7 +2299,7 @@ fn i64_load_32_s(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0, 1, 2])?;
     builder.allocs.operands.push(Some(ValType::I64));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(
             Instruction::I64Load32S(memarg),
             module,
@@ -2324,7 +2320,7 @@ fn i64_load_8_u(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0])?;
     builder.allocs.operands.push(Some(ValType::I64));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(
             Instruction::I64Load8U(memarg),
             module,
@@ -2345,7 +2341,7 @@ fn i64_load_16_u(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0, 1])?;
     builder.allocs.operands.push(Some(ValType::I64));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(
             Instruction::I64Load16U(memarg),
             module,
@@ -2366,7 +2362,7 @@ fn i64_load_32_u(
 ) -> Result<()> {
     let memarg = mem_arg(u, module, builder, &[0, 1, 2])?;
     builder.allocs.operands.push(Some(ValType::I64));
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::load(
             Instruction::I64Load32U(memarg),
             module,
@@ -2398,7 +2394,7 @@ fn i32_store(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I32]);
     let memarg = mem_arg(u, module, builder, &[0, 1, 2])?;
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::store(Instruction::I32Store(memarg), module, builder, instructions);
     } else {
         instructions.push(Instruction::I32Store(memarg));
@@ -2419,7 +2415,7 @@ fn i64_store(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I64]);
     let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?;
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::store(Instruction::I64Store(memarg), module, builder, instructions);
     } else {
         instructions.push(Instruction::I64Store(memarg));
@@ -2440,7 +2436,7 @@ fn f32_store(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::F32]);
     let memarg = mem_arg(u, module, builder, &[0, 1, 2])?;
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::store(Instruction::F32Store(memarg), module, builder, instructions);
     } else {
         instructions.push(Instruction::F32Store(memarg));
@@ -2461,7 +2457,7 @@ fn f64_store(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::F64]);
     let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?;
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::store(Instruction::F64Store(memarg), module, builder, instructions);
     } else {
         instructions.push(Instruction::F64Store(memarg));
@@ -2477,7 +2473,7 @@ fn i32_store_8(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I32]);
     let memarg = mem_arg(u, module, builder, &[0])?;
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::store(
             Instruction::I32Store8(memarg),
             module,
@@ -2498,7 +2494,7 @@ fn i32_store_16(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I32]);
     let memarg = mem_arg(u, module, builder, &[0, 1])?;
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::store(
             Instruction::I32Store16(memarg),
             module,
@@ -2519,7 +2515,7 @@ fn i64_store_8(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I64]);
     let memarg = mem_arg(u, module, builder, &[0])?;
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::store(
             Instruction::I64Store8(memarg),
             module,
@@ -2540,7 +2536,7 @@ fn i64_store_16(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I64]);
     let memarg = mem_arg(u, module, builder, &[0, 1])?;
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::store(
             Instruction::I64Store16(memarg),
             module,
@@ -2561,7 +2557,7 @@ fn i64_store_32(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I64]);
     let memarg = mem_arg(u, module, builder, &[0, 1, 2])?;
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::store(
             Instruction::I64Store32(memarg),
             module,
@@ -2617,9 +2613,9 @@ fn memory_grow(
 
 #[inline]
 fn memory_init_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.bulk_memory_enabled()
+    module.config.bulk_memory_enabled
         && have_data(module, builder)
-        && !module.config.disallow_traps() // Non-trapping memory init not yet implemented
+        && !module.config.disallow_traps // Non-trapping memory init not yet implemented
         && (builder.allocs.memory32.len() > 0
             && builder.types_on_stack(&[ValType::I32, ValType::I32, ValType::I32])
             || (builder.allocs.memory64.len() > 0
@@ -2647,8 +2643,8 @@ fn memory_init(
 
 #[inline]
 fn memory_fill_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.bulk_memory_enabled()
-        && !module.config.disallow_traps() // Non-trapping memory fill generation not yet implemented
+    module.config.bulk_memory_enabled
+        && !module.config.disallow_traps // Non-trapping memory fill generation not yet implemented
         && (builder.allocs.memory32.len() > 0
             && builder.types_on_stack(&[ValType::I32, ValType::I32, ValType::I32])
             || (builder.allocs.memory64.len() > 0
@@ -2674,13 +2670,13 @@ fn memory_fill(
 
 #[inline]
 fn memory_copy_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    if !module.config.bulk_memory_enabled() {
+    if !module.config.bulk_memory_enabled {
         return false;
     }
 
     // The non-trapping case for memory copy has not yet been implemented,
     // so we are excluding them for now
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         return false;
     }
 
@@ -2749,7 +2745,7 @@ fn memory_copy(
 
 #[inline]
 fn data_drop_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    have_data(module, builder) && module.config.bulk_memory_enabled()
+    have_data(module, builder) && module.config.bulk_memory_enabled
 }
 
 fn data_drop(
@@ -3326,7 +3322,7 @@ fn i32_div_s(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I32, ValType::I32]);
     builder.push_operands(&[ValType::I32]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::signed_div_rem(Instruction::I32DivS, builder, instructions);
     } else {
         instructions.push(Instruction::I32DivS);
@@ -3342,7 +3338,7 @@ fn i32_div_u(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I32, ValType::I32]);
     builder.push_operands(&[ValType::I32]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::unsigned_div_rem(Instruction::I32DivU, builder, instructions);
     } else {
         instructions.push(Instruction::I32DivU);
@@ -3358,7 +3354,7 @@ fn i32_rem_s(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I32, ValType::I32]);
     builder.push_operands(&[ValType::I32]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::signed_div_rem(Instruction::I32RemS, builder, instructions);
     } else {
         instructions.push(Instruction::I32RemS);
@@ -3374,7 +3370,7 @@ fn i32_rem_u(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I32, ValType::I32]);
     builder.push_operands(&[ValType::I32]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::unsigned_div_rem(Instruction::I32RemU, builder, instructions);
     } else {
         instructions.push(Instruction::I32RemU);
@@ -3558,7 +3554,7 @@ fn i64_div_s(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I64, ValType::I64]);
     builder.push_operands(&[ValType::I64]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::signed_div_rem(Instruction::I64DivS, builder, instructions);
     } else {
         instructions.push(Instruction::I64DivS);
@@ -3574,7 +3570,7 @@ fn i64_div_u(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I64, ValType::I64]);
     builder.push_operands(&[ValType::I64]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::unsigned_div_rem(Instruction::I64DivU, builder, instructions);
     } else {
         instructions.push(Instruction::I64DivU);
@@ -3590,7 +3586,7 @@ fn i64_rem_s(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I64, ValType::I64]);
     builder.push_operands(&[ValType::I64]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::signed_div_rem(Instruction::I64RemS, builder, instructions);
     } else {
         instructions.push(Instruction::I64RemS);
@@ -3606,7 +3602,7 @@ fn i64_rem_u(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::I64, ValType::I64]);
     builder.push_operands(&[ValType::I64]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::unsigned_div_rem(Instruction::I64RemU, builder, instructions);
     } else {
         instructions.push(Instruction::I64RemU);
@@ -4069,7 +4065,7 @@ fn i32_wrap_i64(
 }
 
 fn nontrapping_f32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.saturating_float_to_int_enabled() && f32_on_stack(module, builder)
+    module.config.saturating_float_to_int_enabled && f32_on_stack(module, builder)
 }
 
 fn i32_trunc_f32_s(
@@ -4080,7 +4076,7 @@ fn i32_trunc_f32_s(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::F32]);
     builder.push_operands(&[ValType::I32]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::trunc(Instruction::I32TruncF32S, builder, instructions);
     } else {
         instructions.push(Instruction::I32TruncF32S);
@@ -4096,7 +4092,7 @@ fn i32_trunc_f32_u(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::F32]);
     builder.push_operands(&[ValType::I32]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::trunc(Instruction::I32TruncF32U, builder, instructions);
     } else {
         instructions.push(Instruction::I32TruncF32U);
@@ -4105,7 +4101,7 @@ fn i32_trunc_f32_u(
 }
 
 fn nontrapping_f64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.saturating_float_to_int_enabled() && f64_on_stack(module, builder)
+    module.config.saturating_float_to_int_enabled && f64_on_stack(module, builder)
 }
 
 fn i32_trunc_f64_s(
@@ -4116,7 +4112,7 @@ fn i32_trunc_f64_s(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::F64]);
     builder.push_operands(&[ValType::I32]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::trunc(Instruction::I32TruncF64S, builder, instructions);
     } else {
         instructions.push(Instruction::I32TruncF64S);
@@ -4132,7 +4128,7 @@ fn i32_trunc_f64_u(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::F64]);
     builder.push_operands(&[ValType::I32]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::trunc(Instruction::I32TruncF64U, builder, instructions);
     } else {
         instructions.push(Instruction::I32TruncF64U);
@@ -4172,7 +4168,7 @@ fn i64_trunc_f32_s(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::F32]);
     builder.push_operands(&[ValType::I64]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::trunc(Instruction::I64TruncF32S, builder, instructions);
     } else {
         instructions.push(Instruction::I64TruncF32S);
@@ -4188,7 +4184,7 @@ fn i64_trunc_f32_u(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::F32]);
     builder.push_operands(&[ValType::I64]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::trunc(Instruction::I64TruncF32U, builder, instructions);
     } else {
         instructions.push(Instruction::I64TruncF32U);
@@ -4204,7 +4200,7 @@ fn i64_trunc_f64_s(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::F64]);
     builder.push_operands(&[ValType::I64]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::trunc(Instruction::I64TruncF64S, builder, instructions);
     } else {
         instructions.push(Instruction::I64TruncF64S);
@@ -4220,7 +4216,7 @@ fn i64_trunc_f64_u(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::F64]);
     builder.push_operands(&[ValType::I64]);
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::trunc(Instruction::I64TruncF64U, builder, instructions);
     } else {
         instructions.push(Instruction::I64TruncF64U);
@@ -4397,7 +4393,7 @@ fn f64_reinterpret_i64(
 }
 
 fn extendable_i32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.sign_extension_ops_enabled() && i32_on_stack(module, builder)
+    module.config.sign_extension_ops_enabled && i32_on_stack(module, builder)
 }
 
 fn i32_extend_8_s(
@@ -4425,7 +4421,7 @@ fn i32_extend_16_s(
 }
 
 fn extendable_i64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.sign_extension_ops_enabled() && i64_on_stack(module, builder)
+    module.config.sign_extension_ops_enabled && i64_on_stack(module, builder)
 }
 
 fn i64_extend_8_s(
@@ -4561,7 +4557,7 @@ fn i64_trunc_sat_f64_u(
 }
 
 fn memory_offset(u: &mut Unstructured, module: &Module, memory_index: u32) -> Result<u64> {
-    let (a, b, c) = module.config.memory_offset_choices();
+    let MemoryOffsetChoices(a, b, c) = module.config.memory_offset_choices;
     assert!(a + b + c != 0);
 
     let memory_type = &module.memories[memory_index as usize];
@@ -4571,7 +4567,7 @@ fn memory_offset(u: &mut Unstructured, module: &Module, memory_index: u32) -> Re
         .map(|max| max.saturating_mul(65536))
         .unwrap_or(u64::MAX);
 
-    let (min, max, true_max) = match (memory_type.memory64, module.config.disallow_traps()) {
+    let (min, max, true_max) = match (memory_type.memory64, module.config.disallow_traps) {
         (true, false) => {
             // 64-bit memories can use the limits calculated above as-is
             (min, max, u64::MAX)
@@ -4652,7 +4648,7 @@ fn data_index(u: &mut Unstructured, module: &Module) -> Result<u32> {
 
 #[inline]
 fn ref_null_valid(module: &Module, _: &mut CodeBuilder) -> bool {
-    module.config.reference_types_enabled()
+    module.config.reference_types_enabled
 }
 
 fn ref_null(
@@ -4669,7 +4665,7 @@ fn ref_null(
 
 #[inline]
 fn ref_func_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.reference_types_enabled() && builder.allocs.referenced_functions.len() > 0
+    module.config.reference_types_enabled && builder.allocs.referenced_functions.len() > 0
 }
 
 fn ref_func(
@@ -4686,7 +4682,7 @@ fn ref_func(
 
 #[inline]
 fn ref_is_null_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.reference_types_enabled()
+    module.config.reference_types_enabled
         && (builder.type_on_stack(ValType::EXTERNREF) || builder.type_on_stack(ValType::FUNCREF))
 }
 
@@ -4704,9 +4700,9 @@ fn ref_is_null(
 
 #[inline]
 fn table_fill_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.reference_types_enabled()
-        && module.config.bulk_memory_enabled()
-        && !module.config.disallow_traps() // Non-trapping table fill generation not yet implemented
+    module.config.reference_types_enabled
+        && module.config.bulk_memory_enabled
+        && !module.config.disallow_traps // Non-trapping table fill generation not yet implemented
         && [ValType::EXTERNREF, ValType::FUNCREF].iter().any(|ty| {
             builder.types_on_stack(&[ValType::I32, *ty, ValType::I32])
                 && module.tables.iter().any(|t| *ty == t.element_type.into())
@@ -4729,8 +4725,8 @@ fn table_fill(
 
 #[inline]
 fn table_set_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.reference_types_enabled()
-    && !module.config.disallow_traps() // Non-trapping table.set generation not yet implemented
+    module.config.reference_types_enabled
+    && !module.config.disallow_traps // Non-trapping table.set generation not yet implemented
         && [ValType::EXTERNREF, ValType::FUNCREF].iter().any(|ty| {
             builder.types_on_stack(&[ValType::I32, *ty])
                 && module.tables.iter().any(|t| *ty == t.element_type.into())
@@ -4752,8 +4748,8 @@ fn table_set(
 
 #[inline]
 fn table_get_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.reference_types_enabled()
-    && !module.config.disallow_traps() // Non-trapping table.get generation not yet implemented
+    module.config.reference_types_enabled
+    && !module.config.disallow_traps // Non-trapping table.get generation not yet implemented
         && builder.type_on_stack(ValType::I32)
         && module.tables.len() > 0
 }
@@ -4774,7 +4770,7 @@ fn table_get(
 
 #[inline]
 fn table_size_valid(module: &Module, _: &mut CodeBuilder) -> bool {
-    module.config.reference_types_enabled() && module.tables.len() > 0
+    module.config.reference_types_enabled && module.tables.len() > 0
 }
 
 fn table_size(
@@ -4791,7 +4787,7 @@ fn table_size(
 
 #[inline]
 fn table_grow_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.reference_types_enabled()
+    module.config.reference_types_enabled
         && [ValType::EXTERNREF, ValType::FUNCREF].iter().any(|ty| {
             builder.types_on_stack(&[*ty, ValType::I32])
                 && module.tables.iter().any(|t| *ty == t.element_type.into())
@@ -4814,8 +4810,8 @@ fn table_grow(
 
 #[inline]
 fn table_copy_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.bulk_memory_enabled()
-    && !module.config.disallow_traps() // Non-trapping table.copy generation not yet implemented
+    module.config.bulk_memory_enabled
+    && !module.config.disallow_traps // Non-trapping table.copy generation not yet implemented
         && module.tables.len() > 0
         && builder.types_on_stack(&[ValType::I32, ValType::I32, ValType::I32])
 }
@@ -4838,8 +4834,8 @@ fn table_copy(
 
 #[inline]
 fn table_init_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.bulk_memory_enabled()
-    && !module.config.disallow_traps() // Non-trapping table.init generation not yet implemented.
+    module.config.bulk_memory_enabled
+    && !module.config.disallow_traps // Non-trapping table.init generation not yet implemented.
         && builder.allocs.table_init_possible
         && builder.types_on_stack(&[ValType::I32, ValType::I32, ValType::I32])
 }
@@ -4869,7 +4865,7 @@ fn table_init(
 
 #[inline]
 fn elem_drop_valid(module: &Module, _builder: &mut CodeBuilder) -> bool {
-    module.config.bulk_memory_enabled() && module.elems.len() > 0
+    module.config.bulk_memory_enabled && module.elems.len() > 0
 }
 
 fn elem_drop(
@@ -4910,138 +4906,138 @@ fn lane_index(u: &mut Unstructured, number_of_lanes: u8) -> Result<u8> {
 
 #[inline]
 fn simd_v128_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && builder.types_on_stack(&[ValType::V128])
 }
 
 #[inline]
 fn simd_v128_on_stack_relaxed(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.relaxed_simd_enabled()
+    !module.config.disallow_traps
+        && module.config.relaxed_simd_enabled
         && builder.types_on_stack(&[ValType::V128])
 }
 
 #[inline]
 fn simd_v128_v128_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && builder.types_on_stack(&[ValType::V128, ValType::V128])
 }
 
 #[inline]
 fn simd_v128_v128_on_stack_relaxed(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.relaxed_simd_enabled()
+    !module.config.disallow_traps
+        && module.config.relaxed_simd_enabled
         && builder.types_on_stack(&[ValType::V128, ValType::V128])
 }
 
 #[inline]
 fn simd_v128_v128_v128_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && builder.types_on_stack(&[ValType::V128, ValType::V128, ValType::V128])
 }
 
 #[inline]
 fn simd_v128_v128_v128_on_stack_relaxed(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.relaxed_simd_enabled()
+    !module.config.disallow_traps
+        && module.config.relaxed_simd_enabled
         && builder.types_on_stack(&[ValType::V128, ValType::V128, ValType::V128])
 }
 
 #[inline]
 fn simd_v128_i32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && builder.types_on_stack(&[ValType::V128, ValType::I32])
 }
 
 #[inline]
 fn simd_v128_i64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && builder.types_on_stack(&[ValType::V128, ValType::I64])
 }
 
 #[inline]
 fn simd_v128_f32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && builder.types_on_stack(&[ValType::V128, ValType::F32])
 }
 
 #[inline]
 fn simd_v128_f64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && builder.types_on_stack(&[ValType::V128, ValType::F64])
 }
 
 #[inline]
 fn simd_i32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && builder.type_on_stack(ValType::I32)
 }
 
 #[inline]
 fn simd_i64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && builder.type_on_stack(ValType::I64)
 }
 
 #[inline]
 fn simd_f32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && builder.type_on_stack(ValType::F32)
 }
 
 #[inline]
 fn simd_f64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && builder.type_on_stack(ValType::F64)
 }
 
 #[inline]
 fn simd_have_memory_and_offset(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && have_memory_and_offset(module, builder)
 }
 
 #[inline]
 fn simd_have_memory_and_offset_and_v128(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && store_valid(module, builder, || ValType::V128)
 }
 
 #[inline]
 fn simd_load_lane_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
     // The SIMD non-trapping case is not yet implemented.
-    !module.config.disallow_traps() && simd_have_memory_and_offset_and_v128(module, builder)
+    !module.config.disallow_traps && simd_have_memory_and_offset_and_v128(module, builder)
 }
 
 #[inline]
 fn simd_v128_store_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    !module.config.disallow_traps()
-        && module.config.simd_enabled()
+    !module.config.disallow_traps
+        && module.config.simd_enabled
         && store_valid(module, builder, || ValType::V128)
 }
 
 #[inline]
 fn simd_store_lane_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
     // The SIMD non-trapping case is not yet implemented.
-    !module.config.disallow_traps() && simd_v128_store_valid(module, builder)
+    !module.config.disallow_traps && simd_v128_store_valid(module, builder)
 }
 
 #[inline]
 fn simd_enabled(module: &Module, _: &mut CodeBuilder) -> bool {
-    module.config.simd_enabled()
+    module.config.simd_enabled
 }
 
 macro_rules! simd_load {
@@ -5055,7 +5051,7 @@ macro_rules! simd_load {
         ) -> Result<()> {
             let memarg = mem_arg(u, module, builder, $alignments)?;
             builder.push_operands(&[ValType::V128]);
-            if module.config.disallow_traps() {
+            if module.config.disallow_traps {
                 no_traps::load(
                     Instruction::$instruction(memarg),
                     module,
@@ -5092,7 +5088,7 @@ fn v128_store(
 ) -> Result<()> {
     builder.pop_operands(&[ValType::V128]);
     let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3, 4])?;
-    if module.config.disallow_traps() {
+    if module.config.disallow_traps {
         no_traps::store(
             Instruction::V128Store(memarg),
             module,
diff --git a/crates/wasm-smith/src/core/encode.rs b/crates/wasm-smith/src/core/encode.rs
index cf4cd5e1..b359f121 100644
--- a/crates/wasm-smith/src/core/encode.rs
+++ b/crates/wasm-smith/src/core/encode.rs
@@ -184,7 +184,7 @@ impl Module {
 
     fn encode_data_count(&self, module: &mut wasm_encoder::Module) {
         // Without bulk memory there's no need for a data count section,
-        if !self.config.bulk_memory_enabled() {
+        if !self.config.bulk_memory_enabled {
             return;
         }
         // ... and also if there's no data no need for a data count section.
diff --git a/crates/wasm-smith/src/lib.rs b/crates/wasm-smith/src/lib.rs
index 1a8d5d56..8d172904 100644
--- a/crates/wasm-smith/src/lib.rs
+++ b/crates/wasm-smith/src/lib.rs
@@ -58,14 +58,15 @@ mod component;
 mod config;
 mod core;
 
-pub use crate::core::{
-    ConfiguredModule, InstructionKind, InstructionKinds, MaybeInvalidModule, Module,
-};
+pub use crate::core::{InstructionKind, InstructionKinds, MaybeInvalidModule, Module};
 use arbitrary::{Result, Unstructured};
-pub use component::{Component, ConfiguredComponent};
-pub use config::{Config, DefaultConfig, SwarmConfig};
+pub use component::Component;
+pub use config::{Config, MemoryOffsetChoices};
 use std::{collections::HashSet, fmt::Write, str};
 
+#[cfg(feature = "_internal_cli")]
+pub use config::InternalOptionalConfig;
+
 /// Do something an arbitrary number of times.
 ///
 /// The callback can return `false` to exit the loop early.
diff --git a/crates/wasmparser/Cargo.toml b/crates/wasmparser/Cargo.toml
index 7410a2af..00031a80 100644
--- a/crates/wasmparser/Cargo.toml
+++ b/crates/wasmparser/Cargo.toml
@@ -13,6 +13,7 @@ edition.workspace = true
 exclude = ["benches/*.wasm"]
 
 [dependencies]
+bitflags = "2.4.1"
 indexmap = { workspace = true }
 semver = { workspace = true }
 
diff --git a/crates/wasmparser/src/readers/core/dylink0.rs b/crates/wasmparser/src/readers/core/dylink0.rs
index 3bdda39f..671022ea 100644
--- a/crates/wasmparser/src/readers/core/dylink0.rs
+++ b/crates/wasmparser/src/readers/core/dylink0.rs
@@ -1,4 +1,4 @@
-use crate::{BinaryReader, Result, Subsection, Subsections};
+use crate::{BinaryReader, Result, Subsection, Subsections, SymbolFlags};
 use std::ops::Range;
 
 /// Parser for the dynamic linking `dylink.0` custom section.
@@ -36,11 +36,7 @@ pub struct MemInfo {
 #[derive(Debug)]
 pub struct ExportInfo<'a> {
     pub name: &'a str,
-
-    /// Note that these flags correspond to those described in
-    /// <https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md>
-    /// with the `WASM_SYM_*` prefix.
-    pub flags: u32,
+    pub flags: SymbolFlags,
 }
 
 #[allow(missing_docs)]
@@ -48,11 +44,7 @@ pub struct ExportInfo<'a> {
 pub struct ImportInfo<'a> {
     pub module: &'a str,
     pub field: &'a str,
-
-    /// Note that these flags correspond to those described in
-    /// <https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md>
-    /// with the `WASM_SYM_*` prefix.
-    pub flags: u32,
+    pub flags: SymbolFlags,
 }
 
 /// Possible subsections of the `dylink.0` custom section.
@@ -91,7 +83,7 @@ impl<'a> Subsection<'a> for Dylink0Subsection<'a> {
                     .map(|_| {
                         Ok(ExportInfo {
                             name: reader.read_string()?,
-                            flags: reader.read_var_u32()?,
+                            flags: reader.read()?,
                         })
                     })
                     .collect::<Result<_, _>>()?,
@@ -102,7 +94,7 @@ impl<'a> Subsection<'a> for Dylink0Subsection<'a> {
                         Ok(ImportInfo {
                             module: reader.read_string()?,
                             field: reader.read_string()?,
-                            flags: reader.read_var_u32()?,
+                            flags: reader.read()?,
                         })
                     })
                     .collect::<Result<_, _>>()?,
diff --git a/crates/wasmparser/src/readers/core/linking.rs b/crates/wasmparser/src/readers/core/linking.rs
index e5a83b3a..f70611e9 100644
--- a/crates/wasmparser/src/readers/core/linking.rs
+++ b/crates/wasmparser/src/readers/core/linking.rs
@@ -3,20 +3,64 @@ use crate::{
 };
 use std::ops::Range;
 
-/// A flag indicating that this is a weak symbol.
-pub const WASM_SYM_BINDING_WEAK: u32 = 1 << 0;
-/// A flag indicating that this is a local symbol (this is exclusive with [WASM_SYM_BINDING_WEAK]).
-pub const WASM_SYM_BINDING_LOCAL: u32 = 1 << 1;
-/// A flag indicating that this is a hidden symbol.
-pub const WASM_SYM_VISIBILITY_HIDDEN: u32 = 1 << 2;
-/// A flag indicating that this symbol is not defined.
-pub const WASM_SYM_UNDEFINED: u32 = 1 << 4;
-/// A flag indiciating that this symbol is intended to be exported from the wasm module to the host environment
-pub const WASM_SYM_EXPORTED: u32 = 1 << 5;
-/// A flag indiciating that this symbol uses an explicit symbol name, rather than reusing the name from a wasm import.
-pub const WASM_SYM_EXPLICIT_NAME: u32 = 1 << 6;
-/// A flag indicating that this symbol is intended to be included in the linker output, regardless of whether it is used by the program.
-pub const WASM_SYM_NO_STRIP: u32 = 1 << 7;
+bitflags::bitflags! {
+    /// Flags for WebAssembly symbols.
+    ///
+    /// These flags correspond to those described in
+    /// <https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md>
+    /// with the `WASM_SYM_*` prefix.
+    #[repr(transparent)]
+    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
+    pub struct SymbolFlags: u32 {
+        /* N.B.:
+            Newly added flags should be keep in sync with `print_dylink0_flags`
+            in `crates/wasmprinter/src/lib.rs`.
+        */
+        /// This is a weak symbol.
+        const BINDING_WEAK = 1 << 0;
+        /// This is a local symbol (this is exclusive with [BINDING_WEAK]).
+        const BINDING_LOCAL = 1 << 1;
+        /// This is a hidden symbol.
+        const VISIBILITY_HIDDEN = 1 << 2;
+        /// This symbol is not defined.
+        const UNDEFINED = 1 << 4;
+        /// This symbol is intended to be exported from the wasm module to the host environment.
+        const EXPORTED = 1 << 5;
+        /// This symbol uses an explicit symbol name, rather than reusing the name from a wasm import.
+        const EXPLICIT_NAME = 1 << 6;
+        /// This symbol is intended to be included in the linker output, regardless of whether it is used by the program.
+        const NO_STRIP = 1 << 7;
+        /// This symbol resides in thread local storage.
+        const TLS = 1 << 8;
+        /// This symbol represents an absolute address.
+        const ABSOLUTE = 1 << 9;
+    }
+
+    /// Flags for WebAssembly segments.
+    ///
+    /// These flags are defined by implementation at the time of writing:
+    /// <https://github.com/llvm/llvm-project/blob/llvmorg-17.0.6/llvm/include/llvm/BinaryFormat/Wasm.h#L391-L394>
+    #[repr(transparent)]
+    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
+    pub struct SegmentFlags: u32 {
+        /// The segment contains only null-terminated strings, which allows the linker to perform merging.
+        const STRINGS = 0x1;
+        /// The segment contains thread-local data.
+        const TLS = 0x2;
+    }
+}
+
+impl<'a> FromReader<'a> for SymbolFlags {
+    fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
+        Ok(Self::from_bits_retain(reader.read_var_u32()?))
+    }
+}
+
+impl<'a> FromReader<'a> for SegmentFlags {
+    fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
+        Ok(Self::from_bits_retain(reader.read_var_u32()?))
+    }
+}
 
 /// A reader for the `linking` custom section of a WebAssembly module.
 ///
@@ -43,14 +87,14 @@ pub struct Segment<'a> {
     /// The required alignment of the segment, encoded as a power of 2.
     pub alignment: u32,
     /// The flags for the segment.
-    pub flags: u32,
+    pub flags: SegmentFlags,
 }
 
 impl<'a> FromReader<'a> for Segment<'a> {
     fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
         let name = reader.read_string()?;
         let alignment = reader.read_var_u32()?;
-        let flags = reader.read_var_u32()?;
+        let flags = reader.read()?;
         Ok(Self {
             name,
             alignment,
@@ -175,7 +219,7 @@ pub enum SymbolInfo<'a> {
     /// The symbol is a function.
     Func {
         /// The flags for the symbol.
-        flags: u32,
+        flags: SymbolFlags,
         /// The index of the function corresponding to this symbol.
         index: u32,
         /// The name for the function, if it is defined or uses an explicit name.
@@ -184,7 +228,7 @@ pub enum SymbolInfo<'a> {
     /// The symbol is a data symbol.
     Data {
         /// The flags for the symbol.
-        flags: u32,
+        flags: SymbolFlags,
         /// The name for the symbol.
         name: &'a str,
         /// The definition of the data symbol, if it is defined.
@@ -193,7 +237,7 @@ pub enum SymbolInfo<'a> {
     /// The symbol is a global.
     Global {
         /// The flags for the symbol.
-        flags: u32,
+        flags: SymbolFlags,
         /// The index of the global corresponding to this symbol.
         index: u32,
         /// The name for the global, if it is defined or uses an explicit name.
@@ -202,14 +246,14 @@ pub enum SymbolInfo<'a> {
     /// The symbol is a section.
     Section {
         /// The flags for the symbol.
-        flags: u32,
+        flags: SymbolFlags,
         /// The index of the function corresponding to this symbol.
         section: u32,
     },
     /// The symbol is an event.
     Event {
         /// The flags for the symbol.
-        flags: u32,
+        flags: SymbolFlags,
         /// The index of the event corresponding to this symbol.
         index: u32,
         /// The name for the event, if it is defined or uses an explicit name.
@@ -218,7 +262,7 @@ pub enum SymbolInfo<'a> {
     /// The symbol is a table.
     Table {
         /// The flags for the symbol.
-        flags: u32,
+        flags: SymbolFlags,
         /// The index of the table corresponding to this symbol.
         index: u32,
         /// The name for the table, if it is defined or uses an explicit name.
@@ -230,10 +274,10 @@ impl<'a> FromReader<'a> for SymbolInfo<'a> {
     fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
         let offset = reader.original_position();
         let kind = reader.read_u8()?;
-        let flags = reader.read_var_u32()?;
+        let flags: SymbolFlags = reader.read()?;
 
-        let defined = (flags & WASM_SYM_UNDEFINED) == 0;
-        let explicit_name = (flags & WASM_SYM_EXPLICIT_NAME) != 0;
+        let defined = !flags.contains(SymbolFlags::UNDEFINED);
+        let explicit_name = flags.contains(SymbolFlags::EXPLICIT_NAME);
 
         const SYMTAB_FUNCTION: u8 = 0;
         const SYMTAB_DATA: u8 = 1;
@@ -283,7 +327,7 @@ impl<'a> FromReader<'a> for SymbolInfo<'a> {
     }
 }
 
-/// Represents a data symbol.
+/// Represents the metadata about a data symbol defined in the wasm file.
 #[derive(Debug, Copy, Clone)]
 pub struct DefinedDataSymbol {
     /// The index of the data segment.
diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs
index 67034637..e011f842 100644
--- a/crates/wasmprinter/src/lib.rs
+++ b/crates/wasmprinter/src/lib.rs
@@ -2657,25 +2657,28 @@ impl Printer {
         Ok(())
     }
 
-    fn print_dylink0_flags(&mut self, mut flags: u32) -> Result<()> {
+    fn print_dylink0_flags(&mut self, mut flags: SymbolFlags) -> Result<()> {
         macro_rules! print_flag {
             ($($name:ident = $text:tt)*) => ({$(
-                if flags & wasmparser::$name != 0 {
-                    flags &= !wasmparser::$name;
+                if flags.contains(SymbolFlags::$name) {
+                    flags.remove(SymbolFlags::$name);
                     self.result.push_str(concat!(" ", $text));
                 }
             )*})
         }
+        // N.B.: Keep in sync with `parse_sym_flags` in `crates/wast/src/core/custom.rs`.
         print_flag! {
-            WASM_SYM_BINDING_WEAK = "binding-weak"
-            WASM_SYM_BINDING_LOCAL = "binding-local"
-            WASM_SYM_VISIBILITY_HIDDEN = "visibility-hidden"
-            WASM_SYM_UNDEFINED = "undefined"
-            WASM_SYM_EXPORTED = "exported"
-            WASM_SYM_EXPLICIT_NAME = "explicit-name"
-            WASM_SYM_NO_STRIP = "no-strip"
-        }
-        if flags != 0 {
+            BINDING_WEAK = "binding-weak"
+            BINDING_LOCAL = "binding-local"
+            VISIBILITY_HIDDEN = "visibility-hidden"
+            UNDEFINED = "undefined"
+            EXPORTED = "exported"
+            EXPLICIT_NAME = "explicit-name"
+            NO_STRIP = "no-strip"
+            TLS = "tls"
+            ABSOLUTE = "absolute"
+        }
+        if !flags.is_empty() {
             write!(self.result, " {:#x}", flags)?;
         }
         Ok(())
diff --git a/crates/wast/src/core/custom.rs b/crates/wast/src/core/custom.rs
index 4fe6c7c2..2205bf51 100644
--- a/crates/wast/src/core/custom.rs
+++ b/crates/wast/src/core/custom.rs
@@ -360,6 +360,7 @@ fn parse_sym_flags(parser: Parser<'_>) -> Result<u32> {
                 }
             })*};
         }
+        // N.B.: Keep in sync with `print_dylink0_flags` in `crates/wasmprinter/src/lib.rs`.
         parse_flags! {
             "binding-weak" = 1 << 0,
             "binding-local" = 1 << 1,
@@ -368,6 +369,8 @@ fn parse_sym_flags(parser: Parser<'_>) -> Result<u32> {
             "exported" = 1 << 5,
             "explicit-name" = 1 << 6,
             "no-strip" = 1 << 7,
+            "tls" = 1 << 8,
+            "absolute" = 1 << 9,
         }
         return Err(l.error());
     }
diff --git a/crates/wast/src/parser.rs b/crates/wast/src/parser.rs
index 9b1d945a..0c85923f 100644
--- a/crates/wast/src/parser.rs
+++ b/crates/wast/src/parser.rs
@@ -2,11 +2,10 @@
 //!
 //! This module contains the traits, abstractions, and utilities needed to
 //! define custom parsers for WebAssembly text format items. This module exposes
-//! a recursive descent parsing strategy and centers around the
-//! [`Parse`](crate::parser::Parse) trait for defining new fragments of
-//! WebAssembly text syntax.
+//! a recursive descent parsing strategy and centers around the [`Parse`] trait
+//! for defining new fragments of WebAssembly text syntax.
 //!
-//! The top-level [`parse`](crate::parser::parse) function can be used to fully parse AST fragments:
+//! The top-level [`parse`] function can be used to fully parse AST fragments:
 //!
 //! ```
 //! use wast::Wat;
@@ -20,8 +19,7 @@
 //! # }
 //! ```
 //!
-//! and you can also define your own new syntax with the
-//! [`Parse`](crate::parser::Parse) trait:
+//! and you can also define your own new syntax with the [`Parse`] trait:
 //!
 //! ```
 //! use wast::kw;
diff --git a/crates/wit-component/src/linking.rs b/crates/wit-component/src/linking.rs
index 56adb738..240197ca 100644
--- a/crates/wit-component/src/linking.rs
+++ b/crates/wit-component/src/linking.rs
@@ -39,7 +39,7 @@ use {
         Instruction as Ins, MemArg, MemorySection, MemoryType, Module, RawCustomSection, RefType,
         StartSection, TableSection, TableType, TypeSection, ValType,
     },
-    wasmparser::WASM_SYM_BINDING_WEAK,
+    wasmparser::SymbolFlags,
 };
 
 mod metadata;
@@ -892,7 +892,7 @@ fn resolve_symbols<'a>(
                             mutable: false,
                         }),
                     },
-                    flags: 0,
+                    flags: SymbolFlags::empty(),
                 },
             );
         }
@@ -924,7 +924,7 @@ fn resolve_symbols<'a>(
                                 results: Vec::new(),
                             }),
                         },
-                        flags: 0,
+                        flags: SymbolFlags::empty(),
                     },
                 ));
             }
@@ -1321,7 +1321,7 @@ impl Linker {
                 && (self.stub_missing_functions
                     || missing
                         .iter()
-                        .all(|(_, export)| 0 != (export.flags & WASM_SYM_BINDING_WEAK)))
+                        .all(|(_, export)| export.flags.contains(SymbolFlags::BINDING_WEAK)))
             {
                 self.stub_missing_functions = false;
                 self.libraries.push((
@@ -1335,7 +1335,7 @@ impl Linker {
                     "unresolved symbol(s):\n{}",
                     missing
                         .iter()
-                        .filter(|(_, export)| 0 == (export.flags & WASM_SYM_BINDING_WEAK))
+                        .filter(|(_, export)| !export.flags.contains(SymbolFlags::BINDING_WEAK))
                         .map(|(importer, export)| { format!("\t{importer} needs {}", export.key) })
                         .collect::<Vec<_>>()
                         .join("\n")
diff --git a/crates/wit-component/src/linking/metadata.rs b/crates/wit-component/src/linking/metadata.rs
index 725d27f8..954f3355 100644
--- a/crates/wit-component/src/linking/metadata.rs
+++ b/crates/wit-component/src/linking/metadata.rs
@@ -9,7 +9,7 @@ use {
     },
     wasmparser::{
         BinaryReader, BinaryReaderError, ExternalKind, FuncType, Parser, Payload, RefType,
-        Subsection, Subsections, TableType, TypeRef, ValType,
+        Subsection, Subsections, SymbolFlags, TableType, TypeRef, ValType,
     },
 };
 
@@ -131,7 +131,7 @@ pub struct Import<'a> {
     pub module: &'a str,
     pub name: &'a str,
     pub ty: Type,
-    pub flags: u32,
+    pub flags: SymbolFlags,
 }
 
 /// Represents a core Wasm export
@@ -151,7 +151,7 @@ impl<'a> fmt::Display for ExportKey<'a> {
 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
 pub struct Export<'a> {
     pub key: ExportKey<'a>,
-    pub flags: u32,
+    pub flags: SymbolFlags,
 }
 
 /// Represents a `WASM_DYLINK_MEM_INFO` value
@@ -212,7 +212,7 @@ pub struct Metadata<'a> {
     pub has_component_exports: bool,
 
     /// The functions imported from the `env` module, if any
-    pub env_imports: BTreeSet<(&'a str, (FunctionType, u32))>,
+    pub env_imports: BTreeSet<(&'a str, (FunctionType, SymbolFlags))>,
 
     /// The memory addresses imported from `GOT.mem`, if any
     pub memory_address_imports: BTreeSet<&'a str>,
@@ -230,14 +230,14 @@ pub struct Metadata<'a> {
 #[derive(Debug)]
 struct ExportInfo<'a> {
     name: &'a str,
-    flags: u32,
+    flags: SymbolFlags,
 }
 
 #[derive(Debug)]
 struct ImportInfo<'a> {
     module: &'a str,
     field: &'a str,
-    flags: u32,
+    flags: SymbolFlags,
 }
 
 #[derive(Debug)]
@@ -270,7 +270,7 @@ impl<'a> Subsection<'a> for DylinkSubsection<'a> {
                     .map(|_| {
                         Ok(ExportInfo {
                             name: reader.read_string()?,
-                            flags: reader.read_var_u32()?,
+                            flags: reader.read()?,
                         })
                     })
                     .collect::<Result<_, _>>()?,
@@ -281,7 +281,7 @@ impl<'a> Subsection<'a> for DylinkSubsection<'a> {
                         Ok(ImportInfo {
                             module: reader.read_string()?,
                             field: reader.read_string()?,
-                            flags: reader.read_var_u32()?,
+                            flags: reader.read()?,
                         })
                     })
                     .collect::<Result<_, _>>()?,
@@ -413,7 +413,10 @@ impl<'a> Metadata<'a> {
                                             FunctionType::try_from(
                                                 &types[usize::try_from(ty).unwrap()],
                                             )?,
-                                            *import_info.get(&("env", name)).unwrap_or(&0),
+                                            import_info
+                                                .get(&("env", name))
+                                                .copied()
+                                                .unwrap_or_default(),
                                         ),
                                     ));
                                 } else {
@@ -463,7 +466,10 @@ impl<'a> Metadata<'a> {
                                         bail!("unsupported import kind for {module}.{name}: {ty:?}",)
                                     }
                                 };
-                                let flags = *import_info.get(&(module, name)).unwrap_or(&0);
+                                let flags = import_info
+                                    .get(&(module, name))
+                                    .copied()
+                                    .unwrap_or_default();
                                 result.imports.insert(Import {
                                     module,
                                     name,
@@ -523,7 +529,8 @@ impl<'a> Metadata<'a> {
                                         )
                                     }
                                 };
-                                let flags = *export_info.get(&export.name).unwrap_or(&0);
+                                let flags =
+                                    export_info.get(&export.name).copied().unwrap_or_default();
                                 result.exports.insert(Export {
                                     key: ExportKey {
                                         name: export.name,
diff --git a/examples/wasm-smith.rs b/examples/wasm-smith.rs
index 73d661b1..a406b001 100644
--- a/examples/wasm-smith.rs
+++ b/examples/wasm-smith.rs
@@ -1,11 +1,11 @@
 use arbitrary::Unstructured;
-use wasm_smith::{DefaultConfig, Module};
+use wasm_smith::{Config, Module};
 use wasmprinter::print_bytes;
 
 fn test_wasm_smith() {
     let seed = "W3B4553MB1Y!!!!!!!!!!!!!!!!!!!!!!!!!!";
     let mut u = Unstructured::new(seed.as_bytes());
-    if let Ok(module) = Module::new(DefaultConfig::default(), &mut u) {
+    if let Ok(module) = Module::new(Config::default(), &mut u) {
         let wasm_buffer = module.to_bytes();
         if let Ok(wat) = print_bytes(wasm_buffer) {
             println!("{}", wat);
diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs
index d3eb1973..e22b03b7 100644
--- a/fuzz/src/lib.rs
+++ b/fuzz/src/lib.rs
@@ -1,6 +1,6 @@
 use libfuzzer_sys::arbitrary::{Result, Unstructured};
 use std::fmt::Debug;
-use wasm_smith::{Component, Module, SwarmConfig};
+use wasm_smith::{Component, Config, Module};
 
 pub mod incremental_parse;
 pub mod mutate;
@@ -14,9 +14,9 @@ pub mod validate_valid_module;
 
 pub fn generate_valid_module(
     u: &mut Unstructured,
-    configure: impl FnOnce(&mut SwarmConfig, &mut Unstructured<'_>) -> Result<()>,
-) -> Result<(Vec<u8>, SwarmConfig)> {
-    let mut config: SwarmConfig = u.arbitrary()?;
+    configure: impl FnOnce(&mut Config, &mut Unstructured<'_>) -> Result<()>,
+) -> Result<(Vec<u8>, Config)> {
+    let mut config: Config = u.arbitrary()?;
 
     // These are disabled in the swarm config by default, but we want to test
     // them. Use the input data to determine whether these features are enabled.
@@ -49,9 +49,9 @@ pub fn generate_valid_module(
 
 pub fn generate_valid_component(
     u: &mut Unstructured,
-    configure: impl FnOnce(&mut SwarmConfig, &mut Unstructured<'_>) -> Result<()>,
-) -> Result<(Vec<u8>, SwarmConfig)> {
-    let mut config: SwarmConfig = u.arbitrary()?;
+    configure: impl FnOnce(&mut Config, &mut Unstructured<'_>) -> Result<()>,
+) -> Result<(Vec<u8>, Config)> {
+    let mut config: Config = u.arbitrary()?;
 
     // These are disabled in the swarm config by default, but we want to test
     // them. Use the input data to determine whether these features are enabled.
diff --git a/fuzz/src/no_traps.rs b/fuzz/src/no_traps.rs
index 12e6f1c3..ba5d4983 100644
--- a/fuzz/src/no_traps.rs
+++ b/fuzz/src/no_traps.rs
@@ -1,5 +1,4 @@
 use arbitrary::{Result, Unstructured};
-use wasm_smith::SwarmConfig;
 #[cfg(feature = "wasmtime")]
 use wasmtime::*;
 
@@ -15,7 +14,8 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
         config.disallow_traps = true;
         config.threads_enabled = false;
         config.exceptions_enabled = false;
-        config.max_memory_pages = config.max_memory_pages.min(100);
+        config.max_memory32_pages = config.max_memory32_pages.min(100);
+        config.max_memory64_pages = config.max_memory64_pages.min(100);
         Ok(())
     })?;
     validate_module(config.clone(), &wasm_bytes);
@@ -29,7 +29,7 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
     #[cfg(feature = "wasmtime")]
     {
         // Configure the engine, module, and store
-        let mut eng_conf = Config::new();
+        let mut eng_conf = wasmtime::Config::new();
         eng_conf.wasm_memory64(true);
         eng_conf.wasm_multi_memory(true);
         eng_conf.consume_fuel(true);
@@ -98,7 +98,7 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
     Ok(())
 }
 
-fn validate_module(config: SwarmConfig, wasm_bytes: &Vec<u8>) {
+fn validate_module(config: wasm_smith::Config, wasm_bytes: &Vec<u8>) {
     // Validate the module or component and assert that it passes validation.
     let mut validator = wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
         component_model: false,
diff --git a/src/bin/wasm-tools/smith.rs b/src/bin/wasm-tools/smith.rs
index 700383c0..dc1c32f1 100644
--- a/src/bin/wasm-tools/smith.rs
+++ b/src/bin/wasm-tools/smith.rs
@@ -1,11 +1,10 @@
 use anyhow::{Context, Result};
 use arbitrary::Arbitrary;
 use clap::Parser;
-use std::borrow::Cow;
 use std::io::{stdin, Read};
 use std::path::PathBuf;
 use std::process;
-use wasm_smith::{InstructionKind, InstructionKinds, MaybeInvalidModule, Module};
+use wasm_smith::{MaybeInvalidModule, Module};
 
 /// A WebAssembly test case generator.
 ///
@@ -73,127 +72,12 @@ pub struct Opts {
     config: Option<PathBuf>,
 
     #[clap(flatten)]
-    module_config: Config,
+    module_config: wasm_smith::InternalOptionalConfig,
 
     #[clap(flatten)]
     general: wasm_tools::GeneralOpts,
 }
 
-#[derive(Default, Debug, Parser, Clone, serde_derive::Deserialize)]
-#[serde(rename_all = "kebab-case")]
-struct Config {
-    #[clap(long = "min-types")]
-    min_types: Option<usize>,
-    #[clap(long = "max-types")]
-    max_types: Option<usize>,
-    #[clap(long = "min-imports")]
-    min_imports: Option<usize>,
-    #[clap(long = "max-imports")]
-    max_imports: Option<usize>,
-    #[clap(long = "min-tags")]
-    min_tags: Option<usize>,
-    #[clap(long = "max-tags")]
-    max_tags: Option<usize>,
-    #[clap(long = "min-funcs")]
-    min_funcs: Option<usize>,
-    #[clap(long = "max-funcs")]
-    max_funcs: Option<usize>,
-    #[clap(long = "min-globals")]
-    min_globals: Option<usize>,
-    #[clap(long = "max-globals")]
-    max_globals: Option<usize>,
-    #[clap(long = "min-exports")]
-    min_exports: Option<usize>,
-    #[clap(long = "max-exports")]
-    max_exports: Option<usize>,
-    #[clap(long = "export-everything")]
-    export_everything: Option<bool>,
-    #[clap(long = "min-element-segments")]
-    min_element_segments: Option<usize>,
-    #[clap(long = "max-element-segments")]
-    max_element_segments: Option<usize>,
-    #[clap(long = "min-data-segments")]
-    min_data_segments: Option<usize>,
-    #[clap(long = "max-data-segments")]
-    max_data_segments: Option<usize>,
-    #[clap(long = "max-instructions")]
-    max_instructions: Option<usize>,
-    #[clap(long = "min-memories")]
-    min_memories: Option<u32>,
-    #[clap(long = "max-memories")]
-    max_memories: Option<usize>,
-    #[clap(long = "min-tables")]
-    min_tables: Option<u32>,
-    #[clap(long = "max-tables")]
-    max_tables: Option<usize>,
-    #[clap(long = "max-memory-pages")]
-    max_memory_pages: Option<u64>,
-    #[clap(long = "memory-max-size-required")]
-    memory_max_size_required: Option<bool>,
-    #[clap(long = "max-table-elements")]
-    max_table_elements: Option<u32>,
-    #[clap(long = "table-max-size-required")]
-    table_max_size_required: Option<bool>,
-    #[clap(long = "max-instances")]
-    max_instances: Option<usize>,
-    #[clap(long = "max-modules")]
-    max_modules: Option<usize>,
-    #[clap(long = "min-uleb-size")]
-    min_uleb_size: Option<u8>,
-    #[clap(long = "bulk-memory")]
-    #[serde(rename = "bulk-memory")]
-    bulk_memory_enabled: Option<bool>,
-    #[clap(long = "reference-types")]
-    #[serde(rename = "reference-types")]
-    reference_types_enabled: Option<bool>,
-    #[clap(long = "tail-call")]
-    #[serde(rename = "tail-call")]
-    tail_call_enabled: Option<bool>,
-    #[clap(long = "simd")]
-    #[serde(rename = "simd")]
-    simd_enabled: Option<bool>,
-    #[clap(long = "relaxed-simd")]
-    #[serde(rename = "relaxed-simd")]
-    relaxed_simd_enabled: Option<bool>,
-    #[clap(long = "exception-handling")]
-    #[serde(rename = "exception-handling")]
-    exceptions_enabled: Option<bool>,
-    #[clap(long = "allow-start")]
-    #[serde(rename = "allow-start")]
-    allow_start_export: Option<bool>,
-    #[clap(long = "max-aliases")]
-    max_aliases: Option<usize>,
-    #[clap(long = "max-nesting-depth")]
-    max_nesting_depth: Option<usize>,
-    #[clap(long = "max-type-size")]
-    max_type_size: Option<u32>,
-    #[clap(long = "memory64")]
-    memory64_enabled: Option<bool>,
-    #[clap(long = "canonicalize-nans")]
-    canonicalize_nans: Option<bool>,
-    #[clap(long = "multi-value")]
-    multi_value_enabled: Option<bool>,
-    #[clap(long = "sign-extension-ops")]
-    sign_extension_ops_enabled: Option<bool>,
-    #[clap(long = "saturating-float-to-int")]
-    saturating_float_to_int_enabled: Option<bool>,
-    #[clap(long = "generate-custom-sections")]
-    generate_custom_sections: Option<bool>,
-    #[clap(long = "available-imports")]
-    available_imports: Option<PathBuf>,
-    /// Limit what kinds of instructions are allowed.
-    ///
-    /// By default, all kinds are allowed; available kinds: numeric, vector,
-    /// reference, parametric, variable, table, memory, control. Specify
-    /// multiple kinds with a comma-separated list: e.g.,
-    /// `--allowed-instructions numeric,control,parametric`
-    #[clap(long = "allowed-instructions", use_value_delimiter = true)]
-    allowed_instructions: Option<Vec<InstructionKind>>,
-    #[clap(long = "threads")]
-    #[serde(rename = "threads")]
-    threads_enabled: Option<bool>,
-}
-
 impl Opts {
     pub fn general_opts(&self) -> &wasm_tools::GeneralOpts {
         &self.general
@@ -231,12 +115,10 @@ impl Opts {
                         format!("failed to decode json config: {}", path.display())
                     })?
                 }
-                None => Config::default(),
-            };
-            let config = CliAndJsonConfig {
-                json,
-                cli: self.module_config.clone(),
+                None => wasm_smith::InternalOptionalConfig::default(),
             };
+            let config = self.module_config.clone().or(json);
+            let config = wasm_smith::Config::try_from(config)?;
             let mut module = Module::new(config, &mut u).unwrap_or_else(|e| {
                 eprintln!("error: failed to generate module: {}", e);
                 process::exit(2);
@@ -254,97 +136,3 @@ impl Opts {
         Ok(())
     }
 }
-
-macro_rules! fields {
-    ($(
-        ($field:ident, $ty:ty, $default:expr),
-    )*) => ($(
-        fn $field(&self) -> $ty {
-            self.cli.$field.or(self.json.$field).unwrap_or($default)
-        }
-    )*)
-}
-
-#[derive(Clone, Debug)]
-struct CliAndJsonConfig {
-    json: Config,
-    cli: Config,
-}
-
-impl wasm_smith::Config for CliAndJsonConfig {
-    fields! {
-        (min_types, usize, 0),
-        (max_types, usize, 100),
-        (min_imports, usize, 0),
-        (max_imports, usize, 100),
-        (min_tags, usize, 0),
-        (max_tags, usize, 100),
-        (min_funcs, usize, 0),
-        (max_funcs, usize, 100),
-        (min_globals, usize, 0),
-        (max_globals, usize, 100),
-        (min_exports, usize, 0),
-        (max_exports, usize, 100),
-        (export_everything, bool, false),
-        (min_element_segments, usize, 0),
-        (max_element_segments, usize, 100),
-        (min_data_segments, usize, 0),
-        (max_data_segments, usize, 100),
-        (max_instructions, usize, 100),
-        (min_memories, u32, 0),
-        (max_memories, usize, 1),
-        (min_tables, u32, 0),
-        (max_tables, usize, 1),
-        (memory_max_size_required, bool, false),
-        (max_table_elements, u32, 1_000_000),
-        (table_max_size_required, bool, false),
-        (max_instances, usize, 10),
-        (max_modules, usize, 10),
-        (min_uleb_size, u8, 1),
-        (bulk_memory_enabled, bool, true),
-        (reference_types_enabled, bool, true),
-        (tail_call_enabled, bool, true),
-        (simd_enabled, bool, true),
-        (relaxed_simd_enabled, bool, false),
-        (exceptions_enabled, bool, false),
-        (multi_value_enabled, bool, true),
-        (saturating_float_to_int_enabled, bool, true),
-        (sign_extension_ops_enabled, bool, true),
-        (memory64_enabled, bool, false),
-        (allow_start_export, bool, true),
-        (max_aliases, usize, 1000),
-        (max_nesting_depth, usize, 1000),
-        (max_type_size, u32, 1000),
-        (canonicalize_nans, bool, false),
-        (generate_custom_sections, bool, false),
-        (threads_enabled, bool, false),
-    }
-
-    fn max_memory_pages(&self, _is_64: bool) -> u64 {
-        self.cli
-            .max_memory_pages
-            .or(self.json.max_memory_pages)
-            .unwrap_or(65536)
-    }
-
-    fn allowed_instructions(&self) -> InstructionKinds {
-        match self
-            .cli
-            .allowed_instructions
-            .as_ref()
-            .or(self.json.allowed_instructions.as_ref())
-        {
-            Some(ks) => InstructionKinds::new(ks),
-            None => InstructionKinds::all(),
-        }
-    }
-
-    fn available_imports(&self) -> Option<Cow<'static, [u8]>> {
-        let file = self
-            .cli
-            .available_imports
-            .as_ref()
-            .or(self.json.available_imports.as_ref())?;
-        Some(wat::parse_file(file).unwrap().into())
-    }
-}


Test patch:
diff --git a/crates/wasm-smith/tests/available_imports.rs b/crates/wasm-smith/tests/available_imports.rs
index 9d3acbd8..a5e3fbf7 100644
--- a/crates/wasm-smith/tests/available_imports.rs
+++ b/crates/wasm-smith/tests/available_imports.rs
@@ -3,7 +3,7 @@
 use arbitrary::{Arbitrary, Unstructured};
 use rand::{rngs::SmallRng, RngCore, SeedableRng};
 use std::collections::HashMap;
-use wasm_smith::{Config, Module, SwarmConfig};
+use wasm_smith::{Config, Module};
 use wasmparser::{Parser, TypeRef, ValType};
 use wasmparser::{Validator, WasmFeatures};
 
@@ -113,10 +113,10 @@ enum AvailableImportKind {
 fn import_config(
     u: &mut Unstructured,
 ) -> (
-    SwarmConfig,
+    Config,
     Vec<(&'static str, &'static str, AvailableImportKind)>,
 ) {
-    let mut config = SwarmConfig::arbitrary(u).expect("arbitrary swarm");
+    let mut config = Config::arbitrary(u).expect("arbitrary swarm");
     config.exceptions_enabled = u.arbitrary().expect("exceptions enabled for swarm");
     let available = {
         use {AvailableImportKind::*, ValType::*};
@@ -156,20 +156,20 @@ fn import_config(
     (config, available)
 }
 
-fn parser_features_from_config(config: &impl Config) -> WasmFeatures {
+fn parser_features_from_config(config: &Config) -> WasmFeatures {
     WasmFeatures {
         mutable_global: true,
-        saturating_float_to_int: config.saturating_float_to_int_enabled(),
-        sign_extension: config.sign_extension_ops_enabled(),
-        reference_types: config.reference_types_enabled(),
-        multi_value: config.multi_value_enabled(),
-        bulk_memory: config.bulk_memory_enabled(),
-        simd: config.simd_enabled(),
-        relaxed_simd: config.relaxed_simd_enabled(),
-        multi_memory: config.max_memories() > 1,
-        exceptions: config.exceptions_enabled(),
-        memory64: config.memory64_enabled(),
-        tail_call: config.tail_call_enabled(),
+        saturating_float_to_int: config.saturating_float_to_int_enabled,
+        sign_extension: config.sign_extension_ops_enabled,
+        reference_types: config.reference_types_enabled,
+        multi_value: config.multi_value_enabled,
+        bulk_memory: config.bulk_memory_enabled,
+        simd: config.simd_enabled,
+        relaxed_simd: config.relaxed_simd_enabled,
+        multi_memory: config.max_memories > 1,
+        exceptions: config.exceptions_enabled,
+        memory64: config.memory64_enabled,
+        tail_call: config.tail_call_enabled,
 
         threads: false,
         floats: true,
diff --git a/crates/wasm-smith/tests/core.rs b/crates/wasm-smith/tests/core.rs
index edf1f2b0..17d9329c 100644
--- a/crates/wasm-smith/tests/core.rs
+++ b/crates/wasm-smith/tests/core.rs
@@ -1,6 +1,6 @@
 use arbitrary::{Arbitrary, Unstructured};
 use rand::{rngs::SmallRng, RngCore, SeedableRng};
-use wasm_smith::{Config, ConfiguredModule, Module, SwarmConfig};
+use wasm_smith::{Config, Module};
 use wasmparser::{Validator, WasmFeatures};
 
 #[test]
@@ -42,13 +42,14 @@ fn smoke_test_swarm_config() {
     let mut buf = vec![0; 2048];
     for _ in 0..1024 {
         rng.fill_bytes(&mut buf);
-        let u = Unstructured::new(&buf);
-        if let Ok(module) = ConfiguredModule::<SwarmConfig>::arbitrary_take_rest(u) {
-            let module = module.module;
-            let wasm_bytes = module.to_bytes();
+        let mut u = Unstructured::new(&buf);
+        if let Ok(config) = Config::arbitrary(&mut u) {
+            if let Ok(module) = Module::new(config, &mut u) {
+                let wasm_bytes = module.to_bytes();
 
-            let mut validator = Validator::new_with_features(wasm_features());
-            validate(&mut validator, &wasm_bytes);
+                let mut validator = Validator::new_with_features(wasm_features());
+                validate(&mut validator, &wasm_bytes);
+            }
         }
     }
 }
@@ -60,7 +61,7 @@ fn multi_value_disabled() {
     for _ in 0..10 {
         rng.fill_bytes(&mut buf);
         let mut u = Unstructured::new(&buf);
-        let mut cfg = SwarmConfig::arbitrary(&mut u).unwrap();
+        let mut cfg = Config::arbitrary(&mut u).unwrap();
         cfg.multi_value_enabled = false;
         if let Ok(module) = Module::new(cfg, &mut u) {
             let wasm_bytes = module.to_bytes();
@@ -79,8 +80,8 @@ fn smoke_can_smith_valid_webassembly_one_point_oh() {
     for _ in 0..100 {
         rng.fill_bytes(&mut buf);
         let mut u = Unstructured::new(&buf);
-        let mut cfg = SwarmConfig::arbitrary(&mut u).unwrap();
-        cfg.sign_extension_enabled = false;
+        let mut cfg = Config::arbitrary(&mut u).unwrap();
+        cfg.sign_extension_ops_enabled = false;
         cfg.saturating_float_to_int_enabled = false;
         cfg.reference_types_enabled = false;
         cfg.multi_value_enabled = false;
@@ -108,7 +109,7 @@ fn smoke_test_no_trapping_mode() {
     for _ in 0..1024 {
         rng.fill_bytes(&mut buf);
         let mut u = Unstructured::new(&buf);
-        let mut cfg = SwarmConfig::arbitrary(&mut u).unwrap();
+        let mut cfg = Config::arbitrary(&mut u).unwrap();
         cfg.disallow_traps = true;
         if let Ok(module) = Module::new(cfg, &mut u) {
             let wasm_bytes = module.to_bytes();
@@ -129,20 +130,20 @@ fn wasm_features() -> WasmFeatures {
     }
 }
 
-fn parser_features_from_config(config: &impl Config) -> WasmFeatures {
+fn parser_features_from_config(config: &Config) -> WasmFeatures {
     WasmFeatures {
         mutable_global: true,
-        saturating_float_to_int: config.saturating_float_to_int_enabled(),
-        sign_extension: config.sign_extension_ops_enabled(),
-        reference_types: config.reference_types_enabled(),
-        multi_value: config.multi_value_enabled(),
-        bulk_memory: config.bulk_memory_enabled(),
-        simd: config.simd_enabled(),
-        relaxed_simd: config.relaxed_simd_enabled(),
-        multi_memory: config.max_memories() > 1,
-        exceptions: config.exceptions_enabled(),
-        memory64: config.memory64_enabled(),
-        tail_call: config.tail_call_enabled(),
+        saturating_float_to_int: config.saturating_float_to_int_enabled,
+        sign_extension: config.sign_extension_ops_enabled,
+        reference_types: config.reference_types_enabled,
+        multi_value: config.multi_value_enabled,
+        bulk_memory: config.bulk_memory_enabled,
+        simd: config.simd_enabled,
+        relaxed_simd: config.relaxed_simd_enabled,
+        multi_memory: config.max_memories > 1,
+        exceptions: config.exceptions_enabled,
+        memory64: config.memory64_enabled,
+        tail_call: config.tail_call_enabled,
 
         threads: false,
         floats: true,
diff --git a/tests/snapshots/local/dylink0.wast/8.print b/tests/snapshots/local/dylink0.wast/8.print
index c11f43cb..88cadfad 100644
--- a/tests/snapshots/local/dylink0.wast/8.print
+++ b/tests/snapshots/local/dylink0.wast/8.print
@@ -1,6 +1,6 @@
 (module
   (@dylink.0
-    (export-info "a" binding-weak binding-local visibility-hidden undefined exported explicit-name no-strip 0xffffff08)
+    (export-info "a" binding-weak binding-local visibility-hidden undefined exported explicit-name no-strip tls absolute 0xfffffc08)
     (export-info "a" binding-local)
     (export-info "a" binding-weak binding-local undefined)
   )