xref: /wasmtime-44.0.1/crates/cli-flags/src/lib.rs (revision 8ca3af0e)
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 #![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../../clippy.toml")))]
6 #![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
7 #![cfg_attr(
8     feature = "cargo-clippy",
9     warn(
10         clippy::float_arithmetic,
11         clippy::mut_mut,
12         clippy::nonminimal_bool,
13         clippy::map_unwrap_or,
14         clippy::unicode_not_nfc,
15         clippy::use_self
16     )
17 )]
18 
19 use anyhow::{bail, Context, Result};
20 use clap::Parser;
21 use std::collections::HashMap;
22 use std::path::PathBuf;
23 use wasmtime::{Config, ProfilingStrategy};
24 #[cfg(feature = "pooling-allocator")]
25 use wasmtime::{InstanceLimits, PoolingAllocationStrategy};
26 
27 pub const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[
28     ("all", "enables all supported WebAssembly features"),
29     (
30         "bulk-memory",
31         "enables support for bulk memory instructions",
32     ),
33     (
34         "multi-memory",
35         "enables support for the multi-memory proposal",
36     ),
37     ("multi-value", "enables support for multi-value functions"),
38     ("reference-types", "enables support for reference types"),
39     ("simd", "enables support for proposed SIMD instructions"),
40     ("threads", "enables support for WebAssembly threads"),
41     ("memory64", "enables support for 64-bit memories"),
42     #[cfg(feature = "component-model")]
43     ("component-model", "enables support for the component model"),
44 ];
45 
46 pub const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[
47     (
48         "default",
49         "enables all stable WASI modules (no experimental modules)",
50     ),
51     (
52         "wasi-common",
53         "enables support for the WASI common APIs, see https://github.com/WebAssembly/WASI",
54     ),
55     (
56         "experimental-wasi-nn",
57         "enables support for the WASI neural network API (experimental), see https://github.com/WebAssembly/wasi-nn",
58     ),
59     (
60         "experimental-wasi-crypto",
61         "enables support for the WASI cryptography APIs (experimental), see https://github.com/WebAssembly/wasi-crypto",
62     ),
63 ];
64 
65 fn pick_profiling_strategy(jitdump: bool, vtune: bool) -> Result<ProfilingStrategy> {
66     Ok(match (jitdump, vtune) {
67         (true, false) => ProfilingStrategy::JitDump,
68         (false, true) => ProfilingStrategy::VTune,
69         (true, true) => {
70             println!("Can't enable --jitdump and --vtune at the same time. Profiling not enabled.");
71             ProfilingStrategy::None
72         }
73         _ => ProfilingStrategy::None,
74     })
75 }
76 
77 fn init_file_per_thread_logger(prefix: &'static str) {
78     file_per_thread_logger::initialize(prefix);
79 
80     // Extending behavior of default spawner:
81     // https://docs.rs/rayon/1.1.0/rayon/struct.ThreadPoolBuilder.html#method.spawn_handler
82     // Source code says DefaultSpawner is implementation detail and
83     // shouldn't be used directly.
84     rayon::ThreadPoolBuilder::new()
85         .spawn_handler(move |thread| {
86             let mut b = std::thread::Builder::new();
87             if let Some(name) = thread.name() {
88                 b = b.name(name.to_owned());
89             }
90             if let Some(stack_size) = thread.stack_size() {
91                 b = b.stack_size(stack_size);
92             }
93             b.spawn(move || {
94                 file_per_thread_logger::initialize(prefix);
95                 thread.run()
96             })?;
97             Ok(())
98         })
99         .build_global()
100         .unwrap();
101 }
102 
103 /// Common options for commands that translate WebAssembly modules
104 #[derive(Parser)]
105 #[cfg_attr(test, derive(Debug, PartialEq))]
106 pub struct CommonOptions {
107     /// Use specified configuration file
108     #[clap(long, parse(from_os_str), value_name = "CONFIG_PATH")]
109     pub config: Option<PathBuf>,
110 
111     /// Disable logging.
112     #[clap(long, conflicts_with = "log-to-files")]
113     pub disable_logging: bool,
114 
115     /// Log to per-thread log files instead of stderr.
116     #[clap(long)]
117     pub log_to_files: bool,
118 
119     /// Generate debug information
120     #[clap(short = 'g')]
121     pub debug_info: bool,
122 
123     /// Disable cache system
124     #[clap(long)]
125     pub disable_cache: bool,
126 
127     /// Enables or disables WebAssembly features
128     #[clap(long, value_name = "FEATURE,FEATURE,...", parse(try_from_str = parse_wasm_features))]
129     pub wasm_features: Option<WasmFeatures>,
130 
131     /// Enables or disables WASI modules
132     #[clap(long, value_name = "MODULE,MODULE,...", parse(try_from_str = parse_wasi_modules))]
133     pub wasi_modules: Option<WasiModules>,
134 
135     /// Generate jitdump file (supported on --features=profiling build)
136     #[clap(long, conflicts_with = "vtune")]
137     pub jitdump: bool,
138 
139     /// Generate vtune (supported on --features=vtune build)
140     #[clap(long, conflicts_with = "jitdump")]
141     pub vtune: bool,
142 
143     /// Run optimization passes on translated functions, on by default
144     #[clap(short = 'O', long)]
145     pub optimize: bool,
146 
147     /// Optimization level for generated functions
148     /// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most"
149     #[clap(
150         long,
151         value_name = "LEVEL",
152         parse(try_from_str = parse_opt_level),
153         verbatim_doc_comment,
154     )]
155     pub opt_level: Option<wasmtime::OptLevel>,
156 
157     /// Set a Cranelift setting to a given value.
158     /// Use `wasmtime settings` to list Cranelift settings for a target.
159     #[clap(long = "cranelift-set", value_name = "NAME=VALUE", number_of_values = 1, verbatim_doc_comment, parse(try_from_str = parse_cranelift_flag))]
160     pub cranelift_set: Vec<(String, String)>,
161 
162     /// Enable a Cranelift boolean setting or preset.
163     /// Use `wasmtime settings` to list Cranelift settings for a target.
164     #[clap(
165         long,
166         value_name = "SETTING",
167         number_of_values = 1,
168         verbatim_doc_comment
169     )]
170     pub cranelift_enable: Vec<String>,
171 
172     /// Maximum size in bytes of wasm memory before it becomes dynamically
173     /// relocatable instead of up-front-reserved.
174     #[clap(long, value_name = "MAXIMUM")]
175     pub static_memory_maximum_size: Option<u64>,
176 
177     /// Force using a "static" style for all wasm memories.
178     #[clap(long)]
179     pub static_memory_forced: bool,
180 
181     /// Byte size of the guard region after static memories are allocated.
182     #[clap(long, value_name = "SIZE")]
183     pub static_memory_guard_size: Option<u64>,
184 
185     /// Byte size of the guard region after dynamic memories are allocated.
186     #[clap(long, value_name = "SIZE")]
187     pub dynamic_memory_guard_size: Option<u64>,
188 
189     /// Enable Cranelift's internal debug verifier (expensive)
190     #[clap(long)]
191     pub enable_cranelift_debug_verifier: bool,
192 
193     /// Enable Cranelift's internal NaN canonicalization
194     #[clap(long)]
195     pub enable_cranelift_nan_canonicalization: bool,
196 
197     /// Enable execution fuel with N units fuel, where execution will trap after
198     /// running out of fuel.
199     ///
200     /// Most WebAssembly instructions consume 1 unit of fuel. Some instructions,
201     /// such as `nop`, `drop`, `block`, and `loop`, consume 0 units, as any
202     /// execution cost associated with them involves other instructions which do
203     /// consume fuel.
204     #[clap(long, value_name = "N")]
205     pub fuel: Option<u64>,
206 
207     /// Executing wasm code will yield when a global epoch counter
208     /// changes, allowing for async operation without blocking the
209     /// executor.
210     #[clap(long)]
211     pub epoch_interruption: bool,
212 
213     /// Disables the on-by-default address map from native code to wasm code.
214     #[clap(long)]
215     pub disable_address_map: bool,
216 
217     /// Disables the default of attempting to initialize linear memory via a
218     /// copy-on-write mapping.
219     #[cfg(feature = "memory-init-cow")]
220     #[clap(long)]
221     pub disable_memory_init_cow: bool,
222 
223     /// Enables the pooling allocator, in place of the on-demand
224     /// allocator.
225     #[cfg(feature = "pooling-allocator")]
226     #[clap(long)]
227     pub pooling_allocator: bool,
228 }
229 
230 impl CommonOptions {
231     pub fn parse_from_str(s: &str) -> Result<Self> {
232         let parts = s.split(" ");
233         let options =
234             Self::try_parse_from(parts).context("unable to parse options from passed flags")?;
235         Ok(options)
236     }
237 
238     pub fn init_logging(&self) {
239         if self.disable_logging {
240             return;
241         }
242         if self.log_to_files {
243             let prefix = "wasmtime.dbg.";
244             init_file_per_thread_logger(prefix);
245         } else {
246             pretty_env_logger::init();
247         }
248     }
249 
250     pub fn config(&self, target: Option<&str>) -> Result<Config> {
251         let mut config = Config::new();
252 
253         // Set the target before setting any cranelift options, since the
254         // target will reset any target-specific options.
255         if let Some(target) = target {
256             config.target(target)?;
257         }
258 
259         config
260             .cranelift_debug_verifier(self.enable_cranelift_debug_verifier)
261             .debug_info(self.debug_info)
262             .cranelift_opt_level(self.opt_level())
263             .profiler(pick_profiling_strategy(self.jitdump, self.vtune)?)?
264             .cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization);
265 
266         self.enable_wasm_features(&mut config);
267 
268         for name in &self.cranelift_enable {
269             unsafe {
270                 config.cranelift_flag_enable(name)?;
271             }
272         }
273 
274         for (name, value) in &self.cranelift_set {
275             unsafe {
276                 config.cranelift_flag_set(name, value)?;
277             }
278         }
279 
280         if !self.disable_cache {
281             match &self.config {
282                 Some(path) => {
283                     config.cache_config_load(path)?;
284                 }
285                 None => {
286                     config.cache_config_load_default()?;
287                 }
288             }
289         }
290 
291         if let Some(max) = self.static_memory_maximum_size {
292             config.static_memory_maximum_size(max);
293         }
294 
295         config.static_memory_forced(self.static_memory_forced);
296 
297         if let Some(size) = self.static_memory_guard_size {
298             config.static_memory_guard_size(size);
299         }
300 
301         if let Some(size) = self.dynamic_memory_guard_size {
302             config.dynamic_memory_guard_size(size);
303         }
304 
305         // If fuel has been configured, set the `consume fuel` flag on the config.
306         if self.fuel.is_some() {
307             config.consume_fuel(true);
308         }
309 
310         config.epoch_interruption(self.epoch_interruption);
311         config.generate_address_map(!self.disable_address_map);
312         #[cfg(feature = "memory-init-cow")]
313         config.memory_init_cow(!self.disable_memory_init_cow);
314 
315         #[cfg(feature = "pooling-allocator")]
316         {
317             if self.pooling_allocator {
318                 let instance_limits = InstanceLimits::default();
319                 config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling {
320                     strategy: PoolingAllocationStrategy::NextAvailable,
321                     instance_limits,
322                 });
323             }
324         }
325 
326         Ok(config)
327     }
328 
329     pub fn enable_wasm_features(&self, config: &mut Config) {
330         let WasmFeatures {
331             simd,
332             bulk_memory,
333             reference_types,
334             multi_value,
335             threads,
336             multi_memory,
337             memory64,
338             #[cfg(feature = "component-model")]
339             component_model,
340         } = self.wasm_features.unwrap_or_default();
341 
342         if let Some(enable) = simd {
343             config.wasm_simd(enable);
344         }
345         if let Some(enable) = bulk_memory {
346             config.wasm_bulk_memory(enable);
347         }
348         if let Some(enable) = reference_types {
349             config.wasm_reference_types(enable);
350         }
351         if let Some(enable) = multi_value {
352             config.wasm_multi_value(enable);
353         }
354         if let Some(enable) = threads {
355             config.wasm_threads(enable);
356         }
357         if let Some(enable) = multi_memory {
358             config.wasm_multi_memory(enable);
359         }
360         if let Some(enable) = memory64 {
361             config.wasm_memory64(enable);
362         }
363         #[cfg(feature = "component-model")]
364         if let Some(enable) = component_model {
365             config.wasm_component_model(enable);
366         }
367     }
368 
369     pub fn opt_level(&self) -> wasmtime::OptLevel {
370         match (self.optimize, self.opt_level.clone()) {
371             (true, _) => wasmtime::OptLevel::Speed,
372             (false, other) => other.unwrap_or(wasmtime::OptLevel::Speed),
373         }
374     }
375 }
376 
377 fn parse_opt_level(opt_level: &str) -> Result<wasmtime::OptLevel> {
378     match opt_level {
379         "s" => Ok(wasmtime::OptLevel::SpeedAndSize),
380         "0" => Ok(wasmtime::OptLevel::None),
381         "1" => Ok(wasmtime::OptLevel::Speed),
382         "2" => Ok(wasmtime::OptLevel::Speed),
383         other => bail!(
384             "unknown optimization level `{}`, only 0,1,2,s accepted",
385             other
386         ),
387     }
388 }
389 
390 #[derive(Default, Clone, Copy)]
391 #[cfg_attr(test, derive(Debug, PartialEq))]
392 pub struct WasmFeatures {
393     pub reference_types: Option<bool>,
394     pub multi_value: Option<bool>,
395     pub bulk_memory: Option<bool>,
396     pub simd: Option<bool>,
397     pub threads: Option<bool>,
398     pub multi_memory: Option<bool>,
399     pub memory64: Option<bool>,
400     #[cfg(feature = "component-model")]
401     pub component_model: Option<bool>,
402 }
403 
404 fn parse_wasm_features(features: &str) -> Result<WasmFeatures> {
405     let features = features.trim();
406 
407     let mut all = None;
408     let mut values: HashMap<_, _> = SUPPORTED_WASM_FEATURES
409         .iter()
410         .map(|(name, _)| (name.to_string(), None))
411         .collect();
412 
413     if features == "all" {
414         all = Some(true);
415     } else if features == "-all" {
416         all = Some(false);
417     } else {
418         for feature in features.split(',') {
419             let feature = feature.trim();
420 
421             if feature.is_empty() {
422                 continue;
423             }
424 
425             let (feature, value) = if feature.starts_with('-') {
426                 (&feature[1..], false)
427             } else {
428                 (feature, true)
429             };
430 
431             if feature == "all" {
432                 bail!("'all' cannot be specified with other WebAssembly features");
433             }
434 
435             match values.get_mut(feature) {
436                 Some(v) => *v = Some(value),
437                 None => bail!("unsupported WebAssembly feature '{}'", feature),
438             }
439         }
440     }
441 
442     Ok(WasmFeatures {
443         reference_types: all.or(values["reference-types"]),
444         multi_value: all.or(values["multi-value"]),
445         bulk_memory: all.or(values["bulk-memory"]),
446         simd: all.or(values["simd"]),
447         threads: all.or(values["threads"]),
448         multi_memory: all.or(values["multi-memory"]),
449         memory64: all.or(values["memory64"]),
450         #[cfg(feature = "component-model")]
451         component_model: all.or(values["component-model"]),
452     })
453 }
454 
455 fn parse_wasi_modules(modules: &str) -> Result<WasiModules> {
456     let modules = modules.trim();
457     match modules {
458         "default" => Ok(WasiModules::default()),
459         "-default" => Ok(WasiModules::none()),
460         _ => {
461             // Starting from the default set of WASI modules, enable or disable a list of
462             // comma-separated modules.
463             let mut wasi_modules = WasiModules::default();
464             let mut set = |module: &str, enable: bool| match module {
465                 "" => Ok(()),
466                 "wasi-common" => Ok(wasi_modules.wasi_common = enable),
467                 "experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable),
468                 "experimental-wasi-crypto" => Ok(wasi_modules.wasi_crypto = enable),
469                 "default" => bail!("'default' cannot be specified with other WASI modules"),
470                 _ => bail!("unsupported WASI module '{}'", module),
471             };
472 
473             for module in modules.split(',') {
474                 let module = module.trim();
475                 let (module, value) = if module.starts_with('-') {
476                     (&module[1..], false)
477                 } else {
478                     (module, true)
479                 };
480                 set(module, value)?;
481             }
482 
483             Ok(wasi_modules)
484         }
485     }
486 }
487 
488 /// Select which WASI modules are available at runtime for use by Wasm programs.
489 #[derive(Debug, Clone, Copy, PartialEq)]
490 pub struct WasiModules {
491     /// Enable the wasi-common implementation; eventually this should be split into its separate
492     /// parts once the implementation allows for it (e.g. wasi-fs, wasi-clocks, etc.).
493     pub wasi_common: bool,
494 
495     /// Enable the experimental wasi-nn implementation.
496     pub wasi_nn: bool,
497 
498     /// Enable the experimental wasi-crypto implementation.
499     pub wasi_crypto: bool,
500 }
501 
502 impl Default for WasiModules {
503     fn default() -> Self {
504         Self {
505             wasi_common: true,
506             wasi_nn: false,
507             wasi_crypto: false,
508         }
509     }
510 }
511 
512 impl WasiModules {
513     /// Enable no modules.
514     pub fn none() -> Self {
515         Self {
516             wasi_common: false,
517             wasi_nn: false,
518             wasi_crypto: false,
519         }
520     }
521 }
522 
523 fn parse_cranelift_flag(name_and_value: &str) -> Result<(String, String)> {
524     let mut split = name_and_value.splitn(2, '=');
525     let name = if let Some(name) = split.next() {
526         name.to_string()
527     } else {
528         bail!("missing name in cranelift flag");
529     };
530     let value = if let Some(value) = split.next() {
531         value.to_string()
532     } else {
533         bail!("missing value in cranelift flag");
534     };
535     Ok((name, value))
536 }
537 
538 #[cfg(test)]
539 mod test {
540     use super::*;
541 
542     #[test]
543     fn test_all_features() -> Result<()> {
544         let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=all"])?;
545 
546         let WasmFeatures {
547             reference_types,
548             multi_value,
549             bulk_memory,
550             simd,
551             threads,
552             multi_memory,
553             memory64,
554         } = options.wasm_features.unwrap();
555 
556         assert_eq!(reference_types, Some(true));
557         assert_eq!(multi_value, Some(true));
558         assert_eq!(bulk_memory, Some(true));
559         assert_eq!(simd, Some(true));
560         assert_eq!(threads, Some(true));
561         assert_eq!(multi_memory, Some(true));
562         assert_eq!(memory64, Some(true));
563 
564         Ok(())
565     }
566 
567     #[test]
568     fn test_no_features() -> Result<()> {
569         let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=-all"])?;
570 
571         let WasmFeatures {
572             reference_types,
573             multi_value,
574             bulk_memory,
575             simd,
576             threads,
577             multi_memory,
578             memory64,
579         } = options.wasm_features.unwrap();
580 
581         assert_eq!(reference_types, Some(false));
582         assert_eq!(multi_value, Some(false));
583         assert_eq!(bulk_memory, Some(false));
584         assert_eq!(simd, Some(false));
585         assert_eq!(threads, Some(false));
586         assert_eq!(multi_memory, Some(false));
587         assert_eq!(memory64, Some(false));
588 
589         Ok(())
590     }
591 
592     #[test]
593     fn test_multiple_features() -> Result<()> {
594         let options = CommonOptions::try_parse_from(vec![
595             "foo",
596             "--wasm-features=-reference-types,simd,multi-memory,memory64",
597         ])?;
598 
599         let WasmFeatures {
600             reference_types,
601             multi_value,
602             bulk_memory,
603             simd,
604             threads,
605             multi_memory,
606             memory64,
607         } = options.wasm_features.unwrap();
608 
609         assert_eq!(reference_types, Some(false));
610         assert_eq!(multi_value, None);
611         assert_eq!(bulk_memory, None);
612         assert_eq!(simd, Some(true));
613         assert_eq!(threads, None);
614         assert_eq!(multi_memory, Some(true));
615         assert_eq!(memory64, Some(true));
616 
617         Ok(())
618     }
619 
620     macro_rules! feature_test {
621         ($test_name:ident, $name:ident, $flag:literal) => {
622             #[test]
623             fn $test_name() -> Result<()> {
624                 let options =
625                     CommonOptions::try_parse_from(vec!["foo", concat!("--wasm-features=", $flag)])?;
626 
627                 let WasmFeatures { $name, .. } = options.wasm_features.unwrap();
628 
629                 assert_eq!($name, Some(true));
630 
631                 let options = CommonOptions::try_parse_from(vec![
632                     "foo",
633                     concat!("--wasm-features=-", $flag),
634                 ])?;
635 
636                 let WasmFeatures { $name, .. } = options.wasm_features.unwrap();
637 
638                 assert_eq!($name, Some(false));
639 
640                 Ok(())
641             }
642         };
643     }
644 
645     feature_test!(
646         test_reference_types_feature,
647         reference_types,
648         "reference-types"
649     );
650     feature_test!(test_multi_value_feature, multi_value, "multi-value");
651     feature_test!(test_bulk_memory_feature, bulk_memory, "bulk-memory");
652     feature_test!(test_simd_feature, simd, "simd");
653     feature_test!(test_threads_feature, threads, "threads");
654     feature_test!(test_multi_memory_feature, multi_memory, "multi-memory");
655     feature_test!(test_memory64_feature, memory64, "memory64");
656 
657     #[test]
658     fn test_default_modules() {
659         let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=default"]).unwrap();
660         assert_eq!(
661             options.wasi_modules.unwrap(),
662             WasiModules {
663                 wasi_common: true,
664                 wasi_nn: false,
665                 wasi_crypto: false
666             }
667         );
668     }
669 
670     #[test]
671     fn test_empty_modules() {
672         let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules="]).unwrap();
673         assert_eq!(
674             options.wasi_modules.unwrap(),
675             WasiModules {
676                 wasi_common: true,
677                 wasi_nn: false,
678                 wasi_crypto: false
679             }
680         );
681     }
682 
683     #[test]
684     fn test_some_modules() {
685         let options = CommonOptions::try_parse_from(vec![
686             "foo",
687             "--wasi-modules=experimental-wasi-nn,-wasi-common",
688         ])
689         .unwrap();
690         assert_eq!(
691             options.wasi_modules.unwrap(),
692             WasiModules {
693                 wasi_common: false,
694                 wasi_nn: true,
695                 wasi_crypto: false
696             }
697         );
698     }
699 
700     #[test]
701     fn test_no_modules() {
702         let options =
703             CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=-default"]).unwrap();
704         assert_eq!(
705             options.wasi_modules.unwrap(),
706             WasiModules {
707                 wasi_common: false,
708                 wasi_nn: false,
709                 wasi_crypto: false
710             }
711         );
712     }
713 
714     #[test]
715     fn test_parse_from_str() {
716         fn use_func(flags: &str) -> CommonOptions {
717             CommonOptions::parse_from_str(flags).unwrap()
718         }
719         fn use_clap_parser(flags: &[&str]) -> CommonOptions {
720             CommonOptions::try_parse_from(flags).unwrap()
721         }
722 
723         assert_eq!(use_func(""), use_clap_parser(&[]));
724         assert_eq!(
725             use_func("foo --wasm-features=threads"),
726             use_clap_parser(&["foo", "--wasm-features=threads"])
727         );
728         assert_eq!(
729             use_func("foo --cranelift-set enable_simd=true"),
730             use_clap_parser(&["foo", "--cranelift-set", "enable_simd=true"])
731         );
732     }
733 }
734