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};
6use wasmparser::Operator;
7
8macro_rules! define_tunables {
9    (
10        $(#[$outer_attr:meta])*
11        pub struct $tunables:ident {
12            $(
13                $(#[$field_attr:meta])*
14                pub $field:ident : $field_ty:ty,
15            )*
16        }
17
18        pub struct $config_tunables:ident {
19            ...
20        }
21    ) => {
22        $(#[$outer_attr])*
23        pub struct $tunables {
24            $(
25                $(#[$field_attr])*
26                pub $field: $field_ty,
27            )*
28        }
29
30        /// Optional tunable configuration options used in `wasmtime::Config`
31        #[derive(Default, Clone)]
32        #[expect(missing_docs, reason = "macro-generated fields")]
33        pub struct $config_tunables {
34            $(pub $field: Option<$field_ty>,)*
35        }
36
37        impl $config_tunables {
38            /// Formats configured fields into `f`.
39            pub fn format(&self, f: &mut fmt::DebugStruct<'_,'_>) {
40                $(
41                    if let Some(val) = &self.$field {
42                        f.field(stringify!($field), val);
43                    }
44                )*
45            }
46
47            /// Configure the `Tunables` provided.
48            pub fn configure(&self, tunables: &mut Tunables) {
49                $(
50                    if let Some(val) = &self.$field {
51                        tunables.$field = val.clone();
52                    }
53                )*
54            }
55        }
56    };
57}
58
59define_tunables! {
60    /// Tunable parameters for WebAssembly compilation.
61    #[derive(Clone, Hash, Serialize, Deserialize, Debug)]
62    pub struct Tunables {
63        /// The garbage collector implementation to use, which implies the layout of
64        /// GC objects and barriers that must be emitted in Wasm code.
65        pub collector: Option<Collector>,
66
67        /// Initial size, in bytes, to be allocated for linear memories.
68        pub memory_reservation: u64,
69
70        /// The size, in bytes, of the guard page region for linear memories.
71        pub memory_guard_size: u64,
72
73        /// The size, in bytes, to allocate at the end of a relocated linear
74        /// memory for growth.
75        pub memory_reservation_for_growth: u64,
76
77        /// Whether or not to generate native DWARF debug information.
78        pub debug_native: bool,
79
80        /// Whether we are enabling precise Wasm-level debugging in
81        /// the guest.
82        pub debug_guest: bool,
83
84        /// Whether or not to retain DWARF sections in compiled modules.
85        pub parse_wasm_debuginfo: bool,
86
87        /// Whether or not fuel is enabled for generated code, meaning that fuel
88        /// will be consumed every time a wasm instruction is executed.
89        pub consume_fuel: bool,
90
91        /// The cost of each operator. If fuel is not enabled, this is ignored.
92        pub operator_cost: OperatorCostStrategy,
93
94        /// Whether or not we use epoch-based interruption.
95        pub epoch_interruption: bool,
96
97        /// Whether or not linear memories are allowed to be reallocated after
98        /// initial allocation at runtime.
99        pub memory_may_move: bool,
100
101        /// Whether or not linear memory allocations will have a guard region at the
102        /// beginning of the allocation in addition to the end.
103        pub guard_before_linear_memory: bool,
104
105        /// Whether to initialize tables lazily, so that instantiation is fast but
106        /// indirect calls are a little slower. If false, tables are initialized
107        /// eagerly from any active element segments that apply to them during
108        /// instantiation.
109        pub table_lazy_init: bool,
110
111        /// Indicates whether an address map from compiled native code back to wasm
112        /// offsets in the original file is generated.
113        pub generate_address_map: bool,
114
115        /// Flag for the component module whether adapter modules have debug
116        /// assertions baked into them.
117        pub debug_adapter_modules: bool,
118
119        /// Whether or not lowerings for relaxed simd instructions are forced to
120        /// be deterministic.
121        pub relaxed_simd_deterministic: bool,
122
123        /// Whether or not Wasm functions target the winch abi.
124        pub winch_callable: bool,
125
126        /// Whether or not the host will be using native signals (e.g. SIGILL,
127        /// SIGSEGV, etc) to implement traps.
128        pub signals_based_traps: bool,
129
130        /// Whether CoW images might be used to initialize linear memories.
131        pub memory_init_cow: bool,
132
133        /// Whether to enable inlining in Wasmtime's compilation orchestration
134        /// or not.
135        pub inlining: bool,
136
137        /// Whether to inline calls within the same core Wasm module or not.
138        pub inlining_intra_module: IntraModuleInlining,
139
140        /// The size of "small callees" that can be inlined regardless of the
141        /// caller's size.
142        pub inlining_small_callee_size: u32,
143
144        /// The general size threshold for the sum of the caller's and callee's
145        /// sizes, past which we will generally not inline calls anymore.
146        pub inlining_sum_size_threshold: u32,
147
148        /// Whether any component model feature related to concurrency is
149        /// enabled.
150        pub concurrency_support: bool,
151
152        /// Whether recording in RR is enabled or not. This is used primarily
153        /// to signal checksum computation for compiled artifacts.
154        pub recording: bool,
155    }
156
157    pub struct ConfigTunables {
158        ...
159    }
160}
161
162impl Tunables {
163    /// Returns a `Tunables` configuration assumed for running code on the host.
164    pub fn default_host() -> Self {
165        if cfg!(miri) {
166            Tunables::default_miri()
167        } else if cfg!(target_pointer_width = "32") {
168            Tunables::default_u32()
169        } else if cfg!(target_pointer_width = "64") {
170            Tunables::default_u64()
171        } else {
172            panic!("unsupported target_pointer_width");
173        }
174    }
175
176    /// Returns the default set of tunables for the given target triple.
177    pub fn default_for_target(target: &Triple) -> Result<Self> {
178        if cfg!(miri) {
179            return Ok(Tunables::default_miri());
180        }
181        let mut ret = match target
182            .pointer_width()
183            .map_err(|_| format_err!("failed to retrieve target pointer width"))?
184        {
185            PointerWidth::U32 => Tunables::default_u32(),
186            PointerWidth::U64 => Tunables::default_u64(),
187            _ => bail!("unsupported target pointer width"),
188        };
189
190        // Pulley targets never use signals-based-traps and also can't benefit
191        // from guard pages, so disable them.
192        if target.is_pulley() {
193            ret.signals_based_traps = false;
194            ret.memory_guard_size = 0;
195        }
196        Ok(ret)
197    }
198
199    /// Returns the default set of tunables for running under MIRI.
200    pub fn default_miri() -> Tunables {
201        Tunables {
202            collector: None,
203
204            // No virtual memory tricks are available on miri so make these
205            // limits quite conservative.
206            memory_reservation: 1 << 20,
207            memory_guard_size: 0,
208            memory_reservation_for_growth: 0,
209
210            // General options which have the same defaults regardless of
211            // architecture.
212            debug_native: false,
213            parse_wasm_debuginfo: true,
214            consume_fuel: false,
215            operator_cost: OperatorCostStrategy::Default,
216            epoch_interruption: false,
217            memory_may_move: true,
218            guard_before_linear_memory: true,
219            table_lazy_init: true,
220            generate_address_map: true,
221            debug_adapter_modules: false,
222            relaxed_simd_deterministic: false,
223            winch_callable: false,
224            signals_based_traps: false,
225            memory_init_cow: true,
226            inlining: false,
227            inlining_intra_module: IntraModuleInlining::WhenUsingGc,
228            inlining_small_callee_size: 50,
229            inlining_sum_size_threshold: 2000,
230            debug_guest: false,
231            concurrency_support: true,
232            recording: false,
233        }
234    }
235
236    /// Returns the default set of tunables for running under a 32-bit host.
237    pub fn default_u32() -> Tunables {
238        Tunables {
239            // For 32-bit we scale way down to 10MB of reserved memory. This
240            // impacts performance severely but allows us to have more than a
241            // few instances running around.
242            memory_reservation: 10 * (1 << 20),
243            memory_guard_size: 0x1_0000,
244            memory_reservation_for_growth: 1 << 20, // 1MB
245            signals_based_traps: true,
246
247            ..Tunables::default_miri()
248        }
249    }
250
251    /// Returns the default set of tunables for running under a 64-bit host.
252    pub fn default_u64() -> Tunables {
253        Tunables {
254            // 64-bit has tons of address space to static memories can have 4gb
255            // address space reservations liberally by default, allowing us to
256            // help eliminate bounds checks.
257            //
258            // A 32MiB default guard size is then allocated so we can remove
259            // explicit bounds checks if any static offset is less than this
260            // value. SpiderMonkey found, for example, that in a large corpus of
261            // wasm modules 20MiB was the maximum offset so this is the
262            // power-of-two-rounded up from that and matches SpiderMonkey.
263            memory_reservation: 1 << 32,
264            memory_guard_size: 32 << 20,
265
266            // We've got lots of address space on 64-bit so use a larger
267            // grow-into-this area, but on 32-bit we aren't as lucky. Miri is
268            // not exactly fast so reduce memory consumption instead of trying
269            // to avoid memory movement.
270            memory_reservation_for_growth: 2 << 30, // 2GB
271
272            signals_based_traps: true,
273            ..Tunables::default_miri()
274        }
275    }
276
277    /// Get the GC heap's memory type, given our configured tunables.
278    pub fn gc_heap_memory_type(&self) -> Memory {
279        Memory {
280            idx_type: IndexType::I32,
281            limits: Limits { min: 0, max: None },
282            shared: false,
283            // We *could* try to match the target architecture's page size, but that
284            // would require exercising a page size for memories that we don't
285            // otherwise support for Wasm; we conservatively avoid that, and just
286            // use the default Wasm page size, for now.
287            page_size_log2: 16,
288        }
289    }
290}
291
292/// The garbage collector implementation to use.
293#[derive(Clone, Copy, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
294pub enum Collector {
295    /// The deferred reference-counting collector.
296    DeferredReferenceCounting,
297    /// The null collector.
298    Null,
299}
300
301impl fmt::Display for Collector {
302    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
303        match self {
304            Collector::DeferredReferenceCounting => write!(f, "deferred reference-counting"),
305            Collector::Null => write!(f, "null"),
306        }
307    }
308}
309
310/// Whether to inline function calls within the same module.
311#[derive(Clone, Copy, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
312#[expect(missing_docs, reason = "self-describing variants")]
313pub enum IntraModuleInlining {
314    Yes,
315    No,
316    WhenUsingGc,
317}
318
319impl FromStr for IntraModuleInlining {
320    type Err = Error;
321
322    fn from_str(s: &str) -> Result<Self, Self::Err> {
323        match s {
324            "y" | "yes" | "true" => Ok(Self::Yes),
325            "n" | "no" | "false" => Ok(Self::No),
326            "gc" => Ok(Self::WhenUsingGc),
327            _ => bail!(
328                "invalid intra-module inlining option string: `{s}`, \
329                 only yes,no,gc accepted"
330            ),
331        }
332    }
333}
334
335/// The cost of each operator.
336///
337/// Note: a more dynamic approach (e.g. a user-supplied callback) can be
338/// added as a variant in the future if needed.
339#[derive(Clone, Hash, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
340pub enum OperatorCostStrategy {
341    /// A table of operator costs.
342    Table(Box<OperatorCost>),
343
344    /// Each cost defaults to 1 fuel unit, except `Nop`, `Drop` and
345    /// a few control flow operators.
346    #[default]
347    Default,
348}
349
350impl OperatorCostStrategy {
351    /// Create a new operator cost strategy with a table of costs.
352    pub fn table(cost: OperatorCost) -> Self {
353        OperatorCostStrategy::Table(Box::new(cost))
354    }
355
356    /// Get the cost of an operator.
357    pub fn cost(&self, op: &Operator) -> i64 {
358        match self {
359            OperatorCostStrategy::Table(cost) => cost.cost(op),
360            OperatorCostStrategy::Default => default_operator_cost(op),
361        }
362    }
363}
364
365const fn default_operator_cost(op: &Operator) -> i64 {
366    match op {
367        // Nop and drop generate no code, so don't consume fuel for them.
368        Operator::Nop | Operator::Drop => 0,
369
370        // Control flow may create branches, but is generally cheap and
371        // free, so don't consume fuel. Note the lack of `if` since some
372        // cost is incurred with the conditional check.
373        Operator::Block { .. }
374        | Operator::Loop { .. }
375        | Operator::Unreachable
376        | Operator::Return
377        | Operator::Else
378        | Operator::End => 0,
379
380        // Everything else, just call it one operation.
381        _ => 1,
382    }
383}
384
385macro_rules! default_cost {
386    // Nop and drop generate no code, so don't consume fuel for them.
387    (Nop) => {
388        0
389    };
390    (Drop) => {
391        0
392    };
393
394    // Control flow may create branches, but is generally cheap and
395    // free, so don't consume fuel. Note the lack of `if` since some
396    // cost is incurred with the conditional check.
397    (Block) => {
398        0
399    };
400    (Loop) => {
401        0
402    };
403    (Unreachable) => {
404        0
405    };
406    (Return) => {
407        0
408    };
409    (Else) => {
410        0
411    };
412    (End) => {
413        0
414    };
415
416    // Everything else, just call it one operation.
417    ($op:ident) => {
418        1
419    };
420}
421
422macro_rules! define_operator_cost {
423    ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*) )*) => {
424        /// The fuel cost of each operator in a table.
425        #[derive(Clone, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
426        #[allow(missing_docs, non_snake_case, reason = "to avoid triggering clippy lints")]
427        pub struct OperatorCost {
428            $(
429                pub $op: u8,
430            )*
431        }
432
433        impl OperatorCost {
434            /// Returns the cost of the given operator.
435            pub fn cost(&self, op: &Operator) -> i64 {
436                match op {
437                    $(
438                        Operator::$op $({ $($arg: _),* })? => self.$op as i64,
439                    )*
440                    unknown => panic!("unknown op: {unknown:?}"),
441                }
442            }
443        }
444
445        impl OperatorCost {
446            /// Creates a new `OperatorCost` table with default costs for each operator.
447            pub const fn new() -> Self {
448                Self {
449                    $(
450                        $op: default_cost!($op),
451                    )*
452                }
453            }
454        }
455
456        impl Default for OperatorCost {
457            fn default() -> Self {
458                Self::new()
459            }
460        }
461    }
462}
463
464wasmparser::for_each_operator!(define_operator_cost);