wasmtime_environ/tunables.rs
1use crate::{IndexType, Limits, Memory, TripleExt};
2use anyhow::{Error, Result, anyhow, bail};
3use core::{fmt, str::FromStr};
4use serde_derive::{Deserialize, Serialize};
5use target_lexicon::{PointerWidth, Triple};
6
7macro_rules! define_tunables {
8 (
9 $(#[$outer_attr:meta])*
10 pub struct $tunables:ident {
11 $(
12 $(#[$field_attr:meta])*
13 pub $field:ident : $field_ty:ty,
14 )*
15 }
16
17 pub struct $config_tunables:ident {
18 ...
19 }
20 ) => {
21 $(#[$outer_attr])*
22 pub struct $tunables {
23 $(
24 $(#[$field_attr])*
25 pub $field: $field_ty,
26 )*
27 }
28
29 /// Optional tunable configuration options used in `wasmtime::Config`
30 #[derive(Default, Clone)]
31 #[expect(missing_docs, reason = "macro-generated fields")]
32 pub struct $config_tunables {
33 $(pub $field: Option<$field_ty>,)*
34 }
35
36 impl $config_tunables {
37 /// Formats configured fields into `f`.
38 pub fn format(&self, f: &mut fmt::DebugStruct<'_,'_>) {
39 $(
40 if let Some(val) = &self.$field {
41 f.field(stringify!($field), val);
42 }
43 )*
44 }
45
46 /// Configure the `Tunables` provided.
47 pub fn configure(&self, tunables: &mut Tunables) {
48 $(
49 if let Some(val) = self.$field {
50 tunables.$field = val;
51 }
52 )*
53 }
54 }
55 };
56}
57
58define_tunables! {
59 /// Tunable parameters for WebAssembly compilation.
60 #[derive(Clone, Hash, Serialize, Deserialize, Debug)]
61 pub struct Tunables {
62 /// The garbage collector implementation to use, which implies the layout of
63 /// GC objects and barriers that must be emitted in Wasm code.
64 pub collector: Option<Collector>,
65
66 /// Initial size, in bytes, to be allocated for linear memories.
67 pub memory_reservation: u64,
68
69 /// The size, in bytes, of the guard page region for linear memories.
70 pub memory_guard_size: u64,
71
72 /// The size, in bytes, to allocate at the end of a relocated linear
73 /// memory for growth.
74 pub memory_reservation_for_growth: u64,
75
76 /// Whether or not to generate native DWARF debug information.
77 pub debug_native: bool,
78
79 /// Whether we are enabling precise Wasm-level debugging in
80 /// the guest.
81 pub debug_guest: bool,
82
83 /// Whether or not to retain DWARF sections in compiled modules.
84 pub parse_wasm_debuginfo: bool,
85
86 /// Whether or not fuel is enabled for generated code, meaning that fuel
87 /// will be consumed every time a wasm instruction is executed.
88 pub consume_fuel: bool,
89
90 /// Whether or not we use epoch-based interruption.
91 pub epoch_interruption: bool,
92
93 /// Whether or not linear memories are allowed to be reallocated after
94 /// initial allocation at runtime.
95 pub memory_may_move: bool,
96
97 /// Whether or not linear memory allocations will have a guard region at the
98 /// beginning of the allocation in addition to the end.
99 pub guard_before_linear_memory: bool,
100
101 /// Whether to initialize tables lazily, so that instantiation is fast but
102 /// indirect calls are a little slower. If false, tables are initialized
103 /// eagerly from any active element segments that apply to them during
104 /// instantiation.
105 pub table_lazy_init: bool,
106
107 /// Indicates whether an address map from compiled native code back to wasm
108 /// offsets in the original file is generated.
109 pub generate_address_map: bool,
110
111 /// Flag for the component module whether adapter modules have debug
112 /// assertions baked into them.
113 pub debug_adapter_modules: bool,
114
115 /// Whether or not lowerings for relaxed simd instructions are forced to
116 /// be deterministic.
117 pub relaxed_simd_deterministic: bool,
118
119 /// Whether or not Wasm functions target the winch abi.
120 pub winch_callable: bool,
121
122 /// Whether or not the host will be using native signals (e.g. SIGILL,
123 /// SIGSEGV, etc) to implement traps.
124 pub signals_based_traps: bool,
125
126 /// Whether CoW images might be used to initialize linear memories.
127 pub memory_init_cow: bool,
128
129 /// Whether to enable inlining in Wasmtime's compilation orchestration
130 /// or not.
131 pub inlining: bool,
132
133 /// Whether to inline calls within the same core Wasm module or not.
134 pub inlining_intra_module: IntraModuleInlining,
135
136 /// The size of "small callees" that can be inlined regardless of the
137 /// caller's size.
138 pub inlining_small_callee_size: u32,
139
140 /// The general size threshold for the sum of the caller's and callee's
141 /// sizes, past which we will generally not inline calls anymore.
142 pub inlining_sum_size_threshold: u32,
143 }
144
145 pub struct ConfigTunables {
146 ...
147 }
148}
149
150impl Tunables {
151 /// Returns a `Tunables` configuration assumed for running code on the host.
152 pub fn default_host() -> Self {
153 if cfg!(miri) {
154 Tunables::default_miri()
155 } else if cfg!(target_pointer_width = "32") {
156 Tunables::default_u32()
157 } else if cfg!(target_pointer_width = "64") {
158 Tunables::default_u64()
159 } else {
160 panic!("unsupported target_pointer_width");
161 }
162 }
163
164 /// Returns the default set of tunables for the given target triple.
165 pub fn default_for_target(target: &Triple) -> Result<Self> {
166 if cfg!(miri) {
167 return Ok(Tunables::default_miri());
168 }
169 let mut ret = match target
170 .pointer_width()
171 .map_err(|_| anyhow!("failed to retrieve target pointer width"))?
172 {
173 PointerWidth::U32 => Tunables::default_u32(),
174 PointerWidth::U64 => Tunables::default_u64(),
175 _ => bail!("unsupported target pointer width"),
176 };
177
178 // Pulley targets never use signals-based-traps and also can't benefit
179 // from guard pages, so disable them.
180 if target.is_pulley() {
181 ret.signals_based_traps = false;
182 ret.memory_guard_size = 0;
183 }
184 Ok(ret)
185 }
186
187 /// Returns the default set of tunables for running under MIRI.
188 pub const fn default_miri() -> Tunables {
189 Tunables {
190 collector: None,
191
192 // No virtual memory tricks are available on miri so make these
193 // limits quite conservative.
194 memory_reservation: 1 << 20,
195 memory_guard_size: 0,
196 memory_reservation_for_growth: 0,
197
198 // General options which have the same defaults regardless of
199 // architecture.
200 debug_native: false,
201 parse_wasm_debuginfo: true,
202 consume_fuel: false,
203 epoch_interruption: false,
204 memory_may_move: true,
205 guard_before_linear_memory: true,
206 table_lazy_init: true,
207 generate_address_map: true,
208 debug_adapter_modules: false,
209 relaxed_simd_deterministic: false,
210 winch_callable: false,
211 signals_based_traps: false,
212 memory_init_cow: true,
213 inlining: false,
214 inlining_intra_module: IntraModuleInlining::WhenUsingGc,
215 inlining_small_callee_size: 50,
216 inlining_sum_size_threshold: 2000,
217 debug_guest: false,
218 }
219 }
220
221 /// Returns the default set of tunables for running under a 32-bit host.
222 pub const fn default_u32() -> Tunables {
223 Tunables {
224 // For 32-bit we scale way down to 10MB of reserved memory. This
225 // impacts performance severely but allows us to have more than a
226 // few instances running around.
227 memory_reservation: 10 * (1 << 20),
228 memory_guard_size: 0x1_0000,
229 memory_reservation_for_growth: 1 << 20, // 1MB
230 signals_based_traps: true,
231
232 ..Tunables::default_miri()
233 }
234 }
235
236 /// Returns the default set of tunables for running under a 64-bit host.
237 pub const fn default_u64() -> Tunables {
238 Tunables {
239 // 64-bit has tons of address space to static memories can have 4gb
240 // address space reservations liberally by default, allowing us to
241 // help eliminate bounds checks.
242 //
243 // A 32MiB default guard size is then allocated so we can remove
244 // explicit bounds checks if any static offset is less than this
245 // value. SpiderMonkey found, for example, that in a large corpus of
246 // wasm modules 20MiB was the maximum offset so this is the
247 // power-of-two-rounded up from that and matches SpiderMonkey.
248 memory_reservation: 1 << 32,
249 memory_guard_size: 32 << 20,
250
251 // We've got lots of address space on 64-bit so use a larger
252 // grow-into-this area, but on 32-bit we aren't as lucky. Miri is
253 // not exactly fast so reduce memory consumption instead of trying
254 // to avoid memory movement.
255 memory_reservation_for_growth: 2 << 30, // 2GB
256
257 signals_based_traps: true,
258 ..Tunables::default_miri()
259 }
260 }
261
262 /// Get the GC heap's memory type, given our configured tunables.
263 pub fn gc_heap_memory_type(&self) -> Memory {
264 Memory {
265 idx_type: IndexType::I32,
266 limits: Limits { min: 0, max: None },
267 shared: false,
268 // We *could* try to match the target architecture's page size, but that
269 // would require exercising a page size for memories that we don't
270 // otherwise support for Wasm; we conservatively avoid that, and just
271 // use the default Wasm page size, for now.
272 page_size_log2: 16,
273 }
274 }
275}
276
277/// The garbage collector implementation to use.
278#[derive(Clone, Copy, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
279pub enum Collector {
280 /// The deferred reference-counting collector.
281 DeferredReferenceCounting,
282 /// The null collector.
283 Null,
284}
285
286impl fmt::Display for Collector {
287 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288 match self {
289 Collector::DeferredReferenceCounting => write!(f, "deferred reference-counting"),
290 Collector::Null => write!(f, "null"),
291 }
292 }
293}
294
295/// Whether to inline function calls within the same module.
296#[derive(Clone, Copy, Hash, Serialize, Deserialize, Debug, PartialEq, Eq)]
297#[expect(missing_docs, reason = "self-describing variants")]
298pub enum IntraModuleInlining {
299 Yes,
300 No,
301 WhenUsingGc,
302}
303
304impl FromStr for IntraModuleInlining {
305 type Err = Error;
306
307 fn from_str(s: &str) -> Result<Self, Self::Err> {
308 match s {
309 "y" | "yes" | "true" => Ok(Self::Yes),
310 "n" | "no" | "false" => Ok(Self::No),
311 "gc" => Ok(Self::WhenUsingGc),
312 _ => bail!(
313 "invalid intra-module inlining option string: `{s}`, \
314 only yes,no,gc accepted"
315 ),
316 }
317 }
318}