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    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 enabled Wasm features.
356    pub fn get_features(&self, default_features: &Features) -> Result<Features> {
357        if self.features.all {
358            return Ok(Features::all());
359        }
360
361        let mut result = default_features.clone();
362        if !self.features.disable_threads {
363            result.threads(true);
364        }
365        if self.features.disable_threads {
366            result.threads(false);
367        }
368        if self.features.multi_value {
369            result.multi_value(true);
370        }
371        if self.features.simd {
372            result.simd(true);
373        }
374        if self.features.bulk_memory {
375            result.bulk_memory(true);
376        }
377        if self.features.reference_types {
378            result.reference_types(true);
379        }
380        Ok(result)
381    }
382
383    #[cfg(feature = "compiler")]
384    /// Get a copy of the default features with user-configured options
385    pub fn get_configured_features(&self) -> Result<Features> {
386        let features = Features::default();
387        self.get_features(&features)
388    }
389
390    /// Detect features from a WebAssembly module binary.
391    pub fn detect_features_from_wasm(
392        &self,
393        wasm_bytes: &[u8],
394    ) -> Result<Features, wasmparser::BinaryReaderError> {
395        if self.features.all {
396            return Ok(Features::all());
397        }
398
399        let mut features = Features::detect_from_wasm(wasm_bytes)?;
400
401        // Merge with user-configured features
402        if !self.features.disable_threads {
403            features.threads(true);
404        }
405        if self.features.reference_types {
406            features.reference_types(true);
407        }
408        if self.features.simd {
409            features.simd(true);
410        }
411        if self.features.bulk_memory {
412            features.bulk_memory(true);
413        }
414        if self.features.multi_value {
415            features.multi_value(true);
416        }
417        if self.features.tail_call {
418            features.tail_call(true);
419        }
420        if self.features.module_linking {
421            features.module_linking(true);
422        }
423        if self.features.multi_memory {
424            features.multi_memory(true);
425        }
426        if self.features.memory64 {
427            features.memory64(true);
428        }
429        if self.features.exceptions {
430            features.exceptions(true);
431        }
432
433        Ok(features)
434    }
435
436    #[cfg(feature = "compiler")]
437    pub fn get_sys_compiler_engine_for_target(
438        &self,
439        target: Target,
440    ) -> std::result::Result<Engine, anyhow::Error> {
441        let backends = self.get_available_backends()?;
442        let compiler_config = self.get_sys_compiler_config(backends.first().unwrap())?;
443        let default_features = compiler_config.default_features_for_target(&target);
444        let features = self.get_features(&default_features)?;
445        Ok(wasmer_compiler::EngineBuilder::new(compiler_config)
446            .set_features(Some(features))
447            .set_target(Some(target))
448            .engine()
449            .into())
450    }
451
452    #[allow(unused_variables)]
453    #[cfg(feature = "compiler")]
454    pub(crate) fn get_sys_compiler_config(
455        &self,
456        rt: &BackendType,
457    ) -> Result<Box<dyn CompilerConfig>> {
458        let compiler_config: Box<dyn CompilerConfig> = match rt {
459            BackendType::Headless => bail!("The headless engine can't be chosen"),
460            #[cfg(feature = "singlepass")]
461            BackendType::Singlepass => {
462                let mut config = wasmer_compiler_singlepass::Singlepass::new();
463                if self.enable_verifier {
464                    config.enable_verifier();
465                }
466                if let Some(p) = &self.profiler {
467                    match p {
468                        Profiler::Perfmap => config.enable_perfmap(),
469                    }
470                }
471                if let Some(mut debug_dir) = self.compiler_debug_dir.clone() {
472                    use wasmer_compiler_singlepass::SinglepassCallbacks;
473
474                    debug_dir.push("singlepass");
475                    config.callbacks(Some(SinglepassCallbacks::new(debug_dir)?));
476                }
477
478                Box::new(config)
479            }
480            #[cfg(feature = "cranelift")]
481            BackendType::Cranelift => {
482                let mut config = wasmer_compiler_cranelift::Cranelift::new();
483                if self.enable_verifier {
484                    config.enable_verifier();
485                }
486                if let Some(p) = &self.profiler {
487                    match p {
488                        Profiler::Perfmap => config.enable_perfmap(),
489                    }
490                }
491                if let Some(mut debug_dir) = self.compiler_debug_dir.clone() {
492                    use wasmer_compiler_cranelift::CraneliftCallbacks;
493
494                    debug_dir.push("cranelift");
495                    config.callbacks(Some(CraneliftCallbacks::new(debug_dir)?));
496                }
497                Box::new(config)
498            }
499            #[cfg(feature = "llvm")]
500            BackendType::LLVM => {
501                use wasmer_compiler_llvm::LLVMCallbacks;
502                use wasmer_types::entity::EntityRef;
503                let mut config = LLVM::new();
504
505                if self.enable_pass_params_opt {
506                    config.enable_pass_params_opt();
507                }
508
509                if let Some(num_threads) = self.llvm_num_threads {
510                    config.num_threads(num_threads);
511                }
512
513                if let Some(mut debug_dir) = self.compiler_debug_dir.clone() {
514                    debug_dir.push("llvm");
515                    config.callbacks(Some(LLVMCallbacks::new(debug_dir)?));
516                }
517                if self.enable_verifier {
518                    config.enable_verifier();
519                }
520                if let Some(p) = &self.profiler {
521                    match p {
522                        Profiler::Perfmap => config.enable_perfmap(),
523                    }
524                }
525
526                Box::new(config)
527            }
528            BackendType::V8 | BackendType::Wamr | BackendType::Wasmi => unreachable!(),
529            #[cfg(not(all(feature = "singlepass", feature = "cranelift", feature = "llvm")))]
530            compiler => {
531                bail!("The `{compiler}` compiler is not included in this binary.")
532            }
533        };
534
535        #[allow(unreachable_code)]
536        Ok(compiler_config)
537    }
538}
539
540/// The compiler used for the store
541#[derive(Debug, PartialEq, Eq, Clone, Copy)]
542#[allow(clippy::upper_case_acronyms, dead_code)]
543pub enum BackendType {
544    /// Singlepass compiler
545    Singlepass,
546
547    /// Cranelift compiler
548    Cranelift,
549
550    /// LLVM compiler
551    LLVM,
552
553    /// V8 runtime
554    V8,
555
556    /// Wamr runtime
557    Wamr,
558
559    /// Wasmi runtime
560    Wasmi,
561
562    /// Headless compiler
563    #[allow(dead_code)]
564    Headless,
565}
566
567impl BackendType {
568    /// Return all enabled compilers
569    pub fn enabled() -> Vec<Self> {
570        vec![
571            #[cfg(feature = "cranelift")]
572            Self::Cranelift,
573            #[cfg(feature = "llvm")]
574            Self::LLVM,
575            #[cfg(feature = "singlepass")]
576            Self::Singlepass,
577            #[cfg(feature = "v8")]
578            Self::V8,
579            #[cfg(feature = "wamr")]
580            Self::Wamr,
581            #[cfg(feature = "wasmi")]
582            Self::Wasmi,
583        ]
584    }
585
586    /// Get an engine for this backend type
587    pub fn get_engine(
588        &self,
589        target: &Target,
590        features: &Features,
591        runtime_opts: &RuntimeOptions,
592    ) -> Result<Engine> {
593        match self {
594            #[cfg(feature = "singlepass")]
595            Self::Singlepass => {
596                let mut config = wasmer_compiler_singlepass::Singlepass::new();
597                if runtime_opts.enable_verifier {
598                    config.enable_verifier();
599                }
600                if let Some(p) = &runtime_opts.profiler {
601                    match p {
602                        Profiler::Perfmap => config.enable_perfmap(),
603                    }
604                }
605                if let Some(mut debug_dir) = runtime_opts.compiler_debug_dir.clone() {
606                    use wasmer_compiler_singlepass::SinglepassCallbacks;
607
608                    debug_dir.push("singlepass");
609                    config.callbacks(Some(SinglepassCallbacks::new(debug_dir)?));
610                }
611                let engine = wasmer_compiler::EngineBuilder::new(config)
612                    .set_features(Some(features.clone()))
613                    .set_target(Some(target.clone()))
614                    .engine()
615                    .into();
616                Ok(engine)
617            }
618            #[cfg(feature = "cranelift")]
619            Self::Cranelift => {
620                let mut config = wasmer_compiler_cranelift::Cranelift::new();
621                if runtime_opts.enable_verifier {
622                    config.enable_verifier();
623                }
624                if let Some(p) = &runtime_opts.profiler {
625                    match p {
626                        Profiler::Perfmap => config.enable_perfmap(),
627                    }
628                }
629                if let Some(mut debug_dir) = runtime_opts.compiler_debug_dir.clone() {
630                    use wasmer_compiler_cranelift::CraneliftCallbacks;
631
632                    debug_dir.push("cranelift");
633                    config.callbacks(Some(CraneliftCallbacks::new(debug_dir)?));
634                }
635                let engine = wasmer_compiler::EngineBuilder::new(config)
636                    .set_features(Some(features.clone()))
637                    .set_target(Some(target.clone()))
638                    .engine()
639                    .into();
640                Ok(engine)
641            }
642            #[cfg(feature = "llvm")]
643            Self::LLVM => {
644                use wasmer_compiler_llvm::LLVMCallbacks;
645                use wasmer_types::entity::EntityRef;
646
647                let mut config = wasmer_compiler_llvm::LLVM::new();
648
649                if let Some(mut debug_dir) = runtime_opts.compiler_debug_dir.clone() {
650                    debug_dir.push("llvm");
651                    config.callbacks(Some(LLVMCallbacks::new(debug_dir)?));
652                }
653                if runtime_opts.enable_verifier {
654                    config.enable_verifier();
655                }
656
657                if runtime_opts.enable_pass_params_opt {
658                    config.enable_pass_params_opt();
659                }
660
661                if let Some(num_threads) = runtime_opts.llvm_num_threads {
662                    config.num_threads(num_threads);
663                }
664
665                if let Some(p) = &runtime_opts.profiler {
666                    match p {
667                        Profiler::Perfmap => config.enable_perfmap(),
668                    }
669                }
670
671                let engine = wasmer_compiler::EngineBuilder::new(config)
672                    .set_features(Some(features.clone()))
673                    .set_target(Some(target.clone()))
674                    .engine()
675                    .into();
676                Ok(engine)
677            }
678            #[cfg(feature = "v8")]
679            Self::V8 => Ok(wasmer::v8::V8::new().into()),
680            #[cfg(feature = "wamr")]
681            Self::Wamr => Ok(wasmer::wamr::Wamr::new().into()),
682            #[cfg(feature = "wasmi")]
683            Self::Wasmi => Ok(wasmer::wasmi::Wasmi::new().into()),
684            Self::Headless => bail!("Headless is not a valid runtime to instantiate directly"),
685            #[allow(unreachable_patterns)]
686            _ => bail!("Unsupported backend type"),
687        }
688    }
689
690    /// Check if this backend supports all the required WebAssembly features
691    #[allow(unreachable_code)]
692    pub fn supports_features(&self, required_features: &Features, target: &Target) -> bool {
693        // Map BackendType to the corresponding wasmer::BackendKind
694        let backend_kind = match self {
695            #[cfg(feature = "singlepass")]
696            Self::Singlepass => wasmer::BackendKind::Singlepass,
697            #[cfg(feature = "cranelift")]
698            Self::Cranelift => wasmer::BackendKind::Cranelift,
699            #[cfg(feature = "llvm")]
700            Self::LLVM => wasmer::BackendKind::LLVM,
701            #[cfg(feature = "v8")]
702            Self::V8 => wasmer::BackendKind::V8,
703            #[cfg(feature = "wamr")]
704            Self::Wamr => wasmer::BackendKind::Wamr,
705            #[cfg(feature = "wasmi")]
706            Self::Wasmi => wasmer::BackendKind::Wasmi,
707            Self::Headless => return false, // Headless can't compile
708            #[allow(unreachable_patterns)]
709            _ => return false,
710        };
711
712        // Get the supported features from the backend
713        let supported = wasmer::Engine::supported_features_for_backend(&backend_kind, target);
714
715        // Check if the backend supports all required features
716        if !supported.contains_features(required_features) {
717            return false;
718        }
719
720        true
721    }
722}
723
724impl From<&BackendType> for wasmer::BackendKind {
725    fn from(backend_type: &BackendType) -> Self {
726        match backend_type {
727            #[cfg(feature = "singlepass")]
728            BackendType::Singlepass => wasmer::BackendKind::Singlepass,
729            #[cfg(feature = "cranelift")]
730            BackendType::Cranelift => wasmer::BackendKind::Cranelift,
731            #[cfg(feature = "llvm")]
732            BackendType::LLVM => wasmer::BackendKind::LLVM,
733            #[cfg(feature = "v8")]
734            BackendType::V8 => wasmer::BackendKind::V8,
735            #[cfg(feature = "wamr")]
736            BackendType::Wamr => wasmer::BackendKind::Wamr,
737            #[cfg(feature = "wasmi")]
738            BackendType::Wasmi => wasmer::BackendKind::Wasmi,
739            _ => {
740                #[cfg(feature = "sys")]
741                {
742                    wasmer::BackendKind::Headless
743                }
744                #[cfg(not(feature = "sys"))]
745                {
746                    unreachable!("No backend enabled!")
747                }
748            }
749        }
750    }
751}
752
753impl std::fmt::Display for BackendType {
754    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
755        write!(
756            f,
757            "{}",
758            match self {
759                Self::Singlepass => "singlepass",
760                Self::Cranelift => "cranelift",
761                Self::LLVM => "llvm",
762                Self::V8 => "v8",
763                Self::Wamr => "wamr",
764                Self::Wasmi => "wasmi",
765                Self::Headless => "headless",
766            }
767        )
768    }
769}