Skip to main content

wasmtime_cli_flags/
lib.rs

1//! Contains the common Wasmtime command line interface (CLI) flags.
2
3use clap::Parser;
4use serde::Deserialize;
5use std::{
6    fmt, fs,
7    num::NonZeroU32,
8    path::{Path, PathBuf},
9    time::Duration,
10};
11use wasmtime::{Config, Result, bail, error::Context as _};
12
13pub mod opt;
14
15#[cfg(feature = "logging")]
16fn init_file_per_thread_logger(prefix: &'static str) {
17    file_per_thread_logger::initialize(prefix);
18    file_per_thread_logger::allow_uninitialized();
19
20    // Extending behavior of default spawner:
21    // https://docs.rs/rayon/1.1.0/rayon/struct.ThreadPoolBuilder.html#method.spawn_handler
22    // Source code says DefaultSpawner is implementation detail and
23    // shouldn't be used directly.
24    #[cfg(feature = "parallel-compilation")]
25    rayon::ThreadPoolBuilder::new()
26        .spawn_handler(move |thread| {
27            let mut b = std::thread::Builder::new();
28            if let Some(name) = thread.name() {
29                b = b.name(name.to_owned());
30            }
31            if let Some(stack_size) = thread.stack_size() {
32                b = b.stack_size(stack_size);
33            }
34            b.spawn(move || {
35                file_per_thread_logger::initialize(prefix);
36                thread.run()
37            })?;
38            Ok(())
39        })
40        .build_global()
41        .unwrap();
42}
43
44wasmtime_option_group! {
45    #[derive(PartialEq, Clone, Deserialize)]
46    #[serde(rename_all = "kebab-case", deny_unknown_fields)]
47    pub struct OptimizeOptions {
48        /// Optimization level of generated code (0-2, s; default: 2)
49        #[serde(default)]
50        #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
51        pub opt_level: Option<wasmtime::OptLevel>,
52
53        /// Register allocator algorithm choice.
54        #[serde(default)]
55        #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
56        pub regalloc_algorithm: Option<wasmtime::RegallocAlgorithm>,
57
58        /// Do not allow Wasm linear memories to move in the host process's
59        /// address space.
60        pub memory_may_move: Option<bool>,
61
62        /// Initial virtual memory allocation size for memories.
63        pub memory_reservation: Option<u64>,
64
65        /// Bytes to reserve at the end of linear memory for growth into.
66        pub memory_reservation_for_growth: Option<u64>,
67
68        /// Size, in bytes, of guard pages for linear memories.
69        pub memory_guard_size: Option<u64>,
70
71        /// Do not allow the GC heap to move in the host process's address
72        /// space.
73        pub gc_heap_may_move: Option<bool>,
74
75        /// Initial virtual memory allocation size for the GC heap.
76        pub gc_heap_reservation: Option<u64>,
77
78        /// Bytes to reserve at the end of the GC heap for growth into.
79        pub gc_heap_reservation_for_growth: Option<u64>,
80
81        /// Size, in bytes, of guard pages for the GC heap.
82        pub gc_heap_guard_size: Option<u64>,
83
84        /// Indicates whether an unmapped region of memory is placed before all
85        /// linear memories.
86        pub guard_before_linear_memory: Option<bool>,
87
88        /// Whether to initialize tables lazily, so that instantiation is
89        /// fast but indirect calls are a little slower. If no, tables are
90        /// initialized eagerly from any active element segments that apply to
91        /// them during instantiation. (default: yes)
92        pub table_lazy_init: Option<bool>,
93
94        /// Enable the pooling allocator, in place of the on-demand allocator.
95        pub pooling_allocator: Option<bool>,
96
97        /// The number of decommits to do per batch. A batch size of 1
98        /// effectively disables decommit batching. (default: 1)
99        pub pooling_decommit_batch_size: Option<usize>,
100
101        /// How many bytes to keep resident between instantiations for the
102        /// pooling allocator in linear memories.
103        pub pooling_memory_keep_resident: Option<usize>,
104
105        /// How many bytes to keep resident between instantiations for the
106        /// pooling allocator in tables.
107        pub pooling_table_keep_resident: Option<usize>,
108
109        /// Enable memory protection keys for the pooling allocator; this can
110        /// optimize the size of memory slots.
111        #[serde(default)]
112        #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
113        pub pooling_memory_protection_keys: Option<wasmtime::Enabled>,
114
115        /// Sets an upper limit on how many memory protection keys (MPK) Wasmtime
116        /// will use. (default: 16)
117        pub pooling_max_memory_protection_keys: Option<usize>,
118
119        /// Configure attempting to initialize linear memory via a
120        /// copy-on-write mapping (default: yes)
121        pub memory_init_cow: Option<bool>,
122
123        /// Threshold below which CoW images are guaranteed to be used and be
124        /// dense.
125        pub memory_guaranteed_dense_image_size: Option<u64>,
126
127        /// The maximum number of WebAssembly instances which can be created
128        /// with the pooling allocator.
129        pub pooling_total_core_instances: Option<u32>,
130
131        /// The maximum number of WebAssembly components which can be created
132        /// with the pooling allocator.
133        pub pooling_total_component_instances: Option<u32>,
134
135        /// The maximum number of WebAssembly memories which can be created with
136        /// the pooling allocator.
137        pub pooling_total_memories: Option<u32>,
138
139        /// The maximum number of WebAssembly tables which can be created with
140        /// the pooling allocator.
141        pub pooling_total_tables: Option<u32>,
142
143        /// The maximum number of WebAssembly stacks which can be created with
144        /// the pooling allocator.
145        pub pooling_total_stacks: Option<u32>,
146
147        /// The maximum runtime size of each linear memory in the pooling
148        /// allocator, in bytes.
149        pub pooling_max_memory_size: Option<usize>,
150
151        /// The maximum table elements for any table defined in a module when
152        /// using the pooling allocator.
153        pub pooling_table_elements: Option<usize>,
154
155        /// The maximum size, in bytes, allocated for a core instance's metadata
156        /// when using the pooling allocator.
157        pub pooling_max_core_instance_size: Option<usize>,
158
159        /// Configures the maximum number of "unused warm slots" to retain in the
160        /// pooling allocator. (default: 100)
161        pub pooling_max_unused_warm_slots: Option<u32>,
162
163        /// How much memory, in bytes, to keep resident for async stacks allocated
164        /// with the pooling allocator. (default: 0)
165        pub pooling_async_stack_keep_resident: Option<usize>,
166
167        /// The maximum size, in bytes, allocated for a component instance's
168        /// `VMComponentContext` metadata. (default: 1MiB)
169        pub pooling_max_component_instance_size: Option<usize>,
170
171        /// The maximum number of core instances a single component may contain
172        /// (default is unlimited).
173        pub pooling_max_core_instances_per_component: Option<u32>,
174
175        /// The maximum number of Wasm linear memories that a single component may
176        /// transitively contain (default is unlimited).
177        pub pooling_max_memories_per_component: Option<u32>,
178
179        /// The maximum number of tables that a single component may transitively
180        /// contain (default is unlimited).
181        pub pooling_max_tables_per_component: Option<u32>,
182
183        /// The maximum number of defined tables for a core module. (default: 1)
184        pub pooling_max_tables_per_module: Option<u32>,
185
186        /// The maximum number of defined linear memories for a module. (default: 1)
187        pub pooling_max_memories_per_module: Option<u32>,
188
189        /// The maximum number of concurrent GC heaps supported. (default: 1000)
190        pub pooling_total_gc_heaps: Option<u32>,
191
192        /// Enable or disable the use of host signal handlers for traps.
193        pub signals_based_traps: Option<bool>,
194
195        /// DEPRECATED: Use `-Cmemory-guard-size=N` instead.
196        pub dynamic_memory_guard_size: Option<u64>,
197
198        /// DEPRECATED: Use `-Cmemory-guard-size=N` instead.
199        pub static_memory_guard_size: Option<u64>,
200
201        /// DEPRECATED: Use `-Cmemory-may-move` instead.
202        pub static_memory_forced: Option<bool>,
203
204        /// DEPRECATED: Use `-Cmemory-reservation=N` instead.
205        pub static_memory_maximum_size: Option<u64>,
206
207        /// DEPRECATED: Use `-Cmemory-reservation-for-growth=N` instead.
208        pub dynamic_memory_reserved_for_growth: Option<u64>,
209
210        /// Whether or not `PAGEMAP_SCAN` ioctls are used to reset linear
211        /// memory.
212        #[serde(default)]
213        #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
214        pub pooling_pagemap_scan: Option<wasmtime::Enabled>,
215
216        /// XXX: For internal fuzzing and debugging use only!
217        #[doc(hidden)]
218        pub gc_zeal_alloc_counter: Option<NonZeroU32>,
219    }
220
221    enum Optimize {
222        ...
223    }
224}
225
226wasmtime_option_group! {
227    #[derive(PartialEq, Clone, Deserialize)]
228    #[serde(rename_all = "kebab-case", deny_unknown_fields)]
229    pub struct CodegenOptions {
230        /// Either `cranelift` or `winch`.
231        ///
232        /// Currently only `cranelift` and `winch` are supported, but not all
233        /// builds of Wasmtime have both built in.
234        #[serde(default)]
235        #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
236        pub compiler: Option<wasmtime::Strategy>,
237        /// Which garbage collector to use: `drc`, `null`, or `copying`.
238        ///
239        /// `drc` is the deferred reference-counting collector.
240        ///
241        /// `null` is the null garbage collector, which does not collect any
242        /// garbage.
243        ///
244        /// `copying` is the copying garbage collector (not yet implemented).
245        ///
246        /// Note that not all builds of Wasmtime will have support for garbage
247        /// collection included.
248        #[serde(default)]
249        #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
250        pub collector: Option<wasmtime::Collector>,
251        /// Enable Cranelift's internal debug verifier (expensive)
252        pub cranelift_debug_verifier: Option<bool>,
253        /// Whether or not to enable caching of compiled modules.
254        pub cache: Option<bool>,
255        /// Configuration for compiled module caching.
256        pub cache_config: Option<String>,
257        /// Whether or not to enable parallel compilation of modules.
258        pub parallel_compilation: Option<bool>,
259        /// Controls whether native unwind information is present in compiled
260        /// object files.
261        pub native_unwind_info: Option<bool>,
262
263        /// Whether to perform function inlining during compilation.
264        pub inlining: Option<bool>,
265
266        #[prefixed = "cranelift"]
267        #[serde(default)]
268        /// Set a cranelift-specific option. Use `wasmtime settings` to see
269        /// all.
270        pub cranelift: Vec<(String, Option<String>)>,
271    }
272
273    enum Codegen {
274        ...
275    }
276}
277
278wasmtime_option_group! {
279    #[derive(PartialEq, Clone, Deserialize)]
280    #[serde(rename_all = "kebab-case", deny_unknown_fields)]
281    pub struct DebugOptions {
282        /// Enable generation of DWARF debug information in compiled code.
283        pub debug_info: Option<bool>,
284        /// Enable guest debugging insrumentation.
285        pub guest_debug: Option<bool>,
286        /// Configure whether compiled code can map native addresses to wasm.
287        pub address_map: Option<bool>,
288        /// Configure whether logging is enabled.
289        pub logging: Option<bool>,
290        /// Configure whether logs are emitted to files
291        pub log_to_files: Option<bool>,
292        /// Enable coredump generation to this file after a WebAssembly trap.
293        pub coredump: Option<String>,
294        /// Load the given debugger component and attach it to the
295        /// main module or component.
296        pub debugger: Option<PathBuf>,
297        /// Pass the given command-line arguments to the debugger
298        /// component. May be specified multiple times.
299        #[serde(default)]
300        pub arg: Vec<String>,
301        /// Allow the debugger component to inherit stdin.Off by
302        /// default.
303        pub inherit_stdin: Option<bool>,
304        /// Allow the debugger component to inherit stdout. Off by
305        /// default.
306        pub inherit_stdout: Option<bool>,
307        /// Allow the debugger component to inherit stderr. Off by
308        /// default.
309        pub inherit_stderr: Option<bool>,
310    }
311
312    enum Debug {
313        ...
314    }
315}
316
317wasmtime_option_group! {
318    #[derive(PartialEq, Clone, Deserialize)]
319    #[serde(rename_all = "kebab-case", deny_unknown_fields)]
320    pub struct WasmOptions {
321        /// Enable canonicalization of all NaN values.
322        pub nan_canonicalization: Option<bool>,
323        /// Enable execution fuel with N units fuel, trapping after running out
324        /// of fuel.
325        ///
326        /// Most WebAssembly instructions consume 1 unit of fuel. Some
327        /// instructions, such as `nop`, `drop`, `block`, and `loop`, consume 0
328        /// units, as any execution cost associated with them involves other
329        /// instructions which do consume fuel.
330        pub fuel: Option<u64>,
331        /// Yield when a global epoch counter changes, allowing for async
332        /// operation without blocking the executor.
333        pub epoch_interruption: Option<bool>,
334        /// Maximum stack size, in bytes, that wasm is allowed to consume before a
335        /// stack overflow is reported.
336        pub max_wasm_stack: Option<usize>,
337        /// Stack size, in bytes, that will be allocated for async stacks.
338        ///
339        /// Note that this must be larger than `max-wasm-stack` and the
340        /// difference between the two is how much stack the host has to execute
341        /// on.
342        pub async_stack_size: Option<usize>,
343        /// Configures whether or not stacks used for async futures are zeroed
344        /// before (re)use as a defense-in-depth mechanism. (default: false)
345        pub async_stack_zeroing: Option<bool>,
346        /// Allow unknown exports when running commands.
347        pub unknown_exports_allow: Option<bool>,
348        /// Allow the main module to import unknown functions, using an
349        /// implementation that immediately traps, when running commands.
350        pub unknown_imports_trap: Option<bool>,
351        /// Allow the main module to import unknown functions, using an
352        /// implementation that returns default values, when running commands.
353        pub unknown_imports_default: Option<bool>,
354        /// Enables memory error checking. (see wmemcheck.md for more info)
355        pub wmemcheck: Option<bool>,
356        /// Maximum size, in bytes, that a linear memory is allowed to reach.
357        ///
358        /// Growth beyond this limit will cause `memory.grow` instructions in
359        /// WebAssembly modules to return -1 and fail.
360        pub max_memory_size: Option<usize>,
361        /// Maximum size, in table elements, that a table is allowed to reach.
362        pub max_table_elements: Option<usize>,
363        /// Maximum number of WebAssembly instances allowed to be created.
364        pub max_instances: Option<usize>,
365        /// Maximum number of WebAssembly tables allowed to be created.
366        pub max_tables: Option<usize>,
367        /// Maximum number of WebAssembly linear memories allowed to be created.
368        pub max_memories: Option<usize>,
369        /// Force a trap to be raised on `memory.grow` and `table.grow` failure
370        /// instead of returning -1 from these instructions.
371        ///
372        /// This is not necessarily a spec-compliant option to enable but can be
373        /// useful for tracking down a backtrace of what is requesting so much
374        /// memory, for example.
375        pub trap_on_grow_failure: Option<bool>,
376        /// Maximum execution time of wasm code before timing out (1, 2s, 100ms, etc)
377        pub timeout: Option<Duration>,
378        /// Configures support for all WebAssembly proposals implemented.
379        pub all_proposals: Option<bool>,
380        /// Configure support for the bulk memory proposal.
381        pub bulk_memory: Option<bool>,
382        /// Configure support for the multi-memory proposal.
383        pub multi_memory: Option<bool>,
384        /// Configure support for the multi-value proposal.
385        pub multi_value: Option<bool>,
386        /// Configure support for the reference-types proposal.
387        pub reference_types: Option<bool>,
388        /// Configure support for the simd proposal.
389        pub simd: Option<bool>,
390        /// Configure support for the relaxed-simd proposal.
391        pub relaxed_simd: Option<bool>,
392        /// Configure forcing deterministic and host-independent behavior of
393        /// the relaxed-simd instructions.
394        ///
395        /// By default these instructions may have architecture-specific behavior as
396        /// allowed by the specification, but this can be used to force the behavior
397        /// of these instructions to match the deterministic behavior classified in
398        /// the specification. Note that enabling this option may come at a
399        /// performance cost.
400        pub relaxed_simd_deterministic: Option<bool>,
401        /// Configure support for the tail-call proposal.
402        pub tail_call: Option<bool>,
403        /// Configure support for the threads proposal.
404        pub threads: Option<bool>,
405        /// Configure the ability to create a `shared` memory.
406        pub shared_memory: Option<bool>,
407        /// Configure support for the shared-everything-threads proposal.
408        pub shared_everything_threads: Option<bool>,
409        /// Configure support for the memory64 proposal.
410        pub memory64: Option<bool>,
411        /// Configure support for the component-model proposal.
412        pub component_model: Option<bool>,
413        /// Component model support for async lifting/lowering.
414        pub component_model_async: Option<bool>,
415        /// Component model support for async lifting/lowering: this corresponds
416        /// to the ๐Ÿš emoji in the component model specification.
417        pub component_model_async_builtins: Option<bool>,
418        /// Component model support for async lifting/lowering: this corresponds
419        /// to the ๐ŸšŸ emoji in the component model specification.
420        pub component_model_async_stackful: Option<bool>,
421        /// Component model support for threading: this corresponds
422        /// to the ๐Ÿงต emoji in the component model specification.
423        pub component_model_threading: Option<bool>,
424        /// Component model support for `error-context`: this corresponds
425        /// to the ๐Ÿ“ emoji in the component model specification.
426        pub component_model_error_context: Option<bool>,
427        /// GC support in the component model: this corresponds to the ๐Ÿ›ธ emoji
428        /// in the component model specification.
429        pub component_model_gc: Option<bool>,
430        /// Map support in the component model.
431        pub component_model_map: Option<bool>,
432        /// Configure support for the function-references proposal.
433        pub function_references: Option<bool>,
434        /// Configure support for the stack-switching proposal.
435        pub stack_switching: Option<bool>,
436        /// Configure support for the GC proposal.
437        pub gc: Option<bool>,
438        /// Configure support for the custom-page-sizes proposal.
439        pub custom_page_sizes: Option<bool>,
440        /// Configure support for the wide-arithmetic proposal.
441        pub wide_arithmetic: Option<bool>,
442        /// Configure support for the extended-const proposal.
443        pub extended_const: Option<bool>,
444        /// Configure support for the exceptions proposal.
445        pub exceptions: Option<bool>,
446        /// Whether or not any GC infrastructure in Wasmtime is enabled or not.
447        pub gc_support: Option<bool>,
448        /// Component model support for fixed-length lists: this corresponds
449        /// to the ๐Ÿ”ง emoji in the component model specification
450        pub component_model_fixed_length_lists: Option<bool>,
451        /// Whether or not any concurrency infrastructure in Wasmtime is
452        /// enabled or not.
453        pub concurrency_support: Option<bool>,
454    }
455
456    enum Wasm {
457        ...
458    }
459}
460
461wasmtime_option_group! {
462    #[derive(PartialEq, Clone, Deserialize)]
463    #[serde(rename_all = "kebab-case", deny_unknown_fields)]
464    pub struct WasiOptions {
465        /// Enable support for WASI CLI APIs, including filesystems, sockets, clocks, and random.
466        pub cli: Option<bool>,
467        /// Enable WASI APIs marked as: @unstable(feature = cli-exit-with-code)
468        pub cli_exit_with_code: Option<bool>,
469        /// Deprecated alias for `cli`
470        pub common: Option<bool>,
471        /// Enable support for WASI neural network imports (experimental)
472        pub nn: Option<bool>,
473        /// Enable support for WASI threading imports (experimental). Implies preview2=false.
474        pub threads: Option<bool>,
475        /// Enable support for WASI HTTP imports
476        pub http: Option<bool>,
477        /// Number of distinct write calls to the outgoing body's output-stream
478        /// that the implementation will buffer.
479        /// Default: 1.
480        pub http_outgoing_body_buffer_chunks: Option<usize>,
481        /// Maximum size allowed in a write call to the outgoing body's output-stream.
482        /// Default: 1024 * 1024.
483        pub http_outgoing_body_chunk_size: Option<usize>,
484        /// Enable support for WASI config imports (experimental)
485        pub config: Option<bool>,
486        /// Enable support for WASI key-value imports (experimental)
487        pub keyvalue: Option<bool>,
488        /// Inherit environment variables and file descriptors following the
489        /// systemd listen fd specification (UNIX only) (legacy wasip1
490        /// implementation only)
491        pub listenfd: Option<bool>,
492        /// Grant access to the given TCP listen socket (experimental, legacy
493        /// wasip1 implementation only)
494        #[serde(default)]
495        pub tcplisten: Vec<String>,
496        /// Enable support for WASI TLS (Transport Layer Security) imports (experimental)
497        pub tls: Option<bool>,
498        /// Implement WASI Preview1 using new Preview2 implementation (true, default) or legacy
499        /// implementation (false)
500        pub preview2: Option<bool>,
501        /// Pre-load machine learning graphs (i.e., models) for use by wasi-nn.
502        ///
503        /// Each use of the flag will preload a ML model from the host directory
504        /// using the given model encoding. The model will be mapped to the
505        /// directory name: e.g., `--wasi-nn-graph openvino:/foo/bar` will preload
506        /// an OpenVINO model named `bar`. Note that which model encodings are
507        /// available is dependent on the backends implemented in the
508        /// `wasmtime_wasi_nn` crate.
509        #[serde(skip)]
510        pub nn_graph: Vec<WasiNnGraph>,
511        /// Flag for WASI preview2 to inherit the host's network within the
512        /// guest so it has full access to all addresses/ports/etc.
513        pub inherit_network: Option<bool>,
514        /// Indicates whether `wasi:sockets/ip-name-lookup` is enabled or not.
515        pub allow_ip_name_lookup: Option<bool>,
516        /// Indicates whether `wasi:sockets` TCP support is enabled or not.
517        pub tcp: Option<bool>,
518        /// Indicates whether `wasi:sockets` UDP support is enabled or not.
519        pub udp: Option<bool>,
520        /// Enable WASI APIs marked as: @unstable(feature = network-error-code)
521        pub network_error_code: Option<bool>,
522        /// Allows imports from the `wasi_unstable` core wasm module.
523        pub preview0: Option<bool>,
524        /// Inherit all environment variables from the parent process.
525        ///
526        /// This option can be further overwritten with `--env` flags.
527        pub inherit_env: Option<bool>,
528        /// Inherit stdin from the parent process. On by default.
529        pub inherit_stdin: Option<bool>,
530        /// Inherit stdout from the parent process. On by default.
531        pub inherit_stdout: Option<bool>,
532        /// Inherit stderr from the parent process. On by default.
533        pub inherit_stderr: Option<bool>,
534        /// Pass a wasi config variable to the program.
535        #[serde(skip)]
536        pub config_var: Vec<KeyValuePair>,
537        /// Preset data for the In-Memory provider of WASI key-value API.
538        #[serde(skip)]
539        pub keyvalue_in_memory_data: Vec<KeyValuePair>,
540        /// Enable support for WASIp3 APIs.
541        pub p3: Option<bool>,
542        /// Maximum resources the guest is allowed to create simultaneously.
543        pub max_resources: Option<usize>,
544        /// Fuel to use for all hostcalls to limit guest<->host data transfer.
545        pub hostcall_fuel: Option<usize>,
546        /// Maximum value, in bytes, for a wasi-random 0.2
547        /// `get{,-insecure}-random-bytes` `len` parameter. Calls with a value
548        /// exceeding this limit will trap.
549        pub max_random_size: Option<u64>,
550        /// Maximum value, in bytes, for the contents of a wasi-http 0.2
551        /// `fields` resource (aka `headers` and `trailers`). `fields` methods
552        /// which cause the contents to exceed this size limit will trap.
553        pub max_http_fields_size: Option<usize>,
554    }
555
556    enum Wasi {
557        ...
558    }
559}
560
561wasmtime_option_group! {
562    #[derive(PartialEq, Clone, Deserialize)]
563    #[serde(rename_all = "kebab-case", deny_unknown_fields)]
564    pub struct RecordOptions {
565        /// Filename for the recorded execution trace (or empty string to skip writing a file).
566        pub path: Option<String>,
567        /// Include (optional) signatures to facilitate validation checks during replay
568        /// (see `wasmtime replay` for details).
569        pub validation_metadata: Option<bool>,
570        /// Window size of internal buffering for record events (large windows offer more opportunities
571        /// for coalescing events at the cost of memory usage).
572        pub event_window_size: Option<usize>,
573    }
574
575    enum Record {
576        ...
577    }
578}
579
580#[derive(Debug, Clone, PartialEq)]
581pub struct WasiNnGraph {
582    pub format: String,
583    pub dir: String,
584}
585
586#[derive(Debug, Clone, PartialEq)]
587pub struct KeyValuePair {
588    pub key: String,
589    pub value: String,
590}
591
592/// Common options for commands that translate WebAssembly modules
593#[derive(Parser, Clone, Deserialize)]
594#[serde(deny_unknown_fields)]
595pub struct CommonOptions {
596    // These options groups are used to parse `-O` and such options but aren't
597    // the raw form consumed by the CLI. Instead they're pushed into the `pub`
598    // fields below as part of the `configure` method.
599    //
600    // Ideally clap would support `pub opts: OptimizeOptions` and parse directly
601    // into that but it does not appear to do so for multiple `-O` flags for
602    // now.
603    /// Optimization and tuning related options for wasm performance, `-O help` to
604    /// see all.
605    #[arg(short = 'O', long = "optimize", value_name = "KEY[=VAL[,..]]")]
606    #[serde(skip)]
607    opts_raw: Vec<opt::CommaSeparated<Optimize>>,
608
609    /// Codegen-related configuration options, `-C help` to see all.
610    #[arg(short = 'C', long = "codegen", value_name = "KEY[=VAL[,..]]")]
611    #[serde(skip)]
612    codegen_raw: Vec<opt::CommaSeparated<Codegen>>,
613
614    /// Debug-related configuration options, `-D help` to see all.
615    #[arg(short = 'D', long = "debug", value_name = "KEY[=VAL[,..]]")]
616    #[serde(skip)]
617    debug_raw: Vec<opt::CommaSeparated<Debug>>,
618
619    /// Options for configuring semantic execution of WebAssembly, `-W help` to see
620    /// all.
621    #[arg(short = 'W', long = "wasm", value_name = "KEY[=VAL[,..]]")]
622    #[serde(skip)]
623    wasm_raw: Vec<opt::CommaSeparated<Wasm>>,
624
625    /// Options for configuring WASI and its proposals, `-S help` to see all.
626    #[arg(short = 'S', long = "wasi", value_name = "KEY[=VAL[,..]]")]
627    #[serde(skip)]
628    wasi_raw: Vec<opt::CommaSeparated<Wasi>>,
629
630    /// Options to enable and configure execution recording, `-R help` to see all.
631    ///
632    /// Generates a serialized trace of the Wasm module execution that captures all
633    /// non-determinism observable by the module. This trace can subsequently be
634    /// re-executed in a deterministic, embedding-agnostic manner (see the `wasmtime replay` command).
635    ///
636    /// Note: Minimal configuration options for deterministic Wasm semantics will be
637    /// enforced during recording by default (NaN canonicalization, deterministic relaxed SIMD).
638    #[arg(short = 'R', long = "record", value_name = "KEY[=VAL[,..]]")]
639    #[serde(skip)]
640    record_raw: Vec<opt::CommaSeparated<Record>>,
641
642    // These fields are filled in by the `configure` method below via the
643    // options parsed from the CLI above. This is what the CLI should use.
644    #[arg(skip)]
645    #[serde(skip)]
646    configured: bool,
647
648    #[arg(skip)]
649    #[serde(rename = "optimize", default)]
650    pub opts: OptimizeOptions,
651
652    #[arg(skip)]
653    #[serde(rename = "codegen", default)]
654    pub codegen: CodegenOptions,
655
656    #[arg(skip)]
657    #[serde(rename = "debug", default)]
658    pub debug: DebugOptions,
659
660    #[arg(skip)]
661    #[serde(rename = "wasm", default)]
662    pub wasm: WasmOptions,
663
664    #[arg(skip)]
665    #[serde(rename = "wasi", default)]
666    pub wasi: WasiOptions,
667
668    #[arg(skip)]
669    #[serde(rename = "record", default)]
670    pub record: RecordOptions,
671
672    /// The target triple; default is the host triple
673    #[arg(long, value_name = "TARGET")]
674    #[serde(skip)]
675    pub target: Option<String>,
676
677    /// Use the specified TOML configuration file.
678    /// This TOML configuration file can provide same configuration options as the
679    /// `--optimize`, `--codegen`, `--debug`, `--wasm`, `--wasi` CLI options, with a couple exceptions.
680    ///
681    /// Additional options specified on the command line will take precedent over options loaded from
682    /// this TOML file.
683    #[arg(long = "config", value_name = "FILE")]
684    #[serde(skip)]
685    pub config: Option<PathBuf>,
686}
687
688macro_rules! match_feature {
689    (
690        [$feat:tt : $config:expr]
691        $val:ident => $e:expr,
692        $p:pat => err,
693    ) => {
694        #[cfg(feature = $feat)]
695        {
696            if let Some($val) = $config {
697                $e;
698            }
699        }
700        #[cfg(not(feature = $feat))]
701        {
702            if let Some($p) = $config {
703                bail!(concat!("support for ", $feat, " disabled at compile time"));
704            }
705        }
706    };
707}
708
709impl CommonOptions {
710    /// Creates a blank new set of [`CommonOptions`] that can be configured.
711    pub fn new() -> CommonOptions {
712        CommonOptions {
713            opts_raw: Vec::new(),
714            codegen_raw: Vec::new(),
715            debug_raw: Vec::new(),
716            wasm_raw: Vec::new(),
717            wasi_raw: Vec::new(),
718            record_raw: Vec::new(),
719            configured: true,
720            opts: Default::default(),
721            codegen: Default::default(),
722            debug: Default::default(),
723            wasm: Default::default(),
724            wasi: Default::default(),
725            record: Default::default(),
726            target: None,
727            config: None,
728        }
729    }
730
731    fn configure(&mut self) -> Result<()> {
732        if self.configured {
733            return Ok(());
734        }
735        self.configured = true;
736        if let Some(toml_config_path) = &self.config {
737            let toml_options = CommonOptions::from_file(toml_config_path)?;
738            self.opts = toml_options.opts;
739            self.codegen = toml_options.codegen;
740            self.debug = toml_options.debug;
741            self.wasm = toml_options.wasm;
742            self.wasi = toml_options.wasi;
743            self.record = toml_options.record;
744        }
745        self.opts.configure_with(&self.opts_raw);
746        self.codegen.configure_with(&self.codegen_raw);
747        self.debug.configure_with(&self.debug_raw);
748        self.wasm.configure_with(&self.wasm_raw);
749        self.wasi.configure_with(&self.wasi_raw);
750        self.record.configure_with(&self.record_raw);
751        Ok(())
752    }
753
754    pub fn init_logging(&mut self) -> Result<()> {
755        self.configure()?;
756        if self.debug.logging == Some(false) {
757            return Ok(());
758        }
759        #[cfg(feature = "logging")]
760        if self.debug.log_to_files == Some(true) {
761            let prefix = "wasmtime.dbg.";
762            init_file_per_thread_logger(prefix);
763        } else {
764            use std::io::IsTerminal;
765            use tracing_subscriber::{EnvFilter, FmtSubscriber};
766            let builder = FmtSubscriber::builder()
767                .with_writer(std::io::stderr)
768                .with_env_filter(EnvFilter::from_env("WASMTIME_LOG"))
769                .with_ansi(std::io::stderr().is_terminal());
770            if std::env::var("WASMTIME_LOG_NO_CONTEXT").is_ok_and(|value| value.eq("1")) {
771                builder
772                    .with_level(false)
773                    .with_target(false)
774                    .without_time()
775                    .init()
776            } else {
777                builder.init();
778            }
779        }
780        #[cfg(not(feature = "logging"))]
781        if self.debug.log_to_files == Some(true) || self.debug.logging == Some(true) {
782            bail!("support for logging disabled at compile time");
783        }
784        Ok(())
785    }
786
787    pub fn config(&mut self, pooling_allocator_default: Option<bool>) -> Result<Config> {
788        self.configure()?;
789        let mut config = Config::new();
790
791        match_feature! {
792            ["cranelift" : self.codegen.compiler]
793            strategy => config.strategy(strategy),
794            _ => err,
795        }
796        match_feature! {
797            ["gc" : self.codegen.collector]
798            collector => config.collector(collector),
799            _ => err,
800        }
801        if let Some(target) = &self.target {
802            config.target(target)?;
803        }
804        match_feature! {
805            ["cranelift" : self.codegen.cranelift_debug_verifier]
806            enable => config.cranelift_debug_verifier(enable),
807            true => err,
808        }
809        if let Some(enable) = self.debug.debug_info {
810            config.debug_info(enable);
811        }
812        match_feature! {
813            ["debug" : self.debug.guest_debug]
814            enable => config.guest_debug(enable),
815            _ => err,
816        }
817        if self.debug.coredump.is_some() {
818            #[cfg(feature = "coredump")]
819            config.coredump_on_trap(true);
820            #[cfg(not(feature = "coredump"))]
821            bail!("support for coredumps disabled at compile time");
822        }
823        match_feature! {
824            ["cranelift" : self.opts.opt_level]
825            level => config.cranelift_opt_level(level),
826            _ => err,
827        }
828        match_feature! {
829            ["cranelift": self.opts.regalloc_algorithm]
830            algo => config.cranelift_regalloc_algorithm(algo),
831            _ => err,
832        }
833        match_feature! {
834            ["cranelift" : self.wasm.nan_canonicalization]
835            enable => config.cranelift_nan_canonicalization(enable),
836            true => err,
837        }
838
839        self.enable_wasm_features(&mut config)?;
840
841        #[cfg(feature = "cranelift")]
842        for (name, value) in self.codegen.cranelift.iter() {
843            let name = name.replace('-', "_");
844            unsafe {
845                match value {
846                    Some(val) => {
847                        config.cranelift_flag_set(&name, val);
848                    }
849                    None => {
850                        config.cranelift_flag_enable(&name);
851                    }
852                }
853            }
854        }
855        #[cfg(not(feature = "cranelift"))]
856        if !self.codegen.cranelift.is_empty() {
857            bail!("support for cranelift disabled at compile time");
858        }
859
860        #[cfg(feature = "cache")]
861        if self.codegen.cache != Some(false) {
862            use wasmtime::Cache;
863            let cache = match &self.codegen.cache_config {
864                Some(path) => Cache::from_file(Some(Path::new(path)))?,
865                None => Cache::from_file(None)?,
866            };
867            config.cache(Some(cache));
868        }
869        #[cfg(not(feature = "cache"))]
870        if self.codegen.cache == Some(true) {
871            bail!("support for caching disabled at compile time");
872        }
873
874        match_feature! {
875            ["parallel-compilation" : self.codegen.parallel_compilation]
876            enable => config.parallel_compilation(enable),
877            true => err,
878        }
879
880        let memory_reservation = self
881            .opts
882            .memory_reservation
883            .or(self.opts.static_memory_maximum_size);
884        if let Some(size) = memory_reservation {
885            config.memory_reservation(size);
886        }
887
888        if let Some(enable) = self.opts.static_memory_forced {
889            config.memory_may_move(!enable);
890        }
891        if let Some(enable) = self.opts.memory_may_move {
892            config.memory_may_move(enable);
893        }
894
895        let memory_guard_size = self
896            .opts
897            .static_memory_guard_size
898            .or(self.opts.dynamic_memory_guard_size)
899            .or(self.opts.memory_guard_size);
900        if let Some(size) = memory_guard_size {
901            config.memory_guard_size(size);
902        }
903
904        let mem_for_growth = self
905            .opts
906            .memory_reservation_for_growth
907            .or(self.opts.dynamic_memory_reserved_for_growth);
908        if let Some(size) = mem_for_growth {
909            config.memory_reservation_for_growth(size);
910        }
911        if let Some(enable) = self.opts.guard_before_linear_memory {
912            config.guard_before_linear_memory(enable);
913        }
914
915        if let Some(size) = self.opts.gc_heap_reservation {
916            config.gc_heap_reservation(size);
917        }
918        if let Some(enable) = self.opts.gc_heap_may_move {
919            config.gc_heap_may_move(enable);
920        }
921        if let Some(size) = self.opts.gc_heap_guard_size {
922            config.gc_heap_guard_size(size);
923        }
924        if let Some(size) = self.opts.gc_heap_reservation_for_growth {
925            config.gc_heap_reservation_for_growth(size);
926        }
927        if let Some(enable) = self.opts.table_lazy_init {
928            config.table_lazy_init(enable);
929        }
930
931        if let Some(n) = self.opts.gc_zeal_alloc_counter
932            && (cfg!(gc_zeal) || cfg!(fuzzing))
933        {
934            config.gc_zeal_alloc_counter(Some(n))?;
935        }
936
937        // If fuel has been configured, set the `consume fuel` flag on the config.
938        if self.wasm.fuel.is_some() {
939            config.consume_fuel(true);
940        }
941
942        if let Some(enable) = self.wasm.epoch_interruption {
943            config.epoch_interruption(enable);
944        }
945        if let Some(enable) = self.debug.address_map {
946            config.generate_address_map(enable);
947        }
948        if let Some(enable) = self.opts.memory_init_cow {
949            config.memory_init_cow(enable);
950        }
951        if let Some(size) = self.opts.memory_guaranteed_dense_image_size {
952            config.memory_guaranteed_dense_image_size(size);
953        }
954        if let Some(enable) = self.opts.signals_based_traps {
955            config.signals_based_traps(enable);
956        }
957        if let Some(enable) = self.codegen.native_unwind_info {
958            config.native_unwind_info(enable);
959        }
960        if let Some(enable) = self.codegen.inlining {
961            config.compiler_inlining(enable);
962        }
963
964        // async_stack_size enabled by either async or stack-switching, so
965        // cannot directly use match_feature!
966        #[cfg(any(feature = "async", feature = "stack-switching"))]
967        {
968            if let Some(size) = self.wasm.async_stack_size {
969                config.async_stack_size(size);
970            }
971        }
972        #[cfg(not(any(feature = "async", feature = "stack-switching")))]
973        {
974            if let Some(_size) = self.wasm.async_stack_size {
975                bail!(concat!(
976                    "support for async/stack-switching disabled at compile time"
977                ));
978            }
979        }
980
981        match_feature! {
982            ["pooling-allocator" : self.opts.pooling_allocator.or(pooling_allocator_default)]
983            enable => {
984                if enable {
985                    let mut cfg = wasmtime::PoolingAllocationConfig::default();
986                    if let Some(size) = self.opts.pooling_memory_keep_resident {
987                        cfg.linear_memory_keep_resident(size);
988                    }
989                    if let Some(size) = self.opts.pooling_table_keep_resident {
990                        cfg.table_keep_resident(size);
991                    }
992                    if let Some(limit) = self.opts.pooling_total_core_instances {
993                        cfg.total_core_instances(limit);
994                    }
995                    if let Some(limit) = self.opts.pooling_total_component_instances {
996                        cfg.total_component_instances(limit);
997                    }
998                    if let Some(limit) = self.opts.pooling_total_memories {
999                        cfg.total_memories(limit);
1000                    }
1001                    if let Some(limit) = self.opts.pooling_total_tables {
1002                        cfg.total_tables(limit);
1003                    }
1004                    if let Some(limit) = self.opts.pooling_table_elements
1005                        .or(self.wasm.max_table_elements)
1006                    {
1007                        cfg.table_elements(limit);
1008                    }
1009                    if let Some(limit) = self.opts.pooling_max_core_instance_size {
1010                        cfg.max_core_instance_size(limit);
1011                    }
1012                    match_feature! {
1013                        ["async" : self.opts.pooling_total_stacks]
1014                        limit => cfg.total_stacks(limit),
1015                        _ => err,
1016                    }
1017                    if let Some(max) = self.opts.pooling_max_memory_size
1018                        .or(self.wasm.max_memory_size)
1019                    {
1020                        cfg.max_memory_size(max);
1021                    }
1022                    if let Some(size) = self.opts.pooling_decommit_batch_size {
1023                        cfg.decommit_batch_size(size);
1024                    }
1025                    if let Some(max) = self.opts.pooling_max_unused_warm_slots {
1026                        cfg.max_unused_warm_slots(max);
1027                    }
1028                    match_feature! {
1029                        ["async" : self.opts.pooling_async_stack_keep_resident]
1030                        size => cfg.async_stack_keep_resident(size),
1031                        _ => err,
1032                    }
1033                    if let Some(max) = self.opts.pooling_max_component_instance_size {
1034                        cfg.max_component_instance_size(max);
1035                    }
1036                    if let Some(max) = self.opts.pooling_max_core_instances_per_component {
1037                        cfg.max_core_instances_per_component(max);
1038                    }
1039                    if let Some(max) = self.opts.pooling_max_memories_per_component {
1040                        cfg.max_memories_per_component(max);
1041                    }
1042                    if let Some(max) = self.opts.pooling_max_tables_per_component {
1043                        cfg.max_tables_per_component(max);
1044                    }
1045                    if let Some(max) = self.opts.pooling_max_tables_per_module {
1046                        cfg.max_tables_per_module(max);
1047                    }
1048                    if let Some(max) = self.opts.pooling_max_memories_per_module {
1049                        cfg.max_memories_per_module(max);
1050                    }
1051                    match_feature! {
1052                        ["memory-protection-keys" : self.opts.pooling_memory_protection_keys]
1053                        enable => cfg.memory_protection_keys(enable),
1054                        _ => err,
1055                    }
1056                    match_feature! {
1057                        ["memory-protection-keys" : self.opts.pooling_max_memory_protection_keys]
1058                        max => cfg.max_memory_protection_keys(max),
1059                        _ => err,
1060                    }
1061                    match_feature! {
1062                        ["gc" : self.opts.pooling_total_gc_heaps]
1063                        max => cfg.total_gc_heaps(max),
1064                        _ => err,
1065                    }
1066                    if let Some(enabled) = self.opts.pooling_pagemap_scan {
1067                        cfg.pagemap_scan(enabled);
1068                    }
1069                    config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling(cfg));
1070                }
1071            },
1072            true => err,
1073        }
1074
1075        if self.opts.pooling_memory_protection_keys.is_some()
1076            && !self.opts.pooling_allocator.unwrap_or(false)
1077        {
1078            bail!("memory protection keys require the pooling allocator");
1079        }
1080
1081        if self.opts.pooling_max_memory_protection_keys.is_some()
1082            && !self.opts.pooling_memory_protection_keys.is_some()
1083        {
1084            bail!("max memory protection keys requires memory protection keys to be enabled");
1085        }
1086
1087        match_feature! {
1088            ["async" : self.wasm.async_stack_zeroing]
1089            enable => config.async_stack_zeroing(enable),
1090            _ => err,
1091        }
1092
1093        if let Some(max) = self.wasm.max_wasm_stack {
1094            config.max_wasm_stack(max);
1095
1096            // If `-Wasync-stack-size` isn't passed then automatically adjust it
1097            // to the wasm stack size provided here too. That prevents the need
1098            // to pass both when one can generally be inferred from the other.
1099            #[cfg(any(feature = "async", feature = "stack-switching"))]
1100            if self.wasm.async_stack_size.is_none() {
1101                const DEFAULT_HOST_STACK: usize = 512 << 10;
1102                config.async_stack_size(max + DEFAULT_HOST_STACK);
1103            }
1104        }
1105
1106        if let Some(enable) = self.wasm.relaxed_simd_deterministic {
1107            config.relaxed_simd_deterministic(enable);
1108        }
1109        match_feature! {
1110            ["cranelift" : self.wasm.wmemcheck]
1111            enable => config.wmemcheck(enable),
1112            true => err,
1113        }
1114
1115        if let Some(enable) = self.wasm.gc_support {
1116            config.gc_support(enable);
1117        }
1118
1119        if let Some(enable) = self.wasm.concurrency_support {
1120            config.concurrency_support(enable);
1121        }
1122
1123        if let Some(enable) = self.wasm.shared_memory {
1124            config.shared_memory(enable);
1125        }
1126
1127        let record = &self.record;
1128        match_feature! {
1129            ["rr" : &record.path]
1130            _path => {
1131                bail!("recording configuration for `rr` feature is not supported yet");
1132            },
1133            _ => err,
1134        }
1135
1136        Ok(config)
1137    }
1138
1139    pub fn enable_wasm_features(&self, config: &mut Config) -> Result<()> {
1140        let all = self.wasm.all_proposals;
1141
1142        if let Some(enable) = self.wasm.simd.or(all) {
1143            config.wasm_simd(enable);
1144        }
1145        if let Some(enable) = self.wasm.relaxed_simd.or(all) {
1146            config.wasm_relaxed_simd(enable);
1147        }
1148        if let Some(enable) = self.wasm.bulk_memory.or(all) {
1149            config.wasm_bulk_memory(enable);
1150        }
1151        if let Some(enable) = self.wasm.multi_value.or(all) {
1152            config.wasm_multi_value(enable);
1153        }
1154        if let Some(enable) = self.wasm.tail_call.or(all) {
1155            config.wasm_tail_call(enable);
1156        }
1157        if let Some(enable) = self.wasm.multi_memory.or(all) {
1158            config.wasm_multi_memory(enable);
1159        }
1160        if let Some(enable) = self.wasm.memory64.or(all) {
1161            config.wasm_memory64(enable);
1162        }
1163        if let Some(enable) = self.wasm.stack_switching {
1164            config.wasm_stack_switching(enable);
1165        }
1166        if let Some(enable) = self.wasm.custom_page_sizes.or(all) {
1167            config.wasm_custom_page_sizes(enable);
1168        }
1169        if let Some(enable) = self.wasm.wide_arithmetic.or(all) {
1170            config.wasm_wide_arithmetic(enable);
1171        }
1172        if let Some(enable) = self.wasm.extended_const.or(all) {
1173            config.wasm_extended_const(enable);
1174        }
1175
1176        macro_rules! handle_conditionally_compiled {
1177            ($(($feature:tt, $field:tt, $method:tt))*) => ($(
1178                if let Some(enable) = self.wasm.$field.or(all) {
1179                    #[cfg(feature = $feature)]
1180                    config.$method(enable);
1181                    #[cfg(not(feature = $feature))]
1182                    if enable && all.is_none() {
1183                        bail!("support for {} was disabled at compile-time", $feature);
1184                    }
1185                }
1186            )*)
1187        }
1188
1189        handle_conditionally_compiled! {
1190            ("component-model", component_model, wasm_component_model)
1191            ("component-model-async", component_model_async, wasm_component_model_async)
1192            ("component-model-async", component_model_async_builtins, wasm_component_model_async_builtins)
1193            ("component-model-async", component_model_async_stackful, wasm_component_model_async_stackful)
1194            ("component-model-async", component_model_threading, wasm_component_model_threading)
1195            ("component-model", component_model_error_context, wasm_component_model_error_context)
1196            ("component-model", component_model_map, wasm_component_model_map)
1197            ("component-model", component_model_fixed_length_lists, wasm_component_model_fixed_length_lists)
1198            ("threads", threads, wasm_threads)
1199            ("gc", gc, wasm_gc)
1200            ("gc", reference_types, wasm_reference_types)
1201            ("gc", function_references, wasm_function_references)
1202            ("gc", exceptions, wasm_exceptions)
1203            ("stack-switching", stack_switching, wasm_stack_switching)
1204        }
1205
1206        if let Some(enable) = self.wasm.component_model_gc {
1207            #[cfg(all(feature = "component-model", feature = "gc"))]
1208            config.wasm_component_model_gc(enable);
1209            #[cfg(not(all(feature = "component-model", feature = "gc")))]
1210            if enable && all.is_none() {
1211                bail!("support for `component-model-gc` was disabled at compile time")
1212            }
1213        }
1214
1215        Ok(())
1216    }
1217
1218    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
1219        let path_ref = path.as_ref();
1220        let file_contents = fs::read_to_string(path_ref)
1221            .with_context(|| format!("failed to read config file: {path_ref:?}"))?;
1222        toml::from_str::<CommonOptions>(&file_contents)
1223            .with_context(|| format!("failed to parse TOML config file {path_ref:?}"))
1224    }
1225}
1226
1227#[cfg(test)]
1228mod tests {
1229    use wasmtime::{OptLevel, RegallocAlgorithm};
1230
1231    use super::*;
1232
1233    #[test]
1234    fn from_toml() {
1235        // empty toml
1236        let empty_toml = "";
1237        let mut common_options: CommonOptions = toml::from_str(empty_toml).unwrap();
1238        common_options.config(None).unwrap();
1239
1240        // basic toml
1241        let basic_toml = r#"
1242            [optimize]
1243            [codegen]
1244            [debug]
1245            [wasm]
1246            [wasi]
1247            [record]
1248        "#;
1249        let mut common_options: CommonOptions = toml::from_str(basic_toml).unwrap();
1250        common_options.config(None).unwrap();
1251
1252        // toml with custom deserialization to match CLI flag parsing
1253        for (opt_value, expected) in [
1254            ("0", Some(OptLevel::None)),
1255            ("1", Some(OptLevel::Speed)),
1256            ("2", Some(OptLevel::Speed)),
1257            ("\"s\"", Some(OptLevel::SpeedAndSize)),
1258            ("\"hello\"", None), // should fail
1259            ("3", None),         // should fail
1260        ] {
1261            let toml = format!(
1262                r#"
1263                    [optimize]
1264                    opt-level = {opt_value}
1265                "#,
1266            );
1267            let parsed_opt_level = toml::from_str::<CommonOptions>(&toml)
1268                .ok()
1269                .and_then(|common_options| common_options.opts.opt_level);
1270
1271            assert_eq!(
1272                parsed_opt_level, expected,
1273                "Mismatch for input '{opt_value}'. Parsed: {parsed_opt_level:?}, Expected: {expected:?}"
1274            );
1275        }
1276
1277        // Regalloc algorithm
1278        for (regalloc_value, expected) in [
1279            ("\"backtracking\"", Some(RegallocAlgorithm::Backtracking)),
1280            ("\"single-pass\"", Some(RegallocAlgorithm::SinglePass)),
1281            ("\"hello\"", None), // should fail
1282            ("3", None),         // should fail
1283            ("true", None),      // should fail
1284        ] {
1285            let toml = format!(
1286                r#"
1287                    [optimize]
1288                    regalloc-algorithm = {regalloc_value}
1289                "#,
1290            );
1291            let parsed_regalloc_algorithm = toml::from_str::<CommonOptions>(&toml)
1292                .ok()
1293                .and_then(|common_options| common_options.opts.regalloc_algorithm);
1294            assert_eq!(
1295                parsed_regalloc_algorithm, expected,
1296                "Mismatch for input '{regalloc_value}'. Parsed: {parsed_regalloc_algorithm:?}, Expected: {expected:?}"
1297            );
1298        }
1299
1300        // Strategy
1301        for (strategy_value, expected) in [
1302            ("\"cranelift\"", Some(wasmtime::Strategy::Cranelift)),
1303            ("\"winch\"", Some(wasmtime::Strategy::Winch)),
1304            ("\"hello\"", None), // should fail
1305            ("5", None),         // should fail
1306            ("true", None),      // should fail
1307        ] {
1308            let toml = format!(
1309                r#"
1310                    [codegen]
1311                    compiler = {strategy_value}
1312                "#,
1313            );
1314            let parsed_strategy = toml::from_str::<CommonOptions>(&toml)
1315                .ok()
1316                .and_then(|common_options| common_options.codegen.compiler);
1317            assert_eq!(
1318                parsed_strategy, expected,
1319                "Mismatch for input '{strategy_value}'. Parsed: {parsed_strategy:?}, Expected: {expected:?}",
1320            );
1321        }
1322
1323        // Collector
1324        for (collector_value, expected) in [
1325            (
1326                "\"drc\"",
1327                Some(wasmtime::Collector::DeferredReferenceCounting),
1328            ),
1329            ("\"null\"", Some(wasmtime::Collector::Null)),
1330            ("\"copying\"", Some(wasmtime::Collector::Copying)),
1331            ("\"hello\"", None), // should fail
1332            ("5", None),         // should fail
1333            ("true", None),      // should fail
1334        ] {
1335            let toml = format!(
1336                r#"
1337                    [codegen]
1338                    collector = {collector_value}
1339                "#,
1340            );
1341            let parsed_collector = toml::from_str::<CommonOptions>(&toml)
1342                .ok()
1343                .and_then(|common_options| common_options.codegen.collector);
1344            assert_eq!(
1345                parsed_collector, expected,
1346                "Mismatch for input '{collector_value}'. Parsed: {parsed_collector:?}, Expected: {expected:?}",
1347            );
1348        }
1349    }
1350}
1351
1352impl Default for CommonOptions {
1353    fn default() -> CommonOptions {
1354        CommonOptions::new()
1355    }
1356}
1357
1358impl fmt::Display for CommonOptions {
1359    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1360        let CommonOptions {
1361            codegen_raw,
1362            codegen,
1363            debug_raw,
1364            debug,
1365            opts_raw,
1366            opts,
1367            wasm_raw,
1368            wasm,
1369            wasi_raw,
1370            wasi,
1371            record_raw,
1372            record,
1373            configured,
1374            target,
1375            config,
1376        } = self;
1377        if let Some(target) = target {
1378            write!(f, "--target {target} ")?;
1379        }
1380        if let Some(config) = config {
1381            write!(f, "--config {} ", config.display())?;
1382        }
1383
1384        let codegen_flags;
1385        let opts_flags;
1386        let wasi_flags;
1387        let wasm_flags;
1388        let debug_flags;
1389        let record_flags;
1390
1391        if *configured {
1392            codegen_flags = codegen.to_options();
1393            debug_flags = debug.to_options();
1394            wasi_flags = wasi.to_options();
1395            wasm_flags = wasm.to_options();
1396            opts_flags = opts.to_options();
1397            record_flags = record.to_options();
1398        } else {
1399            codegen_flags = codegen_raw
1400                .iter()
1401                .flat_map(|t| t.0.iter())
1402                .cloned()
1403                .collect();
1404            debug_flags = debug_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
1405            wasi_flags = wasi_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
1406            wasm_flags = wasm_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
1407            opts_flags = opts_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
1408            record_flags = record_raw
1409                .iter()
1410                .flat_map(|t| t.0.iter())
1411                .cloned()
1412                .collect();
1413        }
1414
1415        for flag in codegen_flags {
1416            write!(f, "-C{flag} ")?;
1417        }
1418        for flag in opts_flags {
1419            write!(f, "-O{flag} ")?;
1420        }
1421        for flag in wasi_flags {
1422            write!(f, "-S{flag} ")?;
1423        }
1424        for flag in wasm_flags {
1425            write!(f, "-W{flag} ")?;
1426        }
1427        for flag in debug_flags {
1428            write!(f, "-D{flag} ")?;
1429        }
1430        for flag in record_flags {
1431            write!(f, "-R{flag} ")?;
1432        }
1433
1434        Ok(())
1435    }
1436}