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