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        /// Whether compiled stack-switching instructions must emit the
159        /// AddressSanitizer fiber-switch handshake expected by this runtime.
160        ///
161        /// This setting is derived from how Wasmtime was built. It
162        /// ensures that the compiled artifact and runtime are ABI
163        /// compatible, e.g. it prevents running a non-ASan artifact
164        /// on an ASan-enabled runtime.
165        pub asan_stack_switching: bool,
166
167        /// An allocation counter that triggers GC when it reaches zero.
168        ///
169        /// Decremented on every allocation and when it hits zero, a GC is
170        /// forced and the counter is reset. Only effective when
171        /// `cfg(gc_zeal)` is enabled.
172        pub gc_zeal_alloc_counter: Option<NonZeroU32>,
173
174        /// Initial size, in bytes, to be allocated for GC heaps.
175        ///
176        /// This is the same as `memory_reservation` but for GC heaps.
177        pub gc_heap_reservation: u64,
178
179        /// The size, in bytes, of the guard page region for GC heaps.
180        ///
181        /// This is the same as `memory_guard_size` but for GC heaps.
182        pub gc_heap_guard_size: u64,
183
184        /// The size, in bytes, to allocate at the end of a relocated GC heap
185        /// for growth.
186        ///
187        /// This is the same as `memory_reservation_for_growth` but for GC
188        /// heaps.
189        pub gc_heap_reservation_for_growth: u64,
190
191        /// The size, in bytes, to set as the minimum for GC heaps.
192        pub gc_heap_initial_size: u64,
193
194        /// Whether or not GC heaps are allowed to be reallocated after initial
195        /// allocation at runtime.
196        ///
197        /// This is the same as `memory_may_move` but for GC heaps.
198        pub gc_heap_may_move: bool,
199
200        /// Boolean to track whether compiled code retains metadata necessary to
201        /// report extra information on internal assertions failing.
202        pub metadata_for_internal_asserts: bool,
203
204        /// Boolean to track whether compiled code retains metadata necessary to
205        /// report extra information on gc heap corruption being detected.
206        pub metadata_for_gc_heap_corruption: bool,
207
208        /// Whether `metadata.code.branch_hint` sections are parsed and used to
209        /// mark cold blocks during compilation.
210        pub branch_hinting: bool,
211    }
212
213    pub struct ConfigTunables {
214        ...
215    }
216}
217
218impl Tunables {
219    /// Returns a `Tunables` configuration assumed for running code on the host.
220    pub fn default_host() -> Self {
221        if cfg!(miri) {
222            Tunables::default_miri()
223        } else if cfg!(target_pointer_width = "32") {
224            Tunables::default_u32()
225        } else if cfg!(target_pointer_width = "64") {
226            Tunables::default_u64()
227        } else {
228            panic!("unsupported target_pointer_width");
229        }
230    }
231
232    /// Returns the default set of tunables for the given target triple.
233    pub fn default_for_target(target: &Triple) -> Result<Self> {
234        if cfg!(miri) {
235            return Ok(Tunables::default_miri());
236        }
237        let mut ret = match target
238            .pointer_width()
239            .map_err(|_| format_err!("failed to retrieve target pointer width"))?
240        {
241            PointerWidth::U32 => Tunables::default_u32(),
242            PointerWidth::U64 => Tunables::default_u64(),
243            _ => bail!("unsupported target pointer width"),
244        };
245
246        // Pulley targets never use signals-based-traps and also can't benefit
247        // from guard pages, so disable them.
248        if target.is_pulley() {
249            ret.signals_based_traps = false;
250            ret.memory_guard_size = 0;
251            ret.gc_heap_guard_size = 0;
252        }
253        Ok(ret)
254    }
255
256    /// Returns the default set of tunables for running under MIRI.
257    pub fn default_miri() -> Tunables {
258        Tunables {
259            collector: None,
260
261            // No virtual memory tricks are available on miri so make these
262            // limits quite conservative.
263            memory_reservation: 1 << 20,
264            memory_guard_size: 0,
265            memory_reservation_for_growth: 0,
266
267            // General options which have the same defaults regardless of
268            // architecture.
269            debug_native: false,
270            parse_wasm_debuginfo: true,
271            consume_fuel: false,
272            operator_cost: OperatorCostStrategy::Default,
273            epoch_interruption: false,
274            memory_may_move: true,
275            guard_before_linear_memory: true,
276            table_lazy_init: true,
277            generate_address_map: true,
278            debug_adapter_modules: false,
279            relaxed_simd_deterministic: false,
280            winch_callable: false,
281            signals_based_traps: false,
282            memory_init_cow: true,
283            inlining: Inlining::No,
284            inlining_small_callee_size: 50,
285            inlining_sum_size_threshold: 2000,
286            debug_guest: false,
287            concurrency_support: true,
288            recording: false,
289            asan_stack_switching: false,
290            gc_zeal_alloc_counter: None,
291            gc_heap_reservation: 0,
292            gc_heap_guard_size: 0,
293            gc_heap_reservation_for_growth: 0,
294            gc_heap_may_move: true,
295            gc_heap_initial_size: 0,
296            metadata_for_internal_asserts: false,
297            metadata_for_gc_heap_corruption: true,
298            branch_hinting: false,
299            debug_symbols: true,
300        }
301    }
302
303    /// Returns the default set of tunables for running under a 32-bit host.
304    pub fn default_u32() -> Tunables {
305        Tunables {
306            // For 32-bit we scale way down to 10MB of reserved memory. This
307            // impacts performance severely but allows us to have more than a
308            // few instances running around.
309            memory_reservation: 10 * (1 << 20),
310            memory_guard_size: 0x1_0000,
311            memory_reservation_for_growth: 1 << 20, // 1MB
312            signals_based_traps: true,
313
314            // GC heaps on 32-bit: conservative defaults similar to linear
315            // memories.
316            gc_heap_reservation: 10 * (1 << 20),
317            gc_heap_guard_size: 0x1_0000,
318            gc_heap_reservation_for_growth: 1 << 20, // 1MB
319
320            ..Tunables::default_miri()
321        }
322    }
323
324    /// Returns the default set of tunables for running under a 64-bit host.
325    pub fn default_u64() -> Tunables {
326        Tunables {
327            // 64-bit has tons of address space to static memories can have 4gb
328            // address space reservations liberally by default, allowing us to
329            // help eliminate bounds checks.
330            //
331            // A 32MiB default guard size is then allocated so we can remove
332            // explicit bounds checks if any static offset is less than this
333            // value. SpiderMonkey found, for example, that in a large corpus of
334            // wasm modules 20MiB was the maximum offset so this is the
335            // power-of-two-rounded up from that and matches SpiderMonkey.
336            memory_reservation: 1 << 32,
337            memory_guard_size: 32 << 20,
338
339            // We've got lots of address space on 64-bit so use a larger
340            // grow-into-this area, but on 32-bit we aren't as lucky. Miri is
341            // not exactly fast so reduce memory consumption instead of trying
342            // to avoid memory movement.
343            memory_reservation_for_growth: 2 << 30, // 2GB
344
345            // GC heaps on 64-bit: use 4GiB reservation and 32MiB guard pages
346            // to enable bounds check elision, matching linear memory defaults.
347            gc_heap_reservation: 1 << 32,
348            gc_heap_guard_size: 32 << 20,
349            gc_heap_reservation_for_growth: 2 << 30, // 2GB
350
351            signals_based_traps: true,
352            ..Tunables::default_miri()
353        }
354    }
355
356    /// Get the GC heap's memory type, given our configured tunables.
357    pub fn gc_heap_memory_type(&self) -> Memory {
358        // We *could* try to match the target architecture's page size, but that
359        // would require exercising a page size for memories that we don't
360        // otherwise support for Wasm; we conservatively avoid that, and just
361        // use the default Wasm page size, for now.
362        let page_size_log2 = 16;
363        let min = self.gc_heap_initial_size.div_ceil(1 << page_size_log2);
364        Memory {
365            idx_type: IndexType::I32,
366            limits: Limits { min, max: None },
367            shared: false,
368            page_size_log2,
369        }
370    }
371}
372
373/// Whether a heap is backing a linear memory or a GC heap.
374///
375/// This is used by [`MemoryTunables`] to select between the memory tunables and
376/// the GC heap tunables.
377#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
378pub enum MemoryKind {
379    /// A WebAssembly linear memory.
380    LinearMemory,
381    /// A GC heap for garbage-collected objects.
382    GcHeap,
383}
384
385/// A view into a [`Tunables`] that selects the appropriate linear-memory or
386/// GC-heap flavor of each tunable based on a [`MemoryKind`].
387pub struct MemoryTunables<'a> {
388    tunables: &'a Tunables,
389    kind: MemoryKind,
390}
391
392impl<'a> MemoryTunables<'a> {
393    /// Create a new `MemoryTunables` view.
394    pub fn new(tunables: &'a Tunables, kind: MemoryKind) -> Self {
395        Self { tunables, kind }
396    }
397
398    /// The virtual memory reservation for this kind of memory.
399    pub fn reservation(&self) -> u64 {
400        match self.kind {
401            MemoryKind::LinearMemory => self.tunables.memory_reservation,
402            MemoryKind::GcHeap => self.tunables.gc_heap_reservation,
403        }
404    }
405
406    /// The size of the guard page region for this kind of memory.
407    pub fn guard_size(&self) -> u64 {
408        match self.kind {
409            MemoryKind::LinearMemory => self.tunables.memory_guard_size,
410            MemoryKind::GcHeap => self.tunables.gc_heap_guard_size,
411        }
412    }
413
414    /// Extra virtual memory to reserve beyond the initially mapped pages for
415    /// this kind of memory.
416    pub fn reservation_for_growth(&self) -> u64 {
417        match self.kind {
418            MemoryKind::LinearMemory => self.tunables.memory_reservation_for_growth,
419            MemoryKind::GcHeap => self.tunables.gc_heap_reservation_for_growth,
420        }
421    }
422
423    /// Whether this kind of memory's base pointer may be relocated at runtime.
424    pub fn may_move(&self) -> bool {
425        match self.kind {
426            MemoryKind::LinearMemory => self.tunables.memory_may_move,
427            MemoryKind::GcHeap => self.tunables.gc_heap_may_move,
428        }
429    }
430
431    /// Get the underlying tunables.
432    ///
433    /// This is ONLY for accessing tunable fields that DO NOT come in a
434    /// linear-memory flavor and a GC-heap flavor.
435    pub fn tunables(&self) -> &'a Tunables {
436        self.tunables
437    }
438}
439
440/// The garbage collector implementation to use.
441#[derive(Clone, Copy, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
442pub enum Collector {
443    /// The deferred reference-counting collector.
444    DeferredReferenceCounting,
445    /// The null collector.
446    Null,
447    /// The copying collector.
448    Copying,
449}
450
451impl fmt::Display for Collector {
452    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
453        match self {
454            Collector::DeferredReferenceCounting => write!(f, "deferred reference-counting"),
455            Collector::Null => write!(f, "null"),
456            Collector::Copying => write!(f, "copying"),
457        }
458    }
459}
460
461/// Inlining modes supported by Wasmtime.
462#[derive(Clone, Copy, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
463pub enum Inlining {
464    /// All inlining is enabled wherever possible.
465    ///
466    /// This includes inter-module inlining (across modules) as well as
467    /// intra-module inlining (within a module).
468    ///
469    /// Note that backtraces may omit inlined stack frames.
470    Yes,
471
472    /// Inter-module inlining (across modules) is allowed, but intra-module
473    /// (within a module) is only allowed when the module is using GC.
474    ///
475    /// Note that backtraces may omit inlined stack frames.
476    InterModuleAndIntraGc,
477
478    /// Inter-module inlining (across modules) is allowed, but intra-module
479    /// (within a module) is not allowed.
480    ///
481    /// Note that backtraces may omit inlined stack frames.
482    InterModule,
483
484    /// No module inlining is allowed, either inter- or intra-module. Only
485    /// inlining Wasmtime's intrinsics are allowed.
486    ///
487    /// This option, for example, never emits WebAssembly stack frames from
488    /// backtraces.
489    Intrinsics,
490
491    /// Inlining is disabled entirely.
492    No,
493}
494
495impl FromStr for Inlining {
496    type Err = Error;
497
498    fn from_str(s: &str) -> Result<Self, Self::Err> {
499        match s {
500            "y" | "yes" | "true" => Ok(Self::Yes),
501            "n" | "no" | "false" => Ok(Self::No),
502            "gc" => Ok(Self::InterModuleAndIntraGc),
503            "inter-module" => Ok(Self::InterModuleAndIntraGc),
504            "intrinsics" => Ok(Self::Intrinsics),
505            _ => bail!(
506                "invalid intra-module inlining option string: `{s}`, \
507                 only yes,no,gc,inter-module,intrinsics accepted"
508            ),
509        }
510    }
511}
512
513impl fmt::Display for Inlining {
514    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
515        match self {
516            Inlining::Yes => write!(f, "yes"),
517            Inlining::InterModuleAndIntraGc => write!(f, "gc"),
518            Inlining::InterModule => write!(f, "inter-module"),
519            Inlining::Intrinsics => write!(f, "intrinsics"),
520            Inlining::No => write!(f, "no"),
521        }
522    }
523}
524
525/// The cost of each operator.
526///
527/// Note: a more dynamic approach (e.g. a user-supplied callback) can be
528/// added as a variant in the future if needed.
529#[derive(Clone, Hash, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
530pub enum OperatorCostStrategy {
531    /// A table of operator costs.
532    Table(Box<OperatorCost>),
533
534    /// Each cost defaults to 1 fuel unit, except `Nop`, `Drop` and
535    /// a few control flow operators.
536    #[default]
537    Default,
538}
539
540impl OperatorCostStrategy {
541    /// Create a new operator cost strategy with a table of costs.
542    pub fn table(cost: OperatorCost) -> Self {
543        OperatorCostStrategy::Table(Box::new(cost))
544    }
545
546    /// Get the cost of an operator.
547    pub fn cost(&self, op: &Operator) -> i64 {
548        match self {
549            OperatorCostStrategy::Table(cost) => cost.cost(op),
550            OperatorCostStrategy::Default => default_operator_cost(op),
551        }
552    }
553
554    /// Get the costs of work whose size is only known at runtime.
555    pub fn variable(&self) -> &VariableOperatorCost {
556        match self {
557            OperatorCostStrategy::Table(cost) => &cost.variable,
558            OperatorCostStrategy::Default => &DEFAULT_VARIABLE_OPERATOR_COST,
559        }
560    }
561}
562
563const DEFAULT_VARIABLE_OPERATOR_COST: VariableOperatorCost = VariableOperatorCost::new();
564
565/// Fuel costs for operators whose work is proportional to a runtime operand.
566///
567/// These costs are charged in addition to the corresponding flat cost in
568/// [`OperatorCost`].
569#[derive(Clone, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
570pub struct VariableOperatorCost {
571    /// Cost per byte copied by `memory.copy`.
572    pub memory_copy_per_byte: u8,
573    /// Cost per byte written by `memory.fill`.
574    pub memory_fill_per_byte: u8,
575    /// Cost per byte copied by `memory.init`.
576    pub memory_init_per_byte: u8,
577    /// Cost per page requested by `memory.grow`.
578    pub memory_grow_per_page: u8,
579
580    /// Cost per element copied by `table.copy`.
581    pub table_copy_per_element: u8,
582    /// Cost per element written by `table.fill`.
583    pub table_fill_per_element: u8,
584    /// Cost per element copied by `table.init`.
585    pub table_init_per_element: u8,
586    /// Cost per element requested by `table.grow`.
587    pub table_grow_per_element: u8,
588
589    /// Cost per element copied by `array.copy`.
590    pub array_copy_per_element: u8,
591    /// Cost per element written by `array.fill`.
592    pub array_fill_per_element: u8,
593    /// Cost per element initialized by `array.new_data`.
594    pub array_new_data_per_element: u8,
595    /// Cost per element initialized by `array.init_data`.
596    pub array_init_data_per_element: u8,
597    /// Cost per element initialized by `array.new_elem`.
598    pub array_new_elem_per_element: u8,
599    /// Cost per element initialized by `array.init_elem`.
600    pub array_init_elem_per_element: u8,
601    /// Cost per element initialized by `array.new_default`.
602    pub array_new_default_per_element: u8,
603    /// Cost per element initialized by `array.new`.
604    pub array_new_per_element: u8,
605}
606
607impl VariableOperatorCost {
608    /// Creates the default variable-cost table.
609    pub const fn new() -> Self {
610        Self {
611            memory_copy_per_byte: 1,
612            memory_fill_per_byte: 1,
613            memory_init_per_byte: 1,
614            memory_grow_per_page: 1,
615            table_copy_per_element: 1,
616            table_fill_per_element: 1,
617            table_init_per_element: 1,
618            table_grow_per_element: 1,
619            array_copy_per_element: 1,
620            array_fill_per_element: 1,
621            array_new_data_per_element: 1,
622            array_init_data_per_element: 1,
623            array_new_elem_per_element: 1,
624            array_init_elem_per_element: 1,
625            array_new_default_per_element: 1,
626            array_new_per_element: 1,
627        }
628    }
629}
630
631impl Default for VariableOperatorCost {
632    fn default() -> Self {
633        Self::new()
634    }
635}
636
637const fn default_operator_cost(op: &Operator) -> i64 {
638    match op {
639        // Nop and drop generate no code, so don't consume fuel for them.
640        Operator::Nop | Operator::Drop => 0,
641
642        // Control flow may create branches, but is generally cheap and
643        // free, so don't consume fuel. Note the lack of `if` since some
644        // cost is incurred with the conditional check.
645        Operator::Block { .. }
646        | Operator::Loop { .. }
647        | Operator::Unreachable
648        | Operator::Return
649        | Operator::Else
650        | Operator::End => 0,
651
652        // Everything else, just call it one operation.
653        _ => 1,
654    }
655}
656
657macro_rules! default_cost {
658    // Nop and drop generate no code, so don't consume fuel for them.
659    (Nop) => {
660        0
661    };
662    (Drop) => {
663        0
664    };
665
666    // Control flow may create branches, but is generally cheap and
667    // free, so don't consume fuel. Note the lack of `if` since some
668    // cost is incurred with the conditional check.
669    (Block) => {
670        0
671    };
672    (Loop) => {
673        0
674    };
675    (Unreachable) => {
676        0
677    };
678    (Return) => {
679        0
680    };
681    (Else) => {
682        0
683    };
684    (End) => {
685        0
686    };
687
688    // Everything else, just call it one operation.
689    ($op:ident) => {
690        1
691    };
692}
693
694macro_rules! define_operator_cost {
695    ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*) )*) => {
696        /// The fuel cost of each operator in a table.
697        #[derive(Clone, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
698        #[allow(missing_docs, non_snake_case, reason = "to avoid triggering clippy lints")]
699        pub struct OperatorCost {
700            $(
701                pub $op: u8,
702            )*
703            /// Costs for work whose size is only known at runtime.
704            pub variable: VariableOperatorCost,
705        }
706
707        impl OperatorCost {
708            /// Returns the cost of the given operator.
709            pub fn cost(&self, op: &Operator) -> i64 {
710                match op {
711                    $(
712                        Operator::$op $({ $($arg: _),* })? => self.$op as i64,
713                    )*
714                    unknown => panic!("unknown op: {unknown:?}"),
715                }
716            }
717        }
718
719        impl OperatorCost {
720            /// Creates a new `OperatorCost` table with default costs for each operator.
721            pub const fn new() -> Self {
722                Self {
723                    $(
724                        $op: default_cost!($op),
725                    )*
726                    variable: VariableOperatorCost::new(),
727                }
728            }
729        }
730
731        impl Default for OperatorCost {
732            fn default() -> Self {
733                Self::new()
734            }
735        }
736    }
737}
738
739wasmparser::for_each_operator!(define_operator_cost);