xref: /wasmtime-44.0.1/src/lib.rs (revision e9e4afe2)
1 //! The Wasmtime command line interface (CLI) crate.
2 //!
3 //! This crate implements the Wasmtime command line tools.
4 
5 #![deny(
6     missing_docs,
7     trivial_numeric_casts,
8     unused_extern_crates,
9     unstable_features
10 )]
11 #![warn(unused_import_braces)]
12 #![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../clippy.toml")))]
13 #![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
14 #![cfg_attr(
15     feature = "cargo-clippy",
16     warn(
17         clippy::float_arithmetic,
18         clippy::mut_mut,
19         clippy::nonminimal_bool,
20         clippy::map_unwrap_or,
21         clippy::unicode_not_nfc,
22         clippy::use_self
23     )
24 )]
25 
26 const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[
27     ("all", "enables all supported WebAssembly features"),
28     (
29         "bulk-memory",
30         "enables support for bulk memory instructions",
31     ),
32     (
33         "module-linking",
34         "enables support for the module-linking proposal",
35     ),
36     (
37         "multi-memory",
38         "enables support for the multi-memory proposal",
39     ),
40     ("multi-value", "enables support for multi-value functions"),
41     ("reference-types", "enables support for reference types"),
42     ("simd", "enables support for proposed SIMD instructions"),
43     ("threads", "enables support for WebAssembly threads"),
44 ];
45 
46 lazy_static::lazy_static! {
47     static ref WASM_FEATURES: String = {
48         use std::fmt::Write;
49 
50         let mut s = String::new();
51         writeln!(&mut s, "Supported values for `--wasm-features`:").unwrap();
52         writeln!(&mut s).unwrap();
53 
54         let max = SUPPORTED_WASM_FEATURES.iter().max_by_key(|(name, _)| name.len()).unwrap();
55 
56         for (name, desc) in SUPPORTED_WASM_FEATURES.iter() {
57             writeln!(&mut s, "{:width$} {}", name, desc, width = max.0.len() + 2).unwrap();
58         }
59 
60         writeln!(&mut s).unwrap();
61         writeln!(&mut s, "Features prefixed with '-' will be disabled.").unwrap();
62 
63         s
64     };
65 }
66 
67 pub mod commands;
68 mod obj;
69 
70 use anyhow::{bail, Result};
71 use std::collections::HashMap;
72 use std::path::PathBuf;
73 use structopt::StructOpt;
74 use target_lexicon::Triple;
75 use wasmtime::{Config, ProfilingStrategy, Strategy};
76 
77 pub use obj::compile_to_obj;
78 
79 fn pick_compilation_strategy(cranelift: bool, lightbeam: bool) -> Result<Strategy> {
80     Ok(match (lightbeam, cranelift) {
81         (true, false) => Strategy::Lightbeam,
82         (false, true) => Strategy::Cranelift,
83         (false, false) => Strategy::Auto,
84         (true, true) => bail!("Can't enable --cranelift and --lightbeam at the same time"),
85     })
86 }
87 
88 fn pick_profiling_strategy(jitdump: bool, vtune: bool) -> Result<ProfilingStrategy> {
89     Ok(match (jitdump, vtune) {
90         (true, false) => ProfilingStrategy::JitDump,
91         (false, true) => ProfilingStrategy::VTune,
92         (true, true) => {
93             println!("Can't enable --jitdump and --vtune at the same time. Profiling not enabled.");
94             ProfilingStrategy::None
95         }
96         _ => ProfilingStrategy::None,
97     })
98 }
99 
100 fn init_file_per_thread_logger(prefix: &'static str) {
101     file_per_thread_logger::initialize(prefix);
102 
103     // Extending behavior of default spawner:
104     // https://docs.rs/rayon/1.1.0/rayon/struct.ThreadPoolBuilder.html#method.spawn_handler
105     // Source code says DefaultSpawner is implementation detail and
106     // shouldn't be used directly.
107     rayon::ThreadPoolBuilder::new()
108         .spawn_handler(move |thread| {
109             let mut b = std::thread::Builder::new();
110             if let Some(name) = thread.name() {
111                 b = b.name(name.to_owned());
112             }
113             if let Some(stack_size) = thread.stack_size() {
114                 b = b.stack_size(stack_size);
115             }
116             b.spawn(move || {
117                 file_per_thread_logger::initialize(prefix);
118                 thread.run()
119             })?;
120             Ok(())
121         })
122         .build_global()
123         .unwrap();
124 }
125 
126 /// Common options for commands that translate WebAssembly modules
127 #[derive(StructOpt)]
128 struct CommonOptions {
129     /// Use specified configuration file
130     #[structopt(long, parse(from_os_str), value_name = "CONFIG_PATH")]
131     config: Option<PathBuf>,
132 
133     /// Use Cranelift for all compilation
134     #[structopt(long, conflicts_with = "lightbeam")]
135     cranelift: bool,
136 
137     /// Disable logging.
138     #[structopt(long, conflicts_with = "log_to_files")]
139     disable_logging: bool,
140 
141     /// Log to per-thread log files instead of stderr.
142     #[structopt(long)]
143     log_to_files: bool,
144 
145     /// Generate debug information
146     #[structopt(short = "g")]
147     debug_info: bool,
148 
149     /// Disable cache system
150     #[structopt(long)]
151     disable_cache: bool,
152 
153     /// Enable support for proposed SIMD instructions (deprecated; use `--wasm-features=simd`)
154     #[structopt(long, hidden = true)]
155     enable_simd: bool,
156 
157     /// Enable support for reference types (deprecated; use `--wasm-features=reference-types`)
158     #[structopt(long, hidden = true)]
159     enable_reference_types: bool,
160 
161     /// Enable support for multi-value functions (deprecated; use `--wasm-features=multi-value`)
162     #[structopt(long, hidden = true)]
163     enable_multi_value: bool,
164 
165     /// Enable support for Wasm threads (deprecated; use `--wasm-features=threads`)
166     #[structopt(long, hidden = true)]
167     enable_threads: bool,
168 
169     /// Enable support for bulk memory instructions (deprecated; use `--wasm-features=bulk-memory`)
170     #[structopt(long, hidden = true)]
171     enable_bulk_memory: bool,
172 
173     /// Enable support for the multi-memory proposal (deprecated; use `--wasm-features=multi-memory`)
174     #[structopt(long, hidden = true)]
175     enable_multi_memory: bool,
176 
177     /// Enable support for the module-linking proposal (deprecated; use `--wasm-features=module-linking`)
178     #[structopt(long, hidden = true)]
179     enable_module_linking: bool,
180 
181     /// Enable all experimental Wasm features (deprecated; use `--wasm-features=all`)
182     #[structopt(long, hidden = true)]
183     enable_all: bool,
184 
185     /// Enables or disables WebAssembly features
186     #[structopt(long, value_name = "FEATURE,FEATURE,...", parse(try_from_str = parse_wasm_features))]
187     wasm_features: Option<wasmparser::WasmFeatures>,
188 
189     /// Use Lightbeam for all compilation
190     #[structopt(long, conflicts_with = "cranelift")]
191     lightbeam: bool,
192 
193     /// Generate jitdump file (supported on --features=profiling build)
194     #[structopt(long, conflicts_with = "vtune")]
195     jitdump: bool,
196 
197     /// Generate vtune (supported on --features=vtune build)
198     #[structopt(long, conflicts_with = "jitdump")]
199     vtune: bool,
200 
201     /// Run optimization passes on translated functions, on by default
202     #[structopt(short = "O", long)]
203     optimize: bool,
204 
205     /// Optimization level for generated functions
206     /// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most"
207     #[structopt(
208         long,
209         value_name = "LEVEL",
210         parse(try_from_str = parse_opt_level),
211         verbatim_doc_comment,
212     )]
213     opt_level: Option<wasmtime::OptLevel>,
214 
215     /// Set a Cranelift setting to a given value.
216     /// Use `wasmtime settings` to list Cranelift settings for a target.
217     #[structopt(long = "cranelift-set", value_name = "NAME=VALUE", number_of_values = 1, verbatim_doc_comment, parse(try_from_str = parse_cranelift_flag))]
218     cranelift_set: Vec<(String, String)>,
219 
220     /// Enable a Cranelift boolean setting or preset.
221     /// Use `wasmtime settings` to list Cranelift settings for a target.
222     #[structopt(
223         long,
224         value_name = "SETTING",
225         number_of_values = 1,
226         verbatim_doc_comment
227     )]
228     cranelift_enable: Vec<String>,
229 
230     /// Maximum size in bytes of wasm memory before it becomes dynamically
231     /// relocatable instead of up-front-reserved.
232     #[structopt(long, value_name = "MAXIMUM")]
233     static_memory_maximum_size: Option<u64>,
234 
235     /// Byte size of the guard region after static memories are allocated.
236     #[structopt(long, value_name = "SIZE")]
237     static_memory_guard_size: Option<u64>,
238 
239     /// Byte size of the guard region after dynamic memories are allocated.
240     #[structopt(long, value_name = "SIZE")]
241     dynamic_memory_guard_size: Option<u64>,
242 
243     /// Enable Cranelift's internal debug verifier (expensive)
244     #[structopt(long)]
245     enable_cranelift_debug_verifier: bool,
246 
247     /// Enable Cranelift's internal NaN canonicalization
248     #[structopt(long)]
249     enable_cranelift_nan_canonicalization: bool,
250 }
251 
252 impl CommonOptions {
253     fn init_logging(&self) {
254         if self.disable_logging {
255             return;
256         }
257         if self.log_to_files {
258             let prefix = "wasmtime.dbg.";
259             init_file_per_thread_logger(prefix);
260         } else {
261             pretty_env_logger::init();
262         }
263     }
264 
265     fn config(&self, target: Option<&str>) -> Result<Config> {
266         let mut config = Config::new();
267 
268         // Set the target before setting any cranelift options
269         if let Some(target) = target {
270             config.target(target)?;
271         }
272 
273         config
274             .cranelift_debug_verifier(self.enable_cranelift_debug_verifier)
275             .debug_info(self.debug_info)
276             .cranelift_opt_level(self.opt_level())
277             .strategy(pick_compilation_strategy(self.cranelift, self.lightbeam)?)?
278             .profiler(pick_profiling_strategy(self.jitdump, self.vtune)?)?
279             .cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization);
280 
281         self.enable_wasm_features(&mut config);
282 
283         for name in &self.cranelift_enable {
284             unsafe {
285                 config.cranelift_flag_enable(name)?;
286             }
287         }
288 
289         for (name, value) in &self.cranelift_set {
290             unsafe {
291                 config.cranelift_flag_set(name, value)?;
292             }
293         }
294 
295         if !self.disable_cache {
296             match &self.config {
297                 Some(path) => {
298                     config.cache_config_load(path)?;
299                 }
300                 None => {
301                     config.cache_config_load_default()?;
302                 }
303             }
304         }
305 
306         if let Some(max) = self.static_memory_maximum_size {
307             config.static_memory_maximum_size(max);
308         }
309 
310         if let Some(size) = self.static_memory_guard_size {
311             config.static_memory_guard_size(size);
312         }
313 
314         if let Some(size) = self.dynamic_memory_guard_size {
315             config.dynamic_memory_guard_size(size);
316         }
317 
318         Ok(config)
319     }
320 
321     fn enable_wasm_features(&self, config: &mut Config) {
322         let features = self.wasm_features.unwrap_or_default();
323 
324         config
325             .wasm_simd(features.simd || self.enable_simd || self.enable_all)
326             .wasm_bulk_memory(features.bulk_memory || self.enable_bulk_memory || self.enable_all)
327             .wasm_reference_types(
328                 features.reference_types || self.enable_reference_types || self.enable_all,
329             )
330             .wasm_multi_value(features.multi_value || self.enable_multi_value || self.enable_all)
331             .wasm_threads(features.threads || self.enable_threads || self.enable_all)
332             .wasm_multi_memory(features.multi_memory || self.enable_multi_memory || self.enable_all)
333             .wasm_module_linking(
334                 features.module_linking || self.enable_module_linking || self.enable_all,
335             );
336     }
337 
338     fn opt_level(&self) -> wasmtime::OptLevel {
339         match (self.optimize, self.opt_level.clone()) {
340             (true, _) => wasmtime::OptLevel::Speed,
341             (false, other) => other.unwrap_or(wasmtime::OptLevel::Speed),
342         }
343     }
344 }
345 
346 fn parse_opt_level(opt_level: &str) -> Result<wasmtime::OptLevel> {
347     match opt_level {
348         "s" => Ok(wasmtime::OptLevel::SpeedAndSize),
349         "0" => Ok(wasmtime::OptLevel::None),
350         "1" => Ok(wasmtime::OptLevel::Speed),
351         "2" => Ok(wasmtime::OptLevel::Speed),
352         other => bail!(
353             "unknown optimization level `{}`, only 0,1,2,s accepted",
354             other
355         ),
356     }
357 }
358 
359 fn parse_wasm_features(features: &str) -> Result<wasmparser::WasmFeatures> {
360     let features = features.trim();
361 
362     let mut all = None;
363     let mut values: HashMap<_, _> = SUPPORTED_WASM_FEATURES
364         .iter()
365         .map(|(name, _)| (name.to_string(), None))
366         .collect();
367 
368     if features == "all" {
369         all = Some(true);
370     } else if features == "-all" {
371         all = Some(false);
372     } else {
373         for feature in features.split(',') {
374             let feature = feature.trim();
375 
376             if feature.is_empty() {
377                 continue;
378             }
379 
380             let (feature, value) = if feature.starts_with('-') {
381                 (&feature[1..], false)
382             } else {
383                 (feature, true)
384             };
385 
386             if feature == "all" {
387                 bail!("'all' cannot be specified with other WebAssembly features");
388             }
389 
390             match values.get_mut(feature) {
391                 Some(v) => *v = Some(value),
392                 None => bail!("unsupported WebAssembly feature '{}'", feature),
393             }
394         }
395     }
396 
397     Ok(wasmparser::WasmFeatures {
398         reference_types: all.unwrap_or(values["reference-types"].unwrap_or(true)),
399         multi_value: all.unwrap_or(values["multi-value"].unwrap_or(true)),
400         bulk_memory: all.unwrap_or(values["bulk-memory"].unwrap_or(true)),
401         module_linking: all.unwrap_or(values["module-linking"].unwrap_or(false)),
402         simd: all.unwrap_or(values["simd"].unwrap_or(false)),
403         threads: all.unwrap_or(values["threads"].unwrap_or(false)),
404         tail_call: false,
405         deterministic_only: false,
406         multi_memory: all.unwrap_or(values["multi-memory"].unwrap_or(false)),
407         exceptions: false,
408         memory64: false,
409     })
410 }
411 fn parse_cranelift_flag(name_and_value: &str) -> Result<(String, String)> {
412     let mut split = name_and_value.splitn(2, '=');
413     let name = if let Some(name) = split.next() {
414         name.to_string()
415     } else {
416         bail!("missing name in cranelift flag");
417     };
418     let value = if let Some(value) = split.next() {
419         value.to_string()
420     } else {
421         bail!("missing value in cranelift flag");
422     };
423     Ok((name, value))
424 }
425 
426 fn parse_target(s: &str) -> Result<Triple> {
427     use std::str::FromStr;
428     Triple::from_str(&s).map_err(|e| anyhow::anyhow!(e))
429 }
430 
431 #[cfg(test)]
432 mod test {
433     use super::*;
434 
435     #[test]
436     fn test_all_features() -> Result<()> {
437         let options = CommonOptions::from_iter_safe(vec!["foo", "--wasm-features=all"])?;
438 
439         let wasmparser::WasmFeatures {
440             reference_types,
441             multi_value,
442             bulk_memory,
443             module_linking,
444             simd,
445             threads,
446             tail_call,
447             deterministic_only,
448             multi_memory,
449             exceptions,
450             memory64,
451         } = options.wasm_features.unwrap();
452 
453         assert!(reference_types);
454         assert!(multi_value);
455         assert!(bulk_memory);
456         assert!(module_linking);
457         assert!(simd);
458         assert!(threads);
459         assert!(!tail_call); // Not supported
460         assert!(!deterministic_only); // Not supported
461         assert!(multi_memory);
462         assert!(!exceptions); // Not supported
463         assert!(!memory64); // Not supported
464 
465         Ok(())
466     }
467 
468     #[test]
469     fn test_no_features() -> Result<()> {
470         let options = CommonOptions::from_iter_safe(vec!["foo", "--wasm-features=-all"])?;
471 
472         let wasmparser::WasmFeatures {
473             reference_types,
474             multi_value,
475             bulk_memory,
476             module_linking,
477             simd,
478             threads,
479             tail_call,
480             deterministic_only,
481             multi_memory,
482             exceptions,
483             memory64,
484         } = options.wasm_features.unwrap();
485 
486         assert!(!reference_types);
487         assert!(!multi_value);
488         assert!(!bulk_memory);
489         assert!(!module_linking);
490         assert!(!simd);
491         assert!(!threads);
492         assert!(!tail_call);
493         assert!(!deterministic_only);
494         assert!(!multi_memory);
495         assert!(!exceptions);
496         assert!(!memory64);
497 
498         Ok(())
499     }
500 
501     #[test]
502     fn test_multiple_features() -> Result<()> {
503         let options = CommonOptions::from_iter_safe(vec![
504             "foo",
505             "--wasm-features=-reference-types,simd,multi-memory",
506         ])?;
507 
508         let wasmparser::WasmFeatures {
509             reference_types,
510             multi_value,
511             bulk_memory,
512             module_linking,
513             simd,
514             threads,
515             tail_call,
516             deterministic_only,
517             multi_memory,
518             exceptions,
519             memory64,
520         } = options.wasm_features.unwrap();
521 
522         assert!(!reference_types);
523         assert!(multi_value);
524         assert!(bulk_memory);
525         assert!(!module_linking);
526         assert!(simd);
527         assert!(!threads);
528         assert!(!tail_call); // Not supported
529         assert!(!deterministic_only); // Not supported
530         assert!(multi_memory);
531         assert!(!exceptions); // Not supported
532         assert!(!memory64); // Not supported
533 
534         Ok(())
535     }
536 
537     macro_rules! feature_test {
538         ($test_name:ident, $name:ident, $flag:literal) => {
539             #[test]
540             fn $test_name() -> Result<()> {
541                 let options =
542                     CommonOptions::from_iter_safe(vec!["foo", concat!("--wasm-features=", $flag)])?;
543 
544                 let wasmparser::WasmFeatures { $name, .. } = options.wasm_features.unwrap();
545 
546                 assert!($name);
547 
548                 let options = CommonOptions::from_iter_safe(vec![
549                     "foo",
550                     concat!("--wasm-features=-", $flag),
551                 ])?;
552 
553                 let wasmparser::WasmFeatures { $name, .. } = options.wasm_features.unwrap();
554 
555                 assert!(!$name);
556 
557                 Ok(())
558             }
559         };
560     }
561 
562     feature_test!(
563         test_reference_types_feature,
564         reference_types,
565         "reference-types"
566     );
567     feature_test!(test_multi_value_feature, multi_value, "multi-value");
568     feature_test!(test_bulk_memory_feature, bulk_memory, "bulk-memory");
569     feature_test!(
570         test_module_linking_feature,
571         module_linking,
572         "module-linking"
573     );
574     feature_test!(test_simd_feature, simd, "simd");
575     feature_test!(test_threads_feature, threads, "threads");
576     feature_test!(test_multi_memory_feature, multi_memory, "multi-memory");
577 }
578