/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); |