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