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