wasmer_cli/
backend.rs

1//! Common module with common used structures across different
2//! commands.
3
4// NOTE: A lot of this code depends on feature flags.
5// To not go crazy with annotations, some lints are disabled for the whole
6// module.
7#![allow(dead_code, unused_imports, unused_variables)]
8
9use std::num::NonZero;
10use std::string::ToString;
11use std::sync::Arc;
12use std::{path::PathBuf, str::FromStr};
13
14use anyhow::{Context, Result, bail};
15#[cfg(feature = "sys")]
16use wasmer::sys::*;
17use wasmer::*;
18use wasmer_types::{Features, target::Target};
19
20#[cfg(feature = "compiler")]
21use wasmer_compiler::CompilerConfig;
22
23use wasmer::Engine;
24
25#[derive(Debug, clap::Parser, Clone, Default)]
26/// The WebAssembly features that can be passed through the
27/// Command Line args.
28pub struct WasmFeatures {
29    /// Enable support for the SIMD proposal.
30    #[clap(long = "enable-simd")]
31    pub simd: bool,
32
33    /// Disable support for the threads proposal.
34    #[clap(long = "disable-threads")]
35    pub disable_threads: bool,
36
37    /// Deprecated, threads are enabled by default.
38    #[clap(long = "enable-threads")]
39    pub _threads: bool,
40
41    /// Enable support for the reference types proposal.
42    #[clap(long = "enable-reference-types")]
43    pub reference_types: bool,
44
45    /// Enable support for the multi value proposal.
46    #[clap(long = "enable-multi-value")]
47    pub multi_value: bool,
48
49    /// Enable support for the bulk memory proposal.
50    #[clap(long = "enable-bulk-memory")]
51    pub bulk_memory: bool,
52
53    /// Enable support for the tail call proposal.
54    #[clap(long = "enable-tail-call")]
55    pub tail_call: bool,
56
57    /// Enable support for the module linking proposal.
58    #[clap(long = "enable-module-linking")]
59    pub module_linking: bool,
60
61    /// Enable support for the multi memory proposal.
62    #[clap(long = "enable-multi-memory")]
63    pub multi_memory: bool,
64
65    /// Enable support for the memory64 proposal.
66    #[clap(long = "enable-memory64")]
67    pub memory64: bool,
68
69    /// Enable support for the exceptions proposal.
70    #[clap(long = "enable-exceptions")]
71    pub exceptions: bool,
72
73    /// Enable support for the relaxed SIMD proposal.
74    #[clap(long = "enable-relaxed-simd")]
75    pub relaxed_simd: bool,
76
77    /// Enable support for the extended constant expressions proposal.
78    #[clap(long = "enable-extended-const")]
79    pub extended_const: bool,
80
81    /// Enable support for all pre-standard proposals.
82    #[clap(long = "enable-all")]
83    pub all: bool,
84}
85
86#[derive(Debug, Clone, clap::Parser, Default)]
87/// The compiler options
88pub struct RuntimeOptions {
89    /// Use Singlepass compiler.
90    #[cfg(feature = "singlepass")]
91    #[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
92        #[cfg(feature = "llvm")]
93        "llvm", 
94        #[cfg(feature = "v8")]
95        "v8", 
96        #[cfg(feature = "cranelift")]
97        "cranelift", 
98        #[cfg(feature = "wamr")]
99        "wamr", 
100        #[cfg(feature = "wasmi")]
101        "wasmi"
102    ]))]
103    singlepass: bool,
104
105    /// Use Cranelift compiler.
106    #[cfg(feature = "cranelift")]
107    #[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
108        #[cfg(feature = "llvm")]
109        "llvm", 
110        #[cfg(feature = "v8")]
111        "v8", 
112        #[cfg(feature = "singlepass")]
113        "singlepass", 
114        #[cfg(feature = "wamr")]
115        "wamr", 
116        #[cfg(feature = "wasmi")]
117        "wasmi"
118    ]))]
119    cranelift: bool,
120
121    /// Use LLVM compiler.
122    #[cfg(feature = "llvm")]
123    #[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
124        #[cfg(feature = "cranelift")]
125        "cranelift", 
126        #[cfg(feature = "v8")]
127        "v8", 
128        #[cfg(feature = "singlepass")]
129        "singlepass", 
130        #[cfg(feature = "wamr")]
131        "wamr", 
132        #[cfg(feature = "wasmi")]
133        "wasmi"
134    ]))]
135    llvm: bool,
136
137    /// Use the V8 runtime.
138    #[cfg(feature = "v8")]
139    #[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
140        #[cfg(feature = "cranelift")]
141        "cranelift", 
142        #[cfg(feature = "llvm")]
143        "llvm", 
144        #[cfg(feature = "singlepass")]
145        "singlepass", 
146        #[cfg(feature = "wamr")]
147        "wamr", 
148        #[cfg(feature = "wasmi")]
149        "wasmi"
150    ]))]
151    v8: bool,
152
153    /// Use WAMR.
154    #[cfg(feature = "wamr")]
155    #[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
156        #[cfg(feature = "cranelift")]
157        "cranelift", 
158        #[cfg(feature = "llvm")]
159        "llvm", 
160        #[cfg(feature = "singlepass")]
161        "singlepass", 
162        #[cfg(feature = "v8")]
163        "v8", 
164        #[cfg(feature = "wasmi")]
165        "wasmi"
166    ]))]
167    wamr: bool,
168
169    /// Use the wasmi runtime.
170    #[cfg(feature = "wasmi")]
171    #[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
172        #[cfg(feature = "cranelift")]
173        "cranelift", 
174        #[cfg(feature = "llvm")]
175        "llvm", 
176        #[cfg(feature = "singlepass")]
177        "singlepass", 
178        #[cfg(feature = "v8")]
179        "v8", 
180        #[cfg(feature = "wamr")]
181        "wamr"
182    ]))]
183    wasmi: bool,
184
185    /// Enable compiler internal verification.
186    ///
187    /// Available for cranelift, LLVM and singlepass.
188    #[clap(long)]
189    enable_verifier: bool,
190
191    /// Debug directory, where IR and object files will be written to.
192    ///
193    /// Available for cranelift, LLVM and singlepass.
194    #[clap(long, alias = "llvm-debug-dir")]
195    compiler_debug_dir: Option<PathBuf>,
196
197    /// Enable a profiler.
198    ///
199    /// Available for cranelift, LLVM and singlepass.
200    #[clap(long, value_enum)]
201    profiler: Option<Profiler>,
202
203    /// Only available for the LLVM compiler. Enable the "pass-params" optimization, where the first (#0)
204    /// global and the first (#0) memory passed between guest functions as explicit parameters.
205    #[cfg(feature = "llvm")]
206    #[clap(long)]
207    enable_pass_params_opt: bool,
208
209    /// Only available for the LLVM compiler. Sets the number of threads used to compile the
210    /// input module(s).
211    #[cfg(feature = "llvm")]
212    #[clap(long)]
213    llvm_num_threads: Option<NonZero<usize>>,
214
215    #[clap(flatten)]
216    features: WasmFeatures,
217}
218
219#[derive(Clone, Debug)]
220pub enum Profiler {
221    /// Perfmap-based profilers.
222    Perfmap,
223}
224
225impl FromStr for Profiler {
226    type Err = anyhow::Error;
227
228    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
229        match s.to_lowercase().as_str() {
230            "perfmap" => Ok(Self::Perfmap),
231            _ => Err(anyhow::anyhow!("Unrecognized profiler: {s}")),
232        }
233    }
234}
235
236impl RuntimeOptions {
237    pub fn get_available_backends(&self) -> Result<Vec<BackendType>> {
238        // If a specific backend is explicitly requested, use it
239        #[cfg(feature = "cranelift")]
240        {
241            if self.cranelift {
242                return Ok(vec![BackendType::Cranelift]);
243            }
244        }
245
246        #[cfg(feature = "llvm")]
247        {
248            if self.llvm {
249                return Ok(vec![BackendType::LLVM]);
250            }
251        }
252
253        #[cfg(feature = "singlepass")]
254        {
255            if self.singlepass {
256                return Ok(vec![BackendType::Singlepass]);
257            }
258        }
259
260        #[cfg(feature = "wamr")]
261        {
262            if self.wamr {
263                return Ok(vec![BackendType::Wamr]);
264            }
265        }
266
267        #[cfg(feature = "v8")]
268        {
269            if self.v8 {
270                return Ok(vec![BackendType::V8]);
271            }
272        }
273
274        #[cfg(feature = "wasmi")]
275        {
276            if self.wasmi {
277                return Ok(vec![BackendType::Wasmi]);
278            }
279        }
280
281        Ok(BackendType::enabled())
282    }
283
284    /// Filter enabled backends based on required WebAssembly features
285    pub fn filter_backends_by_features(
286        backends: Vec<BackendType>,
287        required_features: &Features,
288        target: &Target,
289    ) -> Vec<BackendType> {
290        backends
291            .into_iter()
292            .filter(|backend| backend.supports_features(required_features, target))
293            .collect()
294    }
295
296    pub fn get_store(&self) -> Result<Store> {
297        let engine = self.get_engine(&Target::default())?;
298        Ok(Store::new(engine))
299    }
300
301    pub fn get_engine(&self, target: &Target) -> Result<Engine> {
302        let backends = self.get_available_backends()?;
303        let backend = backends.first().context("no compiler backend enabled")?;
304        let backend_kind = wasmer::BackendKind::from(backend);
305        let required_features = wasmer::Engine::default_features_for_backend(&backend_kind, target);
306        backend.get_engine(target, &required_features, self)
307    }
308
309    pub fn get_engine_for_module(&self, module_contents: &[u8], target: &Target) -> Result<Engine> {
310        let required_features = self
311            .detect_features_from_wasm(module_contents)
312            .unwrap_or_default();
313
314        self.get_engine_for_features(&required_features, target)
315    }
316
317    pub fn get_engine_for_features(
318        &self,
319        required_features: &Features,
320        target: &Target,
321    ) -> Result<Engine> {
322        let backends = self.get_available_backends()?;
323        let filtered_backends =
324            Self::filter_backends_by_features(backends.clone(), required_features, target);
325
326        if filtered_backends.is_empty() {
327            let enabled_backends = BackendType::enabled();
328            if backends.len() == 1 && enabled_backends.len() > 1 {
329                // If the user has chosen an specific backend, we can suggest to use another one
330                let filtered_backends =
331                    Self::filter_backends_by_features(enabled_backends, required_features, target);
332                let extra_text: String = if !filtered_backends.is_empty() {
333                    format!(". You can use --{} instead", filtered_backends[0])
334                } else {
335                    "".to_string()
336                };
337                bail!(
338                    "The {} backend does not support the required features for the Wasm module{}",
339                    backends[0],
340                    extra_text
341                );
342            } else {
343                bail!(
344                    "No backends support the required features for the Wasm module. Feel free to open an issue at https://github.com/wasmerio/wasmer/issues"
345                );
346            }
347        }
348        filtered_backends
349            .first()
350            .unwrap()
351            .get_engine(target, required_features, self)
352    }
353
354    #[cfg(feature = "compiler")]
355    /// Get the enaled Wasm features.
356    pub fn get_features(&self, features: &Features) -> Result<Features> {
357        let mut result = features.clone();
358        if !self.features.disable_threads || self.features.all {
359            result.threads(true);
360        }
361        if self.features.disable_threads && !self.features.all {
362            result.threads(false);
363        }
364        if self.features.multi_value || self.features.all {
365            result.multi_value(true);
366        }
367        if self.features.simd || self.features.all {
368            result.simd(true);
369        }
370        if self.features.bulk_memory || self.features.all {
371            result.bulk_memory(true);
372        }
373        if self.features.reference_types || self.features.all {
374            result.reference_types(true);
375        }
376        Ok(result)
377    }
378
379    #[cfg(feature = "compiler")]
380    /// Get a copy of the default features with user-configured options
381    pub fn get_configured_features(&self) -> Result<Features> {
382        let features = Features::default();
383        self.get_features(&features)
384    }
385
386    /// Detect features from a WebAssembly module binary.
387    pub fn detect_features_from_wasm(
388        &self,
389        wasm_bytes: &[u8],
390    ) -> Result<Features, wasmparser::BinaryReaderError> {
391        let mut features = Features::detect_from_wasm(wasm_bytes)?;
392
393        // Merge with user-configured features
394        if !self.features.disable_threads || self.features.all {
395            features.threads(true);
396        }
397        if self.features.reference_types || self.features.all {
398            features.reference_types(true);
399        }
400        if self.features.simd || self.features.all {
401            features.simd(true);
402        }
403        if self.features.bulk_memory || self.features.all {
404            features.bulk_memory(true);
405        }
406        if self.features.multi_value || self.features.all {
407            features.multi_value(true);
408        }
409        if self.features.tail_call || self.features.all {
410            features.tail_call(true);
411        }
412        if self.features.module_linking || self.features.all {
413            features.module_linking(true);
414        }
415        if self.features.multi_memory || self.features.all {
416            features.multi_memory(true);
417        }
418        if self.features.memory64 || self.features.all {
419            features.memory64(true);
420        }
421        if self.features.exceptions || self.features.all {
422            features.exceptions(true);
423        }
424
425        Ok(features)
426    }
427
428    #[cfg(feature = "compiler")]
429    pub fn get_sys_compiler_engine_for_target(
430        &self,
431        target: Target,
432    ) -> std::result::Result<Engine, anyhow::Error> {
433        let backends = self.get_available_backends()?;
434        let compiler_config = self.get_sys_compiler_config(backends.first().unwrap())?;
435        let default_features = compiler_config.default_features_for_target(&target);
436        let features = self.get_features(&default_features)?;
437        Ok(wasmer_compiler::EngineBuilder::new(compiler_config)
438            .set_features(Some(features))
439            .set_target(Some(target))
440            .engine()
441            .into())
442    }
443
444    #[allow(unused_variables)]
445    #[cfg(feature = "compiler")]
446    pub(crate) fn get_sys_compiler_config(
447        &self,
448        rt: &BackendType,
449    ) -> Result<Box<dyn CompilerConfig>> {
450        let compiler_config: Box<dyn CompilerConfig> = match rt {
451            BackendType::Headless => bail!("The headless engine can't be chosen"),
452            #[cfg(feature = "singlepass")]
453            BackendType::Singlepass => {
454                let mut config = wasmer_compiler_singlepass::Singlepass::new();
455                if self.enable_verifier {
456                    config.enable_verifier();
457                }
458                if let Some(p) = &self.profiler {
459                    match p {
460                        Profiler::Perfmap => config.enable_perfmap(),
461                    }
462                }
463
464                Box::new(config)
465            }
466            #[cfg(feature = "cranelift")]
467            BackendType::Cranelift => {
468                let mut config = wasmer_compiler_cranelift::Cranelift::new();
469                if self.enable_verifier {
470                    config.enable_verifier();
471                }
472                if let Some(p) = &self.profiler {
473                    match p {
474                        Profiler::Perfmap => config.enable_perfmap(),
475                    }
476                }
477                if let Some(mut debug_dir) = self.compiler_debug_dir.clone() {
478                    use wasmer_compiler_cranelift::CraneliftCallbacks;
479
480                    debug_dir.push("cranelift");
481                    config.callbacks(Some(CraneliftCallbacks::new(debug_dir)?));
482                }
483                Box::new(config)
484            }
485            #[cfg(feature = "llvm")]
486            BackendType::LLVM => {
487                use wasmer_compiler_llvm::LLVMCallbacks;
488                use wasmer_types::entity::EntityRef;
489                let mut config = LLVM::new();
490
491                if self.enable_pass_params_opt {
492                    config.enable_pass_params_opt();
493                }
494
495                if let Some(num_threads) = self.llvm_num_threads {
496                    config.num_threads(num_threads);
497                }
498
499                if let Some(mut debug_dir) = self.compiler_debug_dir.clone() {
500                    debug_dir.push("llvm");
501                    config.callbacks(Some(LLVMCallbacks::new(debug_dir)?));
502                }
503                if self.enable_verifier {
504                    config.enable_verifier();
505                }
506                if let Some(p) = &self.profiler {
507                    match p {
508                        Profiler::Perfmap => config.enable_perfmap(),
509                    }
510                }
511
512                Box::new(config)
513            }
514            BackendType::V8 | BackendType::Wamr | BackendType::Wasmi => unreachable!(),
515            #[cfg(not(all(feature = "singlepass", feature = "cranelift", feature = "llvm")))]
516            compiler => {
517                bail!("The `{compiler}` compiler is not included in this binary.")
518            }
519        };
520
521        #[allow(unreachable_code)]
522        Ok(compiler_config)
523    }
524}
525
526/// The compiler used for the store
527#[derive(Debug, PartialEq, Eq, Clone, Copy)]
528#[allow(clippy::upper_case_acronyms, dead_code)]
529pub enum BackendType {
530    /// Singlepass compiler
531    Singlepass,
532
533    /// Cranelift compiler
534    Cranelift,
535
536    /// LLVM compiler
537    LLVM,
538
539    /// V8 runtime
540    V8,
541
542    /// Wamr runtime
543    Wamr,
544
545    /// Wasmi runtime
546    Wasmi,
547
548    /// Headless compiler
549    #[allow(dead_code)]
550    Headless,
551}
552
553impl BackendType {
554    /// Return all enabled compilers
555    pub fn enabled() -> Vec<Self> {
556        vec![
557            #[cfg(feature = "cranelift")]
558            Self::Cranelift,
559            #[cfg(feature = "llvm")]
560            Self::LLVM,
561            #[cfg(feature = "singlepass")]
562            Self::Singlepass,
563            #[cfg(feature = "v8")]
564            Self::V8,
565            #[cfg(feature = "wamr")]
566            Self::Wamr,
567            #[cfg(feature = "wasmi")]
568            Self::Wasmi,
569        ]
570    }
571
572    /// Get an engine for this backend type
573    pub fn get_engine(
574        &self,
575        target: &Target,
576        features: &Features,
577        runtime_opts: &RuntimeOptions,
578    ) -> Result<Engine> {
579        match self {
580            #[cfg(feature = "singlepass")]
581            Self::Singlepass => {
582                let mut config = wasmer_compiler_singlepass::Singlepass::new();
583                if runtime_opts.enable_verifier {
584                    config.enable_verifier();
585                }
586                if let Some(p) = &runtime_opts.profiler {
587                    match p {
588                        Profiler::Perfmap => config.enable_perfmap(),
589                    }
590                }
591                let engine = wasmer_compiler::EngineBuilder::new(config)
592                    .set_features(Some(features.clone()))
593                    .set_target(Some(target.clone()))
594                    .engine()
595                    .into();
596                Ok(engine)
597            }
598            #[cfg(feature = "cranelift")]
599            Self::Cranelift => {
600                let mut config = wasmer_compiler_cranelift::Cranelift::new();
601                if runtime_opts.enable_verifier {
602                    config.enable_verifier();
603                }
604                if let Some(p) = &runtime_opts.profiler {
605                    match p {
606                        Profiler::Perfmap => config.enable_perfmap(),
607                    }
608                }
609                if let Some(mut debug_dir) = runtime_opts.compiler_debug_dir.clone() {
610                    use wasmer_compiler_cranelift::CraneliftCallbacks;
611
612                    debug_dir.push("cranelift");
613                    config.callbacks(Some(CraneliftCallbacks::new(debug_dir)?));
614                }
615                let engine = wasmer_compiler::EngineBuilder::new(config)
616                    .set_features(Some(features.clone()))
617                    .set_target(Some(target.clone()))
618                    .engine()
619                    .into();
620                Ok(engine)
621            }
622            #[cfg(feature = "llvm")]
623            Self::LLVM => {
624                use wasmer_compiler_llvm::LLVMCallbacks;
625                use wasmer_types::entity::EntityRef;
626
627                let mut config = wasmer_compiler_llvm::LLVM::new();
628
629                if let Some(mut debug_dir) = runtime_opts.compiler_debug_dir.clone() {
630                    debug_dir.push("llvm");
631                    config.callbacks(Some(LLVMCallbacks::new(debug_dir)?));
632                }
633                if runtime_opts.enable_verifier {
634                    config.enable_verifier();
635                }
636
637                if runtime_opts.enable_pass_params_opt {
638                    config.enable_pass_params_opt();
639                }
640
641                if let Some(num_threads) = runtime_opts.llvm_num_threads {
642                    config.num_threads(num_threads);
643                }
644
645                if let Some(p) = &runtime_opts.profiler {
646                    match p {
647                        Profiler::Perfmap => config.enable_perfmap(),
648                    }
649                }
650
651                let engine = wasmer_compiler::EngineBuilder::new(config)
652                    .set_features(Some(features.clone()))
653                    .set_target(Some(target.clone()))
654                    .engine()
655                    .into();
656                Ok(engine)
657            }
658            #[cfg(feature = "v8")]
659            Self::V8 => Ok(wasmer::v8::V8::new().into()),
660            #[cfg(feature = "wamr")]
661            Self::Wamr => Ok(wasmer::wamr::Wamr::new().into()),
662            #[cfg(feature = "wasmi")]
663            Self::Wasmi => Ok(wasmer::wasmi::Wasmi::new().into()),
664            Self::Headless => bail!("Headless is not a valid runtime to instantiate directly"),
665            #[allow(unreachable_patterns)]
666            _ => bail!("Unsupported backend type"),
667        }
668    }
669
670    /// Check if this backend supports all the required WebAssembly features
671    #[allow(unreachable_code)]
672    pub fn supports_features(&self, required_features: &Features, target: &Target) -> bool {
673        // Map BackendType to the corresponding wasmer::BackendKind
674        let backend_kind = match self {
675            #[cfg(feature = "singlepass")]
676            Self::Singlepass => wasmer::BackendKind::Singlepass,
677            #[cfg(feature = "cranelift")]
678            Self::Cranelift => wasmer::BackendKind::Cranelift,
679            #[cfg(feature = "llvm")]
680            Self::LLVM => wasmer::BackendKind::LLVM,
681            #[cfg(feature = "v8")]
682            Self::V8 => wasmer::BackendKind::V8,
683            #[cfg(feature = "wamr")]
684            Self::Wamr => wasmer::BackendKind::Wamr,
685            #[cfg(feature = "wasmi")]
686            Self::Wasmi => wasmer::BackendKind::Wasmi,
687            Self::Headless => return false, // Headless can't compile
688            #[allow(unreachable_patterns)]
689            _ => return false,
690        };
691
692        // Get the supported features from the backend
693        let supported = wasmer::Engine::supported_features_for_backend(&backend_kind, target);
694
695        // Check if the backend supports all required features
696        if !supported.contains_features(required_features) {
697            return false;
698        }
699
700        true
701    }
702}
703
704impl From<&BackendType> for wasmer::BackendKind {
705    fn from(backend_type: &BackendType) -> Self {
706        match backend_type {
707            #[cfg(feature = "singlepass")]
708            BackendType::Singlepass => wasmer::BackendKind::Singlepass,
709            #[cfg(feature = "cranelift")]
710            BackendType::Cranelift => wasmer::BackendKind::Cranelift,
711            #[cfg(feature = "llvm")]
712            BackendType::LLVM => wasmer::BackendKind::LLVM,
713            #[cfg(feature = "v8")]
714            BackendType::V8 => wasmer::BackendKind::V8,
715            #[cfg(feature = "wamr")]
716            BackendType::Wamr => wasmer::BackendKind::Wamr,
717            #[cfg(feature = "wasmi")]
718            BackendType::Wasmi => wasmer::BackendKind::Wasmi,
719            _ => {
720                #[cfg(feature = "sys")]
721                {
722                    wasmer::BackendKind::Headless
723                }
724                #[cfg(not(feature = "sys"))]
725                {
726                    unreachable!("No backend enabled!")
727                }
728            }
729        }
730    }
731}
732
733impl std::fmt::Display for BackendType {
734    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
735        write!(
736            f,
737            "{}",
738            match self {
739                Self::Singlepass => "singlepass",
740                Self::Cranelift => "cranelift",
741                Self::LLVM => "llvm",
742                Self::V8 => "v8",
743                Self::Wamr => "wamr",
744                Self::Wasmi => "wasmi",
745                Self::Headless => "headless",
746            }
747        )
748    }
749}