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 /// Kind of memory these tunables are for.
432 pub fn kind(&self) -> MemoryKind {
433 self.kind
434 }
435
436 /// Get the underlying tunables.
437 ///
438 /// This is ONLY for accessing tunable fields that DO NOT come in a
439 /// linear-memory flavor and a GC-heap flavor.
440 pub fn tunables(&self) -> &'a Tunables {
441 self.tunables
442 }
443}
444
445/// The garbage collector implementation to use.
446#[derive(Clone, Copy, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
447pub enum Collector {
448 /// The deferred reference-counting collector.
449 DeferredReferenceCounting,
450 /// The null collector.
451 Null,
452 /// The copying collector.
453 Copying,
454}
455
456impl fmt::Display for Collector {
457 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
458 match self {
459 Collector::DeferredReferenceCounting => write!(f, "deferred reference-counting"),
460 Collector::Null => write!(f, "null"),
461 Collector::Copying => write!(f, "copying"),
462 }
463 }
464}
465
466/// Inlining modes supported by Wasmtime.
467#[derive(Clone, Copy, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
468pub enum Inlining {
469 /// All inlining is enabled wherever possible.
470 ///
471 /// This includes inter-module inlining (across modules) as well as
472 /// intra-module inlining (within a module).
473 ///
474 /// Note that backtraces may omit inlined stack frames.
475 Yes,
476
477 /// Inter-module inlining (across modules) is allowed, but intra-module
478 /// (within a module) is only allowed when the module is using GC.
479 ///
480 /// Note that backtraces may omit inlined stack frames.
481 InterModuleAndIntraGc,
482
483 /// Inter-module inlining (across modules) is allowed, but intra-module
484 /// (within a module) is not allowed.
485 ///
486 /// Note that backtraces may omit inlined stack frames.
487 InterModule,
488
489 /// No module inlining is allowed, either inter- or intra-module. Only
490 /// inlining Wasmtime's intrinsics are allowed.
491 ///
492 /// This option, for example, never emits WebAssembly stack frames from
493 /// backtraces.
494 Intrinsics,
495
496 /// Inlining is disabled entirely.
497 No,
498}
499
500impl FromStr for Inlining {
501 type Err = Error;
502
503 fn from_str(s: &str) -> Result<Self, Self::Err> {
504 match s {
505 "y" | "yes" | "true" => Ok(Self::Yes),
506 "n" | "no" | "false" => Ok(Self::No),
507 "gc" => Ok(Self::InterModuleAndIntraGc),
508 "inter-module" => Ok(Self::InterModuleAndIntraGc),
509 "intrinsics" => Ok(Self::Intrinsics),
510 _ => bail!(
511 "invalid intra-module inlining option string: `{s}`, \
512 only yes,no,gc,inter-module,intrinsics accepted"
513 ),
514 }
515 }
516}
517
518impl fmt::Display for Inlining {
519 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
520 match self {
521 Inlining::Yes => write!(f, "yes"),
522 Inlining::InterModuleAndIntraGc => write!(f, "gc"),
523 Inlining::InterModule => write!(f, "inter-module"),
524 Inlining::Intrinsics => write!(f, "intrinsics"),
525 Inlining::No => write!(f, "no"),
526 }
527 }
528}
529
530/// The cost of each operator.
531///
532/// Note: a more dynamic approach (e.g. a user-supplied callback) can be
533/// added as a variant in the future if needed.
534#[derive(Clone, Hash, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
535pub enum OperatorCostStrategy {
536 /// A table of operator costs.
537 Table(Box<OperatorCost>),
538
539 /// Each cost defaults to 1 fuel unit, except `Nop`, `Drop` and
540 /// a few control flow operators.
541 #[default]
542 Default,
543}
544
545impl OperatorCostStrategy {
546 /// Create a new operator cost strategy with a table of costs.
547 pub fn table(cost: OperatorCost) -> Self {
548 OperatorCostStrategy::Table(Box::new(cost))
549 }
550
551 /// Get the cost of an operator.
552 pub fn cost(&self, op: &Operator) -> i64 {
553 match self {
554 OperatorCostStrategy::Table(cost) => cost.cost(op),
555 OperatorCostStrategy::Default => default_operator_cost(op),
556 }
557 }
558
559 /// Get the costs of work whose size is only known at runtime.
560 pub fn variable(&self) -> &VariableOperatorCost {
561 match self {
562 OperatorCostStrategy::Table(cost) => &cost.variable,
563 OperatorCostStrategy::Default => &DEFAULT_VARIABLE_OPERATOR_COST,
564 }
565 }
566}
567
568const DEFAULT_VARIABLE_OPERATOR_COST: VariableOperatorCost = VariableOperatorCost::new();
569
570/// Fuel costs for operators whose work is proportional to a runtime operand.
571///
572/// These costs are charged in addition to the corresponding flat cost in
573/// [`OperatorCost`].
574#[derive(Clone, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
575pub struct VariableOperatorCost {
576 /// Cost per byte copied by `memory.copy`.
577 pub memory_copy_per_byte: u8,
578 /// Cost per byte written by `memory.fill`.
579 pub memory_fill_per_byte: u8,
580 /// Cost per byte copied by `memory.init`.
581 pub memory_init_per_byte: u8,
582 /// Cost per page requested by `memory.grow`.
583 pub memory_grow_per_page: u8,
584
585 /// Cost per element copied by `table.copy`.
586 pub table_copy_per_element: u8,
587 /// Cost per element written by `table.fill`.
588 pub table_fill_per_element: u8,
589 /// Cost per element copied by `table.init`.
590 pub table_init_per_element: u8,
591 /// Cost per element requested by `table.grow`.
592 pub table_grow_per_element: u8,
593
594 /// Cost per element copied by `array.copy`.
595 pub array_copy_per_element: u8,
596 /// Cost per element written by `array.fill`.
597 pub array_fill_per_element: u8,
598 /// Cost per element initialized by `array.new_data`.
599 pub array_new_data_per_element: u8,
600 /// Cost per element initialized by `array.init_data`.
601 pub array_init_data_per_element: u8,
602 /// Cost per element initialized by `array.new_elem`.
603 pub array_new_elem_per_element: u8,
604 /// Cost per element initialized by `array.init_elem`.
605 pub array_init_elem_per_element: u8,
606 /// Cost per element initialized by `array.new_default`.
607 pub array_new_default_per_element: u8,
608 /// Cost per element initialized by `array.new`.
609 pub array_new_per_element: u8,
610}
611
612impl VariableOperatorCost {
613 /// Creates the default variable-cost table.
614 pub const fn new() -> Self {
615 Self {
616 memory_copy_per_byte: 1,
617 memory_fill_per_byte: 1,
618 memory_init_per_byte: 1,
619 memory_grow_per_page: 1,
620 table_copy_per_element: 1,
621 table_fill_per_element: 1,
622 table_init_per_element: 1,
623 table_grow_per_element: 1,
624 array_copy_per_element: 1,
625 array_fill_per_element: 1,
626 array_new_data_per_element: 1,
627 array_init_data_per_element: 1,
628 array_new_elem_per_element: 1,
629 array_init_elem_per_element: 1,
630 array_new_default_per_element: 1,
631 array_new_per_element: 1,
632 }
633 }
634}
635
636impl Default for VariableOperatorCost {
637 fn default() -> Self {
638 Self::new()
639 }
640}
641
642const fn default_operator_cost(op: &Operator) -> i64 {
643 match op {
644 // Nop and drop generate no code, so don't consume fuel for them.
645 Operator::Nop | Operator::Drop => 0,
646
647 // Control flow may create branches, but is generally cheap and
648 // free, so don't consume fuel. Note the lack of `if` since some
649 // cost is incurred with the conditional check.
650 Operator::Block { .. }
651 | Operator::Loop { .. }
652 | Operator::Unreachable
653 | Operator::Return
654 | Operator::Else
655 | Operator::End => 0,
656
657 // Everything else, just call it one operation.
658 _ => 1,
659 }
660}
661
662macro_rules! default_cost {
663 // Nop and drop generate no code, so don't consume fuel for them.
664 (Nop) => {
665 0
666 };
667 (Drop) => {
668 0
669 };
670
671 // Control flow may create branches, but is generally cheap and
672 // free, so don't consume fuel. Note the lack of `if` since some
673 // cost is incurred with the conditional check.
674 (Block) => {
675 0
676 };
677 (Loop) => {
678 0
679 };
680 (Unreachable) => {
681 0
682 };
683 (Return) => {
684 0
685 };
686 (Else) => {
687 0
688 };
689 (End) => {
690 0
691 };
692
693 // Everything else, just call it one operation.
694 ($op:ident) => {
695 1
696 };
697}
698
699macro_rules! define_operator_cost {
700 ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*) )*) => {
701 /// The fuel cost of each operator in a table.
702 #[derive(Clone, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
703 #[allow(missing_docs, non_snake_case, reason = "to avoid triggering clippy lints")]
704 pub struct OperatorCost {
705 $(
706 pub $op: u8,
707 )*
708 /// Costs for work whose size is only known at runtime.
709 pub variable: VariableOperatorCost,
710 }
711
712 impl OperatorCost {
713 /// Returns the cost of the given operator.
714 pub fn cost(&self, op: &Operator) -> i64 {
715 match op {
716 $(
717 Operator::$op $({ $($arg: _),* })? => self.$op as i64,
718 )*
719 unknown => panic!("unknown op: {unknown:?}"),
720 }
721 }
722 }
723
724 impl OperatorCost {
725 /// Creates a new `OperatorCost` table with default costs for each operator.
726 pub const fn new() -> Self {
727 Self {
728 $(
729 $op: default_cost!($op),
730 )*
731 variable: VariableOperatorCost::new(),
732 }
733 }
734 }
735
736 impl Default for OperatorCost {
737 fn default() -> Self {
738 Self::new()
739 }
740 }
741 }
742}
743
744wasmparser::for_each_operator!(define_operator_cost);