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