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