Skip to main content

wasmtime_environ/
tunables.rs

1use crate::prelude::*;
2use crate::{IndexType, Limits, Memory, TripleExt};
3use core::{fmt, str::FromStr};
4use serde_derive::{Deserialize, Serialize};
5use target_lexicon::{PointerWidth, Triple};
6
7macro_rules! define_tunables {
8    (
9        $(#[$outer_attr:meta])*
10        pub struct $tunables:ident {
11            $(
12                $(#[$field_attr:meta])*
13                pub $field:ident : $field_ty:ty,
14            )*
15        }
16
17        pub struct $config_tunables:ident {
18            ...
19        }
20    ) => {
21        $(#[$outer_attr])*
22        pub struct $tunables {
23            $(
24                $(#[$field_attr])*
25                pub $field: $field_ty,
26            )*
27        }
28
29        /// Optional tunable configuration options used in `wasmtime::Config`
30        #[derive(Default, Clone)]
31        #[expect(missing_docs, reason = "macro-generated fields")]
32        pub struct $config_tunables {
33            $(pub $field: Option<$field_ty>,)*
34        }
35
36        impl $config_tunables {
37            /// Formats configured fields into `f`.
38            pub fn format(&self, f: &mut fmt::DebugStruct<'_,'_>) {
39                $(
40                    if let Some(val) = &self.$field {
41                        f.field(stringify!($field), val);
42                    }
43                )*
44            }
45
46            /// Configure the `Tunables` provided.
47            pub fn configure(&self, tunables: &mut Tunables) {
48                $(
49                    if let Some(val) = self.$field {
50                        tunables.$field = val;
51                    }
52                )*
53            }
54        }
55    };
56}
57
58define_tunables! {
59    /// Tunable parameters for WebAssembly compilation.
60    #[derive(Clone, Hash, Serialize, Deserialize, Debug)]
61    pub struct Tunables {
62        /// The garbage collector implementation to use, which implies the layout of
63        /// GC objects and barriers that must be emitted in Wasm code.
64        pub collector: Option<Collector>,
65
66        /// Initial size, in bytes, to be allocated for linear memories.
67        pub memory_reservation: u64,
68
69        /// The size, in bytes, of the guard page region for linear memories.
70        pub memory_guard_size: u64,
71
72        /// The size, in bytes, to allocate at the end of a relocated linear
73        /// memory for growth.
74        pub memory_reservation_for_growth: u64,
75
76        /// Whether or not to generate native DWARF debug information.
77        pub debug_native: bool,
78
79        /// Whether we are enabling precise Wasm-level debugging in
80        /// the guest.
81        pub debug_guest: bool,
82
83        /// Whether or not to retain DWARF sections in compiled modules.
84        pub parse_wasm_debuginfo: bool,
85
86        /// Whether or not fuel is enabled for generated code, meaning that fuel
87        /// will be consumed every time a wasm instruction is executed.
88        pub consume_fuel: bool,
89
90        /// Whether or not we use epoch-based interruption.
91        pub epoch_interruption: bool,
92
93        /// Whether or not linear memories are allowed to be reallocated after
94        /// initial allocation at runtime.
95        pub memory_may_move: bool,
96
97        /// Whether or not linear memory allocations will have a guard region at the
98        /// beginning of the allocation in addition to the end.
99        pub guard_before_linear_memory: bool,
100
101        /// Whether to initialize tables lazily, so that instantiation is fast but
102        /// indirect calls are a little slower. If false, tables are initialized
103        /// eagerly from any active element segments that apply to them during
104        /// instantiation.
105        pub table_lazy_init: bool,
106
107        /// Indicates whether an address map from compiled native code back to wasm
108        /// offsets in the original file is generated.
109        pub generate_address_map: bool,
110
111        /// Flag for the component module whether adapter modules have debug
112        /// assertions baked into them.
113        pub debug_adapter_modules: bool,
114
115        /// Whether or not lowerings for relaxed simd instructions are forced to
116        /// be deterministic.
117        pub relaxed_simd_deterministic: bool,
118
119        /// Whether or not Wasm functions target the winch abi.
120        pub winch_callable: bool,
121
122        /// Whether or not the host will be using native signals (e.g. SIGILL,
123        /// SIGSEGV, etc) to implement traps.
124        pub signals_based_traps: bool,
125
126        /// Whether CoW images might be used to initialize linear memories.
127        pub memory_init_cow: bool,
128
129        /// Whether to enable inlining in Wasmtime's compilation orchestration
130        /// or not.
131        pub inlining: bool,
132
133        /// Whether to inline calls within the same core Wasm module or not.
134        pub inlining_intra_module: IntraModuleInlining,
135
136        /// The size of "small callees" that can be inlined regardless of the
137        /// caller's size.
138        pub inlining_small_callee_size: u32,
139
140        /// The general size threshold for the sum of the caller's and callee's
141        /// sizes, past which we will generally not inline calls anymore.
142        pub inlining_sum_size_threshold: u32,
143
144        /// Whether any component model feature related to concurrency is
145        /// enabled.
146        pub concurrency_support: bool,
147    }
148
149    pub struct ConfigTunables {
150        ...
151    }
152}
153
154impl Tunables {
155    /// Returns a `Tunables` configuration assumed for running code on the host.
156    pub fn default_host() -> Self {
157        if cfg!(miri) {
158            Tunables::default_miri()
159        } else if cfg!(target_pointer_width = "32") {
160            Tunables::default_u32()
161        } else if cfg!(target_pointer_width = "64") {
162            Tunables::default_u64()
163        } else {
164            panic!("unsupported target_pointer_width");
165        }
166    }
167
168    /// Returns the default set of tunables for the given target triple.
169    pub fn default_for_target(target: &Triple) -> Result<Self> {
170        if cfg!(miri) {
171            return Ok(Tunables::default_miri());
172        }
173        let mut ret = match target
174            .pointer_width()
175            .map_err(|_| format_err!("failed to retrieve target pointer width"))?
176        {
177            PointerWidth::U32 => Tunables::default_u32(),
178            PointerWidth::U64 => Tunables::default_u64(),
179            _ => bail!("unsupported target pointer width"),
180        };
181
182        // Pulley targets never use signals-based-traps and also can't benefit
183        // from guard pages, so disable them.
184        if target.is_pulley() {
185            ret.signals_based_traps = false;
186            ret.memory_guard_size = 0;
187        }
188        Ok(ret)
189    }
190
191    /// Returns the default set of tunables for running under MIRI.
192    pub const fn default_miri() -> Tunables {
193        Tunables {
194            collector: None,
195
196            // No virtual memory tricks are available on miri so make these
197            // limits quite conservative.
198            memory_reservation: 1 << 20,
199            memory_guard_size: 0,
200            memory_reservation_for_growth: 0,
201
202            // General options which have the same defaults regardless of
203            // architecture.
204            debug_native: false,
205            parse_wasm_debuginfo: true,
206            consume_fuel: false,
207            epoch_interruption: false,
208            memory_may_move: true,
209            guard_before_linear_memory: true,
210            table_lazy_init: true,
211            generate_address_map: true,
212            debug_adapter_modules: false,
213            relaxed_simd_deterministic: false,
214            winch_callable: false,
215            signals_based_traps: false,
216            memory_init_cow: true,
217            inlining: false,
218            inlining_intra_module: IntraModuleInlining::WhenUsingGc,
219            inlining_small_callee_size: 50,
220            inlining_sum_size_threshold: 2000,
221            debug_guest: false,
222            concurrency_support: true,
223        }
224    }
225
226    /// Returns the default set of tunables for running under a 32-bit host.
227    pub const fn default_u32() -> Tunables {
228        Tunables {
229            // For 32-bit we scale way down to 10MB of reserved memory. This
230            // impacts performance severely but allows us to have more than a
231            // few instances running around.
232            memory_reservation: 10 * (1 << 20),
233            memory_guard_size: 0x1_0000,
234            memory_reservation_for_growth: 1 << 20, // 1MB
235            signals_based_traps: true,
236
237            ..Tunables::default_miri()
238        }
239    }
240
241    /// Returns the default set of tunables for running under a 64-bit host.
242    pub const fn default_u64() -> Tunables {
243        Tunables {
244            // 64-bit has tons of address space to static memories can have 4gb
245            // address space reservations liberally by default, allowing us to
246            // help eliminate bounds checks.
247            //
248            // A 32MiB default guard size is then allocated so we can remove
249            // explicit bounds checks if any static offset is less than this
250            // value. SpiderMonkey found, for example, that in a large corpus of
251            // wasm modules 20MiB was the maximum offset so this is the
252            // power-of-two-rounded up from that and matches SpiderMonkey.
253            memory_reservation: 1 << 32,
254            memory_guard_size: 32 << 20,
255
256            // We've got lots of address space on 64-bit so use a larger
257            // grow-into-this area, but on 32-bit we aren't as lucky. Miri is
258            // not exactly fast so reduce memory consumption instead of trying
259            // to avoid memory movement.
260            memory_reservation_for_growth: 2 << 30, // 2GB
261
262            signals_based_traps: true,
263            ..Tunables::default_miri()
264        }
265    }
266
267    /// Get the GC heap's memory type, given our configured tunables.
268    pub fn gc_heap_memory_type(&self) -> Memory {
269        Memory {
270            idx_type: IndexType::I32,
271            limits: Limits { min: 0, max: None },
272            shared: false,
273            // We *could* try to match the target architecture's page size, but that
274            // would require exercising a page size for memories that we don't
275            // otherwise support for Wasm; we conservatively avoid that, and just
276            // use the default Wasm page size, for now.
277            page_size_log2: 16,
278        }
279    }
280}
281
282/// The garbage collector implementation to use.
283#[derive(Clone, Copy, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
284pub enum Collector {
285    /// The deferred reference-counting collector.
286    DeferredReferenceCounting,
287    /// The null collector.
288    Null,
289}
290
291impl fmt::Display for Collector {
292    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
293        match self {
294            Collector::DeferredReferenceCounting => write!(f, "deferred reference-counting"),
295            Collector::Null => write!(f, "null"),
296        }
297    }
298}
299
300/// Whether to inline function calls within the same module.
301#[derive(Clone, Copy, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
302#[expect(missing_docs, reason = "self-describing variants")]
303pub enum IntraModuleInlining {
304    Yes,
305    No,
306    WhenUsingGc,
307}
308
309impl FromStr for IntraModuleInlining {
310    type Err = Error;
311
312    fn from_str(s: &str) -> Result<Self, Self::Err> {
313        match s {
314            "y" | "yes" | "true" => Ok(Self::Yes),
315            "n" | "no" | "false" => Ok(Self::No),
316            "gc" => Ok(Self::WhenUsingGc),
317            _ => bail!(
318                "invalid intra-module inlining option string: `{s}`, \
319                 only yes,no,gc accepted"
320            ),
321        }
322    }
323}