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