Coverage Report

Created: 2026-09-14 07:40

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