xref: /wasmtime-44.0.1/crates/cli-flags/src/lib.rs (revision 282edac1)
1 //! Contains the common Wasmtime command line interface (CLI) flags.
2 
3 #![deny(trivial_numeric_casts, unused_extern_crates, unstable_features)]
4 #![warn(unused_import_braces)]
5 
6 use anyhow::Result;
7 use clap::Parser;
8 use std::time::Duration;
9 use wasmtime::Config;
10 
11 pub mod opt;
12 
13 #[cfg(feature = "logging")]
14 fn init_file_per_thread_logger(prefix: &'static str) {
15     file_per_thread_logger::initialize(prefix);
16 
17     // Extending behavior of default spawner:
18     // https://docs.rs/rayon/1.1.0/rayon/struct.ThreadPoolBuilder.html#method.spawn_handler
19     // Source code says DefaultSpawner is implementation detail and
20     // shouldn't be used directly.
21     #[cfg(feature = "parallel-compilation")]
22     rayon::ThreadPoolBuilder::new()
23         .spawn_handler(move |thread| {
24             let mut b = std::thread::Builder::new();
25             if let Some(name) = thread.name() {
26                 b = b.name(name.to_owned());
27             }
28             if let Some(stack_size) = thread.stack_size() {
29                 b = b.stack_size(stack_size);
30             }
31             b.spawn(move || {
32                 file_per_thread_logger::initialize(prefix);
33                 thread.run()
34             })?;
35             Ok(())
36         })
37         .build_global()
38         .unwrap();
39 }
40 
41 wasmtime_option_group! {
42     pub struct OptimizeOptions {
43         /// Optimization level of generated code (0-2, s; default: 0)
44         pub opt_level: Option<wasmtime::OptLevel>,
45 
46         /// Byte size of the guard region after dynamic memories are allocated
47         pub dynamic_memory_guard_size: Option<u64>,
48 
49         /// Force using a "static" style for all wasm memories
50         pub static_memory_forced: Option<bool>,
51 
52         /// Maximum size in bytes of wasm memory before it becomes dynamically
53         /// relocatable instead of up-front-reserved.
54         pub static_memory_maximum_size: Option<u64>,
55 
56         /// Byte size of the guard region after static memories are allocated
57         pub static_memory_guard_size: Option<u64>,
58 
59         /// Bytes to reserve at the end of linear memory for growth for dynamic
60         /// memories.
61         pub dynamic_memory_reserved_for_growth: Option<u64>,
62 
63         /// Enable the pooling allocator, in place of the on-demand allocator.
64         pub pooling_allocator: Option<bool>,
65 
66         /// Configure attempting to initialize linear memory via a
67         /// copy-on-write mapping (default: yes)
68         pub memory_init_cow: Option<bool>,
69     }
70 
71     enum Optimize {
72         ...
73     }
74 }
75 
76 wasmtime_option_group! {
77     pub struct CodegenOptions {
78         /// Either `cranelift` or `winch`.
79         ///
80         /// Currently only `cranelift` and `winch` are supported, but not all
81         /// builds of Wasmtime have both built in.
82         pub compiler: Option<wasmtime::Strategy>,
83         /// Enable Cranelift's internal debug verifier (expensive)
84         pub cranelift_debug_verifier: Option<bool>,
85         /// Whether or not to enable caching of compiled modules.
86         pub cache: Option<bool>,
87         /// Configuration for compiled module caching.
88         pub cache_config: Option<String>,
89         /// Whether or not to enable parallel compilation of modules.
90         pub parallel_compilation: Option<bool>,
91         /// Whether to enable proof-carrying code (PCC)-based validation.
92         pub pcc: Option<bool>,
93 
94         #[prefixed = "cranelift"]
95         /// Set a cranelift-specific option. Use `wasmtime settings` to see
96         /// all.
97         pub cranelift: Vec<(String, Option<String>)>,
98     }
99 
100     enum Codegen {
101         ...
102     }
103 }
104 
105 wasmtime_option_group! {
106     pub struct DebugOptions {
107         /// Enable generation of DWARF debug information in compiled code.
108         pub debug_info: Option<bool>,
109         /// Configure whether compiled code can map native addresses to wasm.
110         pub address_map: Option<bool>,
111         /// Configure whether logging is enabled.
112         pub logging: Option<bool>,
113         /// Configure whether logs are emitted to files
114         pub log_to_files: Option<bool>,
115         /// Enable coredump generation to this file after a WebAssembly trap.
116         pub coredump: Option<String>,
117     }
118 
119     enum Debug {
120         ...
121     }
122 }
123 
124 wasmtime_option_group! {
125     pub struct WasmOptions {
126         /// Enable canonicalization of all NaN values.
127         pub nan_canonicalization: Option<bool>,
128         /// Enable execution fuel with N units fuel, trapping after running out
129         /// of fuel.
130         ///
131         /// Most WebAssembly instructions consume 1 unit of fuel. Some
132         /// instructions, such as `nop`, `drop`, `block`, and `loop`, consume 0
133         /// units, as any execution cost associated with them involves other
134         /// instructions which do consume fuel.
135         pub fuel: Option<u64>,
136         /// Yield when a global epoch counter changes, allowing for async
137         /// operation without blocking the executor.
138         pub epoch_interruption: Option<bool>,
139         /// Maximum stack size, in bytes, that wasm is allowed to consume before a
140         /// stack overflow is reported.
141         pub max_wasm_stack: Option<usize>,
142         /// Allow unknown exports when running commands.
143         pub unknown_exports_allow: Option<bool>,
144         /// Allow the main module to import unknown functions, using an
145         /// implementation that immediately traps, when running commands.
146         pub unknown_imports_trap: Option<bool>,
147         /// Allow the main module to import unknown functions, using an
148         /// implementation that returns default values, when running commands.
149         pub unknown_imports_default: Option<bool>,
150         /// Enables memory error checking. (see wmemcheck.md for more info)
151         pub wmemcheck: Option<bool>,
152         /// Maximum size, in bytes, that a linear memory is allowed to reach.
153         ///
154         /// Growth beyond this limit will cause `memory.grow` instructions in
155         /// WebAssembly modules to return -1 and fail.
156         pub max_memory_size: Option<usize>,
157         /// Maximum size, in table elements, that a table is allowed to reach.
158         pub max_table_elements: Option<u32>,
159         /// Maximum number of WebAssembly instances allowed to be created.
160         pub max_instances: Option<usize>,
161         /// Maximum number of WebAssembly tables allowed to be created.
162         pub max_tables: Option<usize>,
163         /// Maximum number of WebAssembly linear memories allowed to be created.
164         pub max_memories: Option<usize>,
165         /// Force a trap to be raised on `memory.grow` and `table.grow` failure
166         /// instead of returning -1 from these instructions.
167         ///
168         /// This is not necessarily a spec-compliant option to enable but can be
169         /// useful for tracking down a backtrace of what is requesting so much
170         /// memory, for example.
171         pub trap_on_grow_failure: Option<bool>,
172         /// Maximum execution time of wasm code before timing out (1, 2s, 100ms, etc)
173         pub timeout: Option<Duration>,
174         /// Configures support for all WebAssembly proposals implemented.
175         pub all_proposals: Option<bool>,
176         /// Configure support for the bulk memory proposal.
177         pub bulk_memory: Option<bool>,
178         /// Configure support for the multi-memory proposal.
179         pub multi_memory: Option<bool>,
180         /// Configure support for the multi-value proposal.
181         pub multi_value: Option<bool>,
182         /// Configure support for the reference-types proposal.
183         pub reference_types: Option<bool>,
184         /// Configure support for the simd proposal.
185         pub simd: Option<bool>,
186         /// Configure support for the relaxed-simd proposal.
187         pub relaxed_simd: Option<bool>,
188         /// Configure forcing deterministic and host-independent behavior of
189         /// the relaxed-simd instructions.
190         ///
191         /// By default these instructions may have architecture-specific behavior as
192         /// allowed by the specification, but this can be used to force the behavior
193         /// of these instructions to match the deterministic behavior classified in
194         /// the specification. Note that enabling this option may come at a
195         /// performance cost.
196         pub relaxed_simd_deterministic: Option<bool>,
197         /// Configure support for the tail-call proposal.
198         pub tail_call: Option<bool>,
199         /// Configure support for the threads proposal.
200         pub threads: Option<bool>,
201         /// Configure support for the memory64 proposal.
202         pub memory64: Option<bool>,
203         /// Configure support for the component-model proposal.
204         pub component_model: Option<bool>,
205         /// Configure support for the function-references proposal.
206         pub function_references: Option<bool>,
207     }
208 
209     enum Wasm {
210         ...
211     }
212 }
213 
214 wasmtime_option_group! {
215     pub struct WasiOptions {
216         /// Enable support for WASI common APIs
217         pub common: Option<bool>,
218         /// Enable suport for WASI neural network API (experimental)
219         pub nn: Option<bool>,
220         /// Enable suport for WASI threading API (experimental)
221         pub threads: Option<bool>,
222         /// Enable suport for WASI HTTP API (experimental)
223         pub http: Option<bool>,
224         /// Inherit environment variables and file descriptors following the
225         /// systemd listen fd specification (UNIX only)
226         pub listenfd: Option<bool>,
227         /// Grant access to the given TCP listen socket
228         pub tcplisten: Vec<String>,
229         /// Implement WASI with preview2 primitives (experimental).
230         ///
231         /// Indicates that the implementation of WASI preview1 should be backed by
232         /// the preview2 implementation for components.
233         ///
234         /// This will become the default in the future and this option will be
235         /// removed. For now this is primarily here for testing.
236         pub preview2: Option<bool>,
237         /// Pre-load machine learning graphs (i.e., models) for use by wasi-nn.
238         ///
239         /// Each use of the flag will preload a ML model from the host directory
240         /// using the given model encoding. The model will be mapped to the
241         /// directory name: e.g., `--wasi-nn-graph openvino:/foo/bar` will preload
242         /// an OpenVINO model named `bar`. Note that which model encodings are
243         /// available is dependent on the backends implemented in the
244         /// `wasmtime_wasi_nn` crate.
245         pub nn_graph: Vec<WasiNnGraph>,
246         /// Flag for WASI preview2 to inherit the host's network within the
247         /// guest so it has full access to all addresses/ports/etc.
248         pub inherit_network: Option<bool>,
249         /// Indicates whether `wasi:sockets/ip-name-lookup` is enabled or not.
250         pub allow_ip_name_lookup: Option<bool>,
251 
252     }
253 
254     enum Wasi {
255         ...
256     }
257 }
258 
259 #[derive(Debug, Clone)]
260 pub struct WasiNnGraph {
261     pub format: String,
262     pub dir: String,
263 }
264 
265 /// Common options for commands that translate WebAssembly modules
266 #[derive(Parser)]
267 pub struct CommonOptions {
268     // These options groups are used to parse `-O` and such options but aren't
269     // the raw form consumed by the CLI. Instead they're pushed into the `pub`
270     // fields below as part of the `configure` method.
271     //
272     // Ideally clap would support `pub opts: OptimizeOptions` and parse directly
273     // into that but it does not appear to do so for multiple `-O` flags for
274     // now.
275     /// Optimization and tuning related options for wasm performance, `-O help` to
276     /// see all.
277     #[clap(short = 'O', long = "optimize", value_name = "KEY[=VAL[,..]]")]
278     opts_raw: Vec<opt::CommaSeparated<Optimize>>,
279 
280     /// Codegen-related configuration options, `-C help` to see all.
281     #[clap(short = 'C', long = "codegen", value_name = "KEY[=VAL[,..]]")]
282     codegen_raw: Vec<opt::CommaSeparated<Codegen>>,
283 
284     /// Debug-related configuration options, `-D help` to see all.
285     #[clap(short = 'D', long = "debug", value_name = "KEY[=VAL[,..]]")]
286     debug_raw: Vec<opt::CommaSeparated<Debug>>,
287 
288     /// Options for configuring semantic execution of WebAssembly, `-W help` to see
289     /// all.
290     #[clap(short = 'W', long = "wasm", value_name = "KEY[=VAL[,..]]")]
291     wasm_raw: Vec<opt::CommaSeparated<Wasm>>,
292 
293     /// Options for configuring WASI and its proposals, `-S help` to see all.
294     #[clap(short = 'S', long = "wasi", value_name = "KEY[=VAL[,..]]")]
295     wasi_raw: Vec<opt::CommaSeparated<Wasi>>,
296 
297     // These fields are filled in by the `configure` method below via the
298     // options parsed from the CLI above. This is what the CLI should use.
299     #[clap(skip)]
300     configured: bool,
301     #[clap(skip)]
302     pub opts: OptimizeOptions,
303     #[clap(skip)]
304     pub codegen: CodegenOptions,
305     #[clap(skip)]
306     pub debug: DebugOptions,
307     #[clap(skip)]
308     pub wasm: WasmOptions,
309     #[clap(skip)]
310     pub wasi: WasiOptions,
311 }
312 
313 macro_rules! match_feature {
314     (
315         [$feat:tt : $config:expr]
316         $val:ident => $e:expr,
317         $p:pat => err,
318     ) => {
319         #[cfg(feature = $feat)]
320         {
321             if let Some($val) = $config {
322                 $e;
323             }
324         }
325         #[cfg(not(feature = $feat))]
326         {
327             if let Some($p) = $config {
328                 anyhow::bail!(concat!("support for ", $feat, " disabled at compile time"));
329             }
330         }
331     };
332 }
333 
334 impl CommonOptions {
335     fn configure(&mut self) {
336         if self.configured {
337             return;
338         }
339         self.configured = true;
340         self.opts.configure_with(&self.opts_raw);
341         self.codegen.configure_with(&self.codegen_raw);
342         self.debug.configure_with(&self.debug_raw);
343         self.wasm.configure_with(&self.wasm_raw);
344         self.wasi.configure_with(&self.wasi_raw);
345     }
346 
347     pub fn init_logging(&mut self) -> Result<()> {
348         self.configure();
349         if self.debug.logging == Some(false) {
350             return Ok(());
351         }
352         #[cfg(feature = "logging")]
353         if self.debug.log_to_files == Some(true) {
354             let prefix = "wasmtime.dbg.";
355             init_file_per_thread_logger(prefix);
356         } else {
357             use std::io::IsTerminal;
358             use tracing_subscriber::{EnvFilter, FmtSubscriber};
359             let mut b = FmtSubscriber::builder()
360                 .with_writer(std::io::stderr)
361                 .with_env_filter(EnvFilter::from_env("WASMTIME_LOG"));
362             if std::io::stderr().is_terminal() {
363                 b = b.with_ansi(true);
364             }
365             b.init();
366         }
367         #[cfg(not(feature = "logging"))]
368         if self.debug.log_to_files == Some(true) || self.debug.logging == Some(true) {
369             anyhow::bail!("support for logging disabled at compile time");
370         }
371         Ok(())
372     }
373 
374     pub fn config(&mut self, target: Option<&str>) -> Result<Config> {
375         self.configure();
376         let mut config = Config::new();
377 
378         match_feature! {
379             ["cranelift" : self.codegen.compiler]
380             strategy => config.strategy(strategy),
381             _ => err,
382         }
383         match_feature! {
384             ["cranelift" : target]
385             target => config.target(target)?,
386             _ => err,
387         }
388         match_feature! {
389             ["cranelift" : self.codegen.cranelift_debug_verifier]
390             enable => config.cranelift_debug_verifier(enable),
391             true => err,
392         }
393         if let Some(enable) = self.debug.debug_info {
394             config.debug_info(enable);
395         }
396         if self.debug.coredump.is_some() {
397             #[cfg(feature = "coredump")]
398             config.coredump_on_trap(true);
399             #[cfg(not(feature = "coredump"))]
400             anyhow::bail!("support for coredumps disabled at compile time");
401         }
402         match_feature! {
403             ["cranelift" : self.opts.opt_level]
404             level => config.cranelift_opt_level(level),
405             _ => err,
406         }
407         match_feature! {
408             ["cranelift" : self.wasm.nan_canonicalization]
409             enable => config.cranelift_nan_canonicalization(enable),
410             true => err,
411         }
412         match_feature! {
413             ["cranelift" : self.codegen.pcc]
414             enable => config.cranelift_pcc(enable),
415             true => err,
416         }
417 
418         self.enable_wasm_features(&mut config)?;
419 
420         #[cfg(feature = "cranelift")]
421         for (name, value) in self.codegen.cranelift.iter() {
422             let name = name.replace('-', "_");
423             unsafe {
424                 match value {
425                     Some(val) => {
426                         config.cranelift_flag_set(&name, val);
427                     }
428                     None => {
429                         config.cranelift_flag_enable(&name);
430                     }
431                 }
432             }
433         }
434         #[cfg(not(feature = "cranelift"))]
435         if !self.codegen.cranelift.is_empty() {
436             anyhow::bail!("support for cranelift disabled at compile time");
437         }
438 
439         #[cfg(feature = "cache")]
440         if self.codegen.cache != Some(false) {
441             match &self.codegen.cache_config {
442                 Some(path) => {
443                     config.cache_config_load(path)?;
444                 }
445                 None => {
446                     config.cache_config_load_default()?;
447                 }
448             }
449         }
450         #[cfg(not(feature = "cache"))]
451         if self.codegen.cache == Some(true) {
452             anyhow::bail!("support for caching disabled at compile time");
453         }
454 
455         match_feature! {
456             ["parallel-compilation" : self.codegen.parallel_compilation]
457             enable => config.parallel_compilation(enable),
458             true => err,
459         }
460 
461         if let Some(max) = self.opts.static_memory_maximum_size {
462             config.static_memory_maximum_size(max);
463         }
464 
465         if let Some(enable) = self.opts.static_memory_forced {
466             config.static_memory_forced(enable);
467         }
468 
469         if let Some(size) = self.opts.static_memory_guard_size {
470             config.static_memory_guard_size(size);
471         }
472 
473         if let Some(size) = self.opts.dynamic_memory_guard_size {
474             config.dynamic_memory_guard_size(size);
475         }
476         if let Some(size) = self.opts.dynamic_memory_reserved_for_growth {
477             config.dynamic_memory_reserved_for_growth(size);
478         }
479 
480         // If fuel has been configured, set the `consume fuel` flag on the config.
481         if self.wasm.fuel.is_some() {
482             config.consume_fuel(true);
483         }
484 
485         if let Some(enable) = self.wasm.epoch_interruption {
486             config.epoch_interruption(enable);
487         }
488         if let Some(enable) = self.debug.address_map {
489             config.generate_address_map(enable);
490         }
491         if let Some(enable) = self.opts.memory_init_cow {
492             config.memory_init_cow(enable);
493         }
494 
495         match_feature! {
496             ["pooling-allocator" : self.opts.pooling_allocator]
497             enable => {
498                 if enable {
499                     config.allocation_strategy(wasmtime::InstanceAllocationStrategy::pooling());
500                 }
501             },
502             true => err,
503         }
504 
505         if let Some(max) = self.wasm.max_wasm_stack {
506             config.max_wasm_stack(max);
507         }
508 
509         if let Some(enable) = self.wasm.relaxed_simd_deterministic {
510             config.relaxed_simd_deterministic(enable);
511         }
512         match_feature! {
513             ["cranelift" : self.wasm.wmemcheck]
514             enable => config.wmemcheck(enable),
515             true => err,
516         }
517 
518         Ok(config)
519     }
520 
521     pub fn enable_wasm_features(&self, config: &mut Config) -> Result<()> {
522         let all = self.wasm.all_proposals;
523 
524         if let Some(enable) = self.wasm.simd.or(all) {
525             config.wasm_simd(enable);
526         }
527         if let Some(enable) = self.wasm.relaxed_simd.or(all) {
528             config.wasm_relaxed_simd(enable);
529         }
530         if let Some(enable) = self.wasm.bulk_memory.or(all) {
531             config.wasm_bulk_memory(enable);
532         }
533         if let Some(enable) = self.wasm.reference_types.or(all) {
534             config.wasm_reference_types(enable);
535         }
536         if let Some(enable) = self.wasm.function_references.or(all) {
537             config.wasm_function_references(enable);
538         }
539         if let Some(enable) = self.wasm.multi_value.or(all) {
540             config.wasm_multi_value(enable);
541         }
542         if let Some(enable) = self.wasm.tail_call.or(all) {
543             config.wasm_tail_call(enable);
544         }
545         if let Some(enable) = self.wasm.threads.or(all) {
546             config.wasm_threads(enable);
547         }
548         if let Some(enable) = self.wasm.multi_memory.or(all) {
549             config.wasm_multi_memory(enable);
550         }
551         if let Some(enable) = self.wasm.memory64.or(all) {
552             config.wasm_memory64(enable);
553         }
554         if let Some(enable) = self.wasm.component_model.or(all) {
555             #[cfg(feature = "component-model")]
556             config.wasm_component_model(enable);
557             #[cfg(not(feature = "component-model"))]
558             if enable && all.is_none() {
559                 anyhow::bail!("support for the component model was disabled at compile-time");
560             }
561         }
562         Ok(())
563     }
564 }
565