Skip to main content

wasmtime_environ/
tunables.rs

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