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